{"id":"events-holding","slug":"holding-keys","title":"Keys held down","text":"A `KEYDOWN` event comes once, when the key goes down, however long it's held. So with the program of the previous step, the player has to press the arrow again for every step. In most games, holding the key down keeps the character moving.\n\nThe program can know which keys are down by remembering: a key is down from its `KEYDOWN` event until its `KEYUP` event. Keep that in a variable that's `True` in between, and move a little on every frame while it's `True`:\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 = 140\ngoing_left = False\ngoing_right = False\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.KEYDOWN:\n            if event.key == pygame.K_LEFT:\n                going_left = True\n            if event.key == pygame.K_RIGHT:\n                going_right = True\n        if event.type == pygame.KEYUP:\n            if event.key == pygame.K_LEFT:\n                going_left = False\n            if event.key == pygame.K_RIGHT:\n                going_right = False\n        if event.type == pygame.QUIT:\n            exit()\n\n    if going_left:\n        x -= 3\n    if going_right:\n        x += 3\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (x, 200))\n    pygame.display.flip()\n    clock.tick(60)\n```\n\n```window-shot\n{\"image\":\"lessons/windows/6ea8106f9e6a2068.png\",\"size\":[320,240],\"title\":\"pygame window\",\"frames\":80,\"log\":\"fill (0, 0, 0)\\nblit robot.png at (200, 200)\",\"ended\":\"closed\",\"code\":\"import pygame\\n\\npygame.init()\\nwindow = pygame.display.set_mode((320, 240))\\nrobot = pygame.image.load(\\\"robot.png\\\")\\n\\nx = 140\\ngoing_left = False\\ngoing_right = False\\nclock = pygame.time.Clock()\\n\\nwhile True:\\n    for event in pygame.event.get():\\n        if event.type == pygame.KEYDOWN:\\n            if event.key == pygame.K_LEFT:\\n                going_left = True\\n            if event.key == pygame.K_RIGHT:\\n                going_right = True\\n        if event.type == pygame.KEYUP:\\n            if event.key == pygame.K_LEFT:\\n                going_left = False\\n            if event.key == pygame.K_RIGHT:\\n                going_right = False\\n        if event.type == pygame.QUIT:\\n            exit()\\n\\n    if going_left:\\n        x -= 3\\n    if going_right:\\n        x += 3\\n\\n    window.fill((0, 0, 0))\\n    window.blit(robot, (x, 200))\\n    pygame.display.flip()\\n    clock.tick(60)\\n\",\"window\":{\"frames\":80,\"events\":[{\"at\":10,\"type\":\"keydown\",\"key\":\"K_RIGHT\"},{\"at\":40,\"type\":\"keyup\",\"key\":\"K_RIGHT\"},{\"at\":50,\"type\":\"keydown\",\"key\":\"K_LEFT\"},{\"at\":60,\"type\":\"keyup\",\"key\":\"K_LEFT\"}]},\"assets\":[\"robot.png\"]}\n```\n\nEach round of the main loop now does four things, in this order: it handles the events, moves the robot, draws it and shows the frame. The right arrow was held for 30 frames, so the robot moved 3 × 30 = 90 pixels right, and then 30 pixels back left: it ends at x = 140 + 90 - 30 = 200. The clock matters again, since the robot moves on every frame: 3 pixels per frame at 60 frames per second is 180 pixels per second.\n\nIf both arrows are held, the robot moves 3 left and 3 right in each frame, so it stays put, which is what a player would expect.\n\n### Asking which keys are down\n\npygame can also say which keys are down right now. `pygame.key.get_pressed()` returns something you can index with a key's name: `keys[pygame.K_RIGHT]` is `True` while the right arrow is held. With it, the moving part is shorter, and the event loop needs only the `QUIT` event:\n\n```python\nkeys = pygame.key.get_pressed()\nif keys[pygame.K_LEFT]:\n    x -= 3\nif keys[pygame.K_RIGHT]:\n    x += 3\n```\n\nBoth ways work, and the checks here accept either. The variables show what really happens: the keys come in as events, and the program remembers them.\n\n### Staying in the window\n\nNothing stops the robot at the edge: hold the arrow long enough and it walks out of sight. After moving, put it back inside if it went too far. Its x can't be less than 0, and its right edge can't go past the window's right edge, so x can't be more than `320 - robot.get_width()`:\n\n```python\nif x < 0:\n    x = 0\nif x > 320 - robot.get_width():\n    x = 320 - robot.get_width()\n```\n\nUnlike bouncing, nothing turns around here: the robot just stops at the edge for as long as the player keeps pushing.\n","task":"Holding the up arrow should move the robot up, 3 pixels per frame. Fill in the blanks so that the robot stops when the key is let go.\n","kind":"fill","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\ny = 200\ngoing_up = False\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.KEYDOWN:\n            if event.key == pygame.K_UP:\n                going_up = True\n        if event.type == pygame.[[KEYUP]]:\n            if event.key == pygame.K_UP:\n                going_up = [[False]]\n        if event.type == pygame.QUIT:\n            exit()\n\n    if going_up:\n        y -= 3\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (140, y))\n    pygame.display.flip()\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\ny = 200\ngoing_up = False\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.KEYDOWN:\n            if event.key == pygame.K_UP:\n                going_up = True\n        if event.type == pygame.KEYUP:\n            if event.key == pygame.K_UP:\n                going_up = False\n        if event.type == pygame.QUIT:\n            exit()\n\n    if going_up:\n        y -= 3\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (140, y))\n    pygame.display.flip()\n    clock.tick(60)\n","hints":["The key stops being held when it's let go. Which event type is that, and what should the variable become?","Letting go is `pygame.KEYUP`, and then `going_up` becomes `False`."],"tests":[{"name":"Up held for 25 frames","stdin":"","window":{"frames":40,"events":[{"at":5,"type":"keydown","key":"K_UP"},{"at":30,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":40,"log":"fill (0, 0, 0)\nblit robot.png at (140, 125)","size":[320,240],"title":"pygame window","image":"lessons/windows/740dddd847b2779a.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":30,"events":[{"at":2,"type":"keydown","key":"K_UP"},{"at":12,"type":"keyup","key":"K_UP"},{"at":20,"type":"keydown","key":"K_UP"},{"at":25,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":30,"log":"fill (0, 0, 0)\nblit robot.png at (140, 155)","size":[320,240],"title":"pygame window","image":"lessons/windows/9be75fd5cec6c3f0.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["robot.png"],"more":[{"task":"The robot starts at (140, 100). While the up arrow is held, it moves up 4 pixels per frame, and while the down arrow is held, down 4 pixels per frame. It can't leave the window: its y stays between 0 and 200 (the window is 240 high and the robot 40). Use a clock at 60 frames per second, and handle the events, move and draw in that order, as in the lesson.\n","kind":"code","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\ny = 100\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    # Move the robot here.\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (140, y))\n    pygame.display.flip()\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\ny = 100\ngoing_up = False\ngoing_down = False\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.KEYDOWN:\n            if event.key == pygame.K_UP:\n                going_up = True\n            if event.key == pygame.K_DOWN:\n                going_down = True\n        if event.type == pygame.KEYUP:\n            if event.key == pygame.K_UP:\n                going_up = False\n            if event.key == pygame.K_DOWN:\n                going_down = False\n        if event.type == pygame.QUIT:\n            exit()\n\n    if going_up:\n        y -= 4\n    if going_down:\n        y += 4\n    if y < 0:\n        y = 0\n    if y > 240 - robot.get_height():\n        y = 240 - robot.get_height()\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (140, y))\n    pygame.display.flip()\n    clock.tick(60)\n","hints":["Use two variables, `going_up` and `going_down`, set to `True` on `KEYDOWN` and to `False` on `KEYUP` (or use `pygame.key.get_pressed()`).","Where the comment is, change y by 4 for each arrow that's held.","Right after moving, put the robot back inside: if y is below 0, make it 0, and if it's above 200, make it 200."],"tests":[{"name":"Down to the bottom, then up a bit","stdin":"","window":{"frames":90,"events":[{"at":5,"type":"keydown","key":"K_DOWN"},{"at":60,"type":"keyup","key":"K_DOWN"},{"at":70,"type":"keydown","key":"K_UP"},{"at":80,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":90,"log":"fill (0, 0, 0)\nblit robot.png at (140, 160)","size":[320,240],"title":"pygame window","image":"lessons/windows/62de2e74f814d47d.png"},"expect":"","hidden":false},{"name":"Up to the top and beyond","stdin":"","window":{"frames":60,"events":[{"at":2,"type":"keydown","key":"K_UP"},{"at":50,"type":"keyup","key":"K_UP"}]},"expectWindow":{"frames":60,"log":"fill (0, 0, 0)\nblit robot.png at (140, 0)","size":[320,240],"title":"pygame window","image":"lessons/windows/50ad587f16fa6069.png"},"expect":"","hidden":true},{"name":"Test 3","stdin":"","window":{"frames":20,"events":[{"at":5,"type":"keydown","key":"K_DOWN"},{"at":10,"type":"keyup","key":"K_DOWN"}]},"expectWindow":{"frames":20,"log":"fill (0, 0, 0)\nblit robot.png at (140, 120)","size":[320,240],"title":"pygame window","image":"lessons/windows/6ee0232f7ae35b8b.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["robot.png"]},{"task":"Make the robot fly. It starts on the ground at (140, 200). While the space bar is held, it rises 3 pixels per frame; when the space bar isn't held, it falls 2 pixels per frame. Its y stays between 0 and 200, so it can't fly out of the window or fall through the ground. Use a clock at 60 frames per second, and handle the events, move and draw in that order.\n","kind":"code","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nrobot = pygame.image.load(\"robot.png\")\n\ny = 200\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    # Move the robot here.\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (140, y))\n    pygame.display.flip()\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\ny = 200\nflying = False\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:\n            flying = True\n        if event.type == pygame.KEYUP and event.key == pygame.K_SPACE:\n            flying = False\n        if event.type == pygame.QUIT:\n            exit()\n\n    if flying:\n        y -= 3\n    else:\n        y += 2\n    if y < 0:\n        y = 0\n    if y > 200:\n        y = 200\n\n    window.fill((0, 0, 0))\n    window.blit(robot, (140, y))\n    pygame.display.flip()\n    clock.tick(60)\n","hints":["Keep a variable that's `True` while the space bar is held: set it on `KEYDOWN` and clear it on `KEYUP`, when the key is `pygame.K_SPACE`.","In every frame the robot moves one way or the other: an `if` that rises 3 with an `else` that falls 2.","After moving, keep y between 0 and 200, as in the lesson."],"tests":[{"name":"A short flight","stdin":"","window":{"frames":50,"events":[{"at":5,"type":"keydown","key":"K_SPACE"},{"at":45,"type":"keyup","key":"K_SPACE"}]},"expectWindow":{"frames":50,"log":"fill (0, 0, 0)\nblit robot.png at (140, 90)","size":[320,240],"title":"pygame window","image":"lessons/windows/a97c5d37beafde8a.png"},"expect":"","hidden":false},{"name":"Up to the top","stdin":"","window":{"frames":90,"events":[{"at":10,"type":"keydown","key":"K_SPACE"}]},"expectWindow":{"frames":90,"log":"fill (0, 0, 0)\nblit robot.png at (140, 0)","size":[320,240],"title":"pygame window","image":"lessons/windows/50ad587f16fa6069.png"},"expect":"","hidden":true},{"name":"Up and down again","stdin":"","window":{"frames":70,"events":[{"at":3,"type":"keydown","key":"K_SPACE"},{"at":23,"type":"keyup","key":"K_SPACE"},{"at":40,"type":"keydown","key":"K_SPACE"},{"at":44,"type":"keyup","key":"K_SPACE"}]},"expectWindow":{"frames":70,"log":"fill (0, 0, 0)\nblit robot.png at (140, 200)","size":[320,240],"title":"pygame window","image":"lessons/windows/545e1fcb330a16b9.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"assets":["robot.png"]}]}