Skip to content

Instantly share code, notes, and snippets.

@gto76
Last active January 18, 2022 03:35
Show Gist options
  • Save gto76/7766d797bd9d282861e34e571a3348d3 to your computer and use it in GitHub Desktop.
Save gto76/7766d797bd9d282861e34e571a3348d3 to your computer and use it in GitHub Desktop.
A tool for finding good function (and its parameters) that determines how fast numbers move in asterisk and numbers game from coroutines example in [Comprehensive Python Cheatsheet](https://gto76.github.io/python-cheatsheet/#coroutines).
#!/usr/bin/env python3
#
# Usage: python3 explorer.py
#
# A tool for finding a good function (and its parameters) that determines how fast numbers move
# in asterisk and numbers game from coroutines example in Comprehensive Python Cheatsheet.
# (https://gto76.github.io/python-cheatsheet/#coroutines)
#
# Script needs a settings.py file in the same directory. It contains different "settings" or
# "presets". A setting is comprised of whidth, height, parameters and a function that
# accepts number's id and iteration index and must return the number of seconds that the number
# will pause for. All changes to the settings are saved when the program 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.
import asyncio, collections, curses, curses.textpad, enum, random, re, itertools, math, logging
from settings import settings
from decimal import Decimal
from time import sleep
P = collections.namedtuple('P', 'x y') # Position
D = enum.Enum('D', 'n e s w') # Direction
class Setting:
def __init__(self, w, h, offset, factor, args, func):
self.w = w
self.h = h
self.offset = Decimal(offset)
self.factor = Decimal(factor)
self.args = [Decimal(a) for a in args]
self.func = func
def get_duration(self, id_, i):
if not hasattr(self, '_func'):
self._func = eval(f'lambda id_, i, *a: {self.func}')
return float(self.offset) + \
float(self._func(id_, i, *[float(a) for a in self.args])) * \
float(self.factor)
def __str__(self):
sub_arg_with_value = lambda match: str(self.args[int(match.group(1))])
func_with_inserted_values = re.sub('a\[(\d+)\]', sub_arg_with_value, self.func)
full_func = f'{self.offset:.2f} + ({func_with_inserted_values}) * {self.factor:.2f}'
return f'w: {self.w}, h: {self.h}, func: {full_func}'
def to_dict(self):
return f"dict(w={self.w}, h={self.h}, offset='{self.offset}', " + \
f"factor='{self.factor}', args={[str(a) for a in self.args]}, " + \
f"func='{self.func}')"
info = True
s = [Setting(**wargs) for wargs in settings]
s_i = 0
logging.basicConfig(filename='example.log', level=logging.DEBUG)
def main(screen):
curses.curs_set(0) # Makes cursor invisible.
screen.nodelay(True) # Makes getch() non-blocking.
while True:
asyncio.run(main_coroutine(screen)) # Starts running asyncio code.
sleep(1)
async def main_coroutine(screen):
state = {'*': P(0, 0), **{id_: P(s[s_i].w//2, s[s_i].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):
for i in itertools.count(1):
d = random.choice(list(D))
moves.put_nowait((id_, d))
await asyncio.sleep(s[s_i].get_duration(id_, i))
async def human_controller(screen, moves):
global info, s_i
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('a'):
s_i += 1 if s_i < len(s)-1 else 0
elif ch == ord('q'):
s_i -= 1 if s_i != 0 else 0
elif ch == ord('w'):
if s[s_i].w / 2 > s[s_i].h:
s[s_i].h += 1
else:
s[s_i].w += 2
elif ch == ord('s'):
if s[s_i].w / 2 > s[s_i].h:
s[s_i].w -= 2
else:
s[s_i].h -= 1
elif ch == ord('e'):
s[s_i].offset += Decimal('0.01')
elif ch == ord('d'):
s[s_i].offset -= Decimal('0.01' if s[s_i].offset >= Decimal('0.01') else 0)
elif ch == ord('r'):
s[s_i].factor += Decimal('0.01')
elif ch == ord('f'):
s[s_i].factor -= Decimal('0.01' if s[s_i].factor >= Decimal('0.01') else 0)
elif change_parameter(ch, 0, ch_up='t', ch_down='g'):
pass
elif change_parameter(ch, 1, ch_up='y', ch_down='h'):
pass
elif change_parameter(ch, 2, ch_up='u', ch_down='j'):
pass
elif change_parameter(ch, 3, ch_up='i', ch_down='k'):
pass
elif ch == ord(' '):
info = not info
elif ch == ord('z'):
return
await asyncio.sleep(0.005)
def change_parameter(ch, par_i, ch_up, ch_down):
if ch == ord(ch_up) and len(s[s_i].args) > par_i:
s[s_i].args[par_i] += Decimal('0.01')
return True
elif ch == ord(ch_down) and len(s[s_i].args) > par_i:
s[s_i].args[par_i] -= Decimal('0.01' if s[s_i].args[par_i] >= Decimal('0.01') else 0)
return True
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) % s[s_i].w, (y + deltas[d].y) % s[s_i].h)
async def view(state, screen):
while True:
offset = P(x=curses.COLS//2 - s[s_i].w//2, y=curses.LINES//2 - s[s_i].h//2)
screen.erase()
curses.textpad.rectangle(screen, offset.y-1, offset.x-1, offset.y+s[s_i].h,
offset.x+s[s_i].w)
if info:
screen.addstr(0, 0, f'[setting {s_i+1}/{len(s)}] {s[s_i]}')
for id_, p in state.items():
screen.addstr(offset.y + (p.y - state['*'].y + s[s_i].h//2) % s[s_i].h,
offset.x + (p.x - state['*'].x + s[s_i].w//2) % s[s_i].w, str(id_))
await asyncio.sleep(0.005)
def write_to_file(filename, text):
with open(filename, 'w', encoding='utf-8') as file:
file.write(text)
def get_settings_str():
out = ['settings = [']
for setting in s:
out.append(f' {setting.to_dict()},')
out.append(']')
return '\n'.join(out)
if __name__ == '__main__':
try:
curses.wrapper(main)
except KeyboardInterrupt:
out = get_settings_str()
print(out)
write_to_file('settings.py', out)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment