Last active
April 16, 2023 15:10
-
-
Save immjs/d5bcbee9980c818717b1f87ce9ca337a to your computer and use it in GitHub Desktop.
numafs
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
import ion | |
import kandinsky | |
import micropython as mp | |
import time | |
prev_game_states = [-1] | |
game_state = 1 | |
rerender = False | |
levels = [ | |
('Bases', ( | |
("Inverse d'un nombre", { | |
"desc": "Nous apprendrons ici ce qu'est l'inverse d'un nombre et ses proprietes", | |
"prereq": ["N/a"], | |
"cards": [ | |
{ | |
"renderer": 0, | |
"content": "A chaque nombre, l'on associe un inverse par lequel l'on peut multiplier ce nombre pour toujours obtenir 1.", | |
}, | |
{ | |
"renderer": 0, | |
"content": """L'inverse de x se denote | |
$1/x$""", | |
}, | |
], | |
}), | |
)), | |
('Second degre', ()), | |
] | |
""" | |
Formatting will include: | |
0: Base | |
1: Text | |
2: Main | |
3: Crust | |
4: Surface2 | |
5: Overlay2 | |
6: Subtext0 | |
7: Pink | |
8: Mauve | |
9: Red | |
A: Peach | |
B: Yellow | |
C: Green | |
D: Sapphire | |
E: Blue | |
F: Lavender | |
Left digit will be bg, Right digit will be fg | |
""" | |
# Catppuccin Frappe | |
colors = ( | |
(48, 52, 70), | |
(198, 208, 245), | |
(239, 159, 118), # Peach | |
(35, 38, 52), | |
(98, 104, 128), | |
(148, 156, 187), | |
(165, 173, 206), | |
(244, 184, 228), | |
(202, 158, 230), | |
(231, 130, 132), | |
(239, 159, 118), | |
(229, 200, 144), | |
(166, 209, 137), | |
(133, 193, 220), | |
(140, 170, 238), | |
(186, 187, 241), | |
) | |
screen_dim = (32, 12) | |
""" | |
events will be a dictionnary which values will be lists of functions | |
""" | |
events = { | |
"keydown": {}, | |
"keyup": {}, | |
} | |
""" | |
intervals will be a list of 3-tuples of the form: | |
(next_execution_time, interval, function) | |
""" | |
intervals = [] | |
""" | |
delays will be a set of 2-tuples of the form: | |
(last_execution_time, function) | |
""" | |
delays = set() | |
state = { | |
"current": {}, | |
"permanent": {}, | |
} | |
key_states = set() | |
def add_event(evt_type, key, fn): | |
evt_type = "key" + evt_type | |
if evt_type == "keyup" and not key in events["keydown"]: | |
events["keydown"][key] = [] | |
if not key in events[evt_type]: | |
events[evt_type][key] = [] | |
events[evt_type][key].append(fn) | |
def remove_event(evt_type, key, fn): | |
evt_type = "key" + evt_type | |
del events[evt_type][key][events[evt_type][key].index(fn)] | |
if len(events[evt_type][key]) == 0: | |
del events[evt_type][key] | |
if (not key in events["keyup"] or len(events["keyup"][key]) == 0) and len(events["keydown"][key]) == 0: | |
del events["keydown"][key] | |
def add_interval(interval, function): | |
interval /= 1000 | |
intervals.append([time.monotonic() + interval, interval, function]) | |
def add_delay(delay, function): | |
delay /= 1000 | |
delays.add((time.monotonic() + delay, function)) | |
def reset_events(): | |
global events | |
global intervals | |
global delays | |
events["keydown"] = {} | |
events["keyup"] = {} | |
intervals = [] | |
delays = set() | |
no_graphics = False | |
def draw_str(text, y, x, form = None, magic_offset = True): | |
if no_graphics: | |
return text, y, x, form | |
global screen | |
global rerender | |
if form == None: | |
if y == 0: | |
form = 0x20 | |
elif y == 11: | |
form = 0x10 | |
else: | |
form = 0x01 | |
if y >= screen_dim[1]: | |
return | |
if x == 'CENTER': | |
x = (screen_dim[0] - len(text)) / 2 | |
offset = 3 if y == 11 and magic_offset else 0 | |
bg = form // 16 | |
fg = form % 16 | |
kandinsky.draw_string(text[0:screen_dim[0] - round(x)], int(x * 10), y * 18 + offset, colors[fg], colors[bg]) | |
return text, y, x, form | |
def long_text(text, y, x, w, h, form=0x01, rich=True): | |
lines = text.split('\n') | |
pos = 0 | |
for line in lines: | |
if rich: | |
center_contents = re.match(/^\$(.*[^\\]|)\$/, line) | |
if not center_contents is None: | |
print(center_contents) | |
words = line.split(' ') | |
while len(words) != 0: | |
acc = [] | |
while len(' '.join(acc)) <= w and len(words) != 0: | |
acc.append(words.pop(0)) | |
if len(' '.join(acc)) > w: | |
words.insert(0, acc.pop()) | |
draw_str(' '.join(acc), y + pos, x, form) | |
pos += 1 | |
if pos == h: | |
return pos | |
return pos | |
def invert_colors(form): | |
bg = form // 16 | |
fg = form % 16 | |
return fg * 16 + bg | |
def select_blink(prompts, state_kind=None, state_key='select_blink', default_state=0): | |
blink_idx = 0 | |
blink_state = default_state | |
if state_kind: | |
state[state_kind][state_key] = blink_state | |
def blink(x): | |
nonlocal blink_idx | |
nonlocal blink_state | |
if x != True: | |
blink_state = not blink_state | |
draw_str(prompts[blink_idx][0], prompts[blink_idx][1], prompts[blink_idx][2], prompts[blink_idx][3] if blink_state else invert_colors(prompts[blink_idx][3])) | |
add_interval(500, blink) | |
if len(prompts) > 1: | |
def ch_idx(x): | |
nonlocal blink_idx | |
nonlocal blink_state | |
draw_str(prompts[blink_idx][0], prompts[blink_idx][1], prompts[blink_idx][2], prompts[blink_idx][3]) | |
blink_idx += 1 if x[0] == ion.KEY_DOWN else -1 | |
blink_idx = max(0, min(blink_idx, len(prompts) - 1)) | |
if state_kind: | |
state[state_kind][state_key] = blink_idx | |
blink_state = True | |
blink(True) | |
add_event("down", ion.KEY_UP, ch_idx) | |
add_event("down", ion.KEY_DOWN, ch_idx) | |
def loading_screen(): | |
draw_str(" Matte ", 0, 'CENTER', 0x20) | |
select_blink(( | |
draw_str(" Niveaux ", 2, 'CENTER', 0x10), | |
)) | |
draw_str("Sortie [BACK]x2", 11, 0) | |
draw_str("Select. [OK]", 11, 20) | |
# WIDTH: 320 | |
# HEIGHT: 222 | |
def select_level(x): | |
global game_state | |
game_state = 1 | |
add_event("up", ion.KEY_OK, select_level) | |
add_event("up", ion.KEY_EXE, select_level) | |
def level_select(): | |
global levels | |
draw_str(" Matte ", 0, 'CENTER', 0x20) | |
draw_str("[BACK]x2 Sortie", 11, 0) | |
draw_str("Select. [OK]", 11, 20) | |
prompts = [] | |
for i in range(len(levels)): | |
prompts.append(draw_str("%s. %s" % (i + 1, levels[i][0]), 2 + i, 1, 0x01)) | |
def select_sublevel(x): | |
global game_state | |
game_state = 2 | |
add_event("up", ion.KEY_OK, select_sublevel) | |
add_event("up", ion.KEY_EXE, select_sublevel) | |
select_blink(prompts, "permanent", "level") | |
def sublevel_select(): | |
draw_str("[BACK] Retour", 11, 0) | |
draw_str("Select. [OK]", 11, 20) | |
level_index = state["permanent"]["level"] | |
draw_str(" %s. %s " % (level_index + 1, levels[level_index][0]), 0, 'CENTER', 0x20) | |
prompts = [] | |
for i in range(len(levels[level_index][1])): | |
prompts.append(draw_str("%s. %s" % (i + 1, levels[level_index][1][i][0]), 2 + i, 1, 0x01)) | |
def show_level(x): | |
global game_state | |
game_state = 3 | |
add_event("up", ion.KEY_OK, show_level) | |
add_event("up", ion.KEY_EXE, show_level) | |
select_blink(prompts, "permanent", "sublevel") | |
def lvl_launch(): | |
draw_str("[BACK] Retour", 11, 0) | |
draw_str("Lancer [OK]", 11, 21) | |
level_index = state["permanent"]["level"] | |
sublevel_index = state["permanent"]["sublevel"] | |
lvl_name, lvl_data = levels[level_index][1][sublevel_index] | |
draw_str(" %s.%s. %s " % (level_index + 1, sublevel_index + 1, lvl_name), 0, 'CENTER') | |
txt_h = 0 | |
if "prereq" in lvl_data: | |
draw_str(" Prerequis: %s " % ",".join(lvl_data["prereq"]), 2, 1, 0x20) | |
txt_h = long_text(lvl_data["desc"], 4, 1, screen_dim[0] - 2, 4) + 2 | |
else: | |
txt_h = long_text(lvl_data["desc"], 2, 1, screen_dim[0] - 2, 6) | |
select_blink(( | |
draw_str(" Lancer ", txt_h + 3, 'CENTER', 0x10), | |
)) | |
def play_level(x): | |
global game_state | |
game_state = 4 | |
add_event("up", ion.KEY_OK, play_level) | |
add_event("up", ion.KEY_EXE, play_level) | |
def player(): | |
level_index = state["permanent"]["level"] | |
sublevel_index = state["permanent"]["sublevel"] | |
lvl_name, lvl_data = levels[level_index][1][sublevel_index] | |
draw_str(" %s.%s. %s " % (level_index + 1, sublevel_index + 1, lvl_name), 0, 'CENTER') | |
if not "card" in state["current"]: | |
state["current"]["card"] = 0 | |
current_card_idx = state["current"]["card"] | |
if current_card_idx != 0: | |
draw_str("[<] Prec", 11, 0) | |
draw_str("%s/%s" % (current_card_idx + 1, len(lvl_data["cards"])), 11, 'CENTER') | |
draw_str("Suiv [>]", 11, 24) | |
def long_text_renderer(content): | |
long_text(content, 2, 1, screen_dim[0] - 2, 7) | |
renderers = [long_text_renderer] | |
current_card = lvl_data["cards"][current_card_idx] | |
renderers[current_card["renderer"]](current_card["content"]) | |
def card_change(x): | |
global game_state | |
global rerender | |
key, other_data = x | |
state["current"]["card"] += 1 if key == ion.KEY_RIGHT else -1 | |
if state["current"]["card"] == len(lvl_data["cards"]): | |
prev_game_states.pop() | |
prev_game_states.pop() | |
game_state = 5 | |
else: | |
state["current"]["card"] = max(state["current"]["card"], 0) | |
rerender = True | |
add_event("down", ion.KEY_LEFT, card_change) | |
add_event("down", ion.KEY_RIGHT, card_change) | |
def level_complete(): | |
level_index = state["permanent"]["level"] | |
sublevel_index = state["permanent"]["sublevel"] | |
lvl_name, lvl_data = levels[level_index][1][sublevel_index] | |
draw_str(" %s.%s. %s " % (level_index + 1, sublevel_index + 1, lvl_name), 0, 'CENTER') | |
draw_str(" Niveau Fini ", 2, 'CENTER') | |
draw_str("Appuyer sur [BACK] pour revenir", 4, 'CENTER') | |
handlers = [loading_screen, level_select, sublevel_select, lvl_launch, player, level_complete] | |
def default_layout(): | |
if no_graphics: | |
return | |
kandinsky.fill_rect(0, 0, 320, 222, colors[0]) | |
kandinsky.fill_rect(0, 0, 320, 18, colors[2]) | |
kandinsky.fill_rect(0, 198, 320, 24, colors[1]) | |
mp.kbd_intr(-1) | |
game_loop = True | |
while game_loop: | |
curr_time = time.monotonic() | |
if rerender: | |
default_layout() | |
handlers[game_state]() | |
rerender = False | |
elif game_state != prev_game_states[-1]: | |
print('Changed state to', game_state) | |
reset_events() | |
state["current"] = {} | |
events["keydown"][ion.KEY_BACK] = [] | |
def back(x): | |
global game_loop | |
global prev_game_states | |
global game_state | |
prev_game_states.pop() | |
game_state = prev_game_states.pop() | |
if len(prev_game_states) == 0: | |
mp.kbd_intr(ion.KEY_BACK) | |
game_loop = False | |
events["keyup"][ion.KEY_BACK] = [back] | |
default_layout() | |
handlers[game_state]() | |
prev_game_states.append(game_state) | |
# Keyup / Keydown handling | |
for key in events["keydown"].keys(): | |
if ion.keydown(key): | |
if key in events["keydown"] and not key in key_states: | |
for i in range(len(events["keydown"][key])): | |
events["keydown"][key][i]((key, events["keydown"][key][i])) | |
key_states.add(key) | |
elif key in key_states: | |
if key in events["keyup"]: | |
for i in range(len(events["keyup"][key])): | |
events["keyup"][key][i](events["keyup"][key][i]) | |
key_states.remove(key) | |
# Interval handling | |
for interval_idx in range(len(intervals)): | |
next_execution_time, interval, function = intervals[interval_idx] | |
if curr_time > next_execution_time: | |
function(intervals[interval_idx]) | |
intervals[interval_idx][0] += interval | |
# Delay handling | |
# Note that delays are not overwritten and are only deleted after one time use, thus removing | |
# the necessity of having an indexable iterable | |
for delay in delays: | |
execution_time, function = delay | |
if curr_time > execution_time: | |
function(delay) | |
delays.remove(delay) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment