{"id":"animation-bouncing","slug":"bouncing","title":"Turning around at the walls","text":"The robot of the previous step walks out of the window and is gone. Things in games usually turn around at the walls. For that, the program needs to know which way the robot is going, so it keeps the robot's **velocity**, how much its position changes per frame, in a variable of its own:\n\n```asset robot.png\n40x40\n```\n\n```python\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\nx = 0\nspeed = 3\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (x, 100))\n    pygame.display.flip()\n\n    x += speed\n    if x + robot.get_width() >= 320 or x <= 0:\n        speed = -speed\n    clock.tick(60)\n```\n\n```window-shot\n{\"image\":\"lessons/windows/0df993eba5e208b9.png\",\"size\":[320,240],\"title\":\"pygame window\",\"frames\":150,\"log\":\"fill (0, 0, 0)\\nblit robot.png at (117, 100)\",\"ended\":\"closed\",\"code\":\"import pygame\\n\\npygame.init()\\nwindow = pygame.display.set_mode((320, 240))\\nrobot = pygame.image.load(\\\"robot.png\\\")\\n\\nx = 0\\nspeed = 3\\nclock = pygame.time.Clock()\\n\\nwhile True:\\n    for event in pygame.event.get():\\n        if event.type == pygame.QUIT:\\n            exit()\\n\\n    window.fill((0, 0, 0))\\n    window.blit(robot, (x, 100))\\n    pygame.display.flip()\\n\\n    x += speed\\n    if x + robot.get_width() >= 320 or x <= 0:\\n        speed = -speed\\n    clock.tick(60)\\n\",\"window\":{\"frames\":150,\"events\":[]},\"assets\":[\"robot.png\"]}\n```\n\n`speed` is 3 while the robot goes right and -3 while it goes left: `x += speed` with a negative speed makes x smaller. The robot's right edge is at `x + robot.get_width()`. When it reaches the window's right edge, `speed = -speed` turns 3 into -3, and the robot heads back. When its left edge reaches 0, -3 turns back into 3.\n\n### `>=`, not `==`\n\nThe test is `x + robot.get_width() >= 320`, not `== 320`. The robot moves 3 pixels at a time, so its right edge goes 316, 319, 322: it's never exactly 320. With `==` the test would never be true, and the robot would walk out of the window. In the same way, `x <= 0` catches 0 and anything below it. This small program prints the positions near the right edge, with 40 for the robot's width:\n\n```python\nx = 270\nspeed = 3\nfor frame in range(6):\n    x += speed\n    if x + 40 >= 320 or x <= 0:\n        speed = -speed\n    print(x, speed)\n```\n\n```output\n273 3\n276 3\n279 3\n282 -3\n279 -3\n276 -3\n```\n\nFor one frame the robot sticks out 2 pixels past the edge, which nobody notices. Start the robot inside the window, though: one that starts past an edge turns around on every frame and stays stuck there.\n\n### Bouncing in two directions\n\nA ball that moves at a slant has a velocity in each direction: `speed_x` across and `speed_y` up and down. Each one turns around at its own walls: `speed_x` at the left and right edges, `speed_y` at the top and the bottom.\n\n```asset ball.png\n28x28\n```\n\n```python\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nball = pygame.image.load(\"ball.png\")\n\nx = 0\ny = 0\nspeed_x = 4\nspeed_y = 3\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    window.blit(ball, (x, y))\n    pygame.display.flip()\n\n    x += speed_x\n    y += speed_y\n    if x + ball.get_width() >= 320 or x <= 0:\n        speed_x = -speed_x\n    if y + ball.get_height() >= 240 or y <= 0:\n        speed_y = -speed_y\n    clock.tick(60)\n```\n\n```window-shot\n{\"image\":\"lessons/windows/13ca250f0efaec5e.png\",\"size\":[320,240],\"title\":\"pygame window\",\"frames\":240,\"log\":\"fill (0, 0, 0)\\nblit ball.png at (212, 135)\",\"ended\":\"closed\",\"code\":\"import pygame\\n\\npygame.init()\\nwindow = pygame.display.set_mode((320, 240))\\nball = pygame.image.load(\\\"ball.png\\\")\\n\\nx = 0\\ny = 0\\nspeed_x = 4\\nspeed_y = 3\\nclock = pygame.time.Clock()\\n\\nwhile True:\\n    for event in pygame.event.get():\\n        if event.type == pygame.QUIT:\\n            exit()\\n\\n    window.fill((0, 0, 0))\\n    window.blit(ball, (x, y))\\n    pygame.display.flip()\\n\\n    x += speed_x\\n    y += speed_y\\n    if x + ball.get_width() >= 320 or x <= 0:\\n        speed_x = -speed_x\\n    if y + ball.get_height() >= 240 or y <= 0:\\n        speed_y = -speed_y\\n    clock.tick(60)\\n\",\"window\":{\"frames\":240,\"events\":[]},\"assets\":[\"robot.png\",\"ball.png\"]}\n```\n","task":"The robot should go back and forth between the left and the right edges of the window, as in the first example, but the condition for turning around is missing two pieces. Fill in the blanks.\n","kind":"fill","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\nx = 1\nspeed = 2\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (x, 60))\n    pygame.display.flip()\n\n    x += speed\n    if x + robot.[[get_width]]() >= 320 or x [[<=]] 0:\n        speed = -speed\n    clock.tick(60)\n","solution":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\nx = 1\nspeed = 2\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (x, 60))\n    pygame.display.flip()\n\n    x += speed\n    if x + robot.get_width() >= 320 or x <= 0:\n        speed = -speed\n    clock.tick(60)\n","hints":["The robot's right edge is its x plus its width (its height doesn't matter here). It turns around at the left edge when x is 0 or less.","The image's width is `robot.get_width()`, and \"0 or less\" is `x <= 0`. The robot starts at x = 1 and moves 2 pixels at a time, so x is never exactly 0: `x == 0` would let it walk out of the window."],"tests":[{"name":"Test 1","stdin":"","window":{"frames":300,"events":[]},"expectWindow":{"frames":300,"log":"fill (0, 0, 0)\nblit robot.png at (35, 60)","size":[320,240],"title":"pygame window","image":"lessons/windows/1830fc7bb7bd30b4.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":333,"events":[]},"expectWindow":{"frames":333,"log":"fill (0, 0, 0)\nblit robot.png at (101, 60)","size":[320,240],"title":"pygame window","image":"lessons/windows/9187a3a7ebeb29c3.png"},"expect":"","hidden":true}],"require":[{"pattern":"\\.get_width\\s*\\(","message":"The robot's right edge is its x plus its width: `robot.get_width()`."}],"forbid":[],"assets":["robot.png"],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"x = 0\nspeed = 40\nfor frame in range(6):\n    x += speed\n    if x >= 120 or x <= 0:\n        speed = -speed\n    print(x)\n","solution":"x = 0\nspeed = 40\nfor frame in range(6):\n    x += speed\n    if x >= 120 or x <= 0:\n        speed = -speed\n    print(x)\n","hints":["x grows by 40 until it reaches 120. Then the speed turns into -40.","From 120, x goes back down by 40 each time, and at 0 the speed turns around again."],"tests":[{"name":"Output","stdin":"","expect":"40\n80\n120\n80\n40\n0","hidden":false}],"require":[],"forbid":[],"lines":["40","80","120","80","40","0"]},{"task":"The monster patrols between two boxes. The boxes stand at (0, 100) and (280, 100), and the monster walks between them along y = 100, 4 pixels per frame. On the first frame it's at (122, 100), heading right. When its right edge reaches the right box's left edge (x = 280) or goes past it, it turns around, and when its left edge reaches the left box's right edge (x = 40) or goes past it, it turns around again. Only its speed turns: as the robot in the lesson sticks out past the edge for a frame, the monster steps 2 pixels into a box for a frame and isn't moved back. Draw the left box, then the right box, then the monster. As in the lesson, move the monster after each frame is shown, then check for turning.\n","kind":"code","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nbox = pygame.image.load(\"box.png\")\nmonster = pygame.image.load(\"monster.png\")\n\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    window.blit(box, (0, 100))\n    window.blit(box, (280, 100))\n    window.blit(monster, (122, 100))\n    pygame.display.flip()\n\n    clock.tick(60)\n","solution":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nbox = pygame.image.load(\"box.png\")\nmonster = pygame.image.load(\"monster.png\")\n\nx = 122\nspeed = 4\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    window.blit(box, (0, 100))\n    window.blit(box, (280, 100))\n    window.blit(monster, (x, 100))\n    pygame.display.flip()\n\n    x += speed\n    if x + monster.get_width() >= 280 or x <= 40:\n        speed = -speed\n    clock.tick(60)\n","hints":["Keep the monster's x and its speed in variables, as the lesson keeps the robot's.","The walls are the boxes now, not the window's edges. The monster's right edge is `x + monster.get_width()`, and it turns when that is 280 or more. It's never exactly 280 (x goes 122, 126, ..., 238, 242), so `==` would never turn it.","On the left it turns when `x <= 40`, since the left box covers x from 0 to 39."],"tests":[{"name":"Test 1","stdin":"","window":{"frames":100,"events":[]},"expectWindow":{"frames":100,"log":"fill (0, 0, 0)\nblit box.png at (0, 100)\nblit box.png at (280, 100)\nblit monster.png at (110, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/b07d11a1afc96aae.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":177,"events":[]},"expectWindow":{"frames":177,"log":"fill (0, 0, 0)\nblit box.png at (0, 100)\nblit box.png at (280, 100)\nblit monster.png at (66, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/742f49e85ba577b9.png"},"expect":"","hidden":true},{"name":"Test 3","stdin":"","window":{"frames":20,"events":[]},"expectWindow":{"frames":20,"log":"fill (0, 0, 0)\nblit box.png at (0, 100)\nblit box.png at (280, 100)\nblit monster.png at (198, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/787667a163a5c079.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["box.png","monster.png"]}]}