python-cheatsheet
python-cheatsheet copied to clipboard
update coroutine example
I love the coroutine game! To me, it's a lot more fun when the numbers "chase" the player, which is enabled in this PR.
Also, running the example as-is generates a deprecation warning for me, so I changed asyncio.wait(coro) to generating tasks as recommended here: https://stackoverflow.com/questions/66029772/alternative-to-asyncio-wait
Cool, I'm actually also working on a new version for a last few days :) If nothing else thanks for sorting out the deprecation warning.
Here's my version so far (I'm still deciding on the speed of the numbers and the size of the rectangle -> it can be changed in-game with aswd keys):
import asyncio, collections, curses, curses.textpad, enum, random, itertools, math
P = collections.namedtuple('P', 'x y') # Position
D = enum.Enum('D', 'n e s w') # Direction
INFO = False
W, H = 15, 7 # Width, Height
def main(screen):
curses.curs_set(0) # Makes cursor invisible.
screen.nodelay(True) # Makes getch() non-blocking.
asyncio.run(main_coroutine(screen)) # Starts running asyncio code.
async def main_coroutine(screen):
state = {'*': P(0, 0), **{id_: P(W//2, H//2) for id_ in range(10)}}
moves = asyncio.Queue()
coros = (*(random_controller(id_, moves) for id_ in range(10)),
human_controller(screen, moves), model(moves, state), view(state, screen))
await asyncio.wait(coros, return_when=asyncio.FIRST_COMPLETED)
async def random_controller(id_, moves):
while True:
d = random.choice(list(D))
moves.put_nowait((id_, d))
await asyncio.sleep(random.random())
async def human_controller(screen, moves):
global W, H, INFO
while True:
ch = screen.getch()
key_mappings = {259: D.n, 261: D.e, 258: D.s, 260: D.w}
if ch in key_mappings:
moves.put_nowait(('*', key_mappings[ch]))
elif ch == ord('w'):
H += 1
elif ch == ord('s'):
H -= 1
elif ch == ord('d'):
W += 2
elif ch == ord('a'):
W -= 2
elif ch == ord(' '):
INFO = not INFO
await asyncio.sleep(0.005)
async def model(moves, state):
while state['*'] not in {p for id_, p in state.items() if id_ != '*'}:
id_, d = await moves.get()
x, y = state[id_]
deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)}
state[id_] = P((x + deltas[d].x) % W, (y + deltas[d].y) % H)
async def view(state, screen):
while True:
offset = P(x=curses.COLS//2 - W//2, y=curses.LINES//2 - H//2)
screen.erase()
curses.textpad.rectangle(screen, offset.y-1, offset.x-1, offset.y+H, offset.x+W)
if INFO:
screen.addstr(0, 0, f'W:{W}, H:{H}')
for id_, p in state.items():
screen.addstr(offset.y + (p.y - state['*'].y + H//2) % H,
offset.x + (p.x - state['*'].x + W//2) % W, str(id_))
await asyncio.sleep(0.005)
if __name__ == '__main__':
curses.wrapper(main)
I've updated a game in a way that enables you to change the wait function and its parameters in-game: explorer.py settings.py
All the different "presets" are stored in settings.py file. Also all changes are automatically saved back to settings.py when the game is exited with ctrl-c.
Keys:
- q/a - Setting up/down,
- w/s - Size of the field up/down,
- e/d - Offset up/down (Number of seconds that a number is guarantied to pause for),
- r/f - Factor up/down (Factor that the result of a function is multiplied with),
- t/g - Function's first argument up/down,
- y/h - Function's second argument up/down, ...
- z - Restart the game,
- space - Hide parameters.
Disclamer: The script uses eval() to create wait functions from strings, so use at your own risk (Eval is evil :smiling_imp: ).
I have commited the following changes:
Size is now:
W, H = 15, 7
And sleep function for the numbers:
await asyncio.sleep(random.triangular(0.01, 0.65))