{"id":"game-project-boxes","slug":"boxes","title":"Pushing boxes","text":"Now the boxes. A box on the floor and a box on its target look different, so the player can see which ones are done:\n\n```asset box.png\n40x40\n```\n\n```asset box-done.png\n40x40\n```\n\n### A list of boxes\n\nLike the robot, the boxes move, so they don't go in the map. The game keeps a list of their places, which `reset` sets up: `self.boxes = [(2, 2), (6, 4)]`. Whether a square has a box on it is then a question for `in`:\n\n```pycon\n>>> boxes = [(2, 2), (6, 4)]\n>>> (6, 4) in boxes\nTrue\n>>> (4, 6) in boxes\nFalse\n```\n\n`draw` puts each box on top of its square's tile, right after drawing the tile. On a target square, it uses the picture of a finished box:\n\n```python\nif (x, y) in self.boxes:\n    if square == \"X\":\n        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\n    else:\n        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\n```\n\n### The rules of pushing\n\nWhen the robot steps onto a square with a box, the box moves one square further in the same direction. That works only if the square behind the box is free: no wall and no other box. If it isn't free, nothing moves at all, not the box and not the robot. So a robot can't push two boxes in a row.\n\n\"Free\" is a question the game asks often, so it gets a small method of its own:\n\n```python\ndef is_free(self, x, y):\n    return self.map[y][x] != \"#\" and (x, y) not in self.boxes\n```\n\n`try_move` gets a new part between the wall check and the robot's move:\n\n```python\nif (new_x, new_y) in self.boxes:\n    box_x = new_x + dx\n    box_y = new_y + dy\n    if not self.is_free(box_x, box_y):\n        return\n    self.boxes.remove((new_x, new_y))\n    self.boxes.append((box_x, box_y))\n```\n\nThe box's place is a tuple, and a tuple can't be changed. So the old place is removed from the list and the new place added. Trying to change the tuple itself stops the program:\n\n```python\nboxes = [(2, 2), (6, 4)]\nbox = boxes[0]\nbox[0] = 3\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 3, in <module>\n    box[0] = 3\n    ~~~^^^\nTypeError: 'tuple' object does not support item assignment\n```\n\n### Code that says what it means\n\nThere are other ways to store this game. One is to give every kind of square a number and keep everything in a single grid of numbers: say 7 for a wall, 2 for a box and 6 for a box that's on its target. It can be shorter, but a line like `if grid[y][x] == 7:` means nothing unless you remember that 7 stands for a wall. Numbers like these, whose meaning lives only in the programmer's head, are called **magic numbers**, and they make code hard to read and easy to break.\n\nThis game uses characters that look like what they mean, `#` for a wall, and attributes with names, `self.robot` and `self.boxes`, for what moves. When working code is hard to read, it's worth rewriting it to be clearer without changing what it does. That's called **refactoring**.\n\nHere's the game with boxes. Besides the two box pictures, it loads the pictures from before:\n\n```asset floor.png\n40x40\n```\n\n```asset wall.png\n40x40\n```\n\n```asset target.png\n40x40\n```\n\n```asset robot.png\n40x40\n```\n\nPush the box on the right onto the target above it:\n\n```python\nimport pygame\n\nSIZE = 40\nLEVEL = [\n    \"#########\",\n    \"#...#...#\",\n    \"#.....X.#\",\n    \"#...#...#\",\n    \"#..X#...#\",\n    \"#.......#\",\n    \"#########\",\n]\n\n\nclass BoxGame:\n    def __init__(self):\n        pygame.init()\n        self.load_pictures()\n        self.map = LEVEL\n        self.height = len(self.map)\n        self.width = len(self.map[0])\n        self.window = pygame.display.set_mode((self.width * SIZE, self.height * SIZE))\n        pygame.display.set_caption(\"Box Robot\")\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def load_pictures(self):\n        self.tiles = {\n            \"#\": pygame.image.load(\"wall.png\"),\n            \".\": pygame.image.load(\"floor.png\"),\n            \"X\": pygame.image.load(\"target.png\"),\n        }\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.box_picture = pygame.image.load(\"box.png\")\n        self.done_picture = pygame.image.load(\"box-done.png\")\n\n    def reset(self):\n        self.robot = (2, 3)\n        self.boxes = [(2, 2), (6, 4)]\n\n    def run(self):\n        while True:\n            self.handle_events()\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            if event.type == pygame.KEYDOWN:\n                if event.key == pygame.K_LEFT:\n                    self.try_move(-1, 0)\n                if event.key == pygame.K_RIGHT:\n                    self.try_move(1, 0)\n                if event.key == pygame.K_UP:\n                    self.try_move(0, -1)\n                if event.key == pygame.K_DOWN:\n                    self.try_move(0, 1)\n\n    def is_free(self, x, y):\n        return self.map[y][x] != \"#\" and (x, y) not in self.boxes\n\n    def try_move(self, dx, dy):\n        x, y = self.robot\n        new_x = x + dx\n        new_y = y + dy\n        if self.map[new_y][new_x] == \"#\":\n            return\n        if (new_x, new_y) in self.boxes:\n            box_x = new_x + dx\n            box_y = new_y + dy\n            if not self.is_free(box_x, box_y):\n                return\n            self.boxes.remove((new_x, new_y))\n            self.boxes.append((box_x, box_y))\n        self.robot = (new_x, new_y)\n\n    def draw(self):\n        self.window.fill((0, 0, 0))\n        for y in range(self.height):\n            for x in range(self.width):\n                square = self.map[y][x]\n                self.window.blit(self.tiles[square], (x * SIZE, y * SIZE))\n                if (x, y) in self.boxes:\n                    if square == \"X\":\n                        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\n                    else:\n                        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\n        robot_x, robot_y = self.robot\n        self.window.blit(self.robot_picture, (robot_x * SIZE, robot_y * SIZE))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = BoxGame()\n    game.run()\n```\n\n```window-shot\n{\"image\":\"lessons/windows/cde2af6b6af47e69.png\",\"size\":[360,280],\"title\":\"Box Robot\",\"frames\":95,\"log\":\"fill (0, 0, 0)\\nblit wall.png at (0, 0)\\nblit wall.png at (40, 0)\\nblit wall.png at (80, 0)\\nblit wall.png at (120, 0)\\nblit wall.png at (160, 0)\\nblit wall.png at (200, 0)\\nblit wall.png at (240, 0)\\nblit wall.png at (280, 0)\\nblit wall.png at (320, 0)\\nblit wall.png at (0, 40)\\nblit floor.png at (40, 40)\\nblit floor.png at (80, 40)\\nblit floor.png at (120, 40)\\nblit wall.png at (160, 40)\\nblit floor.png at (200, 40)\\nblit floor.png at (240, 40)\\nblit floor.png at (280, 40)\\nblit wall.png at (320, 40)\\nblit wall.png at (0, 80)\\nblit floor.png at (40, 80)\\nblit floor.png at (80, 80)\\nblit box.png at (80, 80)\\nblit floor.png at (120, 80)\\nblit floor.png at (160, 80)\\nblit floor.png at (200, 80)\\nblit target.png at (240, 80)\\nblit box-done.png at (240, 80)\\nblit floor.png at (280, 80)\\nblit wall.png at (320, 80)\\nblit wall.png at (0, 120)\\nblit floor.png at (40, 120)\\nblit floor.png at (80, 120)\\nblit floor.png at (120, 120)\\nblit wall.png at (160, 120)\\nblit floor.png at (200, 120)\\nblit floor.png at (240, 120)\\nblit floor.png at (280, 120)\\nblit wall.png at (320, 120)\\nblit wall.png at (0, 160)\\nblit floor.png at (40, 160)\\nblit floor.png at (80, 160)\\nblit target.png at (120, 160)\\nblit wall.png at (160, 160)\\nblit floor.png at (200, 160)\\nblit floor.png at (240, 160)\\nblit floor.png at (280, 160)\\nblit wall.png at (320, 160)\\nblit wall.png at (0, 200)\\nblit floor.png at (40, 200)\\nblit floor.png at (80, 200)\\nblit floor.png at (120, 200)\\nblit floor.png at (160, 200)\\nblit floor.png at (200, 200)\\nblit floor.png at (240, 200)\\nblit floor.png at (280, 200)\\nblit wall.png at (320, 200)\\nblit wall.png at (0, 240)\\nblit wall.png at (40, 240)\\nblit wall.png at (80, 240)\\nblit wall.png at (120, 240)\\nblit wall.png at (160, 240)\\nblit wall.png at (200, 240)\\nblit wall.png at (240, 240)\\nblit wall.png at (280, 240)\\nblit wall.png at (320, 240)\\nblit robot.png at (240, 120)\",\"ended\":\"closed\",\"code\":\"import pygame\\n\\nSIZE = 40\\nLEVEL = [\\n    \\\"#########\\\",\\n    \\\"#...#...#\\\",\\n    \\\"#.....X.#\\\",\\n    \\\"#...#...#\\\",\\n    \\\"#..X#...#\\\",\\n    \\\"#.......#\\\",\\n    \\\"#########\\\",\\n]\\n\\n\\nclass BoxGame:\\n    def __init__(self):\\n        pygame.init()\\n        self.load_pictures()\\n        self.map = LEVEL\\n        self.height = len(self.map)\\n        self.width = len(self.map[0])\\n        self.window = pygame.display.set_mode((self.width * SIZE, self.height * SIZE))\\n        pygame.display.set_caption(\\\"Box Robot\\\")\\n        self.clock = pygame.time.Clock()\\n        self.reset()\\n\\n    def load_pictures(self):\\n        self.tiles = {\\n            \\\"#\\\": pygame.image.load(\\\"wall.png\\\"),\\n            \\\".\\\": pygame.image.load(\\\"floor.png\\\"),\\n            \\\"X\\\": pygame.image.load(\\\"target.png\\\"),\\n        }\\n        self.robot_picture = pygame.image.load(\\\"robot.png\\\")\\n        self.box_picture = pygame.image.load(\\\"box.png\\\")\\n        self.done_picture = pygame.image.load(\\\"box-done.png\\\")\\n\\n    def reset(self):\\n        self.robot = (2, 3)\\n        self.boxes = [(2, 2), (6, 4)]\\n\\n    def run(self):\\n        while True:\\n            self.handle_events()\\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            if event.type == pygame.KEYDOWN:\\n                if event.key == pygame.K_LEFT:\\n                    self.try_move(-1, 0)\\n                if event.key == pygame.K_RIGHT:\\n                    self.try_move(1, 0)\\n                if event.key == pygame.K_UP:\\n                    self.try_move(0, -1)\\n                if event.key == pygame.K_DOWN:\\n                    self.try_move(0, 1)\\n\\n    def is_free(self, x, y):\\n        return self.map[y][x] != \\\"#\\\" and (x, y) not in self.boxes\\n\\n    def try_move(self, dx, dy):\\n        x, y = self.robot\\n        new_x = x + dx\\n        new_y = y + dy\\n        if self.map[new_y][new_x] == \\\"#\\\":\\n            return\\n        if (new_x, new_y) in self.boxes:\\n            box_x = new_x + dx\\n            box_y = new_y + dy\\n            if not self.is_free(box_x, box_y):\\n                return\\n            self.boxes.remove((new_x, new_y))\\n            self.boxes.append((box_x, box_y))\\n        self.robot = (new_x, new_y)\\n\\n    def draw(self):\\n        self.window.fill((0, 0, 0))\\n        for y in range(self.height):\\n            for x in range(self.width):\\n                square = self.map[y][x]\\n                self.window.blit(self.tiles[square], (x * SIZE, y * SIZE))\\n                if (x, y) in self.boxes:\\n                    if square == \\\"X\\\":\\n                        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\\n                    else:\\n                        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\\n        robot_x, robot_y = self.robot\\n        self.window.blit(self.robot_picture, (robot_x * SIZE, robot_y * SIZE))\\n        pygame.display.flip()\\n\\n\\nif __name__ == \\\"__main__\\\":\\n    game = BoxGame()\\n    game.run()\\n\",\"window\":{\"frames\":95,\"events\":[{\"at\":10,\"type\":\"keydown\",\"key\":\"K_RIGHT\"},{\"at\":10,\"type\":\"keyup\",\"key\":\"K_RIGHT\"},{\"at\":18,\"type\":\"keydown\",\"key\":\"K_UP\"},{\"at\":18,\"type\":\"keyup\",\"key\":\"K_UP\"},{\"at\":26,\"type\":\"keydown\",\"key\":\"K_RIGHT\"},{\"at\":26,\"type\":\"keyup\",\"key\":\"K_RIGHT\"},{\"at\":34,\"type\":\"keydown\",\"key\":\"K_RIGHT\"},{\"at\":34,\"type\":\"keyup\",\"key\":\"K_RIGHT\"},{\"at\":42,\"type\":\"keydown\",\"key\":\"K_DOWN\"},{\"at\":42,\"type\":\"keyup\",\"key\":\"K_DOWN\"},{\"at\":50,\"type\":\"keydown\",\"key\":\"K_DOWN\"},{\"at\":50,\"type\":\"keyup\",\"key\":\"K_DOWN\"},{\"at\":58,\"type\":\"keydown\",\"key\":\"K_DOWN\"},{\"at\":58,\"type\":\"keyup\",\"key\":\"K_DOWN\"},{\"at\":66,\"type\":\"keydown\",\"key\":\"K_RIGHT\"},{\"at\":66,\"type\":\"keyup\",\"key\":\"K_RIGHT\"},{\"at\":74,\"type\":\"keydown\",\"key\":\"K_UP\"},{\"at\":74,\"type\":\"keyup\",\"key\":\"K_UP\"},{\"at\":82,\"type\":\"keydown\",\"key\":\"K_UP\"},{\"at\":82,\"type\":\"keyup\",\"key\":\"K_UP\"}]},\"assets\":[\"box.png\",\"box-done.png\",\"floor.png\",\"wall.png\",\"target.png\",\"robot.png\"]}\n```\n","task":"The boxes have gone missing from the window, although the robot can still push them. Fill in the blanks in `draw`, so that a box is drawn on every square in the list of boxes, with the finished box's picture on a target.\n","kind":"fill","seed":"import pygame\n\nSIZE = 40\nLEVEL = [\n    \"#########\",\n    \"#...#...#\",\n    \"#.....X.#\",\n    \"#...#...#\",\n    \"#..X#...#\",\n    \"#.......#\",\n    \"#########\",\n]\n\n\nclass BoxGame:\n    def __init__(self):\n        pygame.init()\n        self.load_pictures()\n        self.map = LEVEL\n        self.height = len(self.map)\n        self.width = len(self.map[0])\n        self.window = pygame.display.set_mode((self.width * SIZE, self.height * SIZE))\n        pygame.display.set_caption(\"Box Robot\")\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def load_pictures(self):\n        self.tiles = {\n            \"#\": pygame.image.load(\"wall.png\"),\n            \".\": pygame.image.load(\"floor.png\"),\n            \"X\": pygame.image.load(\"target.png\"),\n        }\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.box_picture = pygame.image.load(\"box.png\")\n        self.done_picture = pygame.image.load(\"box-done.png\")\n\n    def reset(self):\n        self.robot = (2, 3)\n        self.boxes = [(2, 2), (6, 4)]\n\n    def run(self):\n        while True:\n            self.handle_events()\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            if event.type == pygame.KEYDOWN:\n                if event.key == pygame.K_LEFT:\n                    self.try_move(-1, 0)\n                if event.key == pygame.K_RIGHT:\n                    self.try_move(1, 0)\n                if event.key == pygame.K_UP:\n                    self.try_move(0, -1)\n                if event.key == pygame.K_DOWN:\n                    self.try_move(0, 1)\n\n    def is_free(self, x, y):\n        return self.map[y][x] != \"#\" and (x, y) not in self.boxes\n\n    def try_move(self, dx, dy):\n        x, y = self.robot\n        new_x = x + dx\n        new_y = y + dy\n        if self.map[new_y][new_x] == \"#\":\n            return\n        if (new_x, new_y) in self.boxes:\n            box_x = new_x + dx\n            box_y = new_y + dy\n            if not self.is_free(box_x, box_y):\n                return\n            self.boxes.remove((new_x, new_y))\n            self.boxes.append((box_x, box_y))\n        self.robot = (new_x, new_y)\n\n    def draw(self):\n        self.window.fill((0, 0, 0))\n        for y in range(self.height):\n            for x in range(self.width):\n                square = self.map[y][x]\n                self.window.blit(self.tiles[square], (x * SIZE, y * SIZE))\n                if (x, y) [[in]] self.boxes:\n                    if square == [[\"X\"]]:\n                        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\n                    else:\n                        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\n        robot_x, robot_y = self.robot\n        self.window.blit(self.robot_picture, (robot_x * SIZE, robot_y * SIZE))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = BoxGame()\n    game.run()\n","solution":"import pygame\n\nSIZE = 40\nLEVEL = [\n    \"#########\",\n    \"#...#...#\",\n    \"#.....X.#\",\n    \"#...#...#\",\n    \"#..X#...#\",\n    \"#.......#\",\n    \"#########\",\n]\n\n\nclass BoxGame:\n    def __init__(self):\n        pygame.init()\n        self.load_pictures()\n        self.map = LEVEL\n        self.height = len(self.map)\n        self.width = len(self.map[0])\n        self.window = pygame.display.set_mode((self.width * SIZE, self.height * SIZE))\n        pygame.display.set_caption(\"Box Robot\")\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def load_pictures(self):\n        self.tiles = {\n            \"#\": pygame.image.load(\"wall.png\"),\n            \".\": pygame.image.load(\"floor.png\"),\n            \"X\": pygame.image.load(\"target.png\"),\n        }\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.box_picture = pygame.image.load(\"box.png\")\n        self.done_picture = pygame.image.load(\"box-done.png\")\n\n    def reset(self):\n        self.robot = (2, 3)\n        self.boxes = [(2, 2), (6, 4)]\n\n    def run(self):\n        while True:\n            self.handle_events()\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            if event.type == pygame.KEYDOWN:\n                if event.key == pygame.K_LEFT:\n                    self.try_move(-1, 0)\n                if event.key == pygame.K_RIGHT:\n                    self.try_move(1, 0)\n                if event.key == pygame.K_UP:\n                    self.try_move(0, -1)\n                if event.key == pygame.K_DOWN:\n                    self.try_move(0, 1)\n\n    def is_free(self, x, y):\n        return self.map[y][x] != \"#\" and (x, y) not in self.boxes\n\n    def try_move(self, dx, dy):\n        x, y = self.robot\n        new_x = x + dx\n        new_y = y + dy\n        if self.map[new_y][new_x] == \"#\":\n            return\n        if (new_x, new_y) in self.boxes:\n            box_x = new_x + dx\n            box_y = new_y + dy\n            if not self.is_free(box_x, box_y):\n                return\n            self.boxes.remove((new_x, new_y))\n            self.boxes.append((box_x, box_y))\n        self.robot = (new_x, new_y)\n\n    def draw(self):\n        self.window.fill((0, 0, 0))\n        for y in range(self.height):\n            for x in range(self.width):\n                square = self.map[y][x]\n                self.window.blit(self.tiles[square], (x * SIZE, y * SIZE))\n                if (x, y) in self.boxes:\n                    if square == \"X\":\n                        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\n                    else:\n                        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\n        robot_x, robot_y = self.robot\n        self.window.blit(self.robot_picture, (robot_x * SIZE, robot_y * SIZE))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = BoxGame()\n    game.run()\n","hints":["The first blank asks whether the place `(x, y)` is one of the items of the list `self.boxes`.","Fill the first blank with `in`. The second blank is the character of a target square in the map."],"tests":[{"name":"A box onto its target","stdin":"","window":{"frames":60,"events":[{"at":5,"type":"keydown","key":"K_RIGHT"},{"at":5,"type":"keyup","key":"K_RIGHT"},{"at":10,"type":"keydown","key":"K_UP"},{"at":10,"type":"keyup","key":"K_UP"},{"at":15,"type":"keydown","key":"K_RIGHT"},{"at":15,"type":"keyup","key":"K_RIGHT"},{"at":20,"type":"keydown","key":"K_RIGHT"},{"at":20,"type":"keyup","key":"K_RIGHT"},{"at":25,"type":"keydown","key":"K_DOWN"},{"at":25,"type":"keyup","key":"K_DOWN"},{"at":30,"type":"keydown","key":"K_DOWN"},{"at":30,"type":"keyup","key":"K_DOWN"},{"at":35,"type":"keydown","key":"K_DOWN"},{"at":35,"type":"keyup","key":"K_DOWN"},{"at":40,"type":"keydown","key":"K_RIGHT"},{"at":40,"type":"keyup","key":"K_RIGHT"},{"at":45,"type":"keydown","key":"K_UP"},{"at":45,"type":"keyup","key":"K_UP"},{"at":50,"type":"keydown","key":"K_UP"},{"at":50,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":60,"log":"fill (0, 0, 0)\nblit wall.png at (0, 0)\nblit wall.png at (40, 0)\nblit wall.png at (80, 0)\nblit wall.png at (120, 0)\nblit wall.png at (160, 0)\nblit wall.png at (200, 0)\nblit wall.png at (240, 0)\nblit wall.png at (280, 0)\nblit wall.png at (320, 0)\nblit wall.png at (0, 40)\nblit floor.png at (40, 40)\nblit floor.png at (80, 40)\nblit floor.png at (120, 40)\nblit wall.png at (160, 40)\nblit floor.png at (200, 40)\nblit floor.png at (240, 40)\nblit floor.png at (280, 40)\nblit wall.png at (320, 40)\nblit wall.png at (0, 80)\nblit floor.png at (40, 80)\nblit floor.png at (80, 80)\nblit box.png at (80, 80)\nblit floor.png at (120, 80)\nblit floor.png at (160, 80)\nblit floor.png at (200, 80)\nblit target.png at (240, 80)\nblit box-done.png at (240, 80)\nblit floor.png at (280, 80)\nblit wall.png at (320, 80)\nblit wall.png at (0, 120)\nblit floor.png at (40, 120)\nblit floor.png at (80, 120)\nblit floor.png at (120, 120)\nblit wall.png at (160, 120)\nblit floor.png at (200, 120)\nblit floor.png at (240, 120)\nblit floor.png at (280, 120)\nblit wall.png at (320, 120)\nblit wall.png at (0, 160)\nblit floor.png at (40, 160)\nblit floor.png at (80, 160)\nblit target.png at (120, 160)\nblit wall.png at (160, 160)\nblit floor.png at (200, 160)\nblit floor.png at (240, 160)\nblit floor.png at (280, 160)\nblit wall.png at (320, 160)\nblit wall.png at (0, 200)\nblit floor.png at (40, 200)\nblit floor.png at (80, 200)\nblit floor.png at (120, 200)\nblit floor.png at (160, 200)\nblit floor.png at (200, 200)\nblit floor.png at (240, 200)\nblit floor.png at (280, 200)\nblit wall.png at (320, 200)\nblit wall.png at (0, 240)\nblit wall.png at (40, 240)\nblit wall.png at (80, 240)\nblit wall.png at (120, 240)\nblit wall.png at (160, 240)\nblit wall.png at (200, 240)\nblit wall.png at (240, 240)\nblit wall.png at (280, 240)\nblit wall.png at (320, 240)\nblit robot.png at (240, 120)","size":[360,280],"title":"Box Robot","image":"lessons/windows/cde2af6b6af47e69.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":20,"events":[{"at":5,"type":"keydown","key":"K_LEFT"},{"at":5,"type":"keyup","key":"K_LEFT"},{"at":10,"type":"keydown","key":"K_UP"},{"at":10,"type":"keyup","key":"K_UP"},{"at":15,"type":"keydown","key":"K_RIGHT"},{"at":15,"type":"keyup","key":"K_RIGHT"}]},"expectWindow":{"frames":20,"log":"fill (0, 0, 0)\nblit wall.png at (0, 0)\nblit wall.png at (40, 0)\nblit wall.png at (80, 0)\nblit wall.png at (120, 0)\nblit wall.png at (160, 0)\nblit wall.png at (200, 0)\nblit wall.png at (240, 0)\nblit wall.png at (280, 0)\nblit wall.png at (320, 0)\nblit wall.png at (0, 40)\nblit floor.png at (40, 40)\nblit floor.png at (80, 40)\nblit floor.png at (120, 40)\nblit wall.png at (160, 40)\nblit floor.png at (200, 40)\nblit floor.png at (240, 40)\nblit floor.png at (280, 40)\nblit wall.png at (320, 40)\nblit wall.png at (0, 80)\nblit floor.png at (40, 80)\nblit floor.png at (80, 80)\nblit floor.png at (120, 80)\nblit box.png at (120, 80)\nblit floor.png at (160, 80)\nblit floor.png at (200, 80)\nblit target.png at (240, 80)\nblit floor.png at (280, 80)\nblit wall.png at (320, 80)\nblit wall.png at (0, 120)\nblit floor.png at (40, 120)\nblit floor.png at (80, 120)\nblit floor.png at (120, 120)\nblit wall.png at (160, 120)\nblit floor.png at (200, 120)\nblit floor.png at (240, 120)\nblit floor.png at (280, 120)\nblit wall.png at (320, 120)\nblit wall.png at (0, 160)\nblit floor.png at (40, 160)\nblit floor.png at (80, 160)\nblit target.png at (120, 160)\nblit wall.png at (160, 160)\nblit floor.png at (200, 160)\nblit floor.png at (240, 160)\nblit box.png at (240, 160)\nblit floor.png at (280, 160)\nblit wall.png at (320, 160)\nblit wall.png at (0, 200)\nblit floor.png at (40, 200)\nblit floor.png at (80, 200)\nblit floor.png at (120, 200)\nblit floor.png at (160, 200)\nblit floor.png at (200, 200)\nblit floor.png at (240, 200)\nblit floor.png at (280, 200)\nblit wall.png at (320, 200)\nblit wall.png at (0, 240)\nblit wall.png at (40, 240)\nblit wall.png at (80, 240)\nblit wall.png at (120, 240)\nblit wall.png at (160, 240)\nblit wall.png at (200, 240)\nblit wall.png at (240, 240)\nblit wall.png at (280, 240)\nblit wall.png at (320, 240)\nblit robot.png at (80, 80)","size":[360,280],"title":"Box Robot","image":"lessons/windows/dd368dfccad0ccb6.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["floor.png","wall.png","target.png","robot.png","box.png","box-done.png"],"more":[{"task":"Write the method `is_free(x, y)`. It returns `True` when square (x, y) has no wall and no box on it, and `False` otherwise. `try_move` uses it to check the square behind a box, so as it is now, the robot can push boxes into walls and onto other boxes.\n","kind":"code","seed":"import pygame\n\nSIZE = 40\nLEVEL = [\n    \"#########\",\n    \"#...#...#\",\n    \"#.....X.#\",\n    \"#...#...#\",\n    \"#..X#...#\",\n    \"#.......#\",\n    \"#########\",\n]\n\n\nclass BoxGame:\n    def __init__(self):\n        pygame.init()\n        self.load_pictures()\n        self.map = LEVEL\n        self.height = len(self.map)\n        self.width = len(self.map[0])\n        self.window = pygame.display.set_mode((self.width * SIZE, self.height * SIZE))\n        pygame.display.set_caption(\"Box Robot\")\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def load_pictures(self):\n        self.tiles = {\n            \"#\": pygame.image.load(\"wall.png\"),\n            \".\": pygame.image.load(\"floor.png\"),\n            \"X\": pygame.image.load(\"target.png\"),\n        }\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.box_picture = pygame.image.load(\"box.png\")\n        self.done_picture = pygame.image.load(\"box-done.png\")\n\n    def reset(self):\n        self.robot = (2, 3)\n        self.boxes = [(2, 2), (6, 4)]\n\n    def run(self):\n        while True:\n            self.handle_events()\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            if event.type == pygame.KEYDOWN:\n                if event.key == pygame.K_LEFT:\n                    self.try_move(-1, 0)\n                if event.key == pygame.K_RIGHT:\n                    self.try_move(1, 0)\n                if event.key == pygame.K_UP:\n                    self.try_move(0, -1)\n                if event.key == pygame.K_DOWN:\n                    self.try_move(0, 1)\n\n    def is_free(self, x, y):\n        # Return True when square (x, y) has no wall and no box.\n        return True\n\n    def try_move(self, dx, dy):\n        x, y = self.robot\n        new_x = x + dx\n        new_y = y + dy\n        if self.map[new_y][new_x] == \"#\":\n            return\n        if (new_x, new_y) in self.boxes:\n            box_x = new_x + dx\n            box_y = new_y + dy\n            if not self.is_free(box_x, box_y):\n                return\n            self.boxes.remove((new_x, new_y))\n            self.boxes.append((box_x, box_y))\n        self.robot = (new_x, new_y)\n\n    def draw(self):\n        self.window.fill((0, 0, 0))\n        for y in range(self.height):\n            for x in range(self.width):\n                square = self.map[y][x]\n                self.window.blit(self.tiles[square], (x * SIZE, y * SIZE))\n                if (x, y) in self.boxes:\n                    if square == \"X\":\n                        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\n                    else:\n                        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\n        robot_x, robot_y = self.robot\n        self.window.blit(self.robot_picture, (robot_x * SIZE, robot_y * SIZE))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = BoxGame()\n    game.run()\n","solution":"import pygame\n\nSIZE = 40\nLEVEL = [\n    \"#########\",\n    \"#...#...#\",\n    \"#.....X.#\",\n    \"#...#...#\",\n    \"#..X#...#\",\n    \"#.......#\",\n    \"#########\",\n]\n\n\nclass BoxGame:\n    def __init__(self):\n        pygame.init()\n        self.load_pictures()\n        self.map = LEVEL\n        self.height = len(self.map)\n        self.width = len(self.map[0])\n        self.window = pygame.display.set_mode((self.width * SIZE, self.height * SIZE))\n        pygame.display.set_caption(\"Box Robot\")\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def load_pictures(self):\n        self.tiles = {\n            \"#\": pygame.image.load(\"wall.png\"),\n            \".\": pygame.image.load(\"floor.png\"),\n            \"X\": pygame.image.load(\"target.png\"),\n        }\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.box_picture = pygame.image.load(\"box.png\")\n        self.done_picture = pygame.image.load(\"box-done.png\")\n\n    def reset(self):\n        self.robot = (2, 3)\n        self.boxes = [(2, 2), (6, 4)]\n\n    def run(self):\n        while True:\n            self.handle_events()\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            if event.type == pygame.KEYDOWN:\n                if event.key == pygame.K_LEFT:\n                    self.try_move(-1, 0)\n                if event.key == pygame.K_RIGHT:\n                    self.try_move(1, 0)\n                if event.key == pygame.K_UP:\n                    self.try_move(0, -1)\n                if event.key == pygame.K_DOWN:\n                    self.try_move(0, 1)\n\n    def is_free(self, x, y):\n        return self.map[y][x] != \"#\" and (x, y) not in self.boxes\n\n    def try_move(self, dx, dy):\n        x, y = self.robot\n        new_x = x + dx\n        new_y = y + dy\n        if self.map[new_y][new_x] == \"#\":\n            return\n        if (new_x, new_y) in self.boxes:\n            box_x = new_x + dx\n            box_y = new_y + dy\n            if not self.is_free(box_x, box_y):\n                return\n            self.boxes.remove((new_x, new_y))\n            self.boxes.append((box_x, box_y))\n        self.robot = (new_x, new_y)\n\n    def draw(self):\n        self.window.fill((0, 0, 0))\n        for y in range(self.height):\n            for x in range(self.width):\n                square = self.map[y][x]\n                self.window.blit(self.tiles[square], (x * SIZE, y * SIZE))\n                if (x, y) in self.boxes:\n                    if square == \"X\":\n                        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\n                    else:\n                        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\n        robot_x, robot_y = self.robot\n        self.window.blit(self.robot_picture, (robot_x * SIZE, robot_y * SIZE))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = BoxGame()\n    game.run()\n","hints":["The square is not free if the map has a wall there, or if its place is in the list of boxes.","Read the map row first: `self.map[y][x] != \"#\"`. The box check is `(x, y) not in self.boxes`.","Both conditions must hold for a free square, so combine them with `and` and return the result."],"tests":[{"name":"A box against the wall","stdin":"","window":{"frames":20,"events":[{"at":5,"type":"keydown","key":"K_UP"},{"at":5,"type":"keyup","key":"K_UP"},{"at":10,"type":"keydown","key":"K_UP"},{"at":10,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":20,"log":"fill (0, 0, 0)\nblit wall.png at (0, 0)\nblit wall.png at (40, 0)\nblit wall.png at (80, 0)\nblit wall.png at (120, 0)\nblit wall.png at (160, 0)\nblit wall.png at (200, 0)\nblit wall.png at (240, 0)\nblit wall.png at (280, 0)\nblit wall.png at (320, 0)\nblit wall.png at (0, 40)\nblit floor.png at (40, 40)\nblit floor.png at (80, 40)\nblit box.png at (80, 40)\nblit floor.png at (120, 40)\nblit wall.png at (160, 40)\nblit floor.png at (200, 40)\nblit floor.png at (240, 40)\nblit floor.png at (280, 40)\nblit wall.png at (320, 40)\nblit wall.png at (0, 80)\nblit floor.png at (40, 80)\nblit floor.png at (80, 80)\nblit floor.png at (120, 80)\nblit floor.png at (160, 80)\nblit floor.png at (200, 80)\nblit target.png at (240, 80)\nblit floor.png at (280, 80)\nblit wall.png at (320, 80)\nblit wall.png at (0, 120)\nblit floor.png at (40, 120)\nblit floor.png at (80, 120)\nblit floor.png at (120, 120)\nblit wall.png at (160, 120)\nblit floor.png at (200, 120)\nblit floor.png at (240, 120)\nblit floor.png at (280, 120)\nblit wall.png at (320, 120)\nblit wall.png at (0, 160)\nblit floor.png at (40, 160)\nblit floor.png at (80, 160)\nblit target.png at (120, 160)\nblit wall.png at (160, 160)\nblit floor.png at (200, 160)\nblit floor.png at (240, 160)\nblit box.png at (240, 160)\nblit floor.png at (280, 160)\nblit wall.png at (320, 160)\nblit wall.png at (0, 200)\nblit floor.png at (40, 200)\nblit floor.png at (80, 200)\nblit floor.png at (120, 200)\nblit floor.png at (160, 200)\nblit floor.png at (200, 200)\nblit floor.png at (240, 200)\nblit floor.png at (280, 200)\nblit wall.png at (320, 200)\nblit wall.png at (0, 240)\nblit wall.png at (40, 240)\nblit wall.png at (80, 240)\nblit wall.png at (120, 240)\nblit wall.png at (160, 240)\nblit wall.png at (200, 240)\nblit wall.png at (240, 240)\nblit wall.png at (280, 240)\nblit wall.png at (320, 240)\nblit robot.png at (80, 80)","size":[360,280],"title":"Box Robot","image":"lessons/windows/814c49c32807ed59.png"},"expect":"","hidden":false},{"name":"BoxGame().is_free(1, 1)","stdin":"","call":"BoxGame().is_free(1, 1)\n","expect":"True","hidden":false,"returns":true},{"name":"BoxGame().is_free(4, 1)","stdin":"","call":"BoxGame().is_free(4, 1)\n","expect":"False","hidden":true,"returns":true},{"name":"BoxGame().is_free(2, 2)","stdin":"","call":"BoxGame().is_free(2, 2)\n","expect":"False","hidden":true,"returns":true},{"name":"BoxGame().is_free(3, 4)","stdin":"","call":"BoxGame().is_free(3, 4)\n","expect":"True","hidden":true,"returns":true},{"name":"Test 6","stdin":"","window":{"frames":70,"events":[{"at":5,"type":"keydown","key":"K_LEFT"},{"at":5,"type":"keyup","key":"K_LEFT"},{"at":10,"type":"keydown","key":"K_UP"},{"at":10,"type":"keyup","key":"K_UP"},{"at":15,"type":"keydown","key":"K_RIGHT"},{"at":15,"type":"keyup","key":"K_RIGHT"},{"at":20,"type":"keydown","key":"K_RIGHT"},{"at":20,"type":"keyup","key":"K_RIGHT"},{"at":25,"type":"keydown","key":"K_RIGHT"},{"at":25,"type":"keyup","key":"K_RIGHT"},{"at":30,"type":"keydown","key":"K_RIGHT"},{"at":30,"type":"keyup","key":"K_RIGHT"},{"at":35,"type":"keydown","key":"K_DOWN"},{"at":35,"type":"keyup","key":"K_DOWN"},{"at":40,"type":"keydown","key":"K_DOWN"},{"at":40,"type":"keyup","key":"K_DOWN"},{"at":45,"type":"keydown","key":"K_DOWN"},{"at":45,"type":"keyup","key":"K_DOWN"},{"at":50,"type":"keydown","key":"K_RIGHT"},{"at":50,"type":"keyup","key":"K_RIGHT"},{"at":55,"type":"keydown","key":"K_UP"},{"at":55,"type":"keyup","key":"K_UP"},{"at":60,"type":"keydown","key":"K_UP"},{"at":60,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":70,"log":"fill (0, 0, 0)\nblit wall.png at (0, 0)\nblit wall.png at (40, 0)\nblit wall.png at (80, 0)\nblit wall.png at (120, 0)\nblit wall.png at (160, 0)\nblit wall.png at (200, 0)\nblit wall.png at (240, 0)\nblit wall.png at (280, 0)\nblit wall.png at (320, 0)\nblit wall.png at (0, 40)\nblit floor.png at (40, 40)\nblit floor.png at (80, 40)\nblit floor.png at (120, 40)\nblit wall.png at (160, 40)\nblit floor.png at (200, 40)\nblit floor.png at (240, 40)\nblit floor.png at (280, 40)\nblit wall.png at (320, 40)\nblit wall.png at (0, 80)\nblit floor.png at (40, 80)\nblit floor.png at (80, 80)\nblit floor.png at (120, 80)\nblit floor.png at (160, 80)\nblit floor.png at (200, 80)\nblit target.png at (240, 80)\nblit box-done.png at (240, 80)\nblit floor.png at (280, 80)\nblit wall.png at (320, 80)\nblit wall.png at (0, 120)\nblit floor.png at (40, 120)\nblit floor.png at (80, 120)\nblit floor.png at (120, 120)\nblit wall.png at (160, 120)\nblit floor.png at (200, 120)\nblit floor.png at (240, 120)\nblit box.png at (240, 120)\nblit floor.png at (280, 120)\nblit wall.png at (320, 120)\nblit wall.png at (0, 160)\nblit floor.png at (40, 160)\nblit floor.png at (80, 160)\nblit target.png at (120, 160)\nblit wall.png at (160, 160)\nblit floor.png at (200, 160)\nblit floor.png at (240, 160)\nblit floor.png at (280, 160)\nblit wall.png at (320, 160)\nblit wall.png at (0, 200)\nblit floor.png at (40, 200)\nblit floor.png at (80, 200)\nblit floor.png at (120, 200)\nblit floor.png at (160, 200)\nblit floor.png at (200, 200)\nblit floor.png at (240, 200)\nblit floor.png at (280, 200)\nblit wall.png at (320, 200)\nblit wall.png at (0, 240)\nblit wall.png at (40, 240)\nblit wall.png at (80, 240)\nblit wall.png at (120, 240)\nblit wall.png at (160, 240)\nblit wall.png at (200, 240)\nblit wall.png at (240, 240)\nblit wall.png at (280, 240)\nblit wall.png at (320, 240)\nblit robot.png at (240, 160)","size":[360,280],"title":"Box Robot","image":"lessons/windows/747b9bf5cce333a2.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["floor.png","wall.png","target.png","robot.png","box.png","box-done.png"]},{"task":"The robot walks over the boxes as if they weren't there. Add the pushing to `try_move`: when the robot steps onto a box, the box moves one square further in the same direction, if that square is free. If it isn't free, neither the box nor the robot moves. The method `is_free` is ready to use.\n","kind":"code","seed":"import pygame\n\nSIZE = 40\nLEVEL = [\n    \"#########\",\n    \"#...#...#\",\n    \"#.....X.#\",\n    \"#...#...#\",\n    \"#..X#...#\",\n    \"#.......#\",\n    \"#########\",\n]\n\n\nclass BoxGame:\n    def __init__(self):\n        pygame.init()\n        self.load_pictures()\n        self.map = LEVEL\n        self.height = len(self.map)\n        self.width = len(self.map[0])\n        self.window = pygame.display.set_mode((self.width * SIZE, self.height * SIZE))\n        pygame.display.set_caption(\"Box Robot\")\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def load_pictures(self):\n        self.tiles = {\n            \"#\": pygame.image.load(\"wall.png\"),\n            \".\": pygame.image.load(\"floor.png\"),\n            \"X\": pygame.image.load(\"target.png\"),\n        }\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.box_picture = pygame.image.load(\"box.png\")\n        self.done_picture = pygame.image.load(\"box-done.png\")\n\n    def reset(self):\n        self.robot = (2, 3)\n        self.boxes = [(2, 2), (6, 4)]\n\n    def run(self):\n        while True:\n            self.handle_events()\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            if event.type == pygame.KEYDOWN:\n                if event.key == pygame.K_LEFT:\n                    self.try_move(-1, 0)\n                if event.key == pygame.K_RIGHT:\n                    self.try_move(1, 0)\n                if event.key == pygame.K_UP:\n                    self.try_move(0, -1)\n                if event.key == pygame.K_DOWN:\n                    self.try_move(0, 1)\n\n    def is_free(self, x, y):\n        return self.map[y][x] != \"#\" and (x, y) not in self.boxes\n\n    def try_move(self, dx, dy):\n        x, y = self.robot\n        new_x = x + dx\n        new_y = y + dy\n        if self.map[new_y][new_x] == \"#\":\n            return\n        # Push a box here.\n        self.robot = (new_x, new_y)\n\n    def draw(self):\n        self.window.fill((0, 0, 0))\n        for y in range(self.height):\n            for x in range(self.width):\n                square = self.map[y][x]\n                self.window.blit(self.tiles[square], (x * SIZE, y * SIZE))\n                if (x, y) in self.boxes:\n                    if square == \"X\":\n                        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\n                    else:\n                        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\n        robot_x, robot_y = self.robot\n        self.window.blit(self.robot_picture, (robot_x * SIZE, robot_y * SIZE))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = BoxGame()\n    game.run()\n","solution":"import pygame\n\nSIZE = 40\nLEVEL = [\n    \"#########\",\n    \"#...#...#\",\n    \"#.....X.#\",\n    \"#...#...#\",\n    \"#..X#...#\",\n    \"#.......#\",\n    \"#########\",\n]\n\n\nclass BoxGame:\n    def __init__(self):\n        pygame.init()\n        self.load_pictures()\n        self.map = LEVEL\n        self.height = len(self.map)\n        self.width = len(self.map[0])\n        self.window = pygame.display.set_mode((self.width * SIZE, self.height * SIZE))\n        pygame.display.set_caption(\"Box Robot\")\n        self.clock = pygame.time.Clock()\n        self.reset()\n\n    def load_pictures(self):\n        self.tiles = {\n            \"#\": pygame.image.load(\"wall.png\"),\n            \".\": pygame.image.load(\"floor.png\"),\n            \"X\": pygame.image.load(\"target.png\"),\n        }\n        self.robot_picture = pygame.image.load(\"robot.png\")\n        self.box_picture = pygame.image.load(\"box.png\")\n        self.done_picture = pygame.image.load(\"box-done.png\")\n\n    def reset(self):\n        self.robot = (2, 3)\n        self.boxes = [(2, 2), (6, 4)]\n\n    def run(self):\n        while True:\n            self.handle_events()\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            if event.type == pygame.KEYDOWN:\n                if event.key == pygame.K_LEFT:\n                    self.try_move(-1, 0)\n                if event.key == pygame.K_RIGHT:\n                    self.try_move(1, 0)\n                if event.key == pygame.K_UP:\n                    self.try_move(0, -1)\n                if event.key == pygame.K_DOWN:\n                    self.try_move(0, 1)\n\n    def is_free(self, x, y):\n        return self.map[y][x] != \"#\" and (x, y) not in self.boxes\n\n    def try_move(self, dx, dy):\n        x, y = self.robot\n        new_x = x + dx\n        new_y = y + dy\n        if self.map[new_y][new_x] == \"#\":\n            return\n        if (new_x, new_y) in self.boxes:\n            box_x = new_x + dx\n            box_y = new_y + dy\n            if not self.is_free(box_x, box_y):\n                return\n            self.boxes.remove((new_x, new_y))\n            self.boxes.append((box_x, box_y))\n        self.robot = (new_x, new_y)\n\n    def draw(self):\n        self.window.fill((0, 0, 0))\n        for y in range(self.height):\n            for x in range(self.width):\n                square = self.map[y][x]\n                self.window.blit(self.tiles[square], (x * SIZE, y * SIZE))\n                if (x, y) in self.boxes:\n                    if square == \"X\":\n                        self.window.blit(self.done_picture, (x * SIZE, y * SIZE))\n                    else:\n                        self.window.blit(self.box_picture, (x * SIZE, y * SIZE))\n        robot_x, robot_y = self.robot\n        self.window.blit(self.robot_picture, (robot_x * SIZE, robot_y * SIZE))\n        pygame.display.flip()\n\n\nif __name__ == \"__main__\":\n    game = BoxGame()\n    game.run()\n","hints":["First check whether there's a box on the robot's new square: `if (new_x, new_y) in self.boxes:`.","The box would go to `new_x + dx`, `new_y + dy`. If `self.is_free` says that square isn't free, `return` before anything moves.","Otherwise move the box: remove its old place from `self.boxes` and append the new one. The robot's move after that stays as it is."],"tests":[{"name":"A box onto its target","stdin":"","window":{"frames":60,"events":[{"at":5,"type":"keydown","key":"K_RIGHT"},{"at":5,"type":"keyup","key":"K_RIGHT"},{"at":10,"type":"keydown","key":"K_UP"},{"at":10,"type":"keyup","key":"K_UP"},{"at":15,"type":"keydown","key":"K_RIGHT"},{"at":15,"type":"keyup","key":"K_RIGHT"},{"at":20,"type":"keydown","key":"K_RIGHT"},{"at":20,"type":"keyup","key":"K_RIGHT"},{"at":25,"type":"keydown","key":"K_DOWN"},{"at":25,"type":"keyup","key":"K_DOWN"},{"at":30,"type":"keydown","key":"K_DOWN"},{"at":30,"type":"keyup","key":"K_DOWN"},{"at":35,"type":"keydown","key":"K_DOWN"},{"at":35,"type":"keyup","key":"K_DOWN"},{"at":40,"type":"keydown","key":"K_RIGHT"},{"at":40,"type":"keyup","key":"K_RIGHT"},{"at":45,"type":"keydown","key":"K_UP"},{"at":45,"type":"keyup","key":"K_UP"},{"at":50,"type":"keydown","key":"K_UP"},{"at":50,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":60,"log":"fill (0, 0, 0)\nblit wall.png at (0, 0)\nblit wall.png at (40, 0)\nblit wall.png at (80, 0)\nblit wall.png at (120, 0)\nblit wall.png at (160, 0)\nblit wall.png at (200, 0)\nblit wall.png at (240, 0)\nblit wall.png at (280, 0)\nblit wall.png at (320, 0)\nblit wall.png at (0, 40)\nblit floor.png at (40, 40)\nblit floor.png at (80, 40)\nblit floor.png at (120, 40)\nblit wall.png at (160, 40)\nblit floor.png at (200, 40)\nblit floor.png at (240, 40)\nblit floor.png at (280, 40)\nblit wall.png at (320, 40)\nblit wall.png at (0, 80)\nblit floor.png at (40, 80)\nblit floor.png at (80, 80)\nblit box.png at (80, 80)\nblit floor.png at (120, 80)\nblit floor.png at (160, 80)\nblit floor.png at (200, 80)\nblit target.png at (240, 80)\nblit box-done.png at (240, 80)\nblit floor.png at (280, 80)\nblit wall.png at (320, 80)\nblit wall.png at (0, 120)\nblit floor.png at (40, 120)\nblit floor.png at (80, 120)\nblit floor.png at (120, 120)\nblit wall.png at (160, 120)\nblit floor.png at (200, 120)\nblit floor.png at (240, 120)\nblit floor.png at (280, 120)\nblit wall.png at (320, 120)\nblit wall.png at (0, 160)\nblit floor.png at (40, 160)\nblit floor.png at (80, 160)\nblit target.png at (120, 160)\nblit wall.png at (160, 160)\nblit floor.png at (200, 160)\nblit floor.png at (240, 160)\nblit floor.png at (280, 160)\nblit wall.png at (320, 160)\nblit wall.png at (0, 200)\nblit floor.png at (40, 200)\nblit floor.png at (80, 200)\nblit floor.png at (120, 200)\nblit floor.png at (160, 200)\nblit floor.png at (200, 200)\nblit floor.png at (240, 200)\nblit floor.png at (280, 200)\nblit wall.png at (320, 200)\nblit wall.png at (0, 240)\nblit wall.png at (40, 240)\nblit wall.png at (80, 240)\nblit wall.png at (120, 240)\nblit wall.png at (160, 240)\nblit wall.png at (200, 240)\nblit wall.png at (240, 240)\nblit wall.png at (280, 240)\nblit wall.png at (320, 240)\nblit robot.png at (240, 120)","size":[360,280],"title":"Box Robot","image":"lessons/windows/cde2af6b6af47e69.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":20,"events":[{"at":5,"type":"keydown","key":"K_UP"},{"at":5,"type":"keyup","key":"K_UP"},{"at":10,"type":"keydown","key":"K_UP"},{"at":10,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":20,"log":"fill (0, 0, 0)\nblit wall.png at (0, 0)\nblit wall.png at (40, 0)\nblit wall.png at (80, 0)\nblit wall.png at (120, 0)\nblit wall.png at (160, 0)\nblit wall.png at (200, 0)\nblit wall.png at (240, 0)\nblit wall.png at (280, 0)\nblit wall.png at (320, 0)\nblit wall.png at (0, 40)\nblit floor.png at (40, 40)\nblit floor.png at (80, 40)\nblit box.png at (80, 40)\nblit floor.png at (120, 40)\nblit wall.png at (160, 40)\nblit floor.png at (200, 40)\nblit floor.png at (240, 40)\nblit floor.png at (280, 40)\nblit wall.png at (320, 40)\nblit wall.png at (0, 80)\nblit floor.png at (40, 80)\nblit floor.png at (80, 80)\nblit floor.png at (120, 80)\nblit floor.png at (160, 80)\nblit floor.png at (200, 80)\nblit target.png at (240, 80)\nblit floor.png at (280, 80)\nblit wall.png at (320, 80)\nblit wall.png at (0, 120)\nblit floor.png at (40, 120)\nblit floor.png at (80, 120)\nblit floor.png at (120, 120)\nblit wall.png at (160, 120)\nblit floor.png at (200, 120)\nblit floor.png at (240, 120)\nblit floor.png at (280, 120)\nblit wall.png at (320, 120)\nblit wall.png at (0, 160)\nblit floor.png at (40, 160)\nblit floor.png at (80, 160)\nblit target.png at (120, 160)\nblit wall.png at (160, 160)\nblit floor.png at (200, 160)\nblit floor.png at (240, 160)\nblit box.png at (240, 160)\nblit floor.png at (280, 160)\nblit wall.png at (320, 160)\nblit wall.png at (0, 200)\nblit floor.png at (40, 200)\nblit floor.png at (80, 200)\nblit floor.png at (120, 200)\nblit floor.png at (160, 200)\nblit floor.png at (200, 200)\nblit floor.png at (240, 200)\nblit floor.png at (280, 200)\nblit wall.png at (320, 200)\nblit wall.png at (0, 240)\nblit wall.png at (40, 240)\nblit wall.png at (80, 240)\nblit wall.png at (120, 240)\nblit wall.png at (160, 240)\nblit wall.png at (200, 240)\nblit wall.png at (240, 240)\nblit wall.png at (280, 240)\nblit wall.png at (320, 240)\nblit robot.png at (80, 80)","size":[360,280],"title":"Box Robot","image":"lessons/windows/814c49c32807ed59.png"},"expect":"","hidden":true},{"name":"Test 3","stdin":"","window":{"frames":70,"events":[{"at":5,"type":"keydown","key":"K_LEFT"},{"at":5,"type":"keyup","key":"K_LEFT"},{"at":10,"type":"keydown","key":"K_UP"},{"at":10,"type":"keyup","key":"K_UP"},{"at":15,"type":"keydown","key":"K_RIGHT"},{"at":15,"type":"keyup","key":"K_RIGHT"},{"at":20,"type":"keydown","key":"K_RIGHT"},{"at":20,"type":"keyup","key":"K_RIGHT"},{"at":25,"type":"keydown","key":"K_RIGHT"},{"at":25,"type":"keyup","key":"K_RIGHT"},{"at":30,"type":"keydown","key":"K_RIGHT"},{"at":30,"type":"keyup","key":"K_RIGHT"},{"at":35,"type":"keydown","key":"K_DOWN"},{"at":35,"type":"keyup","key":"K_DOWN"},{"at":40,"type":"keydown","key":"K_DOWN"},{"at":40,"type":"keyup","key":"K_DOWN"},{"at":45,"type":"keydown","key":"K_DOWN"},{"at":45,"type":"keyup","key":"K_DOWN"},{"at":50,"type":"keydown","key":"K_RIGHT"},{"at":50,"type":"keyup","key":"K_RIGHT"},{"at":55,"type":"keydown","key":"K_UP"},{"at":55,"type":"keyup","key":"K_UP"},{"at":60,"type":"keydown","key":"K_UP"},{"at":60,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":70,"log":"fill (0, 0, 0)\nblit wall.png at (0, 0)\nblit wall.png at (40, 0)\nblit wall.png at (80, 0)\nblit wall.png at (120, 0)\nblit wall.png at (160, 0)\nblit wall.png at (200, 0)\nblit wall.png at (240, 0)\nblit wall.png at (280, 0)\nblit wall.png at (320, 0)\nblit wall.png at (0, 40)\nblit floor.png at (40, 40)\nblit floor.png at (80, 40)\nblit floor.png at (120, 40)\nblit wall.png at (160, 40)\nblit floor.png at (200, 40)\nblit floor.png at (240, 40)\nblit floor.png at (280, 40)\nblit wall.png at (320, 40)\nblit wall.png at (0, 80)\nblit floor.png at (40, 80)\nblit floor.png at (80, 80)\nblit floor.png at (120, 80)\nblit floor.png at (160, 80)\nblit floor.png at (200, 80)\nblit target.png at (240, 80)\nblit box-done.png at (240, 80)\nblit floor.png at (280, 80)\nblit wall.png at (320, 80)\nblit wall.png at (0, 120)\nblit floor.png at (40, 120)\nblit floor.png at (80, 120)\nblit floor.png at (120, 120)\nblit wall.png at (160, 120)\nblit floor.png at (200, 120)\nblit floor.png at (240, 120)\nblit box.png at (240, 120)\nblit floor.png at (280, 120)\nblit wall.png at (320, 120)\nblit wall.png at (0, 160)\nblit floor.png at (40, 160)\nblit floor.png at (80, 160)\nblit target.png at (120, 160)\nblit wall.png at (160, 160)\nblit floor.png at (200, 160)\nblit floor.png at (240, 160)\nblit floor.png at (280, 160)\nblit wall.png at (320, 160)\nblit wall.png at (0, 200)\nblit floor.png at (40, 200)\nblit floor.png at (80, 200)\nblit floor.png at (120, 200)\nblit floor.png at (160, 200)\nblit floor.png at (200, 200)\nblit floor.png at (240, 200)\nblit floor.png at (280, 200)\nblit wall.png at (320, 200)\nblit wall.png at (0, 240)\nblit wall.png at (40, 240)\nblit wall.png at (80, 240)\nblit wall.png at (120, 240)\nblit wall.png at (160, 240)\nblit wall.png at (200, 240)\nblit wall.png at (240, 240)\nblit wall.png at (280, 240)\nblit wall.png at (320, 240)\nblit robot.png at (240, 160)","size":[360,280],"title":"Box Robot","image":"lessons/windows/747b9bf5cce333a2.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["floor.png","wall.png","target.png","robot.png","box.png","box-done.png"]}]}