Created
November 27, 2022 21:49
-
-
Save mincrmatt12/d520ecad64f195ca17f181adb6d2a512 to your computer and use it in GitHub Desktop.
craptype
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 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