sudo-noclip 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
main.py ADDED
@@ -0,0 +1,1433 @@
1
+ from __future__ import annotations
2
+ from typing import cast
3
+ import time
4
+ import os
5
+ import random
6
+ import shutil
7
+ import math
8
+ import sys
9
+ from numba import njit, prange
10
+ import numpy as np
11
+ from functools import lru_cache
12
+
13
+ class Terminal:
14
+ def __init__(self):
15
+ self.plat = sys.platform
16
+ self.fd = None
17
+ self.orig = None
18
+
19
+ if self.plat != "win32":
20
+ import termios
21
+ import tty
22
+
23
+ self.fd = sys.stdin.fileno()
24
+ self.orig = termios.tcgetattr(self.fd)
25
+ tty.setcbreak(self.fd)
26
+
27
+ def hide(self):
28
+ print("\033[?25l", end="", flush=True)
29
+
30
+ def show(self):
31
+ print("\033[?25h", end="", flush=True)
32
+ self.restore()
33
+
34
+ def restore(self):
35
+ if self.plat != "win32" and self.fd is not None and self.orig is not None:
36
+ import termios
37
+ try:
38
+ termios.tcsetattr(self.fd, termios.TCSADRAIN, self.orig)
39
+ except Exception:
40
+ pass
41
+
42
+ def get_char(self) -> str:
43
+ if sys.platform == "win32":
44
+ import msvcrt
45
+ if msvcrt.kbhit():
46
+ ch = msvcrt.getch()
47
+ while msvcrt.kbhit():
48
+ msvcrt.getch()
49
+ return ch.decode("utf-8", errors="ignore")
50
+ return ""
51
+ else:
52
+ import select
53
+ import termios
54
+
55
+ if self.fd is not None and select.select([self.fd], [], [], 0)[0]:
56
+ ch = os.read(self.fd, 1).decode("utf-8", errors="ignore")
57
+ termios.tcflush(self.fd, termios.TCIFLUSH)
58
+ return ch
59
+ return ""
60
+
61
+ class Matrix():
62
+ def __init__(self, raw: list[list[float]], angle: float | None = None):
63
+ if angle is not None:
64
+ self.raw = [[math.cos(angle), -math.sin(angle)],
65
+ [math.sin(angle), math.cos(angle)]]
66
+ else:
67
+ self.raw = raw
68
+
69
+ def __matmul__(self, other: Matrix | Vec): # type: ignore
70
+ if isinstance(other, Vec):
71
+ out = (self.raw[0][0] * other.raw[0] + self.raw[0][1] * other.raw[1], self.raw[1][0] * other.raw[0] + self.raw[1][1] * other.raw[1])
72
+ return Vec(out)
73
+ else:
74
+ out = [[self.raw[0][0] * other.raw[0][0] + self.raw[0][1] * other.raw[1][0], self.raw[0][0] * other.raw[0][1] + self.raw[0][1] * other.raw[1][1]],
75
+ [self.raw[1][0] * other.raw[0][0] + self.raw[1][1] * other.raw[1][0], self.raw[1][0] * other.raw[0][1] + self.raw[1][1] * other.raw[1][1]]]
76
+ return Matrix(out)
77
+
78
+ class Vec():
79
+ def __init__(self, raw: tuple[float, float]):
80
+ self.raw = raw
81
+
82
+ def __mul__(self, other: float):
83
+ return Vec((self.raw[0] * other, self.raw[1] * other))
84
+
85
+ def __add__(self, other):
86
+ return Vec((self.raw[0] + other.raw[0], self.raw[1] + other.raw[1]))
87
+ def __sub__(self, other):
88
+ return Vec((self.raw[0] - other.raw[0], self.raw[1] - other.raw[1]))
89
+
90
+ def col(x: str, r: float, g: float, b: float, block: bool = False) -> str:
91
+ return f"\033[{48 if block else 38};2;{min(255, round(r))};{min(255, round(g))};{min(255, round(b))}m{x}\033[0m"
92
+
93
+ def clear():
94
+ os.system("clear" if sys.platform!="win32" else "cls")
95
+
96
+ def guide(term: Terminal):
97
+ clear()
98
+
99
+ out = col("[GUIDE]", 255, 0, 0)
100
+ out+="\n\n"+col("[1]", 255, 255, 0)+"\tFear\n"+col("[2]", 255, 255, 0)+"\tStability\n"+col("[3]", 255, 255, 0)+"\tCorrupted Fragments\n"+col("[4]", 255, 255, 0)+"\tPockets\n"+col("[5]", 255, 255, 0)+"\tRifts\n"+col("[6]", 255, 255, 0)+"\tTrapped Rifts\n\n"+col("[7]", 255, 255, 0)+"\tBack\n"+"\n\nSelect an option (1-7)"
101
+
102
+ for ch in out:
103
+ print(ch, end="", flush=True)
104
+ time.sleep(0.005)
105
+
106
+ while True:
107
+ try:
108
+ opt = min(max(int(term.get_char()), 1), 7)
109
+ clear()
110
+ match opt:
111
+ case 1:
112
+ out = col("[FEAR]", 255, 0, 0)
113
+ out+="\n\nFear is a metric influenced by anomalies and entities.\nIt can be used to navigate safely.\nAs a rule of thumb: if your Fear spikes whilst exploring in a direction, "+col("turn back immediately.", 255, 0, 0)
114
+ case 2:
115
+ out = col("[STABILITY]", 255, 0, 0)
116
+ out+="\n\nThe Dimension Stability indicates the structural integrity of your current dimension.\nIf your presence drives the dimension into instability, the "+col("?????????", 255, 0, 0)+" will rip, causing a Rift to form and the dimension to corrupt.\n."
117
+ out+="An unstable dimension may show signs of corruption prior to tearing, repairing itself once relieved of human influence."
118
+ case 3:
119
+ out = col("[CORRUPTED FRAGMENTS]", 255, 0, 0)
120
+ out+="\n\nCorrupted Fragments are notes left behind by past visitors.\nDue to their time spent in the "+col("?????????", 255, 0, 0)+", these notes are often fragmented and corrupted, hence the name."
121
+ out+="\nFinding and recovering these yields information about the past experiences of others, which may be useful."
122
+ case 4:
123
+ out = col("[POCKETS]", 255, 0, 0)
124
+ out+="\n\nPockets are spatial anomalies containing items left behind by previous visitors.\nAppearing as jagged tears in the world's geometry, they mimic the colour of adjacent walls yet possess a visibly distorted and unnatural form."
125
+ out+="\nThey react to the physical proximity of a human, opening automatically when approached."
126
+ case 5:
127
+ out = col("[RIFTS]", 255, 0, 0)
128
+ out+="\n\nRifts are physical tears in the "+col("?????????", 255, 0, 0)+", caused when a Dimension becomes unstable due to human presence.\nUnlike Pockets, Rifts take on a blinding white colour and serve as doorways to other dimensions."
129
+ out+="\nIn the past, Rifts required decades to form due to the immense stability of the "+col("?????????", 255, 0, 0)+".\nHowever, as of late, Rifts have begun occurring alarmingly often; the cause remains unknown."
130
+ case 6:
131
+ out = col("[TRAPPED RIFTS]", 255, 0, 0)
132
+ out+="\n\nTrapped Rifts appear identical to Rifts when viewed from afar, but drastically change colour when approached.\nThese form when an organic anomaly is forced to tear open a dimension, typically by a higher power."
133
+ out+="\nAlthough rare, one would still advise caution towards Rifts in the event that they are traps."
134
+ case 7:
135
+ clear()
136
+ return
137
+ out+="\n\n\nPress "+col("[B]", 255, 255, 0)+" to return to the Main Menu."
138
+ for ch in out:
139
+ print(ch, end="", flush=True)
140
+ time.sleep(0.005)
141
+
142
+ while term.get_char().lower()!="b":
143
+ time.sleep(0.05)
144
+ clear()
145
+ return
146
+ except:
147
+ pass
148
+
149
+ clear()
150
+
151
+ def help(term: Terminal):
152
+ clear()
153
+
154
+ out = col("[HELP]", 255, 0, 0)
155
+ out+="\n\nControls:\nUse the "+col("[WASD]", 0, 255, 0)+" keys to move around,\n"+col("[O]", 0, 255, 0)+" to turn to the left,\nand "+col("[P]", 0, 255, 0)+" to turn to the right.\nTravelling through water will cause you to move slower."
156
+ out+="\n\nStatus Bar:\nThe Status Bar at the bottom of the display tracks vital information.\nIt displays core controls, technical info, "+col("[Fear]", 0, 255, 0)+" Level, and Dimension "+col("[Stability]", 0, 255, 0)+"."
157
+ out+="\n\nObjective:\nYou have noclipped into the "+col("?????????", 255, 0, 0)+" and must find a way out.\nSurvive the "+col("??????", 255, 0, 0)+", recover information, and find Rifts to navigate your way out."
158
+ out+="\n\n\nPress "+col("[B]", 255, 255, 0)+" to return to the Main Menu."
159
+
160
+ for ch in out:
161
+ print(ch, end="", flush=True)
162
+ time.sleep(0.005)
163
+
164
+ while term.get_char().lower()!="b":
165
+ time.sleep(0.05)
166
+ clear()
167
+
168
+ sett = {"raycast": 8, "quant": 1/128}
169
+
170
+ def settings(term: Terminal):
171
+ clear()
172
+
173
+ out = col("[SETTINGS]", 255, 0, 0)
174
+ out+="\n\nThis is a very CPU-intensive game. Adjust your settings accordingly.\n\n"+col("[1]", 255, 255, 0)+"\tRaycasting\n"+col("[2]", 255, 255, 0)+"\tColour Quantisation\n\n"+col("[3]", 255, 255, 0)+"\tBack"
175
+
176
+ for ch in out:
177
+ print(ch, end="", flush=True)
178
+ time.sleep(0.005)
179
+
180
+ while True:
181
+ try:
182
+ opt = min(max(int(term.get_char()), 1), 3)
183
+ clear()
184
+ key = "raycast"
185
+ values = []
186
+ match opt:
187
+ case 1:
188
+ out = col("[RAYCASTING]", 255, 0, 0)
189
+ out+="\n\nRaycasting is how this game renders scenes.\nThis setting controls how precise the raycasting is.\nA lower setting increases performance at the cost of aliasing.\n\n"
190
+ out+=col("[1]", 255, 255, 0)+"\t4 (Fastest)\n"+col("[2]", 255, 255, 0)+"\t8\n"+col("[3]", 255, 255, 0)+"\t16\n"+col("[4]", 255, 255, 0)+"\t32 (Highest Quality)\n\n"+col("[5]", 255, 255, 0)+"\tBack"
191
+ key = "raycast"
192
+ values = [4, 8, 16, 32]
193
+ case 2:
194
+ out = col("[COLOUR QUANTISATION]", 255, 0, 0)
195
+ out+="\n\nThis game uses ANSI escape codes to colour individual characters for pixels.\nANSI colours are expensive on CPU (irrelevant if you are on a GPU accelerated terminal),\nso near-identical colours are merged to increase performance.\n"
196
+ out+="Increasing this setting increases the total amount of colour shades\nthat can be displayed, at the cost of performance.\n\n"
197
+ out+=col("[1]", 255, 255, 0)+"\t32 Shades\n"+col("[2]", 255, 255, 0)+"\t64 Shades\n"+col("[3]", 255, 255, 0)+"\t128 Shades\n"+col("[4]", 255, 255, 0)+"\t256 Shades\n\n"+col("[5]", 255, 255, 0)+"\tBack"
198
+ key = "quant"
199
+ values = [1/32, 1/64, 1/128, 1/256]
200
+ case 3:
201
+ clear()
202
+ return
203
+ for ch in out:
204
+ print(ch, end="", flush=True)
205
+ time.sleep(0.005)
206
+
207
+ while True:
208
+ char = term.get_char()
209
+ match char.lower():
210
+ case "1":
211
+ sett[key] = values[0]
212
+ break
213
+ case "2":
214
+ sett[key] = values[1]
215
+ break
216
+ case "3":
217
+ sett[key] = values[2]
218
+ break
219
+ case "4":
220
+ sett[key] = values[3]
221
+ break
222
+ case "5":
223
+ break
224
+ clear()
225
+ return
226
+ except:
227
+ pass
228
+
229
+ def credits(term: Terminal, rand: list[str]):
230
+ clear()
231
+
232
+ while term.get_char().lower()!="b":
233
+ print("\033[1;1H")
234
+ out = "[CREDITS]"
235
+ out+="\n\n\n"+col("Made by:\t", 40, 0, 255)+col(random.choice(rand)+random.choice(rand)+random.choice(rand)+random.choice(rand), 135, 0, 255)+col("[STEFAN LUPASCU]", 40, 0, 255)+col(random.choice(rand)+random.choice(rand)+random.choice(rand)+random.choice(rand), 135, 0, 255)
236
+ out+="\n\n\nPress "+col("[B]", 255, 255, 0)+" to return to the Main Menu."
237
+
238
+ print(out, end="", flush=True)
239
+
240
+ time.sleep(0.05)
241
+ clear()
242
+
243
+ def menu(rand: list[str], term: Terminal):
244
+ clear()
245
+ print()
246
+
247
+ while True:
248
+ print("\033[1;1H", end="")
249
+ out = col(f"{random.choice(rand)}{random.choice(rand)}{random.choice(rand)}[SUDO NOCLIP]{random.choice(rand)}{random.choice(rand)}{random.choice(rand)}", 255, 0, 0)
250
+ out+="\n\n"+col("[1]", 255, 255, 0)+"\tPlay\n"+col("[2]", 255, 255, 0)+"\tGuide\n"+col("[3]", 255, 255, 0)+"\tHelp\n"+col("[4]", 255, 255, 0)+"\tSettings\n"+col("[5]", 255, 255, 0)+"\tCredits\n\n"+col("[6]", 255, 255, 0)+"\tQuit"+"\n\n\nSelect an option (1-6)"
251
+ print(out, end="", flush=True)
252
+
253
+ try:
254
+ opt = min(max(int(term.get_char()), 1), 6)
255
+ match opt:
256
+ case 1:
257
+ clear()
258
+ return
259
+ case 2:
260
+ guide(term)
261
+ case 3:
262
+ help(term)
263
+ case 4:
264
+ settings(term)
265
+ case 5:
266
+ credits(term, rand)
267
+ case 6:
268
+ term.show()
269
+ sys.exit()
270
+ except Exception:
271
+ pass
272
+
273
+ time.sleep(0.05)
274
+
275
+ def read_note(num: str, lines: list[str]):
276
+ clear()
277
+ print("\033[1;1H")
278
+ print()
279
+ time.sleep(2)
280
+
281
+ for ch in f"[RECOVERING CORRUPTED FRAGMENT: ANONYMOUS_USER_0-X{num}]":
282
+ print("\033[3m"+ch+"\033[0m", end="", flush=True)
283
+ if ch!=" ": time.sleep(0.2)
284
+ time.sleep(0.5)
285
+
286
+ print()
287
+ print()
288
+ for line in lines:
289
+ for ch in line:
290
+ print(col(ch, 255, 255, 0), end="", flush=True)
291
+ if ch!=" ": time.sleep(0.2)
292
+ time.sleep(0.5)
293
+ print()
294
+ print()
295
+ time.sleep(4)
296
+
297
+ def load(width: int, heigth: int, term: Terminal):
298
+ rand = [chr(x) for x in range(33, 127)]
299
+ menu(rand, term)
300
+
301
+ def skip():
302
+ if term.get_char()=="x":
303
+ print()
304
+ return 1/0
305
+
306
+ print()
307
+ print("Press x to skip intro")
308
+ print()
309
+ opt = [" ", ". ", ".. ", "..."]
310
+ for _ in range(4):
311
+ skip()
312
+ for suff in opt:
313
+ print("\rLoading"+suff, end="")
314
+ time.sleep(0.25)
315
+
316
+ gl = 72
317
+ for i in range(gl):
318
+ skip()
319
+ out = ""
320
+ for ch in "Loading"+opt[i%4]:
321
+ chan = (i/(gl-1))**3
322
+ if random.random()>chan:
323
+ out+=ch
324
+ else:
325
+ out+=col(random.choice(rand), 255, 0, 0)
326
+
327
+ print("\r\033[K"+out+"\t"+out+"\t"+out, end="", flush=True)
328
+ time.sleep(0.05+((1-(i/(gl-1))) * 0.15))
329
+
330
+ for _ in range(16):
331
+ skip()
332
+ out = ""
333
+ for _ in range(width):
334
+ out+=random.choice(rand)
335
+ print("\r\033[K"+col(out, 255, 0, 0), end="", flush=True)
336
+ time.sleep(0.04)
337
+
338
+ for _ in range(16):
339
+ skip()
340
+ out = ""
341
+ for _ in range(heigth):
342
+ for _ in range(width):
343
+ out+=random.choice(rand)
344
+ out+="\n"
345
+ out = out[:-2]
346
+ print("\r\033[K"+col(out, 255, 0, 0), end="", flush=True)
347
+ time.sleep(0.04)
348
+ skip()
349
+
350
+ clear()
351
+ time.sleep(1.5)
352
+
353
+ print()
354
+ for ch in "Greetings, Traveller.":
355
+ skip()
356
+ print(col(ch, 0, 180, 200), end="", flush=True)
357
+ if ch!=" ": time.sleep(0.2)
358
+ print("\n")
359
+ for ch in "Trust your heart":
360
+ skip()
361
+ print(col(ch, 0, 180, 200), end="", flush=True)
362
+ if ch!=" ": time.sleep(0.2)
363
+ print(col(".", 255, 0, 0), end="", flush=True)
364
+ print()
365
+ for ch in "Stay in the light":
366
+ skip()
367
+ print(col(ch, 0, 180, 200), end="", flush=True)
368
+ if ch!=" ": time.sleep(0.2)
369
+ print(col(".", 255, 0, 0), end="", flush=True)
370
+ print()
371
+ for ch in "Do not close your eyes":
372
+ skip()
373
+ print(col(ch, 0, 180, 200), end="", flush=True)
374
+ if ch!=" ": time.sleep(0.2)
375
+ print(col(".", 255, 0, 0), end="", flush=True)
376
+ print()
377
+ for ch in "Do not let your light go out":
378
+ skip()
379
+ print(col(ch, 0, 180, 200), end="", flush=True)
380
+ if ch!=" ": time.sleep(0.2)
381
+ for ch in "...":
382
+ print(col(ch, 255, 0, 0), end="", flush=True)
383
+ time.sleep(1.5)
384
+ print()
385
+
386
+ read_note("0000", ["My heart sank at the truth,", "That darkness was no fleeting guest.", "My eyes pleaded for an escape,", "But the light offered me none."])
387
+
388
+ clear()
389
+
390
+ def gen_maze(nx: int, ny: int, scale: int, loop: float) -> tuple[np.ndarray, tuple[float, float], np.ndarray, np.ndarray, np.ndarray]:
391
+ def is_valid(x: list) -> bool:
392
+ return x[0]>0 and x[0]*2<nx and x[1]>0 and x[1]*2 <ny
393
+
394
+ out = []
395
+ mask = []
396
+ lights = []
397
+ entity = [9999, 9999]
398
+ for _ in range(ny):
399
+ row = []
400
+ for _ in range(nx):
401
+ row.append(1)
402
+ out.append(row)
403
+ for _ in range(ny//2):
404
+ row = []
405
+ for _ in range(nx//2):
406
+ row.append(0)
407
+ mask.append(row)
408
+
409
+ start = [nx//4, ny//4]
410
+ stack = [start]
411
+ mask[start[1]][start[0]] = 1
412
+
413
+ dirs = [[0,1],[0,-1],[1,0],[-1,0]]
414
+ while stack:
415
+ node = stack[-1]
416
+
417
+ options = []
418
+ for d in dirs:
419
+ o = [node[0]+d[0], node[1]+d[1]]
420
+ if is_valid(o) and (mask[o[1]][o[0]] == 0 or (random.random()<loop)):
421
+ options.append(o)
422
+ if not options:
423
+ stack.pop()
424
+ continue
425
+
426
+ n = random.choice(options)
427
+ mask[n[1]][n[0]] = 1
428
+
429
+ cx, cy = node[0]*2, node[1]*2
430
+ tx, ty = n[0]*2, n[1]*2
431
+
432
+ fx, fy = cx+((tx-cx)//2), cy+((ty-cy)//2)
433
+ out[cy][cx] = 0
434
+ out[ty][tx] = 0
435
+ out[fy][fx] = 0
436
+
437
+ shift = (scale-1) /2
438
+ lights.append((fx*scale + shift, fy*scale + shift))
439
+
440
+ entity[0], entity[1] = (cx, cy) if cy<entity[1] else (entity[0], entity[1])
441
+ stack.append(n)
442
+
443
+ expanded = []
444
+ for row in out:
445
+ expanded_row = [cell for cell in row for _ in range(scale)]
446
+ expanded.extend([expanded_row.copy() for _ in range(scale)])
447
+
448
+ clear()
449
+ return (np.array(expanded), (entity[0] * scale, entity[1] * scale), np.array(out), np.array(lights), np.zeros((nx*ny*scale*scale, 3), dtype=np.int32))
450
+
451
+ @njit(fastmath=True, cache=True, boundscheck=False)
452
+ def pathfind(grid: np.ndarray, target: tuple[float, float], origin: tuple[float, float], stack: np.ndarray, width: int, height: int) -> tuple[int, int]:
453
+ out = np.full((height, width), -1, dtype=np.int32)
454
+
455
+ tx, ty = round(target[0]), round(target[1])
456
+ ox, oy = round(origin[0]), round(origin[1])
457
+
458
+ if (ox, oy) == (tx, ty):
459
+ return (ox, oy)
460
+
461
+ out[oy][ox] = 0
462
+
463
+ stack_top = 0
464
+ stack[0] = (ox, oy, 3)
465
+
466
+ dirs = ((0, 1), (0, -1), (1, 0), (-1, 0))
467
+ while len(stack)>0:
468
+ cx, cy, dist = stack[stack_top]
469
+ stack_top-=1
470
+ dist+=1
471
+
472
+ for (dx, dy) in dirs:
473
+ nx, ny = cx + dx, cy + dy
474
+ if out[ny][nx]==-1:
475
+
476
+ if grid[ny][nx] == 0 or (nx == tx and ny == ty):
477
+ out[ny][nx] = dist
478
+
479
+ stack[stack_top+1] = nx, ny, dist
480
+ stack_top+=1
481
+
482
+ if nx == tx and ny == ty:
483
+ return (cx, cy)
484
+ return (-1, -1)
485
+
486
+ @njit(fastmath=True, cache=True, boundscheck=False)
487
+ def raycast(dir: tuple[float, float], grid: np.ndarray, origin: tuple[float, float], max_dist: float, mini: int, l: int, level: int, mask: np.ndarray, scale: int) -> tuple[bool, float]:
488
+ steps = 1
489
+ while True:
490
+ if steps>= max_dist*mini:
491
+ break
492
+
493
+ sdivm = steps/mini
494
+ px, py = origin[0] + (dir[0] * (sdivm)), origin[1] + (dir[1] * (sdivm))
495
+ x, y = round(px), round(py)
496
+
497
+ if px < 0 or py < 0 or px >= l or py >= l:
498
+ return (False, 1.0)
499
+
500
+ if level!=4:
501
+ cx, cy = int(px // scale), int(py // scale)
502
+ sx, sy = cx*scale, cy*scale
503
+ if mask[cy][cx] == 0:
504
+ dy = (((sy+scale - py) if dir[1]>0 else (py-sy)) / abs(dir[1])) if dir[1]!=0 else 99999.0
505
+ dx = (((sx+scale - px) if dir[0]>0 else (px-sx)) / abs(dir[0])) if dir[0]!=0 else 99999.0
506
+
507
+ steps += round(min(dy, dx)*mini) + 1
508
+ continue
509
+
510
+ match grid[y][x]:
511
+ case 1:
512
+ return (True, (steps)/(max_dist*mini))
513
+ case 2:
514
+ if level==4:
515
+ return (True, -10.0)
516
+ else:
517
+ return (True, -9999.0)
518
+ case 3:
519
+
520
+ return (True, 0.0)
521
+ steps+=1
522
+
523
+ return (False, 1.0)
524
+
525
+ @njit(fastmath=True, cache=True, boundscheck=False, parallel=True)
526
+ def ray_trace(grid: np.ndarray, mask: np.ndarray, scale:int, lights: np.ndarray, light_str: float, light_max: float, kernel: int) -> np.ndarray:
527
+ rt = grid.astype(np.float32)
528
+
529
+ l = len(rt)
530
+ for y in prange(l):
531
+ for x in range(len(rt[0])):
532
+ if grid[y][x]==1:
533
+ rt[y][x] = 0.0
534
+ continue
535
+
536
+ shade = 0.0
537
+ for (lx, ly) in lights:
538
+ dx, dy = lx-x, ly-y
539
+ contr, hit = trace_light(dx, dy, x, y, light_max, l, grid, mask, scale)
540
+ shade+=(light_str/contr) if hit else 0.0
541
+
542
+ rt[y][x] = min(shade, 1.0)
543
+
544
+ print()
545
+ out = np.zeros((len(rt), len(rt[0])), dtype=np.float32)
546
+ l0, l1 = len(out), len(out[0])
547
+
548
+ for py in prange(l0):
549
+ for px in range(l1):
550
+ out[py][px] = sample_light(rt, (px, py), kernel, (l0, l1))
551
+
552
+ return out
553
+
554
+ @njit(fastmath=True, cache=True, boundscheck=False)
555
+ def trace_light(dx: float, dy: float, x: float, y: float, light_max: float, l: int, grid: np.ndarray, mask: np.ndarray, scale: int) -> tuple[float, bool]:
556
+
557
+ mag = math.sqrt(dx**2 + dy**2) + 1e-6
558
+ nx, ny = dx/mag, dy/mag
559
+
560
+ if mag<=light_max:
561
+ hit, _ = raycast((nx, ny), grid, (x, y), mag+0.001, 1, l, 0, mask, scale)
562
+ if not hit:
563
+ return (1 + mag**2, True)
564
+
565
+ return (0.0, False)
566
+
567
+ @njit(fastmath=True, cache=True, boundscheck=False)
568
+ def sample_light(lightmap: np.ndarray, pos: tuple, kernel: int, dim: tuple[int, int]) -> float:
569
+ l = dim[0]
570
+ l1 = dim[1]
571
+
572
+ shade = 0.0
573
+ n = 0
574
+
575
+ for dy in range(-kernel, kernel+1):
576
+ for dx in range(-kernel, kernel+1):
577
+ x, y = round(pos[0]+dx), round(pos[1]+dy)
578
+ if 0<=y<l and 0<=x<l1:
579
+ s = float(lightmap[y][x])
580
+ if s>0.0:
581
+ n+=1
582
+ shade+= s
583
+
584
+ return 0.0 if n==0 else shade/n
585
+
586
+ @njit(fastmath=True, cache=True, boundscheck=False)
587
+ def raster(
588
+ ct: float,
589
+ columns: np.ndarray,
590
+ floor_col: tuple[int, int, int],
591
+ roof_col: tuple[int, int, int],
592
+ half: float,
593
+ wall_proj: float,
594
+ player: tuple[float, float],
595
+ grid_n: int,
596
+ lm: np.ndarray | None,
597
+ gamma: float,
598
+ quantise: float,
599
+ in_water: bool,
600
+ wet_wall: float,
601
+ ao: float,
602
+ water_shine: float,
603
+ wave_shade: float,
604
+ wave_freq: float,
605
+ wave_speed: float,
606
+ move_ema: float,
607
+ global_shade: float,
608
+ grad: float,
609
+ js: bool,
610
+ width: int,
611
+ height: int,
612
+ render_dist: int,
613
+ ambient: float,
614
+ ) -> np.ndarray:
615
+ out = np.zeros((height, width, 3), dtype=np.int32)
616
+
617
+ for y in range(height):
618
+ refl_fade = (1.0 - (y-half)/((height-1) -half)) ** 2
619
+
620
+ dy = max(abs(y-half), 0.5)
621
+ d = max(wall_proj * height/(2*dy), 0.25) /render_dist
622
+
623
+ base_eff = (max(0.0, 1-d)**2.0) * global_shade * grad
624
+
625
+ is_roof = y < half
626
+ base = roof_col if is_roof else floor_col
627
+ wave = 1.0
628
+ if (not is_roof) and in_water:
629
+ wave -= wave_shade * (1-refl_fade) * move_ema *(math.sin(y*wave_freq+ct*wave_speed) ** 2)
630
+ base_floor = (round(base[0]*wave), round(base[1]*wave), round(base[2]*wave))
631
+
632
+ for px, (hit, top, bottom, wall0, wall1, wall2, ray_dir0, ray_dir1, ray_dist) in enumerate(columns):
633
+ top = int(top)
634
+ bottom = int(bottom)
635
+
636
+ if js:
637
+ if wall0==0.0 and wall1==0.0 and wall2==0.0:
638
+ if y==height//2 and ((px==width//2 +1) or (px==(width//2 -1))):
639
+ out[y][px] = (255, 255, 255)
640
+ continue
641
+
642
+ floor_dist = max(0.0, d * ray_dist - 2.0)
643
+ hx = round(player[0] + ray_dir0 * floor_dist)
644
+ hy = round(player[1] + ray_dir1 * floor_dist)
645
+
646
+ if lm is not None and 0<=hy<grid_n and 0<=hx<grid_n:
647
+ light = float(lm[hy][hx] * (1-d))
648
+ else:
649
+ light = 1.0
650
+
651
+ eff = round((base_eff * max(ambient, light) *gamma) / quantise) * quantise
652
+
653
+ floor = (round(base_floor[0]*eff), round(base_floor[1]*eff), round(base_floor[2]*eff))
654
+
655
+ if (hit==1.0) and top <= y <= bottom:
656
+ if (not (top <= y-1 <= bottom)) or (not (top <= y+1 <= bottom)):
657
+ if in_water and y>half:
658
+ colour = (wall0*wet_wall + base_floor[0]*(1-wet_wall)*eff, wall1*wet_wall + base_floor[1]*(1-wet_wall)*eff, wall2*wet_wall + base_floor[2]*(1-wet_wall)*eff)
659
+ else:
660
+ colour = (wall0*ao, wall1*ao, wall2*ao)
661
+ else:
662
+ colour = (wall0, wall1, wall2)
663
+ else:
664
+ if ((top <= y-1 <= bottom) or (top <= y+1 <= bottom)) and not in_water:
665
+ colour = (base[0]*eff*ao, base[1]*eff*ao, base[2]*eff*ao)
666
+ else:
667
+ h = bottom
668
+ ref = round(h-(y-h))
669
+ if in_water and (ref>=0) and (not is_roof) and (ref<y):
670
+ sr, sg, sb = out[ref][px]
671
+
672
+ shine = water_shine * refl_fade
673
+ colour = (sr*shine + floor[0]*(1-shine), sg*shine + floor[1]*(1-shine), sb*shine + floor[2]*(1-shine))
674
+ else:
675
+ colour = floor
676
+
677
+ out[y][px][0] = min(255, round(colour[0]))
678
+ out[y][px][1] = min(255, round(colour[1]))
679
+ out[y][px][2] = min(255, round(colour[2]))
680
+
681
+ return out
682
+
683
+ @lru_cache(maxsize=2**15)
684
+ def get_ansi(r: int, g: int, b: int, glyph: str) -> str:
685
+ return f"\033[38;2;{r};{g};{b}m{glyph}"
686
+
687
+ class Theme():
688
+ def __init__(self, wall: tuple[int, int, int], floor: tuple[int, int, int], roof: tuple[int, int, int]):
689
+ self.wall = wall
690
+ self.floor = floor
691
+ self.roof = roof
692
+ def update(self, wall: tuple[int, int, int], floor: tuple[int, int, int], roof: tuple[int, int, int]):
693
+ self.wall = wall
694
+ self.floor = floor
695
+ self.roof = roof
696
+
697
+ class State():
698
+ def __init__(self, grid: int):
699
+ width, height = shutil.get_terminal_size(fallback=(80, 24))
700
+ self.width = width
701
+ self.height = height
702
+
703
+ self.term = Terminal()
704
+ self.term.hide()
705
+
706
+ try:
707
+ load(width, height, self.term)
708
+ except Exception:
709
+ pass
710
+
711
+ clear()
712
+
713
+ self.in_grid = grid
714
+ self.rand = [chr(x) for x in range(33, 127)]
715
+
716
+ self.scale = 8
717
+ self.grid, entity, self.mask, self.lights, self.stack = gen_maze(grid, grid, self.scale, 0.1)
718
+ self.grid_n = len(self.grid)
719
+
720
+ self.player = Vec((self.scale * (grid // 2) + 1, self.scale * (grid // 2) + 1))
721
+ self.view = Matrix([], angle=0)
722
+ self.fov = math.radians(90)
723
+ self.left = Matrix([], angle=math.radians(-15))
724
+ self.right = Matrix([], angle=math.radians(15))
725
+
726
+ self.walk_speed = 0.7
727
+ self.water_speed = 0.45
728
+ self.in_water = False
729
+
730
+ self.render_dist = 32
731
+ self.sub_steps = int(sett["raycast"])
732
+ self.global_shade = 0.8
733
+ self.eff_exp = 1.0
734
+ self.wall_proj = 5.0
735
+
736
+ self.entity = Vec(entity)
737
+ self.entity_step = 0.07
738
+
739
+ self.theme = Theme((228, 230, 168), (156, 147, 88), (207, 204, 162))
740
+ self.rand = [chr(x) for x in range(33, 127)]
741
+ self.quantise = float(sett["quant"])
742
+
743
+ self.horizon_fac = 0.5
744
+ self.wobble = 0.05
745
+ self.move_ema = 1.0
746
+ self.ema_fac = 0.8
747
+ self.wob_fac = 16.0
748
+ self.wob_ema = 0.0
749
+ self.wob_ema_fac = 0.6
750
+
751
+ self.ao = 0.92
752
+ self.wet_wall = 0.75
753
+ self.water_shine = 0.7
754
+
755
+ self.light_strength = 6.0
756
+ self.light_off = 0.01
757
+ self.ambient = 0.1
758
+ self.light_max = math.sqrt((self.light_strength/self.light_off) - 1)
759
+ self.blur_kernel = 2
760
+
761
+ print("Lighting the map", flush=True)
762
+ print()
763
+ print("Ray Tracing...", flush=True)
764
+ self.lm = ray_trace(self.grid, self.mask, self.scale, self.lights, self.light_strength, self.light_max, self.blur_kernel)
765
+
766
+ self.wave_shade = 0.85
767
+ self.wave_speed = 2.75
768
+ self.wave_freq = 3.55
769
+
770
+ self.js_every = 30.0
771
+ self.js_jitter = 5.0
772
+ self.next_js = time.time()+(random.random()*self.js_jitter*2 + (self.js_every-self.js_jitter))
773
+ self.since_js = -1.0
774
+ self.js_time = 0.2
775
+ self.js = False
776
+
777
+ self.glyphs = ["█"]
778
+ self.death_glyphs = ["▓", "░"]
779
+ self.mac = False
780
+
781
+ self.escape = False
782
+ self.level = 1
783
+ self.rift = Vec((-999, -999))
784
+ self.flash = False
785
+
786
+ self.angles = [Vec((math.cos(((i/(self.width- 1)-0.5)) * self.fov), math.sin(((i / (self.width-1)- 0.5)) * self.fov)))for i in range(self.width)]
787
+
788
+ self.fps = 60
789
+ self.rift_time = 220
790
+ self.since_rift = time.time()
791
+
792
+ self.DEV = False
793
+
794
+ self.pocket_every = 40
795
+ self.pocket = Vec((-999, -999))
796
+ self.since_pocket = time.time()
797
+ self.pocket_n = -1
798
+ self.pockets_l1 = [
799
+ [
800
+ "I swear I've been here before.",
801
+ "Have I been going round in circles? No, not possible.",
802
+ "Surely not?"
803
+ ],
804
+ [
805
+ "I measured this corridor.",
806
+ "Forty paces long an hour ago. Now it's sixty.",
807
+ "Stop looking at the corners."
808
+ ],
809
+ [
810
+ "Don't look at the lights.",
811
+ "They ain't wired to no power.",
812
+ "They buzz and pulse like open eyes.",
813
+ "When the eyes die out, run."
814
+ ],
815
+ [
816
+ "One step, two step, three step, four.",
817
+ "Five step, six step, seven...?",
818
+ "They're not mine.",
819
+ "THEY'RE NOT..."
820
+ ],
821
+ [
822
+ "If Fair is Foul,",
823
+ "And Foul is Fair",
824
+ "Why are the walls filthy,",
825
+ "As well as the air?"
826
+ ],
827
+ ]
828
+ self.p_nums_1 = ["1A4F", "28B3", "3F7C", "48A1", "5D2E"]
829
+ self.p_nums_2 = ["8142", "9C55", "A38F", "B1C6", "C409"]
830
+ self.p_nums_3 = ["E831", "F10A", "F5C4", "FA88", "FFFF"]
831
+ self.p_notes = 5
832
+ self.pockets_l2 = [
833
+ [
834
+ "Dutch? Is this Tahiti?"
835
+ ],
836
+ [
837
+ "You Sir, are a Fish."
838
+ ],
839
+ [
840
+ "Silver for monsters. Steel for humans. Water for...",
841
+ "What do I draw for shadows?"
842
+ ],
843
+ [
844
+ "Geppetto, where art thou?",
845
+ "Is that you?",
846
+ "Or is that me?",
847
+ ],
848
+ [
849
+ "GET OUT OF MY SWAMP!"
850
+ ],
851
+ ]
852
+ self.pockets_l3 = [
853
+ [
854
+ "i am sane i am sane i am sane i am sane",
855
+ "i am sane i am sane i am sane i am sane",
856
+ "i am sane i am sane i am sane"
857
+ ],
858
+ [
859
+ "Always wait thirty minutes after swimming before eating.",
860
+ "What's the time Mister Wolf?"
861
+ ],
862
+ [
863
+ "Look! Look at the funny one hiding behind the pillar!"
864
+ ],
865
+ [
866
+ "Wake up Samurai,",
867
+ "We have a city to poke.",
868
+ "Wait, come back!"
869
+ ],
870
+ [
871
+ "If he be Mister Hyde,", "then I shall be Mister Seek!"
872
+ ],
873
+ ]
874
+
875
+ def lose(self):
876
+ print("\033[1;1H")
877
+ out = ""
878
+ for _ in range(self.height):
879
+ for _ in range(self.width):
880
+ out+=random.choice(self.rand)
881
+ out+="\n"
882
+ out = out[:-2]
883
+ print(col(out, 255, 0, 0), end="", flush=True)
884
+ time.sleep(3)
885
+
886
+ print("\033[999;999H")
887
+ print("\n\n")
888
+ time.sleep(2)
889
+ for ch in "Your heart falls silent":
890
+ print(ch, end="", flush=True)
891
+ if ch!=" ": time.sleep(0.1)
892
+ print(col(".", 255, 0, 0), end="", flush=True)
893
+ time.sleep(0.25)
894
+
895
+ print()
896
+ for ch in "You step into the darkness":
897
+ print(ch, end="", flush=True)
898
+ if ch!=" ": time.sleep(0.1)
899
+ print(col(".", 255, 0, 0), end="", flush=True)
900
+ time.sleep(0.25)
901
+
902
+ print()
903
+ for ch in "You close your eyes":
904
+ print(ch, end="", flush=True)
905
+ if ch!=" ": time.sleep(0.1)
906
+ print(col(".", 255, 0, 0), end="", flush=True)
907
+ time.sleep(0.25)
908
+
909
+ print()
910
+ for ch in "Your light flickers":
911
+ print(ch, end="", flush=True)
912
+ if ch!=" ": time.sleep(0.1)
913
+ for ch in " ... ":
914
+ print(col(ch, 255, 0, 0), end="", flush=True)
915
+ time.sleep(0.1)
916
+
917
+ for ch in "then fades":
918
+ print(ch, end="", flush=True)
919
+ if ch!=" ": time.sleep(0.1)
920
+ print(col(".", 255, 0, 0), flush=True)
921
+ time.sleep(0.5)
922
+
923
+ print()
924
+ for ch in "Until we meet again, fellow Traveller":
925
+ print(ch, end="", flush=True)
926
+ if ch!=" ": time.sleep(0.2)
927
+ for ch in "...":
928
+ print(col(ch, 255, 0, 0), end="", flush=True)
929
+ time.sleep(0.5)
930
+ print()
931
+ print()
932
+
933
+ sys.tracebacklimit = 0
934
+ raise ConnectionError("\n[Exit Code: 404 - Connection Lost: Gateway timed out. Host is no longer responding.]")
935
+
936
+ def win(self):
937
+ clear()
938
+ print("\033[1;1H")
939
+ print()
940
+ time.sleep(5)
941
+
942
+ for ch in "Our hearts knew you would come":
943
+ print(col(ch, 0, 180, 200), end="", flush=True)
944
+ if ch!=" ": time.sleep(0.2)
945
+ print(col(".", 255, 0, 0), flush=True)
946
+ time.sleep(0.5)
947
+
948
+ for ch in "The darkness is too vast alone":
949
+ print(col(ch, 0, 180, 200), end="", flush=True)
950
+ if ch!=" ": time.sleep(0.2)
951
+ print(col(".", 255, 0, 0), flush=True)
952
+ time.sleep(0.5)
953
+
954
+ for ch in "Our eyes watched you reach for the light":
955
+ print(col(ch, 0, 180, 200), end="", flush=True)
956
+ if ch!=" ": time.sleep(0.2)
957
+ print(col(".", 255, 0, 0), flush=True)
958
+ time.sleep(0.5)
959
+
960
+ for ch in "So now we can share yours":
961
+ print(col(ch, 0, 180, 200), end="", flush=True)
962
+ if ch!=" ": time.sleep(0.2)
963
+ for ch in ".":
964
+ print(col(ch, 255, 0, 0), end="", flush=True)
965
+ time.sleep(0.5)
966
+
967
+ time.sleep(1)
968
+ print()
969
+ print()
970
+ print()
971
+
972
+ for ch in "Your heart stops its struggle.":
973
+ print(col(ch, 255, 0, 0), end="", flush=True)
974
+ if ch!=" ": time.sleep(0.2)
975
+ time.sleep(0.5)
976
+
977
+ print()
978
+ for ch in "You join the darkness.":
979
+ print(col(ch, 255, 0, 0), end="", flush=True)
980
+ if ch!=" ": time.sleep(0.2)
981
+ time.sleep(0.5)
982
+
983
+ print()
984
+ for ch in "You let your eyes rest.":
985
+ print(col(ch, 255, 0, 0), end="", flush=True)
986
+ if ch!=" ": time.sleep(0.2)
987
+ time.sleep(0.5)
988
+
989
+ print()
990
+ for ch in "Your light flickers ":
991
+ print(col(ch, 255, 0, 0), end="", flush=True)
992
+ if ch!=" ": time.sleep(0.2)
993
+ for ch in "...":
994
+ print(col(ch, 255, 0, 0), end="", flush=True)
995
+ time.sleep(1.0)
996
+
997
+ for ch in " then spreads":
998
+ print(col(ch, 255, 0, 0), end="", flush=True)
999
+ if ch!=" ": time.sleep(0.2)
1000
+ time.sleep(0.5)
1001
+ for ch in "...":
1002
+ print(col(ch, 255, 0, 0), end="", flush=True)
1003
+ time.sleep(1.0)
1004
+ time.sleep(1.0)
1005
+ print()
1006
+ print()
1007
+ sys.tracebacklimit = 0
1008
+ raise ConnectionError("\n[Exit Code: 403 - Access Denied]")
1009
+
1010
+ def resize(self):
1011
+ width, height = shutil.get_terminal_size(fallback=(80, 24))
1012
+ if width!=self.width or height!=self.height:
1013
+ self.height = height
1014
+ self.width = width
1015
+
1016
+ self.angles = [Vec((math.cos(((i/(self.width- 1)-0.5)) * self.fov), math.sin(((i / (self.width-1)- 0.5)) * self.fov)))for i in range(self.width)]
1017
+
1018
+ def is_valid(self, pos: Vec)-> bool:
1019
+ return self.grid[round(pos.raw[1])][round(pos.raw[0])] == 0
1020
+
1021
+ def update(self):
1022
+ move = False
1023
+ speed = self.water_speed if self.in_water else self.walk_speed
1024
+ ch = self.term.get_char()
1025
+
1026
+ if ch == "x":
1027
+ self.lose()
1028
+ if ch == "w":
1029
+ new = self.player + (self.view @ Vec((speed, 0)))
1030
+ if self.is_valid(new):
1031
+ self.player = new
1032
+ self.move_ema = self.move_ema * self.ema_fac + (1-self.ema_fac)
1033
+ move = True
1034
+ elif ch == "a":
1035
+ new = self.player + (self.view @ Vec((0, -speed)))
1036
+ if self.is_valid(new):
1037
+ self.player = new
1038
+ self.move_ema = self.move_ema * self.ema_fac + (1-self.ema_fac)
1039
+ move = True
1040
+ elif ch == "s":
1041
+ new = self.player + (self.view @ Vec((-speed, 0)))
1042
+ if self.is_valid(new):
1043
+ self.player = new
1044
+ self.move_ema = self.move_ema * self.ema_fac + (1-self.ema_fac)
1045
+ move = True
1046
+ elif ch == "d":
1047
+ new = self.player + (self.view @ Vec((0, speed)))
1048
+ if self.is_valid(new):
1049
+ self.player = new
1050
+ self.move_ema = self.move_ema * self.ema_fac + (1-self.ema_fac)
1051
+ move = True
1052
+ if not move:
1053
+ self.move_ema*=self.ema_fac
1054
+ if self.move_ema < 0.05:
1055
+ self.move_ema = 0.0
1056
+ if ch == "o":
1057
+ self.view = cast(Matrix, self.view @ self.left)
1058
+ elif ch == "p":
1059
+ self.view = cast(Matrix, self.view @ self.right)
1060
+ elif ch == "1":
1061
+ self.quantise = 1/32
1062
+ self.sub_steps = 4
1063
+ elif ch == "2":
1064
+ self.quantise = 1/64
1065
+ self.sub_steps = 8
1066
+ elif ch == "3":
1067
+ self.quantise = 1/128
1068
+ self.sub_steps = 16
1069
+ elif ch == "4":
1070
+ self.quantise = 1/256
1071
+ self.sub_steps = 32
1072
+
1073
+ elif ch == "t":
1074
+ if self.mac:
1075
+ self.mac = False
1076
+ #self.glyphs = ["▉", "█"]
1077
+ self.glyphs = ["█"]
1078
+ else:
1079
+ self.mac = True
1080
+ self.glyphs = ["▓", "█"]
1081
+
1082
+ line = self.player-self.pocket
1083
+ if (math.sqrt(line.raw[0]**2 + line.raw[1]**2))<=2 and self.level!=4:
1084
+ s = time.time()
1085
+ match self.level:
1086
+ case 1:
1087
+ note = self.pockets_l1[self.pocket_n]
1088
+ num = self.p_nums_1[self.pocket_n]
1089
+ case 2:
1090
+ note = self.pockets_l2[self.pocket_n]
1091
+ num = self.p_nums_2[self.pocket_n]
1092
+ case 3:
1093
+ note = self.pockets_l3[self.pocket_n]
1094
+ num = self.p_nums_3[self.pocket_n]
1095
+ case _:
1096
+ note = ["How did you even get this??"]
1097
+ num = "????"
1098
+
1099
+ if not self.DEV: read_note(num, note)
1100
+ self.since_rift += (time.time()-s)
1101
+ self.since_pocket += (time.time()-s)
1102
+
1103
+ self.grid[round(self.pocket.raw[1])][round(self.pocket.raw[0])] = 0
1104
+ self.pocket = Vec((-9999, -9999))
1105
+
1106
+ line = self.player-self.rift
1107
+ if (math.sqrt(line.raw[0]**2 + line.raw[1]**2))<=3 and self.level!=4:
1108
+ self.level+=1
1109
+ self.escape = False
1110
+ self.entity.raw = (9999, 99999)
1111
+ self.grid[round(self.rift.raw[1])][round(self.rift.raw[0])] = 1
1112
+ self.rift.raw = (9999, 9999)
1113
+
1114
+ match self.level:
1115
+ case 2:
1116
+ if not self.DEV: read_note("6E90", ["My heart grew sick of the rotting yellow.", "The darkness chased me down the halls.", "My eyes embraced the quiet blue.", "But the unforgiving light chased me still..."])
1117
+
1118
+ self.theme.update((232, 232, 230), (66, 248, 220), (230, 245, 240))
1119
+ self.render_dist = 128
1120
+ self.horizon_fac = 0.4
1121
+ self.wob_fac = 5.0
1122
+ self.wall_proj = 10.0
1123
+
1124
+ self.global_shade = 0.9
1125
+ self.ao = 0.95
1126
+ self.light_strength = 8.0
1127
+ self.light_max = math.sqrt((self.light_strength/self.light_off) - 1)
1128
+
1129
+ self.scale = 16
1130
+ self.grid, entity, self.mask, self.lights, self.stack = gen_maze(self.in_grid, self.in_grid, self.scale, 0.15)
1131
+ self.entity = Vec(entity)
1132
+ self.grid_n = len(self.grid)
1133
+ self.temp = [[-1 for _ in range(self.grid_n)] for _ in range(self.grid_n)]
1134
+
1135
+ print("Lighting the map", flush=True)
1136
+ print()
1137
+ print("Ray Tracing...", flush=True)
1138
+ self.lm = ray_trace(self.grid, self.mask, self.scale, self.lights, self.light_strength, self.light_max, self.blur_kernel)
1139
+
1140
+ self.in_water = True
1141
+ self.entity_step = 0.15
1142
+ self.player = Vec((self.scale * (self.in_grid // 2) + 1, self.scale * (self.in_grid // 2) + 1))
1143
+ self.pocket_n=-1
1144
+
1145
+ clear()
1146
+ case 3:
1147
+ if not self.DEV: read_note("D27E", ["My heart had drowned in the pale blue,", "Where clinical white rivalled the dark.", "My eyes had found comfort in the deep,", "Where my fading light fought on..."])
1148
+
1149
+ self.theme.update((85, 105, 100), (25, 55, 65), (60, 75, 70))
1150
+ self.render_dist = 64
1151
+
1152
+ self.light_strength = 3.0
1153
+ self.global_shade = 0.8
1154
+ self.blur_kernel = 1
1155
+ self.entity_step = 0.25
1156
+ self.light_max = math.sqrt((self.light_strength/self.light_off) - 1)
1157
+
1158
+ self.wall_proj = 6.0
1159
+
1160
+ self.ao = 0.7
1161
+
1162
+ self.scale = 8
1163
+ self.grid, entity, self.mask, self.lights, self.stack = gen_maze(self.in_grid, self.in_grid, self.scale, 0.15)
1164
+ self.entity = Vec(entity)
1165
+ self.grid_n = len(self.grid)
1166
+ self.temp = [[-1 for _ in range(self.grid_n)] for _ in range(self.grid_n)]
1167
+
1168
+
1169
+ print("Lighting the map", flush=True)
1170
+ print()
1171
+ print("Ray Tracing...", flush=True)
1172
+ self.lm = ray_trace(self.grid, self.mask, self.scale, self.lights, self.light_strength, self.light_max, self.blur_kernel)
1173
+
1174
+ self.player = Vec((self.scale * (self.in_grid // 2) + 1, self.scale * (self.in_grid // 2) + 1))
1175
+ self.pocket_n=-1
1176
+ case 4:
1177
+ clear()
1178
+ print("\033[1;1H")
1179
+ print()
1180
+ time.sleep(2)
1181
+
1182
+ for ch in "[Decoding Incoming Message]":
1183
+ print("\033[3m"+ch, end="", flush=True)
1184
+ time.sleep(0.2)
1185
+
1186
+ time.sleep(2.0)
1187
+ print()
1188
+ print()
1189
+ for ch in f"RUN":
1190
+ print("\033[3m\033[1m"+col(ch, 255, 0, 0), end="", flush=True)
1191
+ time.sleep(2.0)
1192
+ time.sleep(2.0)
1193
+
1194
+ self.theme.update((255, 10, 20), (70, 22, 26), (220, 20, 40))
1195
+ self.render_dist = 96
1196
+ self.horizon_fac = 0.5
1197
+ self.wall_proj = 5.0
1198
+ self.wob_fac = 16.0
1199
+ self.ao = 0.1
1200
+ self.lm = None
1201
+
1202
+ self.fov = math.radians(120)
1203
+ self.angles = [Vec((math.cos(((i/(self.width- 1)-0.5)) * self.fov), math.sin(((i / (self.width-1)- 0.5)) * self.fov)))for i in range(self.width)]
1204
+
1205
+ self.global_shade = 0.6
1206
+ self.eff_exp = 2.0
1207
+
1208
+ self.walk_speed = 0.4
1209
+ self.entity_step = 0.3
1210
+
1211
+ self.scale = 1
1212
+ self.grid = np.array([
1213
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
1214
+ [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1],
1215
+ [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1],
1216
+ [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
1217
+ [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2],
1218
+ [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
1219
+ [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1],
1220
+ [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1],
1221
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
1222
+ ])
1223
+ self.view = Matrix([], angle=0)
1224
+
1225
+ self.grid_n = len(self.grid[0])
1226
+ self.temp = [[-1 for _ in range(self.grid_n)] for _ in range(len(self.grid))]
1227
+
1228
+ self.in_water = False
1229
+ self.player = Vec((self.grid_n//2, len(self.grid)//2))
1230
+ self.entity = Vec((2, len(self.grid)//2))
1231
+ self.rift = Vec((self.grid_n-1, len(self.grid)//2))
1232
+
1233
+ self.flash = True
1234
+
1235
+ clear()
1236
+
1237
+ self.since_rift = time.time()
1238
+ self.since_pocket = time.time()
1239
+
1240
+ line = self.player-self.entity
1241
+ if (math.sqrt(line.raw[0]**2 + line.raw[1]**2))<=2:
1242
+ if self.level==4:
1243
+ self.win()
1244
+ else:
1245
+ clear()
1246
+ self.lose()
1247
+
1248
+ def render(self):
1249
+ horiz = ""
1250
+ dist = []
1251
+
1252
+ line = self.player-self.entity
1253
+ grad = (min((math.sqrt(line.raw[0]**self.eff_exp + line.raw[1]**2))/(self.grid_n/2), 1))**2.0
1254
+
1255
+ gamma = 1.0
1256
+
1257
+ nx, ny = pathfind(self.grid, self.entity.raw, self.player.raw, self.stack, self.grid.shape[1], self.grid.shape[0])
1258
+ if nx >= 0 and ny >= 0:
1259
+ nv = Vec((nx, ny))
1260
+ self.entity -= ((self.entity - nv) * self.entity_step)
1261
+
1262
+ s = time.perf_counter()
1263
+ for px in range(self.width):
1264
+ rot = self.view @ self.angles[px]
1265
+ res = raycast(cast(Vec, rot).raw, self.grid, self.player.raw, self.render_dist, self.sub_steps, self.grid_n, self.level, self.mask, self.scale)
1266
+ horiz += "1" if res[0] else "0"
1267
+ dist.append(res[1])
1268
+ rend = time.perf_counter()-s
1269
+
1270
+ columns = np.zeros((self.width, 9))
1271
+
1272
+ half = self.horizon_fac
1273
+ self.wob_ema = self.wob_ema * self.wob_ema_fac + self.wobble * math.sin((time.time() * self.wob_fac) % (2 * math.pi)) * self.move_ema * (1-self.wob_ema_fac)
1274
+ half+=self.wob_ema
1275
+
1276
+ half*=self.height
1277
+
1278
+ s = time.perf_counter()
1279
+
1280
+ if self.flash:
1281
+ gamma = 2.0
1282
+ self.flash = False
1283
+
1284
+ if (time.time()-self.since_pocket) > self.pocket_every and (self.pocket_n + 1 < self.p_notes) and self.level!=4:
1285
+ if horiz[self.width//2] == "1":
1286
+ target = self.player + (cast(Vec, (self.view @ Vec((1, 0)))) * (max(dist[self.width//2], 1/self.render_dist)*self.render_dist))
1287
+ else:
1288
+ target = self.player + (cast(Vec, self.view @ Vec((1, 0))) * (0.2*self.render_dist))
1289
+
1290
+ self.grid[round(target.raw[1])][round(target.raw[0])] = 3
1291
+ self.mask[int(target.raw[1]//self.scale)][int(target.raw[0]//self.scale)] = 1
1292
+
1293
+ if self.pocket.raw[0]>0 and self.pocket.raw[1]>0:
1294
+ self.grid[round(self.pocket.raw[1])][round(self.pocket.raw[0])] = 0
1295
+
1296
+ self.pocket = target
1297
+ self.pocket_n+=1
1298
+ self.since_pocket = time.time()
1299
+ print("\a", end="", flush=True)
1300
+
1301
+ if (time.time()-(self.since_rift))>=self.rift_time and not self.escape and not (self.level==4):
1302
+ if horiz[self.width//2] == "1":
1303
+ target = self.player + ((self.view @ Vec((1, 0))) * (dist[self.width//2]*self.render_dist))
1304
+
1305
+ if self.grid[round(target.raw[1])][round(target.raw[0])] == 1:
1306
+ self.grid[round(target.raw[1])][round(target.raw[0])] = 2
1307
+ self.mask[int(target.raw[1]//self.scale)][int(target.raw[0]//self.scale)] = 1
1308
+ self.rift = target
1309
+
1310
+ self.entity_step/=1.5
1311
+ self.escape = True
1312
+
1313
+ gamma = 10.0
1314
+ self.flash = True
1315
+ print("\a", end="", flush=True)
1316
+
1317
+ for px, (hit, p_dist) in enumerate(zip(horiz, dist)):
1318
+ if hit == "1" and p_dist >= 0:
1319
+ p_dist *= self.angles[px].raw[0]
1320
+
1321
+ wh = int((self.wall_proj * self.height) / max(p_dist * self.render_dist, 0.25))
1322
+ top = half - (wh // 2)
1323
+ bottom = half + (wh // 2)
1324
+
1325
+ ray_dir = cast(Vec, self.view @ self.angles[px])
1326
+ ray_depth = max(0.0, (dist[px] * self.render_dist) - 1.0)
1327
+ hit_pos = self.player + ray_dir * ray_depth
1328
+
1329
+ x, y = round(hit_pos.raw[0]), round(hit_pos.raw[1])
1330
+ if self.lm is not None and 0<=y<self.grid_n and 0<=x<self.grid_n:
1331
+ light = self.lm[y][x] * (1-p_dist)
1332
+ else:
1333
+ light = 1.0
1334
+
1335
+ eff = (max(0.0, 1-p_dist)**2.0) * self.global_shade * grad * max(self.ambient, light)
1336
+ eff = round(eff / self.quantise)*self.quantise
1337
+
1338
+ wall = (min(round(self.theme.wall[0] * eff), 255), min(round(self.theme.wall[1] * eff), 255), min(round(self.theme.wall[2] * eff), 255))
1339
+
1340
+ ray_dist = self.render_dist/max(self.angles[px].raw[0], 1e-5)
1341
+ columns[px] = ((float(hit == "1"), top, bottom, wall[0], wall[1], wall[2], ray_dir.raw[0], ray_dir.raw[1], ray_dist))
1342
+
1343
+ if self.since_js<0 and not self.js:
1344
+ center = self.width // 2
1345
+ if time.time() >= self.next_js and self.level != 4:
1346
+ if horiz[center - 1] == "0" and horiz[center + 1] == "0":
1347
+ self.since_js = time.time()
1348
+ self.js = True
1349
+ if self.js:
1350
+ if (time.time()-self.since_js)>(self.js_time + 0.3*(1-grad)):
1351
+ self.js = False
1352
+ self.since_js = -1.0
1353
+ self.next_js = time.time()+random.random()*self.js_jitter*2 + (self.js_every-self.js_jitter)
1354
+
1355
+ pool = self.glyphs if grad>=0.25 else self.death_glyphs
1356
+ get_glpyh = (lambda _: random.choice(pool)) if len(self.glyphs)>1 else (lambda _: self.glyphs[0])
1357
+
1358
+ inst = max(0.0, (1-(time.time()-self.since_rift)/self.rift_time), 0.0)
1359
+
1360
+ ct = time.time()
1361
+
1362
+ out = raster(ct, columns, self.theme.floor, self.theme.roof, half, self.wall_proj, self.player.raw, self.grid_n, self.lm, gamma, self.quantise, self.in_water, self.wet_wall, self.ao, self.water_shine, self.wave_shade, self.wave_freq, self.wave_speed, self.move_ema, self.global_shade, grad, self.js, self.width, self.height, self.render_dist, self.ambient)
1363
+
1364
+ tb = 0.0
1365
+
1366
+ screen = []
1367
+ flat = out.reshape(-1, 3)
1368
+ prev = np.empty_like(flat)
1369
+
1370
+ prev[0] = [-1,-1,-1]
1371
+ prev[1:] = flat[:-1]
1372
+ change = np.any(flat != prev, axis=1)
1373
+ change[0] = True
1374
+
1375
+ flat = flat.reshape(self.height, self.width, 3)
1376
+ change = change.reshape(self.height, self.width)
1377
+
1378
+ for iy in range(self.height):
1379
+ row = flat[iy]
1380
+ chg_row = change[iy]
1381
+
1382
+ screen.append("".join(get_ansi(r, g, b, get_glpyh(0)) if chg else get_glpyh(0) for (r, g, b), chg in zip(row, chg_row)))
1383
+
1384
+ buffer = "\n".join(screen)
1385
+
1386
+ shade = time.perf_counter()-s
1387
+
1388
+ print("\033[H" + col(buffer, 0, 0, 0, block=True), end="", flush=True)
1389
+
1390
+ f_col = f"Fear: {round((1-grad)*100)}%"
1391
+ s_col = f"Stability: {round(inst*100)- (0 if self.level!=4 else 150)}%"
1392
+ msg = f"WASD to walk, O/P to turn, 1/2/3/4 to select graphics quality, T to toggle Pixel Preset (current: {'Block Grain' if self.mac else 'No Grain'}), and X to accept defeat | FPS: {self.fps} | {rend*1000.0:.2f}ms Render time | {shade*1000.0:.2f}ms Shade time | {f_col} | {s_col} | "
1393
+ print(f"\r\033[{self.height-1};1H{''.join(['_' for _ in range(len(msg)-1)])} ", end="", flush=True)
1394
+
1395
+ msg = msg.replace(f_col, col(f"Fear: {round((1-grad)*100)}%", 255 if (1-grad>=0.3) else 0, 255 if (1-grad<0.6) else 0, 0))
1396
+ msg = msg.replace(s_col, col(f"Stability: {round(inst*100)- (0 if self.level!=4 else 100)}%", 255 if (inst<0.7 or self.level==4) else 0, 0 if (inst<0.3 or self.level==4) else 255, 0))
1397
+ print(f"\r\033[{self.height};1H{msg}", end="", flush=True)
1398
+
1399
+ def main():
1400
+ state = State(24)
1401
+ target = 1 / 30.0
1402
+ since_resize = 0
1403
+ since30 = time.perf_counter()
1404
+ frames = 0
1405
+ try:
1406
+ state.term.hide()
1407
+ while True:
1408
+ if since_resize >= 100:
1409
+ since_resize = 0
1410
+ state.resize()
1411
+ else:
1412
+ since_resize+=1
1413
+
1414
+ s = time.perf_counter()
1415
+ state.update()
1416
+ state.render()
1417
+
1418
+ elap = time.perf_counter() - s
1419
+ time.sleep(max(0.0, target - elap))
1420
+
1421
+ frames+=1
1422
+ if frames>=30:
1423
+ state.fps = round(30.0/(time.perf_counter()-since30))
1424
+ frames = 0
1425
+ since30 = time.perf_counter()
1426
+
1427
+ finally:
1428
+ state.term.show()
1429
+
1430
+ if __name__ == "__main__":
1431
+ main()
1432
+
1433
+ # you sir, are a fish