-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathmain.py
139 lines (110 loc) · 4.25 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# -*- coding: utf-8 -*-
"""Asynchronous event handling using generators"""
# pylint: disable=no-member
# pylint: disable=invalid-name
# pylint: disable=c-extension-no-member
import time
from typing import Any, Generator, List, Optional, Callable, Tuple
from dataclasses import dataclass
import pygame
Event = Generator[None, None, None]
Callback = Callable[[], Any]
Colour = Tuple[int, int, int]
class Delay:
"""
Iterable delay class that can be used in an asynchronous event to delay for an amount of time.
Can be used like this to asynchronously delay by 1 second: yield from Delay(1)
Note: this is equivalent to the following: for _ in Delay(1): yield
An optional wait_callback can be specified, which will be called each tick while delaying.
"""
def __init__(self, duration: float, wait_callback: Optional[Callback] = None):
self.duration = duration
self.wait_callback = wait_callback
self.start_time = time.time()
def __iter__(self):
while time.time() - self.start_time < self.duration:
if self.wait_callback:
self.wait_callback()
yield
class EventQueue:
"""
Encapsulates the asynchronous event handling by handling a single generated
result per event per tick.
"""
def __init__(self):
self._events: List[Event] = []
def update(self) -> None:
"""Updates the event queue"""
expired_events: List[Event] = []
for event in self._events:
try:
next(event)
except StopIteration:
expired_events.append(event)
self._events = [x for x in self._events if x not in expired_events]
def append(self, event: Event) -> None:
"""Appends the specified event to the event queue"""
self._events.append(event)
@dataclass
class Player:
"""Example player class (unrelated to asynchronous events)"""
x: int
y: int
colour: Colour
visible: bool = True
def render(self, screen: pygame.surface.Surface) -> None:
"""Renders the player on the screen"""
if not self.visible:
return
pygame.draw.rect(screen, self.colour, (self.x, self.y, 25, 25)) # type: ignore
def change_background_colour(screen: pygame.surface.Surface) -> Event:
"""Async event changing the background colour first to red, then to white"""
yield from Delay(0.5, wait_callback=lambda: screen.fill((255, 0, 0)))
yield from Delay(0.5, wait_callback=lambda: screen.fill((255, 255, 255)))
def move_player(player: Player) -> Event:
"""Async event to move the player by several steps in the x direction"""
player.x += 5
yield from Delay(0.3)
player.x += 10
yield from Delay(0.3)
player.x += 15
def hurt_player(player: Player) -> Event:
"""Async event to toggle player visibility a few times"""
visible_values = [False, True] * 3
for visible in visible_values:
player.visible = visible
yield from Delay(0.3)
def wobble_player(player: Player) -> Event:
"""Async event to animate player gently shifting up and down"""
offsets = [0, 2, 2, 1, 0, -1, -2, -2, -2, -2, -1, 0, 1, 2, 2] * 3
for offset in offsets:
player.y += offset
yield from Delay(0.05)
def main():
"""Main function"""
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
event_queue = EventQueue()
player = Player(x=0, y=150, colour=(200, 200, 200))
terminated = False
while not terminated:
for event in pygame.event.get():
if event.type == pygame.QUIT:
terminated = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_f:
event_queue.append(change_background_colour(screen))
if event.key == pygame.K_h:
event_queue.append(hurt_player(player))
if event.key == pygame.K_m:
event_queue.append(move_player(player))
if event.key == pygame.K_w:
event_queue.append(wobble_player(player))
screen.fill((0, 0, 0))
event_queue.update()
player.render(screen)
pygame.display.flip()
clock.tick(60)
if __name__ == "__main__":
main()