{"id":"animation-circles","slug":"circles","title":"Going round in circles","text":"Moving in a straight line needs only adding. Moving in a circle needs a little trigonometry, and the `math` module has the functions for it.\n\nA point on a circle is given by its **angle**. `math.cos` and `math.sin` take the angle in **radians**, in which a full circle is 2π, about 6.28 (`math.pi` is π). For a circle with its center at (cx, cy) and radius r, the point at the angle `a` is\n\n```python\nx = cx + math.cos(a) * r\ny = cy + math.sin(a) * r\n```\n\nAt the angle 0 the point is straight to the right of the center, since the cosine of 0 is 1 and its sine is 0. A quarter of a circle later, at π / 2, the cosine is 0 and the sine is 1:\n\n```pycon\n>>> import math\n>>> math.cos(0), math.sin(0)\n(1.0, 0.0)\n>>> round(math.cos(math.pi / 2), 2), round(math.sin(math.pi / 2), 2)\n(0.0, 1.0)\n>>> round(2 * math.pi, 2)\n6.28\n```\n\nIn pygame y grows downward, so the point at π / 2 is below the center: as the angle grows, the point goes round clockwise.\n\n### Round and round\n\nTo go round, add a little to the angle on every frame:\n\n```asset robot.png\n40x40\n```\n\n```python\nimport math\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\nangle = 0\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    x = 160 + math.cos(angle) * 80 - robot.get_width() / 2\n    y = 120 + math.sin(angle) * 80 - robot.get_height() / 2\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (x, y))\n    pygame.display.flip()\n\n    angle += 0.05\n    clock.tick(60)\n```\n\n```window-shot\n{\"image\":\"lessons/windows/0f24947edebc4dcd.png\",\"size\":[320,240],\"title\":\"pygame window\",\"frames\":90,\"log\":\"fill (0, 0, 0)\\nblit robot.png at (119, 22)\",\"ended\":\"closed\",\"code\":\"import math\\nimport pygame\\n\\npygame.init()\\nwindow = pygame.display.set_mode((320, 240))\\nrobot = pygame.image.load(\\\"robot.png\\\")\\n\\nangle = 0\\nclock = pygame.time.Clock()\\n\\nwhile True:\\n    for event in pygame.event.get():\\n        if event.type == pygame.QUIT:\\n            exit()\\n\\n    x = 160 + math.cos(angle) * 80 - robot.get_width() / 2\\n    y = 120 + math.sin(angle) * 80 - robot.get_height() / 2\\n\\n    window.fill((0, 0, 0))\\n    window.blit(robot, (x, y))\\n    pygame.display.flip()\\n\\n    angle += 0.05\\n    clock.tick(60)\\n\",\"window\":{\"frames\":90,\"events\":[]},\"assets\":[\"robot.png\"]}\n```\n\nThe robot's center goes round the window's center (160, 120) at a distance of 80 pixels. Subtracting half of the robot's width and height moves its top-left corner so that the image is centered on the point. The angle grows by 0.05 per frame, so one round takes 6.28 / 0.05, about 126 frames: a little over two seconds at 60 frames per second. The positions are floats, and `blit` drops their fractions, so the window as text shows whole numbers.\n\nSeveral things go round the same circle when their angles are spread evenly: for six coins, 2π / 6 apart. In each frame, coin number `i` is at the angle `angle + i * 2 * math.pi / 6`.\n\n### Turning an image\n\n`pygame.transform.rotate(image, degrees)` gives a new image: the old one turned counterclockwise by that many **degrees** (not radians: a full circle is 360). The turned image is bigger, because its corners stick out: the robot turned by 45 degrees needs a 56 x 56 image. So to keep a spinning image in place, center it by the turned image's own size:\n\n```python\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\nangle = 0\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    turned = pygame.transform.rotate(robot, angle)\n    x = 160 - turned.get_width() // 2\n    y = 120 - turned.get_height() // 2\n\n    window.fill((0, 0, 0))\n    window.blit(turned, (x, y))\n    pygame.display.flip()\n\n    angle += 3\n    clock.tick(60)\n```\n\n```window-shot\n{\"image\":\"lessons/windows/8dc1132ce5392d7b.png\",\"size\":[320,240],\"title\":\"pygame window\",\"frames\":15,\"log\":\"fill (0, 0, 0)\\nblit robot.png rotated 42 at (132, 92)\",\"ended\":\"closed\",\"code\":\"import pygame\\n\\npygame.init()\\nwindow = pygame.display.set_mode((320, 240))\\nrobot = pygame.image.load(\\\"robot.png\\\")\\n\\nangle = 0\\nclock = pygame.time.Clock()\\n\\nwhile True:\\n    for event in pygame.event.get():\\n        if event.type == pygame.QUIT:\\n            exit()\\n\\n    turned = pygame.transform.rotate(robot, angle)\\n    x = 160 - turned.get_width() // 2\\n    y = 120 - turned.get_height() // 2\\n\\n    window.fill((0, 0, 0))\\n    window.blit(turned, (x, y))\\n    pygame.display.flip()\\n\\n    angle += 3\\n    clock.tick(60)\\n\",\"window\":{\"frames\":15,\"events\":[]},\"assets\":[\"robot.png\"]}\n```\n\nTurn the original image by the whole angle in every frame, as here. Turning an already turned image again and again would make it bigger and blurrier each time. The window as text says what was drawn: `blit robot.png rotated 42 at (132, 92)`.\n","task":"Fill in the blanks so the robot goes round the center of the window on a circle of radius 80, as in the lesson.\n","kind":"fill","seed":"import math\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\nangle = 0\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    x = 160 + math.[[cos]](angle) * 80 - 20\n    y = 120 + math.[[sin]](angle) * 80 - 20\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (x, y))\n    pygame.display.flip()\n\n    angle += 0.04\n    clock.tick(60)\n","solution":"import math\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\nangle = 0\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    x = 160 + math.cos(angle) * 80 - 20\n    y = 120 + math.sin(angle) * 80 - 20\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (x, y))\n    pygame.display.flip()\n\n    angle += 0.04\n    clock.tick(60)\n","hints":["One of the functions gives the x direction and the other the y direction.","x uses `math.cos` and y uses `math.sin`."],"tests":[{"name":"Test 1","stdin":"","window":{"frames":50,"events":[]},"expectWindow":{"frames":50,"log":"fill (0, 0, 0)\nblit robot.png at (109, 174)","size":[320,240],"title":"pygame window","image":"lessons/windows/3fa32579c5cfb9b3.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":120,"events":[]},"expectWindow":{"frames":120,"log":"fill (0, 0, 0)\nblit robot.png at (143, 20)","size":[320,240],"title":"pygame window","image":"lessons/windows/609731fe76276759.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["robot.png"],"more":[{"task":"A coin goes round the robot. The robot stays at (140, 100), so its center is the window's center (160, 120). The coin's center goes round that point on a circle of radius 60: the angle is 0 on the first frame and grows by 0.1 after each frame. The coin is 24 x 24, so its top-left corner is 12 pixels left of and above its center. Draw the robot first, then the coin. Give `blit` the coin's position as it comes out, floats and all: as in the lesson, `blit` drops the fractions, and the window is checked at the whole-number positions it draws at. Don't round the position yourself, since rounding sometimes gives the next pixel.\n","kind":"code","seed":"import math\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\ncoin = pygame.image.load(\"coin.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(robot, (140, 100))\n    pygame.display.flip()\n\n    clock.tick(60)\n","solution":"import math\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\ncoin = pygame.image.load(\"coin.png\")\n\nangle = 0\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    x = 160 + math.cos(angle) * 60 - 12\n    y = 120 + math.sin(angle) * 60 - 12\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (140, 100))\n    window.blit(coin, (x, y))\n    pygame.display.flip()\n\n    angle += 0.1\n    clock.tick(60)\n","hints":["Keep the angle in a variable that starts at 0, and add 0.1 to it after each frame.","The coin's center is at `160 + math.cos(angle) * 60` across and `120 + math.sin(angle) * 60` down.","Subtract 12 from both to get the coin's top-left corner, and draw the coin there after the robot, without rounding."],"tests":[{"name":"Test 1","stdin":"","window":{"frames":40,"events":[]},"expectWindow":{"frames":40,"log":"fill (0, 0, 0)\nblit robot.png at (140, 100)\nblit coin.png at (104, 66)","size":[320,240],"title":"pygame window","image":"lessons/windows/a951a664d7765d10.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":77,"events":[]},"expectWindow":{"frames":77,"log":"fill (0, 0, 0)\nblit robot.png at (140, 100)\nblit coin.png at (163, 166)","size":[320,240],"title":"pygame window","image":"lessons/windows/362834e92cc68317.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["robot.png","coin.png"]},{"task":"Make the monster spin in the center of the window: on the first frame it's turned by 0 degrees, and after each frame it turns 5 degrees more, counterclockwise. Turn the original image by the whole angle in each frame, and put the turned image's top-left corner at (160 - width // 2, 120 - height // 2), where width and height are the turned image's.\n","kind":"code","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\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(monster, (140, 100))\n    pygame.display.flip()\n\n    clock.tick(60)\n","solution":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nmonster = pygame.image.load(\"monster.png\")\n\nangle = 0\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    turned = pygame.transform.rotate(monster, angle)\n    x = 160 - turned.get_width() // 2\n    y = 120 - turned.get_height() // 2\n\n    window.fill((0, 0, 0))\n    window.blit(turned, (x, y))\n    pygame.display.flip()\n\n    angle += 5\n    clock.tick(60)\n","hints":["Keep the angle in a variable. In each frame, `pygame.transform.rotate(monster, angle)` gives the turned image.","The turned image's size changes with the angle, so ask it: `turned.get_width()` and `turned.get_height()`.","Draw `turned`, not `monster`, and add 5 to the angle after each frame."],"tests":[{"name":"Test 1","stdin":"","window":{"frames":30,"events":[]},"expectWindow":{"frames":30,"log":"fill (0, 0, 0)\nblit monster.png rotated 145 at (133, 93)","size":[320,240],"title":"pygame window","image":"lessons/windows/391dd86d0a40d1cf.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":50,"events":[]},"expectWindow":{"frames":50,"log":"fill (0, 0, 0)\nblit monster.png rotated 245 at (134, 94)","size":[320,240],"title":"pygame window","image":"lessons/windows/790e69f1a3775c37.png"},"expect":"","hidden":true},{"name":"Test 3","stdin":"","window":{"frames":9,"events":[]},"expectWindow":{"frames":9,"log":"fill (0, 0, 0)\nblit monster.png rotated 40 at (132, 92)","size":[320,240],"title":"pygame window","image":"lessons/windows/b5cfa1e340a37ab5.png"},"expect":"","hidden":true}],"require":[{"pattern":"pygame\\.transform\\.rotate\\s*\\(","message":"Turn the image with `pygame.transform.rotate`."}],"forbid":[],"assets":["monster.png"]}]}