Last active
January 18, 2022 03:35
-
-
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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