{"id":15722413,"url":"https://github.com/defold/tutorial-colorslide","last_synced_at":"2025-10-20T04:31:45.083Z","repository":{"id":54880301,"uuid":"129040131","full_name":"defold/tutorial-colorslide","owner":"defold","description":null,"archived":false,"fork":false,"pushed_at":"2024-10-22T09:24:54.000Z","size":13940,"stargazers_count":11,"open_issues_count":1,"forks_count":8,"subscribers_count":19,"default_branch":"master","last_synced_at":"2025-01-30T15:24:50.216Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/defold.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-11T05:41:29.000Z","updated_at":"2025-01-15T22:53:04.000Z","dependencies_parsed_at":"2023-01-19T02:45:28.450Z","dependency_job_id":null,"html_url":"https://github.com/defold/tutorial-colorslide","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/defold%2Ftutorial-colorslide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/defold%2Ftutorial-colorslide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/defold%2Ftutorial-colorslide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/defold%2Ftutorial-colorslide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/defold","download_url":"https://codeload.github.com/defold/tutorial-colorslide/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237267964,"owners_count":19282319,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T22:07:29.756Z","updated_at":"2025-10-20T04:31:45.076Z","avatar_url":"https://github.com/defold.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Colorslide tutorial\n\nWelcome to the Colorslide game tutorial. It will take you through the process of adding a simple GUI flow to an existing multi level mobile game. It is assumed that you know your way around the editor. If you don't, please check out [our manuals and beginner tutorials](https://www.defold.com/learn).\n\nThe starting point for this tutorial is this project. It contains everything you need:\n\n- A simple but fully playable game where the player slides colored bricks on a tiled board until every brick's color matches the tile they sit on.\n- 4 example levels are included. They are of various difficulty. Each level is built in its own collection.\n- Assets are included so you can build any number of levels, using the built in tile editor.\n\nAfter having finished the tutorial, you will have accomplished the following:\n\n- You have added a level selection screen from where the player can start any of the 4 levels.\n- You have added a level completion message allowing the player to continue to the next level.\n- You have added a start screen.\n- You have added buttons so the user can navigate between these screens.\n\n## Understanding the game setup\n\nBefore beginning the tutorial, [try running the game](defold://project.build), then open [\"main.collection\"](defold://open?path=/main/main.collection) to see how the game is set up.\n\n\u003cimg src=\"doc/main_collection.png\" srcset=\"doc/main_collection@2x.png 2x\"\u003e\n\nThe whole game is contained in a subcollection called \"level\" inside \"main.collection\". Currently, \"level\" references the file \"/main/level_2/level_2.collection\". Opening the \"level\" collection file reveals two game objects:\n\n1. One game object with id \"board\". This one contains the tilemap. Notice that there are two layers on the tilemap, one is the actual playfield (with layer id \"board\") and one contains the intitial setup for the bricks (with layer id \"setup\"). When the game starts, it looks at the setup layer and replaces the brick tiles with separate game objects that can be animated freely. It then clears the layer.\n\n2. One game object with id \"level\". This one contains the game logic script (\"level.script\") and a factory used to spawn bricks on game start. This game object is stored in a separate file called \"/main/level.go\" so game objects of this blueprint file can be instantiated in each separate level collection.\n\nNow try some of the other levels:\n\nOpen \"main.collection\".\n\nMark \"level\" change its reference in the *Path* property to \"/main/level_3/level_3.collection\".\n\nBuild and run the game again (\u003ckbd\u003eProject ▸ Build\u003c/kbd\u003e). Do the same for levels 1 and 4.\n\n## Loading collections via proxy\n\nWhat is needed for this game is a way to make the level loading automatic and depending on player choice. Defold contains two mechanisms for loading collections dynamically:\n\n1. Collection factories. This is a good choice for spawning hierarchies of objects into a running game, like enemy units, effects or interactive objects. Any spawned object will be part of the startup main collection world and live until the game shuts down, unless you explicitly delete the objects yourself.\n\n2. Collection proxies. This is a good choice when you want to load larger chunks of a game dynamically, for instance a game level. With a proxy you create a new \"world\" based on the collection. Any object that is spawned from the collection content will be part of the created world and be automatically destroyed when the collection is unloaded from the proxy. The new world that is created has an overhead cost attached to it so proxies are not a good fit for spawning large quantities of small collections simultaneously.\n\nFor this game, proxies will be the best choice.\n\nOpen \"main.collection\" and remove the \"level\" collection reference. Instead, add a new game object and give it id \"loader\".\n\nAdd a collection proxy component to the game object, name it \"proxy_level_1\" and set its *Collection* property to \"/main/level_1/level_1.collection\".\n\nAdd a new script file called \"loader.script\" and add it as a script component to the \"loader\" game object.\n\nNOTE: The Defold editor will automatically add `.script` to the filename you provide. Make sure to only type in `loader` as name in the New Script popup.\n\n\u003cimg src=\"doc/main_proxy.png\" srcset=\"doc/main_proxy@2x.png 2x\"\u003e\n\nOpen \"loader.script\" and change its content to the following:\n\n```lua\nfunction init(self)\n    msg.post(\"#proxy_level_1\", \"load\")                      -- [1]\nend\n\nfunction on_message(self, message_id, message, sender)\n    if message_id == hash(\"proxy_loaded\") then              -- [2]\n        msg.post(sender, \"init\")\n        msg.post(sender, \"enable\")\n    end\nend\n```\n1. Send a message to the proxy component telling it to start loading its collection.\n2. When the proxy component is done loading, it sends a \"proxy_loaded\" message back. You can then send \"init\" and \"enable\" messages to the collection to initialize and enable the collection content. We can send these messages back to \"sender\" which is the proxy component.\n\nNow try to run the game.\n\nUnfortunately there is an instant error. The console says:\n\n```\nERROR:GAMEOBJECT: The collection 'default' could not be created since there is already a socket with the same name.\nERROR:GAMEOBJECT: AcquireResources NewCollection RESULT_OUT_OF_RESOURCES\nWARNING:RESOURCE: Unable to create resource: /main/level_1/level_1.collectionc: OUT_OF_RESOURCES\nERROR:GAMESYS: The collection /main/level_1/level_1.collectionc could not be loaded.\n```\n\nThis error occurs because the proxy tries to create a new world (socket) with the name \"default\". But a world with that name already exists - the one created from \"main.collection\" at engine boot. The socket name is set in the properties of the collection root so it's very easy to fix:\n\nOpen the file \"/main/level_1/level_1.collection\", mark the root of the collection and set the *Name* property to \"level_1\". And, mark the level.go and set the *Id* property to \"level_1\". Save the file.\n\n\u003cimg src=\"doc/socket_name.png\" srcset=\"doc/socket_name@2x.png 2x\"\u003e\n\nTry running the game again.\n\nThe level now shows up, but if you try to click on the board to move a tile, nothing happens. Why is that?\n\nThe problem is that the script that deals with input is now inside the proxied world. The input system works like this: \n\n* It sends input to all game objects in the bootstrap collection that has acquired input focus.\n* If one of these objects listening to input contains a proxy, input is directed to any object in the game world behind the proxy that has acquired input focus.\n\nSo in order to get input into the proxied collection, the game object that contains the proxy component must listen to input.\n\nOpen \"loader.script\" and add a line to the `init()` function:\n\n```\nfunction init(self)\n    msg.post(\"#proxy_level_1\", \"load\")\n    msg.post(\".\", \"acquire_input_focus\")                -- [1]\nend\n```\n1. Since this game object holds the proxy for the collection that needs input, this game object needs to acquire input focus too.\n\nRun the game again. Now everything should work as expected.\n\nBecause the game contains four levels you need to add proxy components for the remaining three levels. Don't forget to change the *Id* property to a unique name for each level collection so the socket names don't collide when a proxy loads.\n\n\u003cimg src=\"doc/main_proxies.png\" srcset=\"doc/main_proxies@2x.png 2x\"\u003e\n\nTest that each level loads by altering the proxy component you send the \"load\" message:\n\n* `msg.post(\"#proxy_level_1\", \"load\")`\n* `msg.post(\"#proxy_level_2\", \"load\")`\n* `msg.post(\"#proxy_level_3\", \"load\")`\n* `msg.post(\"#proxy_level_4\", \"load\")`\n\n## The level selection screen\n\nNow you have built the setup required to load any level at any moment so it is time to construct an interface to the level loading.\n\nCreate a new GUI file and call it \"level_select.gui\".\n\nAdd the \"headings\" font to the *Font* section of the GUI (\u003ckbd\u003eright click\u003c/kbd\u003e the *Fonts* item in the outline and select \u003ckbd\u003eAdd ▸ Fonts...\u003c/kbd\u003e).\n\nAdd the \"bricks\" atlas to the *Textures* section of the GUI (\u003ckbd\u003eright click\u003c/kbd\u003e the *Textures* item in the outline and select \u003ckbd\u003eAdd ▸ Textures...\u003c/kbd\u003e).\n\nConstruct an interface with 4 buttons, one for each level. For each button:\n\n1. Create one root Box node (\u003ckbd\u003eright click\u003c/kbd\u003e *Nodes* and select \u003ckbd\u003eAdd ▸ Box\u003c/kbd\u003e).\n2. Set the *Id* to \"level_1\".\n3. Set the *Size Mode* to `Manual` and the *Size* to 100, 100, 0.\n4. Set the *Alpha* to `0` so the node will be invisible.\n5. Create a child Box node to \"level_1\" (\u003ckbd\u003eright click\u003c/kbd\u003e \"level_1\" and select \u003ckbd\u003eAdd ▸ Box\u003c/kbd\u003e).\n6. Set the *Id* of the child node to \"1_bg\".\n7. Set the *Texture* of the node to `bricks/button`.\n8. Uncheck *Inherit Alpha* on the node so it renders even if its parent is transparent.\n9. Create a child Text node to \"level_1\" (\u003ckbd\u003eright click\u003c/kbd\u003e \"level_1\" and select \u003ckbd\u003eAdd ▸ Text\u003c/kbd\u003e).\n10. Set the *Id* of the child node to \"1_text\".\n11. Set the *Text* of the node to \"1\".\n12. Set the *Font* of the node to `headings`.\n13. Uncheck *Inherit Alpha* on the node so it renders even if its parent is transparent.\n\nIf you change the size of your graphics make sure that each root node is big enough to cover the whole button graphics because the root node will be used to test input against.\n\nRepeat the above steps for all 4 level buttons and move each root node into position. Also add a text node header:\n\n1. Create one Text node (\u003ckbd\u003eright click\u003c/kbd\u003e *Nodes* and select \u003ckbd\u003eAdd ▸ Text\u003c/kbd\u003e).\n2. Set the *Id* to \"select_header\".\n3. Set the *Font* to \"headings\".\n4. Set the *Text* to \"SELECT LEVEL\".\n\n\u003cimg src=\"doc/level_select.png\" srcset=\"doc/level_select@2x.png 2x\"\u003e\n\nCreate a new GUI script file and call it \"level_select.gui_script\".\n\nOpen \"level_select.gui_script\" and change the script to the following:\n\n```lua\nfunction init(self)\n    msg.post(\".\", \"acquire_input_focus\")\n    msg.post(\"#\", \"show_level_select\")                                  -- [1]\n    self.active = false\nend\n\nfunction on_message(self, message_id, message, sender)\n    if message_id == hash(\"show_level_select\") then                     -- [2]\n        msg.post(\"#\", \"enable\")\n        self.active = true\n    elseif message_id == hash(\"hide_level_select\") then                 -- [3]\n        msg.post(\"#\", \"disable\")\n        self.active = false\n    end\nend\n\nfunction on_input(self, action_id, action)\n    if action_id == hash(\"touch\") and action.pressed and self.active then\n        for n = 1,4 do                                                  -- [4]\n            local node = gui.get_node(\"level_\" .. n)\n            if gui.pick_node(node, action.x, action.y) then             -- [5]\n                msg.post(\"/loader#loader\", \"load_level\", { level = n }) -- [6]\n                msg.post(\"#\", \"hide_level_select\")                      -- [7]\n            end\n        end\n    end\nend\n```\n1. Set up the GUI.\n2. Showing and hiding the GUI is triggered via messaging so it can be done from other scripts.\n3. React to the pressing of touch input (as already set up in the [input bindings](defold://open?path=/input/game.input_binding)).\n4. The button nodes are named \"level_1\" to \"level_4\" so they can be looped over.\n5. Check if the touch action happens within the boundaries of node \"level_n\". This means that the click happened on the button.\n6. Send a message to the loader script to load level `n`. Notice that a \"load\" message is not sent directly to the proxy from here since this script does not deal with the rest of the proxy loading logic, as a reaction to \"proxy_loaded\".\n7. Hide this GUI.\n\nOpen \"level_select.gui\" and set the *Script* property on the root node to the new script.\n\n\u003cimg src=\"doc/level_select_script.png\" srcset=\"doc/level_select_script@2x.png 2x\"\u003e\n\nTo finish off this step, the loader script needs a bit of new code to react to the \"load_level\" message, and the proxy loading on init should be removed.\n\nOpen \"loader.script\" and change the `init()` and `on_message()` functions:\n\n```lua\nfunction init(self)\n    msg.post(\".\", \"acquire_input_focus\")\nend\n\nfunction on_message(self, message_id, message, sender)\n    if message_id == hash(\"load_level\") then\n        local proxy = \"#proxy_level_\" .. message.level                  -- [1]\n        msg.post(proxy, \"load\")\n    elseif message_id == hash(\"proxy_loaded\") then\n        msg.post(sender, \"init\")\n        msg.post(sender, \"enable\")\n    end\nend\n```\n1. Construct which proxy to load based on message data.\n\nOpen \"main.collection\" and add a new game object with id \"guis\".\n\nAdd \"level_select.gui\" as a GUI component to the new \"guis\" game object.\n\nRun the game and test the level selector screen. You should be able to click any of the level buttons and the corresponding level will load and be playable.\n\n## In game GUI\n\nYou can now start and play a level but there is no way to go back. The next step is to add an in game GUI that allows you to navigate back to the level selection screen. It should also congratulate the player when the level is completed and allow moving directly to the next level:\n\nCreate a new GUI file and call it \"level.gui\".\n\nAdd \"headings\" to the *Font* section and the \"bricks\" atlas to the *Textures* section of the GUI.\n\nBuild one back-button at the top and one level number indicator at the top.\n\nBuild a level complete message with a \"well done\" message and a \"next\"-button. Child these to a panel (a colored box node), call it \"done\" and place it outside of the view so they can be slide into view when the level is completed:\n\n\u003cimg src=\"doc/level_gui.png\" srcset=\"doc/level_gui@2x.png 2x\"\u003e\n\nCreate a new GUI script file and call it \"level.gui_script\".\n\nOpen \"level.gui_script\" and change the script to the following:\n\n```lua\nfunction on_message(self, message_id, message, sender)\n    if message_id == hash(\"level_completed\") then                       -- [1]\n        local done = gui.get_node(\"done\")\n        gui.animate(done, \"position.x\", 320, gui.EASING_OUTSINE, 1, 1.5)\n    end\nend\n\nfunction on_input(self, action_id, action)                              -- [2]\n    if action_id == hash(\"touch\") and action.pressed then\n        local back = gui.get_node(\"back\")\n        if gui.pick_node(back, action.x, action.y) then\n            msg.post(\"default:/guis#level_select\", \"show_level_select\")  -- [3]\n            msg.post(\"default:/loader#loader\", \"unload_level\")\n        end\n\n        local next = gui.get_node(\"next\")\n        if gui.pick_node(next, action.x, action.y) then\n            msg.post(\"default:/loader#loader\", \"next_level\")            -- [4]\n        end\n    end\nend\n```\n1. If message \"level_complete\" is received, slide the \"done\" panel with the \"next\" button into view.\n2. This GUI will be put on the \"level\" game object which already acquires input focus (through  \"level.script\") so this script should not do that.\n3. If the player presses \"back\", tell the level selector to show itself and the loader to unload the level. Note that the socket name of the bootstrap collection is used in the address.\n4. If the player presses \"next\", tell the loader to load the next level.\n\nOpen \"level.gui\" and set the *Script* property on the root node to the new script.\n\nOpen \"loader.script\" and change it to the following:\n\n```lua\nfunction init(self)\n    msg.post(\".\", \"acquire_input_focus\")\n    self.current_level = 0                                              -- [1]\nend\n\nfunction on_message(self, message_id, message, sender)\n    if message_id == hash(\"load_level\") then\n        self.current_level = message.level\n        local proxy = \"#proxy_level_\" .. self.current_level\n        msg.post(proxy, \"load\")\n    elseif message_id == hash(\"next_level\") then                        -- [2]\n        msg.post(\"#\", \"unload_level\")\n        msg.post(\"#\", \"load_level\", { level = self.current_level + 1 })\n    elseif message_id == hash(\"unload_level\") then                      -- [3]\n        local proxy = \"#proxy_level_\" .. self.current_level\n        msg.post(proxy, \"disable\")\n        msg.post(proxy, \"final\")\n        msg.post(proxy, \"unload\")\n    elseif message_id == hash(\"proxy_loaded\") then\n        msg.post(sender, \"init\")\n        msg.post(sender, \"enable\")\n    end\nend\n```\n1. Keep track of the currently loaded level so it can be unloaded and it is possible to advance to the next one.\n2. Load next level. Note that there is no check if there actually exists a next level.\n3. Unload the currently loaded level.\n\nOpen \"level.script\" and add a message to the level gui when the game is finished at the end of `on_input()`:\n\n```lua\n                ...\n                -- check if the board is solved\n                if all_correct(self.bricks) then\n                    msg.post(\"#gui\", \"level_completed\")                 -- [1]\n                    self.completed = true\n                end\n            end\n            ...\n```\n1. Tell the GUI to show the level completed panel.\n\nFinally, open \"level.go\" and add \"level.gui\" as a GUI component to the game object. Make sure to set the *Id* property of the component to \"gui\".\n\nRun the game. You should be able to select a game, go back to the level selection screen (with the \"back\" button) and also start the next level when one is finished.\n\n## Start screen\n\nThe final piece of the puzzle is the start screen:\n\nCreate a new GUI file and call it \"start.gui\".\n\nAdd \"headings\" to the *Font* section and the \"bricks\" atlas to the *Textures* section of the GUI.\n\nBuild the front screen. Add logo and a \"start\" button:\n\n\u003cimg src=\"doc/start_gui.png\" srcset=\"doc/start_gui@2x.png 2x\"\u003e\n\nCreate a new GUI script file and call it \"start.gui_script\".\n\nOpen \"start.gui_script\" and change the script to the following:\n\n```lua\nfunction init(self)\n    msg.post(\"#\", \"show_start\")                                         -- [1]\n    self.active = false\nend\n\nfunction on_message(self, message_id, message, sender)\n    if message_id == hash(\"show_start\") then                            -- [2]\n        msg.post(\"#\", \"enable\")\n        self.active = true\n    elseif message_id == hash(\"hide_start\") then\n        msg.post(\"#\", \"disable\")\n        self.active = false\n    end\nend\n\nfunction on_input(self, action_id, action)\n    if action_id == hash(\"touch\") and action.pressed and self.active then\n        local start = gui.get_node(\"start\")\n        if gui.pick_node(start, action.x, action.y) then                -- [3]\n            msg.post(\"#\", \"hide_start\")\n            msg.post(\"#level_select\", \"show_level_select\")\n        end\n    end\nend\n```\n1. Start by showing this screen.\n2. Messages to show and hide this screen.\n3. If the player presses the \"start\" button, hide this screen and tell the level selection GUI to show itself.\n\nOpen \"start.gui\" and set the *Script* property on the root node to the new script.\n\nOpen \"main.collection\" and add \"start.gui\" as a GUI component to the \"guis\" game object.\n\nNow open \"level_select.gui\" and add a \"back\" button. You can copy and paste the one you made in \"level.gui\" if you want.\n\n\u003cimg src=\"doc/level_select_back.png\" srcset=\"doc/level_select_back@2x.png 2x\"\u003e\n\nOpen \"level_select.gui_script\" and add the code for returning to the start screen in `on_input()`:\n\n```lua\nfunction on_input(self, action_id, action)\n    if action_id == hash(\"touch\") and action.pressed and self.active then\n        for n = 1,4 do\n            local node = gui.get_node(\"level_\" .. n)\n            if gui.pick_node(node, action.x, action.y) then\n                msg.post(\"/loader#loader\", \"load_level\", { level = n })\n                msg.post(\"#level_select\", \"hide_level_select\")\n            end\n        end\n\n        local back = gui.get_node(\"back\")                               -- [1]\n        if gui.pick_node(back, action.x, action.y) then\n            msg.post(\"#level_select\", \"hide_level_select\")\n            msg.post(\"#start\", \"show_start\")\n        end\n    end\nend\n```\n1. Check if the player clicks \"back\". If so, hide this GUI and show the start screen.\n\nAlso edit the `init()` function so the level select GUI is hidden on startup.\n\n```\nfunction init(self)\n    msg.post(\".\", \"acquire_input_focus\")\n    msg.post(\"#\", \"hide_level_select\")                                  -- [1]\n    self.active = false\nend\n```\n1. Hide the GUI on startup\n\nAnd that's it. You are done! Run the game and verify that everything works as expected.\n\nThe full source code for the project at this stage can be found in the [tutorial-done branch](https://github.com/defold/tutorial-colorslide/tree/tutorial-done).\n\n## What next?\n\nThis GUI implementation is pretty simple. Each screen deals with its own state and contains the code to hand over control to the next screen by sending messages to the other GUI component.\n\nIf your game does not feature advanced GUI flows this method is sufficient and clear enough. However, for advanced GUIs things can get hairy and in that case you might want to use some sort of screen manager that controls the flow from a central location. You can either roll your own or include an existing one as a library. Check out asset portal for community written [GUI libraries](https://defold.com/tags/stars/gui/).\n\nIf you want to continue experimenting with this tutorial project, here are some exercise suggestions:\n\n1. You may have noticed that the \"Level 1\" header while playing a level is static. Add functionality so the header text shows the correct level number.\n2. Implement unlocking of levels. Start the game with all but the first level locked and unlock them one by one as the game progresses.\n3. Implement saving of the level unlock progression state.\n4. Fix the case where the player completes the last level and there is no \"next\" one.\n5. Use GUI templates to create the buttons.\n6. Make the buttons response visually (react to press) and with sound.\n7. Add sound to the game.\n8. Create a solution to when there are more levels than what fits the screen.\n\nCheck out [the documentation pages](https://defold.com/learn) for more examples, tutorials, manuals and API docs.\n\nIf you run into trouble, help is available in [our forum](https://forum.defold.com).\n\nHappy Defolding!\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdefold%2Ftutorial-colorslide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdefold%2Ftutorial-colorslide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdefold%2Ftutorial-colorslide/lists"}