Skip to content

Instantly share code, notes, and snippets.

@immjs
Last active April 16, 2023 15:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save immjs/d5bcbee9980c818717b1f87ce9ca337a to your computer and use it in GitHub Desktop.
Save immjs/d5bcbee9980c818717b1f87ce9ca337a to your computer and use it in GitHub Desktop.
numafs
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