python-cheatsheet icon indicating copy to clipboard operation
python-cheatsheet copied to clipboard

update coroutine example

Open ntjess opened this issue 3 years ago • 3 comments

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

ntjess avatar Jan 13 '22 17:01 ntjess

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)

gto76 avatar Jan 14 '22 04:01 gto76

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: ).

gto76 avatar Jan 18 '22 02:01 gto76

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))

gto76 avatar Feb 04 '22 17:02 gto76