{"id":"own-game-plan","slug":"plan","title":"Planning your own game","text":"You've built a whole game. The last project of the course is a game of your own: your idea, written with what you've learned. This module helps you plan it, and builds a small game that you can use as a starting point.\n\n### What makes a game\n\nThink of a small game you like. It has a goal: you know what you're trying to do, and when you've won or lost. You steer something. On the way, there are things that help you or get in the way, such as coins to pick up or monsters to dodge. And a number on the screen, such as points, time left or lives, shows how it's going. The box game had them all: a puzzle to solve, a robot to steer, boxes and walls, and the count of moves. So you already know how to write each one.\n\n### Ideas\n\nChoose something small enough to finish. You can always add to a game that works. A few ideas:\n\n- **Coin chase**: collect coins that appear in random places, while a monster follows you.\n- **Dodge**: monsters bounce around the window, and the robot has to keep away from them. The score is how long it survives.\n- **Maze run**: build a maze from the tiles of the box game, and find the way out in as few moves as possible, or before the time runs out.\n- **Keep it up**: bounce a ball off a paddle at the bottom of the window, and count the bounces.\n\nBesides the robot and the tiles of the box game, the site has these pictures for your games:\n\n```asset coin.png\n24x24\n```\n\n```asset ball.png\n28x28\n```\n\n```asset monster.png\n40x40\n```\n\n### From an idea to a plan\n\nBefore writing any code, write down four things in plain words. For the coin chase:\n\n1. **What's in the game**: the robot, a coin, a monster, and the number of coins collected so far.\n2. **What the player does**: the arrow keys move the robot.\n3. **What happens by itself**: the monster moves toward the robot, and a collected coin jumps to a new place.\n4. **How it ends**: the monster catches the robot.\n\nThe plan turns into code almost by itself, with one method for each job. Point 1 is the game's state: the attributes that `reset` sets up. Point 2 is what the player does. A single key press, such as R to start a new game, is an event for `handle_events`, but keys that are held down, such as the arrow keys here, are checked in `update` on every frame. Points 3 and 4 go in `update` too: it moves the game on by one frame, and checks whether the game has ended. `draw` shows the state in the window.\n\n### A game that moves by itself\n\nThe box game waited for the player: between two key presses, nothing changed. In the coin chase, things move on every frame, so the main loop calls `update` between handling the events and drawing:\n\n```python\ndef run(self):\n    while True:\n        self.handle_events()\n        self.update()\n        self.draw()\n        self.clock.tick(60)\n```\n\nNow the clock matters: at 60 frames a second, a robot that moves 3 pixels a frame goes 180 pixels a second. To move smoothly, the robot moves on every frame while an arrow key is held down, and as in part 13, `pygame.key.get_pressed()` tells which keys are down right now:\n\n```python\nkeys = pygame.key.get_pressed()\nif keys[pygame.K_LEFT]:\n    self.robot_x -= 3\n```\n\n### Did they touch?\n\nMost games ask on every frame whether two things touch: the robot and a coin, the robot and a monster. This is called **collision detection**, and you met pygame's tool for it in part 13: `pygame.Rect` and its method `colliderect`. The game keeps its positions as plain numbers in attributes, and makes the rectangles from them whenever it checks, so they always match the latest positions:\n\n```python\nimport pygame\n\nrobot_x, robot_y = 100, 50\ncoin_x, coin_y = 130, 80\nrobot = pygame.Rect(robot_x, robot_y, 40, 40)\ncoin = pygame.Rect(coin_x, coin_y, 24, 24)\nprint(robot.colliderect(coin))\n\ncoin_x = 140\ncoin = pygame.Rect(coin_x, coin_y, 24, 24)\nprint(robot.colliderect(coin))\n```\n\n```output\nTrue\nFalse\n```\n\nThe robot covers x from 100 to 139. The coin at x = 130 overlaps it, but once the coin has moved to x = 140, their edges only touch, and that isn't an overlap.\n\n### The first version\n\nGet a small version working first, then add one thing at a time and try the game after each one, the way the box game was built. Here's the start of the coin chase, without the monster: the robot moves with the arrow keys, and a coin waits at a random place. The robot can't pick the coin up yet, so it walks right over it. Picking it up, with the rectangles from above, is the next thing to add: that's the second challenge below. On this site, `random` gives the same numbers every time a window runs, so the coin is in the same place every time you play here.\n\n```asset robot.png\n40x40\n```\n\n```python\nimport random\n\nimport pygame\n\nWIDTH = 480\nHEIGHT = 360\n\n\nclass CoinChase:\n    def __init__(self):\n        pygame.init()\n        self.window = pygame.display.set_mode((WIDTH, HEIGHT))\n        pygame.display.set_caption(\"Coin Chase\")\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.coin_picture = pygame.image.load(\"coin.png\")\n        self.font = pygame.font.SysFont(\"Arial\", 24)\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def reset(self):\n        self.robot_x = 40\n        self.robot_y = 160\n        self.coins = 0\n        self.place_coin()\n\n    def place_coin(self):\n        self.coin_x = random.randint(0, WIDTH - 24)\n        self.coin_y = random.randint(40, HEIGHT - 24)\n\n    def run(self):\n        while True:\n            self.handle_events()\n            self.update()\n            self.draw()\n            self.clock.tick(60)\n\n    def handle_events(self):\n        for event in pygame.event.get():\n            if event.type == pygame.QUIT:\n                exit()\n\n    def update(self):\n        keys = pygame.key.get_pressed()\n        if keys[pygame.K_LEFT]:\n            self.robot_x -= 3\n        if keys[pygame.K_RIGHT]:\n            self.robot_x += 3\n        if keys[pygame.K_UP]:\n            self.robot_y -= 3\n        if keys[pygame.K_DOWN]:\n            self.robot_y += 3\n\n    def draw(self):\n        self.window.fill((30, 90, 60))\n        self.window.blit(self.coin_picture, (self.coin_x, self.coin_y))\n        self.window.blit(self.robot_picture, (self.robot_x, self.robot_y))\n        text = self.font.render(f\"Coins: {self.coins}\", True, (255, 255, 255))\n        self.window.blit(text, (10, 10))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = CoinChase()\n    game.run()\n```\n\n```window-shot\n{\"image\":\"lessons/windows/4220477af769c257.png\",\"size\":[480,360],\"title\":\"Coin Chase\",\"frames\":70,\"log\":\"fill (30, 90, 60)\\nblit coin.png at (68, 331)\\nblit robot.png at (40, 310)\\nblit text \\\"Coins: 0\\\" at (10, 10)\",\"ended\":\"closed\",\"code\":\"import random\\n\\nimport pygame\\n\\nWIDTH = 480\\nHEIGHT = 360\\n\\n\\nclass CoinChase:\\n    def __init__(self):\\n        pygame.init()\\n        self.window = pygame.display.set_mode((WIDTH, HEIGHT))\\n        pygame.display.set_caption(\\\"Coin Chase\\\")\\n        self.robot_picture = pygame.image.load(\\\"robot.png\\\")\\n        self.coin_picture = pygame.image.load(\\\"coin.png\\\")\\n        self.font = pygame.font.SysFont(\\\"Arial\\\", 24)\\n        self.clock = pygame.time.Clock()\\n        self.reset()\\n\\n    def reset(self):\\n        self.robot_x = 40\\n        self.robot_y = 160\\n        self.coins = 0\\n        self.place_coin()\\n\\n    def place_coin(self):\\n        self.coin_x = random.randint(0, WIDTH - 24)\\n        self.coin_y = random.randint(40, HEIGHT - 24)\\n\\n    def run(self):\\n        while True:\\n            self.handle_events()\\n            self.update()\\n            self.draw()\\n            self.clock.tick(60)\\n\\n    def handle_events(self):\\n        for event in pygame.event.get():\\n            if event.type == pygame.QUIT:\\n                exit()\\n\\n    def update(self):\\n        keys = pygame.key.get_pressed()\\n        if keys[pygame.K_LEFT]:\\n            self.robot_x -= 3\\n        if keys[pygame.K_RIGHT]:\\n            self.robot_x += 3\\n        if keys[pygame.K_UP]:\\n            self.robot_y -= 3\\n        if keys[pygame.K_DOWN]:\\n            self.robot_y += 3\\n\\n    def draw(self):\\n        self.window.fill((30, 90, 60))\\n        self.window.blit(self.coin_picture, (self.coin_x, self.coin_y))\\n        self.window.blit(self.robot_picture, (self.robot_x, self.robot_y))\\n        text = self.font.render(f\\\"Coins: {self.coins}\\\", True, (255, 255, 255))\\n        self.window.blit(text, (10, 10))\\n        pygame.display.flip()\\n\\n\\nif __name__ == \\\"__main__\\\":\\n    game = CoinChase()\\n    game.run()\\n\",\"window\":{\"frames\":70,\"events\":[{\"at\":5,\"type\":\"keydown\",\"key\":\"K_DOWN\"},{\"at\":55,\"type\":\"keyup\",\"key\":\"K_DOWN\"}]},\"assets\":[\"coin.png\",\"ball.png\",\"monster.png\",\"robot.png\"]}\n```\n","task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"import pygame\n\nplayer = pygame.Rect(0, 0, 40, 40)\nwall = pygame.Rect(40, 0, 10, 100)\ncoin = pygame.Rect(30, 30, 24, 24)\nprint(player.right, player.bottom)\nprint(player.colliderect(wall))\nprint(player.colliderect(coin))\n","solution":"import pygame\n\nplayer = pygame.Rect(0, 0, 40, 40)\nwall = pygame.Rect(40, 0, 10, 100)\ncoin = pygame.Rect(30, 30, 24, 24)\nprint(player.right, player.bottom)\nprint(player.colliderect(wall))\nprint(player.colliderect(coin))\n","hints":["`right` is x + width, and `bottom` is y + height.","The wall starts at x = 40, exactly where the player ends. Edges that only touch don't count as overlapping.","The coin's top left corner (30, 30) is inside the player's rectangle."],"tests":[{"name":"Output","stdin":"","expect":"40 40\nFalse\nTrue","hidden":false}],"require":[],"forbid":[],"lines":["40 40","False","True"],"more":[{"task":"The robot walks over the coin without picking it up. In `update`, make a `pygame.Rect` for the robot (40 by 40 pixels) and one for the coin (24 by 24). When they overlap, the coin counter grows by one, and the coin moves to a new place: call `self.place_coin()` for that.\n","kind":"code","seed":"import random\n\nimport pygame\n\nWIDTH = 480\nHEIGHT = 360\n\n\nclass CoinChase:\n    def __init__(self):\n        pygame.init()\n        self.window = pygame.display.set_mode((WIDTH, HEIGHT))\n        pygame.display.set_caption(\"Coin Chase\")\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.coin_picture = pygame.image.load(\"coin.png\")\n        self.font = pygame.font.SysFont(\"Arial\", 24)\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def reset(self):\n        self.robot_x = 40\n        self.robot_y = 160\n        self.coins = 0\n        self.place_coin()\n\n    def place_coin(self):\n        self.coin_x = random.randint(0, WIDTH - 24)\n        self.coin_y = random.randint(40, HEIGHT - 24)\n\n    def run(self):\n        while True:\n            self.handle_events()\n            self.update()\n            self.draw()\n            self.clock.tick(60)\n\n    def handle_events(self):\n        for event in pygame.event.get():\n            if event.type == pygame.QUIT:\n                exit()\n\n    def update(self):\n        keys = pygame.key.get_pressed()\n        if keys[pygame.K_LEFT]:\n            self.robot_x -= 3\n        if keys[pygame.K_RIGHT]:\n            self.robot_x += 3\n        if keys[pygame.K_UP]:\n            self.robot_y -= 3\n        if keys[pygame.K_DOWN]:\n            self.robot_y += 3\n        # Collect the coin here.\n\n    def draw(self):\n        self.window.fill((30, 90, 60))\n        self.window.blit(self.coin_picture, (self.coin_x, self.coin_y))\n        self.window.blit(self.robot_picture, (self.robot_x, self.robot_y))\n        text = self.font.render(f\"Coins: {self.coins}\", True, (255, 255, 255))\n        self.window.blit(text, (10, 10))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = CoinChase()\n    game.run()\n","solution":"import random\n\nimport pygame\n\nWIDTH = 480\nHEIGHT = 360\n\n\nclass CoinChase:\n    def __init__(self):\n        pygame.init()\n        self.window = pygame.display.set_mode((WIDTH, HEIGHT))\n        pygame.display.set_caption(\"Coin Chase\")\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.coin_picture = pygame.image.load(\"coin.png\")\n        self.font = pygame.font.SysFont(\"Arial\", 24)\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def reset(self):\n        self.robot_x = 40\n        self.robot_y = 160\n        self.coins = 0\n        self.place_coin()\n\n    def place_coin(self):\n        self.coin_x = random.randint(0, WIDTH - 24)\n        self.coin_y = random.randint(40, HEIGHT - 24)\n\n    def run(self):\n        while True:\n            self.handle_events()\n            self.update()\n            self.draw()\n            self.clock.tick(60)\n\n    def handle_events(self):\n        for event in pygame.event.get():\n            if event.type == pygame.QUIT:\n                exit()\n\n    def update(self):\n        keys = pygame.key.get_pressed()\n        if keys[pygame.K_LEFT]:\n            self.robot_x -= 3\n        if keys[pygame.K_RIGHT]:\n            self.robot_x += 3\n        if keys[pygame.K_UP]:\n            self.robot_y -= 3\n        if keys[pygame.K_DOWN]:\n            self.robot_y += 3\n        robot = pygame.Rect(self.robot_x, self.robot_y, 40, 40)\n        coin = pygame.Rect(self.coin_x, self.coin_y, 24, 24)\n        if robot.colliderect(coin):\n            self.coins += 1\n            self.place_coin()\n\n    def draw(self):\n        self.window.fill((30, 90, 60))\n        self.window.blit(self.coin_picture, (self.coin_x, self.coin_y))\n        self.window.blit(self.robot_picture, (self.robot_x, self.robot_y))\n        text = self.font.render(f\"Coins: {self.coins}\", True, (255, 255, 255))\n        self.window.blit(text, (10, 10))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = CoinChase()\n    game.run()\n","hints":["The robot's rectangle is `pygame.Rect(self.robot_x, self.robot_y, 40, 40)`, and the coin's is made the same way from `self.coin_x` and `self.coin_y`.","`if robot.colliderect(coin):` is true while they overlap.","Inside the `if`, add one to `self.coins` and call `self.place_coin()` once."],"tests":[{"name":"Down to the coin","stdin":"","window":{"frames":70,"events":[{"at":5,"type":"keydown","key":"K_DOWN"},{"at":55,"type":"keyup","key":"K_DOWN"}]},"expectWindow":{"frames":70,"log":"fill (30, 90, 60)\nblit coin.png at (433, 72)\nblit robot.png at (40, 310)\nblit text \"Coins: 1\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/1e353af59707b819.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":270,"events":[{"at":5,"type":"keydown","key":"K_DOWN"},{"at":55,"type":"keyup","key":"K_DOWN"},{"at":55,"type":"keydown","key":"K_RIGHT"},{"at":185,"type":"keyup","key":"K_RIGHT"},{"at":185,"type":"keydown","key":"K_UP"},{"at":265,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":270,"log":"fill (30, 90, 60)\nblit coin.png at (130, 100)\nblit robot.png at (430, 70)\nblit text \"Coins: 2\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/1dc386f85c719818.png"},"expect":"","hidden":true},{"name":"Passing just right of the coin","stdin":"","window":{"frames":80,"events":[{"at":5,"type":"keydown","key":"K_RIGHT"},{"at":23,"type":"keyup","key":"K_RIGHT"},{"at":23,"type":"keydown","key":"K_DOWN"},{"at":75,"type":"keyup","key":"K_DOWN"}]},"expectWindow":{"frames":80,"log":"fill (30, 90, 60)\nblit coin.png at (68, 331)\nblit robot.png at (94, 316)\nblit text \"Coins: 0\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/e7ee8588afd07972.png"},"expect":"","hidden":true},{"name":"Over the coin's right half","stdin":"","window":{"frames":80,"events":[{"at":5,"type":"keydown","key":"K_RIGHT"},{"at":15,"type":"keyup","key":"K_RIGHT"},{"at":15,"type":"keydown","key":"K_DOWN"},{"at":75,"type":"keyup","key":"K_DOWN"}]},"expectWindow":{"frames":80,"log":"fill (30, 90, 60)\nblit coin.png at (433, 72)\nblit robot.png at (70, 340)\nblit text \"Coins: 1\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/bb3f19632daddd4a.png"},"expect":"","hidden":true},{"name":"Edges that only touch","stdin":"","window":{"frames":80,"events":[{"at":5,"type":"keydown","key":"K_LEFT"},{"at":9,"type":"keyup","key":"K_LEFT"},{"at":9,"type":"keydown","key":"K_DOWN"},{"at":75,"type":"keyup","key":"K_DOWN"}]},"expectWindow":{"frames":80,"log":"fill (30, 90, 60)\nblit coin.png at (68, 331)\nblit robot.png at (28, 358)\nblit text \"Coins: 0\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/539f549c28cda95c.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["robot.png","coin.png"]},{"task":"The robot can walk out of the window and get lost. Make `update` keep it inside: right after the robot moves, its x must be between 0 and `WIDTH - 40`, and its y between 0 and `HEIGHT - 40` (the robot is 40 pixels wide and high). A robot that tries to go further stays at the edge.\n","kind":"code","seed":"import random\n\nimport pygame\n\nWIDTH = 480\nHEIGHT = 360\n\n\nclass CoinChase:\n    def __init__(self):\n        pygame.init()\n        self.window = pygame.display.set_mode((WIDTH, HEIGHT))\n        pygame.display.set_caption(\"Coin Chase\")\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.coin_picture = pygame.image.load(\"coin.png\")\n        self.font = pygame.font.SysFont(\"Arial\", 24)\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def reset(self):\n        self.robot_x = 40\n        self.robot_y = 160\n        self.coins = 0\n        self.place_coin()\n\n    def place_coin(self):\n        self.coin_x = random.randint(0, WIDTH - 24)\n        self.coin_y = random.randint(40, HEIGHT - 24)\n\n    def run(self):\n        while True:\n            self.handle_events()\n            self.update()\n            self.draw()\n            self.clock.tick(60)\n\n    def handle_events(self):\n        for event in pygame.event.get():\n            if event.type == pygame.QUIT:\n                exit()\n\n    def update(self):\n        keys = pygame.key.get_pressed()\n        if keys[pygame.K_LEFT]:\n            self.robot_x -= 3\n        if keys[pygame.K_RIGHT]:\n            self.robot_x += 3\n        if keys[pygame.K_UP]:\n            self.robot_y -= 3\n        if keys[pygame.K_DOWN]:\n            self.robot_y += 3\n        robot = pygame.Rect(self.robot_x, self.robot_y, 40, 40)\n        coin = pygame.Rect(self.coin_x, self.coin_y, 24, 24)\n        if robot.colliderect(coin):\n            self.coins += 1\n            self.place_coin()\n\n    def draw(self):\n        self.window.fill((30, 90, 60))\n        self.window.blit(self.coin_picture, (self.coin_x, self.coin_y))\n        self.window.blit(self.robot_picture, (self.robot_x, self.robot_y))\n        text = self.font.render(f\"Coins: {self.coins}\", True, (255, 255, 255))\n        self.window.blit(text, (10, 10))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = CoinChase()\n    game.run()\n","solution":"import random\n\nimport pygame\n\nWIDTH = 480\nHEIGHT = 360\n\n\nclass CoinChase:\n    def __init__(self):\n        pygame.init()\n        self.window = pygame.display.set_mode((WIDTH, HEIGHT))\n        pygame.display.set_caption(\"Coin Chase\")\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.coin_picture = pygame.image.load(\"coin.png\")\n        self.font = pygame.font.SysFont(\"Arial\", 24)\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def reset(self):\n        self.robot_x = 40\n        self.robot_y = 160\n        self.coins = 0\n        self.place_coin()\n\n    def place_coin(self):\n        self.coin_x = random.randint(0, WIDTH - 24)\n        self.coin_y = random.randint(40, HEIGHT - 24)\n\n    def run(self):\n        while True:\n            self.handle_events()\n            self.update()\n            self.draw()\n            self.clock.tick(60)\n\n    def handle_events(self):\n        for event in pygame.event.get():\n            if event.type == pygame.QUIT:\n                exit()\n\n    def update(self):\n        keys = pygame.key.get_pressed()\n        if keys[pygame.K_LEFT]:\n            self.robot_x -= 3\n        if keys[pygame.K_RIGHT]:\n            self.robot_x += 3\n        if keys[pygame.K_UP]:\n            self.robot_y -= 3\n        if keys[pygame.K_DOWN]:\n            self.robot_y += 3\n        self.robot_x = max(0, min(self.robot_x, WIDTH - 40))\n        self.robot_y = max(0, min(self.robot_y, HEIGHT - 40))\n        robot = pygame.Rect(self.robot_x, self.robot_y, 40, 40)\n        coin = pygame.Rect(self.coin_x, self.coin_y, 24, 24)\n        if robot.colliderect(coin):\n            self.coins += 1\n            self.place_coin()\n\n    def draw(self):\n        self.window.fill((30, 90, 60))\n        self.window.blit(self.coin_picture, (self.coin_x, self.coin_y))\n        self.window.blit(self.robot_picture, (self.robot_x, self.robot_y))\n        text = self.font.render(f\"Coins: {self.coins}\", True, (255, 255, 255))\n        self.window.blit(text, (10, 10))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = CoinChase()\n    game.run()\n","hints":["If `self.robot_x` is less than 0, set it to 0. If it's more than `WIDTH - 40`, set it to `WIDTH - 40`. Then the same for y.","Or in one line each: `min(self.robot_x, WIDTH - 40)` is never too big, and `max(0, ...)` around it is never too small.","Put the checks after the four arrow keys have moved the robot, before the coin check."],"tests":[{"name":"Holding the left arrow","stdin":"","window":{"frames":50,"events":[{"at":5,"type":"keydown","key":"K_LEFT"},{"at":45,"type":"keyup","key":"K_LEFT"}]},"expectWindow":{"frames":50,"log":"fill (30, 90, 60)\nblit coin.png at (68, 331)\nblit robot.png at (0, 160)\nblit text \"Coins: 0\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/49cfe58d1d48fa30.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":85,"events":[{"at":5,"type":"keydown","key":"K_UP"},{"at":80,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":85,"log":"fill (30, 90, 60)\nblit coin.png at (68, 331)\nblit robot.png at (40, 0)\nblit text \"Coins: 0\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/7b4a909e0146231a.png"},"expect":"","hidden":true},{"name":"Test 3","stdin":"","window":{"frames":205,"events":[{"at":5,"type":"keydown","key":"K_RIGHT"},{"at":200,"type":"keyup","key":"K_RIGHT"}]},"expectWindow":{"frames":205,"log":"fill (30, 90, 60)\nblit coin.png at (68, 331)\nblit robot.png at (440, 160)\nblit text \"Coins: 0\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/0152e2ef1c9ce11d.png"},"expect":"","hidden":true},{"name":"Test 4","stdin":"","window":{"frames":105,"events":[{"at":5,"type":"keydown","key":"K_DOWN"},{"at":100,"type":"keyup","key":"K_DOWN"}]},"expectWindow":{"frames":105,"log":"fill (30, 90, 60)\nblit coin.png at (433, 72)\nblit robot.png at (40, 320)\nblit text \"Coins: 1\" at (10, 10)","size":[480,360],"title":"Coin Chase","image":"lessons/windows/fe66645a1535ceda.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["robot.png","coin.png"]}]}