Skip to content

Instantly share code, notes, and snippets.

@mincrmatt12
Created November 27, 2022 21:49
Show Gist options
  • Save mincrmatt12/d520ecad64f195ca17f181adb6d2a512 to your computer and use it in GitHub Desktop.
Save mincrmatt12/d520ecad64f195ca17f181adb6d2a512 to your computer and use it in GitHub Desktop.
craptype
import evdev
import curses
import string
import random
import math
import sys
import copy
import time
from selectors import DefaultSelector, EVENT_READ
# get the dictionary data
if len(sys.argv) < 2:
print("supply a dictionary file on stdarg")
exit(1)
dictionary = []
with open(sys.argv[1], 'r') as f:
for line in f:
if len(line) < 3:
continue
if '.' in line:
continue
dictionary.append(line.strip().lower())
# get a list of keyboards
kbds = []
for device in [evdev.InputDevice(x) for x in evdev.list_devices()]:
if "keyboard" in device.name.lower():
for x in kbds.copy():
if x.phys.split("/")[0] == device.phys.split("/")[0]:
kbds.remove(x)
kbds.append(device)
ksel = DefaultSelector()
for kbd in kbds:
ksel.register(kbd, EVENT_READ)
if not kbds:
print("no keyboards detected! you may need to run me as root")
# log
log = open("log.txt", "w")
_print = print
def print(*args, **kwargs):
global log
kwargs["file"] = log
kwargs["flush"] = True
_print(*args, **kwargs)
# helper routines
def getch(timeout=None):
while True:
for key, mask in ksel.select(timeout):
dev = key.fileobj
kindex = kbds.index(dev)
for event in dev.read():
if event.type == evdev.ecodes.EV_KEY and event.value == 1:
return event.code, kindex
if timeout is not None:
return (None, None)
def kchr(keycode):
"""
convert keycode to ascii, a la chr
"""
# really dirty hack
nm = evdev.ecodes.KEY[keycode]
mapping = {
evdev.ecodes.KEY_COMMA: ",",
evdev.ecodes.KEY_APOSTROPHE: "'",
evdev.ecodes.KEY_SPACE: " ",
evdev.ecodes.KEY_DOT: ".",
evdev.ecodes.KEY_MINUS: "-"
}
if len(nm) == 5:
return nm[-1].lower()
elif keycode in mapping:
return mapping[keycode]
else:
return "\x00"
# reset cursor and clear screen
stdscr = curses.initscr()
curses.start_color()
curses.use_default_colors()
curses.curs_set(0)
num_rows, num_cols = stdscr.getmaxyx()
curses.noecho()
for i in range(0, curses.COLORS):
curses.init_pair(i + 1, i, -1)
# clear screen
stdscr.clear()
MENU_WIDTH = 140
def menu_keyblob(menu_window, blobs):
"""
show the keyblobs
"""
PROMPT = "press a key on each keyboard"
PROMPT2 = "or press esc to disable other keyboards"
BLOBS = " ".join([[" o ", "[O]"][x] for x in blobs])
menu_window.addstr(30, int((MENU_WIDTH / 2) - len(PROMPT) / 2), PROMPT, curses.A_BOLD)
menu_window.addstr(31, int((MENU_WIDTH / 2) - len(PROMPT2) / 2), PROMPT2, curses.A_BOLD)
menu_window.addstr(36, int((MENU_WIDTH / 2) - len(BLOBS) / 2), BLOBS, curses.color_pair(sum(int(x) for x in blobs) + 4))
def menu_header(menu_window):
"""
show menu header
"""
HEADER = "CrapType -- the only typing game that requires more than one keyboard!"
KEYTEXT = "detected {} keyboards".format(len(kbds))
menu_window.border()
menu_window.addstr(5, int((MENU_WIDTH / 2) - len(HEADER) / 2), HEADER, curses.color_pair(random.randint(1, 16)))
menu_window.addstr(7, int((MENU_WIDTH / 2) - len(KEYTEXT) / 2), KEYTEXT)
def menu(final=False):
"""
display a menu in a window
"""
PROMPT = "press a key to start or escape to exit"
menu_window = curses.newwin(50, MENU_WIDTH, 0, 0)
blobs = [int(final) for x in kbds]
final = False
while sum(blobs) != len(kbds) or not final:
menu_window.clear()
menu_header(menu_window)
if sum(blobs) != len(kbds):
menu_keyblob(menu_window, blobs)
else:
menu_window.addstr(30, int((MENU_WIDTH / 2) - len(PROMPT) / 2), PROMPT, curses.A_BOLD | curses.color_pair(curses.COLOR_GREEN))
stdscr.refresh()
menu_window.refresh()
if sum(blobs) != len(kbds):
k, i = getch()
blobs[i] = 1
if k == evdev.ecodes.KEY_ESC:
indices = sorted([x for x, j in enumerate(blobs) if j == 0], reverse=True)
for poo in indices:
del kbds[poo]
else:
k, _ = getch()
if k == evdev.ecodes.KEY_ESC:
raise ValueError("exit")
final = True
del menu_window
SCORE_WIDTH = 14
SCORE_HEIGHT = 50
GAME_WIDTH = 100
ENTER_LINE = 40
dialog = None
dialog_window = None
def start_dialog(text, color):
global dialog, dialog_window
CLOSE = "press enter to close"
if len(text) < len(CLOSE) + 4:
text += " " * (len(CLOSE) + 4 - len(text))
dialog = text, color
if dialog_window:
del dialog_window
dialog_window = None
dialog_window = curses.newwin(5, (len(text) + 4), int(SCORE_HEIGHT / 2 - 3), int(GAME_WIDTH / 2 - (len(text) + 4) / 2))
dialog_window.border()
dialog_window.addstr(2, 2, dialog[0], curses.color_pair(dialog[1]))
dialog_window.addstr(4, len(text) + 2 - len(CLOSE), CLOSE)
def do_dialog():
global dialog, dialog_window
if dialog is None:
return False
else:
dialog_window.touchwin()
dialog_window.refresh()
while True:
k, _ = getch()
if k == 28: # enter
break
dialog_window.clear()
stdscr.refresh()
dialog_window.refresh()
del dialog_window
dialog_window = None
dialog = None
return True
def game_score(score_window, score, score_history, ui_color=7):
"""
draw the score in a window
"""
if score_history[-1] != score:
score_history.append(score)
HEADER = "score"
MSG = str(score)
SCORE_HISTORY_START = 25
HISTORICAL = []
x0 = score_history[-1]
for j, x1 in enumerate(reversed(score_history[-7:-1])):
HISTORICAL.append((format(x0 - x1, "+"), 245 - j * 2))
x0 = x1
SCORE_START = 30
score_window.erase()
score_window.border()
score_window.addstr(SCORE_START, int((SCORE_WIDTH / 2) - len(HEADER) / 2), HEADER, curses.A_BOLD | curses.color_pair(ui_color))
score_window.addstr(SCORE_START + 2, int((SCORE_WIDTH / 2) - len(MSG) / 2), MSG)
y = SCORE_HISTORY_START
for x, c in HISTORICAL:
score_window.addstr(y, int((SCORE_WIDTH / 2) - len(x) / 2), x, curses.color_pair(c))
y -= 1
def game_level(score_window, level, ui_color=7):
"""
draw the level to the score window
"""
HEADER = "level"
MSG = str(level+1)
SCORE_START = 35
score_window.addstr(SCORE_START, int((SCORE_WIDTH / 2) - len(HEADER) / 2), HEADER, curses.A_BOLD | curses.color_pair(ui_color))
score_window.addstr(SCORE_START + 2, int((SCORE_WIDTH / 2) - len(MSG) / 2), MSG)
alarms = []
alarmto = None
def _realarm():
global alarms, alarmto
c = time.time()
if not alarms:
alarmto = None
else:
alarmto = max(0.0, min(x - c for x, _ in alarms))
print("realarm picking {}".format(alarmto))
def alarm_in(timeout, fun):
"""
run fun in timeout seconds
"""
global alarms, alarmto
alarms.append([time.time() + timeout, fun])
_realarm()
def alarm_at(tim, fun):
"""
run fun at tim
"""
global alarms, alarmto
alarms.append([tim, fun])
_realarm()
def rm_alarm(fun):
global alarms
delme = None
for j, (_, v) in enumerate(alarms):
if v == fun:
delme = j
if delme is not None:
del alarms[delme]
_realarm()
def run_alarms():
"""
run the alarms
"""
global alarms
c = time.time()
done = []
ac = alarms.copy()
for j, (x, f) in enumerate(ac):
if x < c:
f()
done.append(j)
for i in sorted(done, reverse=True):
del alarms[i]
_realarm()
flashflag = None
def game_flash(window, color):
global flashflag
"""
draw the flash
"""
flashflag = False
if color == flashflag:
return
flashflag = color
attr = curses.color_pair(color)
for y in range(1, SCORE_HEIGHT - 1):
window.addstr(y, 1, '█', attr)
window.addstr(y, GAME_WIDTH - 2, '█', attr)
for x in range(1, GAME_WIDTH - 1):
window.addstr(1, x, '█', attr)
window.addstr(SCORE_HEIGHT - 2, x, '█', attr)
def def_compute_word_points(word, entered):
"""
compute the states of each letter:
0 - unentered
1 - correct
2 - wrong keyboard
3 - incorrect
4 - incorrect, wrong keyboard
--- MODE 1 --- normal mode function
"""
runcycle = [0 for x in kbds]
out = [0 for x in word]
for i in range(len(entered)):
key, board = entered[i]
if sum(runcycle) == len(kbds):
runcycle = [0 for x in kbds]
wrongboard = runcycle[board]
runcycle[board] = 1
wrongkey = key != word[i]
out[i] = {
(True, True): 4,
(False, False): 1,
(False, True): 2,
(True, False): 3
}[wrongkey, wrongboard]
print(out, word, entered)
return out
def alt1_compute_word_points(word, entered):
"""
compute the states for mode 2
double/triple/etc. entry
"""
out = [0 for x in word]
for j, datas in enumerate(zip(*(entered[x::len(kbds)] for x in range(len(kbds))))):
wrongkey = any(x[0] != word[j] for x in datas)
wrongboard = len(datas) != len(kbds) or len(set(x[1] for x in datas)) != len(kbds)
out[j] = {
(True, True): 4,
(False, False): 1,
(False, True): 2,
(True, False): 3
}[wrongkey, wrongboard]
return out
# score function
def compute_score(word_points, time_elapsed, cps=2):
"""
compute score:
each character is worth 80 points with no errors
wrong letters are worth 0 points
wrong keyboards are worth 40 points, then 20 points, then 10, etc. until 0
wrong both are -5 points each.
80 points is for cps 8.33, divide length by time elapsed
"""
factor = min(1.2, max(0.5, len(word_points) / (time_elapsed * cps)))
tally = 60
score = 0
for i in word_points:
if not i:
continue
if i == 1:
score += 80 * factor
elif i == 2:
tally /= 2
score += tally
elif i == 3:
continue
else:
score -= 5
return int(score)
def def_compute_length_multiplier():
return 1
def alt1_compute_length_multiplier():
return len(kbds)
def game_draw_text(window, word, entered):
"""
draw the text part of the screen (the word)
"""
states = compute_word_points(word, entered)
colortable = {
0: 8,
1: 12,
2: 14,
3: 10,
4: 2
}
# move cursor to beginning
word_x = int(GAME_WIDTH / 2 - len(word) / 2)
window.move(ENTER_LINE, word_x)
for x, c in zip(word, states):
window.addstr(x, curses.color_pair(colortable[c]))
if len(entered) < len(word):
window.addstr(ENTER_LINE + 1, word_x + int(len(entered) / compute_length_multiplier()), "^")
def game_draw_nextword(window, word):
window.addstr(ENTER_LINE + 4, int(GAME_WIDTH / 2 - len(word) / 2), word, 242)
# keytrails -- animated characters used for a variety of things
keytrails = {} # structure, list of lists: [(list of positions), length, (character, attr), index]
kt_count = 0
def game_keytrails(window):
global keytrails
for pos, length, (char, attr), idx in keytrails.values():
if type(attr) is list or type(attr) is tuple:
attr = attr[idx]
if type(char) is list or type(char) is list:
char = char[idx]
print(pos, char, attr)
window.addstr(pos[idx][0], pos[idx][1], char, attr)
def _kt_alarm(idx):
global keytrails
keytrails[idx][3] += 1
if keytrails[idx][3] == len(keytrails[idx][0]):
del keytrails[idx]
else:
alarm_in(keytrails[idx][1] / len(keytrails[idx][0]), lambda: _kt_alarm(idx))
# keytrail generation functions
def keytrail_line(start, end, tim, char, attr=None):
"""
send a keytrail in a line form start to end in tim seconds
"""
global keytrails, kt_count
PRECISION = (end[0] - start[0])**2 + (end[1] - start[1]) **2
PRECISION = int(math.sqrt(PRECISION) / 1.5)
kt_count += 1
interp = (end[0] - start[0]) / PRECISION, (end[1] - start[1]) / PRECISION
pos = [(int(start[0] + interp[0] * x), int(start[1] + interp[1] * x)) for x in range(PRECISION + 1)]
keytrails[kt_count] = [pos, tim, (char, attr), -1]
_kt_alarm(kt_count)
def keytrail_fade(pos, chars, attr, tim):
"""
generate a keytrail that just stays in place animating a character without attrs
"""
global keytrails, kt_count
pos = [pos for x in chars]
kt_count += 1
keytrails[kt_count] = [pos, tim, (chars, attr), -1]
_kt_alarm(kt_count)
def keytrail_attacks(characters, holepos, tim):
word_x = int(GAME_WIDTH / 2 - len(characters) / 2)
for j, (char, pos) in enumerate(zip(characters, holepos)):
x_start = word_x + j
y_start = ENTER_LINE + 1
keytrail_line((y_start, x_start), pos, tim, char, curses.color_pair(28))
def keytrail_invalids(states, word):
global keytrails, kt_count
word_x = int(GAME_WIDTH / 2 - len(word) / 2)
TEMPLATE = [
[
[0, 0],
[0, 1],
[0, 2],
[0, 2],
[0, 3],
[0, 3],
[0, 4],
[0, 4],
[0, 4],
[0, 5],
[0, 5],
[0, 5],
[0, 5],
[0, 5]
],
0.5,
[' ',
[2, 2, 2, 2, 197, 197, 197, 239, 239, 239, 238, 238, 237, 237, 235, 235]
],
-1]
for j, (x, c) in enumerate(zip(states, word)):
if x in [3, 4]:
kt = copy.deepcopy(TEMPLATE)
for y in range(len(TEMPLATE[0])):
kt[0][y][0] = int(kt[0][y][1] / 2 + ENTER_LINE+2)
kt[0][y][1] = word_x + j
kt[2][0] = c
for y in range(len(TEMPLATE[0])):
kt[2][1][y] = curses.color_pair(kt[2][1][y])
kt_count += 1
keytrails[kt_count] = kt
_kt_alarm(kt_count)
# wallofdoom
# stored as 2d array
def game_wallofdoom(window, arr):
"""
draw the WALL OF DOOM
"""
y = 1
texts = [''.join('█' if y else ' ' for y in x) for x in arr]
for i in texts:
window.addstr(y, 1, i, curses.color_pair(3))
window.addstr(y + 1, 1, i, curses.color_pair(3))
y += 2
# wod manipulation
def push_wallofdoom(arr):
"""
add row to wallofdoom
"""
arr.insert(0, [True for x in range(GAME_WIDTH - 2)])
def find_good_holes(arr, holes):
"""
find places that can be removed given a set of characters (n units wide)
return list of x-pos at most length arr[0]
"""
if not arr:
return []
valid = [x for x, j in enumerate(arr[-1]) if j]
holepos = []
i = 0
while len(valid) and i < len(holes):
hole_start = random.choice(valid)
for j in reversed(range(holes[i])):
try:
valid.remove(hole_start + j)
except ValueError:
continue
holepos.append(hole_start)
i += 1
return holepos
def wod_punchholes(arr, holepos, holes, dryrun=False):
"""
punch holes in the wall of doom
returns the places that were cleared
"""
clears = []
for j, x in enumerate(holepos):
try:
width = holes[j]
except IndexError:
break
for y in range(x, x+width):
clears.append(y)
cleared = []
for clear in clears:
try:
if clear >= GAME_WIDTH or clear < 0:
continue
g = -1
print(clear, g, len(arr))
while abs(g) <= len(arr) and not arr[g][clear]:
g -= 1
if abs(g) > len(arr):
continue
if not dryrun:
arr[g][clear] = False
cleared.append(((len(arr)+g)*2 + 1, clear + 1)) # pos
cleared.append(((len(arr)+g)*2 + 2, clear + 1)) # pos
except IndexError:
continue
if not dryrun:
while arr and sum(arr[-1]) <= 1:
del arr[-1]
return cleared
# mode text
def game_modetext(game_window, mode):
TEXT = MODE_NAMES[mode]
game_window.addstr(SCORE_HEIGHT - 1, GAME_WIDTH - len(TEXT) - 3, TEXT)
# level table
LEVEL_CPM = [
2,
4,
5,
6,
7,
7,
8,
8,
8,
8,
8
]
LEVEL_COUNT = 11
LEVEL_WALLSPEED = [
10,
7,
6,
5,
5,
5,
5,
4,
4,
4,
3,
3
]
LEVEL_BOUNDARY = 30000
LEVEL_MODES = [
[1],
[1],
[1, 2],
[1, 2],
[1, 2],
[1, 2, 3],
[1, 2, 3],
[1, 2, 3],
[1, 2, 3],
[1, 2, 3, 4],
[1, 2, 3, 4]
]
MODE_NAMES = [
"<null>",
"cycle",
"duplicate",
"cycleboard",
"echo"
]
MODE_TEXTS = [
"you dun broke me",
"this should never appear",
"in this mode, you must press each key on every keyboard before moving on to the next letter",
"in this mode, type each word on a different keyboard",
"in this mode, type the word multiple times, but interleaved (1:1, 1:2, 2:1, 1:3, 2:2 b:i)"
]
compute_word_points = None
compute_length_multiplier = None
def update_fptrs(mode):
global compute_word_points, compute_length_multiplier
compute_word_points = {
1: def_compute_word_points,
2: alt1_compute_word_points,
3: def_compute_word_points, # todo
4: def_compute_word_points
}[mode]
compute_length_multiplier = {
1: def_compute_length_multiplier,
2: alt1_compute_length_multiplier,
3: def_compute_length_multiplier, # todo
4: def_compute_length_multiplier
}[mode]
def game():
"""
run the game
"""
score = 0
word = random.choice(dictionary)
nextword = random.choice(dictionary)
entered = []
score_history = [0]
ui_color = random.randint(3, 15)
flashcolor = 1
strt = time.time()
wod = [] # wall of doom
level = 0
lastlevel = 0
mode = 1
seenmode = [mode]
update_fptrs(mode)
tutstate = 0
score_window = curses.newwin(SCORE_HEIGHT, SCORE_WIDTH, 0, GAME_WIDTH+1)
game_window = curses.newwin(SCORE_HEIGHT, GAME_WIDTH, 0, 0)
# flash routines
def flash(color):
nonlocal flashcolor
flashcolor = color
print("ded")
if color != 1:
alarm_in(0.2, lambda: flash(1))
print("alarming")
# tutorial routines
def start_tutorial_maybe():
nonlocal tutstate
if tutstate == 0:
start_dialog("type the first letter on any keyboard", 6)
rm_alarm(update_wod)
tutstate = 1
def update_tutorial():
nonlocal tutstate, strt
if tutstate == 2:
start_dialog("now type the next letter on a different keyboard", 6)
tutstate = 3
elif tutstate == 4:
start_dialog("keep going to the end of the word", 6)
tutstate = 5
elif tutstate == 6:
start_dialog("now press enter", 6)
strt = time.time()
tutstate = 7
elif tutstate == 8:
start_dialog("keep going, and make sure the wall of doom doesn't hit you", 12)
update_wod()
tutstate = 20
elif tutstate == 10:
start_dialog("well, you pressed the wrong key. you can see that in the color.", 10)
tutstate = 2
alarm_in(0, update_tutorial)
elif tutstate == 11:
start_dialog("well, you used the wrong keyboard. you can see that in the color.", 10)
tutstate = 4
elif tutstate == 21:
start_dialog("the game is now in duplicate mode. now you must type each letter on each keyboard.", 6)
strt = time.time()
tutstate = -1
def update_wod():
push_wallofdoom(wod)
alarm_in(LEVEL_WALLSPEED[level], update_wod)
def break_holes(holes, holepos):
clears = wod_punchholes(wod, holepos, holes)
for x in clears:
keytrail_fade(x, ['█', '▓', '▒', '░'], curses.color_pair(3), 0.3)
alarm_in(2.0, start_tutorial_maybe)
print(word)
alarm_in(LEVEL_WALLSPEED[level] * len(kbds), update_wod)
if len(kbds) == 1:
start_dialog("with only one keyboard, this game plays like any other typing game", 6)
while True:
# ... draw windows ...
game_score(score_window, score, score_history, ui_color)
game_level(score_window, level, ui_color)
game_window.erase()
game_window.border()
# ... game screen underlays ...
game_flash(game_window, flashcolor)
# ... game elements ...
game_draw_text(game_window, word, entered)
game_draw_nextword(game_window, nextword)
game_wallofdoom(game_window, wod)
game_modetext(game_window, mode)
# ... game screen overlays
game_keytrails(game_window)
# ... update screen ...
stdscr.refresh()
game_window.refresh()
score_window.refresh()
if do_dialog():
game_window.touchwin()
# ... game logic ...
key, board = getch(alarmto)
run_alarms()
if len(wod) >= (ENTER_LINE) / 2:
time.sleep(1.0)
# animate ending
for i in range(1, SCORE_HEIGHT-1):
game_window.addstr(i, 1, '█' * GAME_WIDTH, curses.color_pair(10))
stdscr.refresh()
game_window.refresh()
time.sleep(0.02)
time.sleep(0.5)
del game_window
del score_window
return score
if key is None:
# continue here so alarms can modify the screen
continue
if kchr(key) in string.ascii_lowercase + " '-.,/":
# enter a key
if len(entered) < len(word) * compute_length_multiplier():
entered.append([kchr(key), board])
else:
flash(10)
if len(entered) == 1:
# did just enter the first character
strt = time.time()
# tutorial crap
if tutstate == 0:
tutstate = -1 # user knows what to do, don't show the tutorial
elif tutstate == 1 and kchr(key) == word[0]:
tutstate = 2
alarm_in(0.2, update_tutorial)
elif tutstate == 1:
tutstate = 10
alarm_in(0.2, update_tutorial)
if tutstate == 3 and (board != entered[0][1] or len(kbds) == 1):
tutstate = 4
alarm_in(0.2, update_tutorial)
elif tutstate == 3:
tutstate = 11
alarm_in(0.2, update_tutorial)
elif tutstate == 5 and len(entered) == len(word):
tutstate = 6
alarm_in(0.2, update_tutorial)
elif key == evdev.ecodes.KEY_BACKSPACE:
if len(entered) == 0:
flash(10)
else:
entered.pop(-1)
elif key == evdev.ecodes.KEY_ENTER:
# increase score
score += compute_score(compute_word_points(word, entered), time.time() - strt, LEVEL_CPM[level] / (len(kbds) / 1.5))
keytrail_invalids(compute_word_points(word, entered), word)
# fire the texts
holes = [random.randint(2, 4) for x in compute_word_points(word, entered) if x == 1]
if holes:
TIME = 0.55
holepos = find_good_holes(wod, holes)
try:
keytrail_attacks([x for j, x in zip(compute_word_points(word, entered), word) if j == 1], wod_punchholes(wod, holepos, holes, True)[::2], TIME)
except ZeroDivisionError:
pass
finally:
alarm_in(TIME, lambda: break_holes(holes, holepos))
if score - lastlevel > LEVEL_BOUNDARY:
level += 1
if level > LEVEL_COUNT:
level -= 1
flash(random.randint(1, 255))
alarm_in(0.4, lambda: flash(random.randint(1, 255)))
alarm_in(0.8, lambda: flash(random.randint(1, 255)))
lastlevel = score
if len(LEVEL_MODES[level]) > 1 and tutstate == 20:
tutstate = 21
mode = 2
update_fptrs(mode)
alarm_in(0.2, update_tutorial)
else:
oldmode = mode
mode = random.choice(LEVEL_MODES[level])
if oldmode != mode:
update_fptrs(mode)
if mode not in seenmode:
start_dialog(MODE_TEXTS[mode], 174)
seenmode.append(mode)
# generate a new word
word = nextword[:]
nextword = random.choice(dictionary)
ui_color = random.randint(3, 15)
entered = []
# tutorial
if tutstate == 7:
tutstate = 8
alarm_in(0.2, update_tutorial)
elif key == evdev.ecodes.KEY_ESC:
start_dialog("paused (use home to exit)", 244)
elif key == evdev.ecodes.KEY_HOME:
del game_window
del score_window
return score # die
# gameover screen
def gameover(finalscore):
"""
show gameover screen
"""
go_window = curses.newwin(50, MENU_WIDTH, 0, 0)
MSG = "you died"
SCORE = "final score: {}".format(finalscore)
PROMPT = "press enter to return to the menu"
showed_at = time.time()
while True:
go_window.clear()
go_window.border()
go_window.addstr(6, int(MENU_WIDTH / 2 - len(MSG) / 2), MSG, curses.A_BOLD | curses.color_pair(10))
go_window.addstr(20, int(MENU_WIDTH / 2- len(SCORE) / 2), SCORE, random.randint(2, 15))
go_window.addstr(45, int(MENU_WIDTH / 2- len(PROMPT) / 2), PROMPT, random.randint(2, 15))
stdscr.refresh()
go_window.refresh()
k, _ = getch()
if k == evdev.ecodes.KEY_ENTER and time.time() - showed_at > 3.5:
break
del go_window
try:
menu()
stdscr.clear()
while True:
finalscore = game()
stdscr.clear()
stdscr.refresh()
gameover(finalscore)
try:
menu(True)
except:
break
finally:
# exit
curses.flushinp() # flush the character out of the buffer
curses.echo()
curses.endwin()
print = _print
print("Copyright (c) 2019 Matthew Mirvish. All rights reserved")
print("Licensed under the GPLv3")
print("Thank you for playing!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment