Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/scripts/cpr_test.py
6570 views
1
import os
2
import random
3
import re
4
import select
5
import sys
6
import termios
7
import time
8
import tty
9
10
11
def read_cpr(fd: int, timeout=1.0):
12
os.write(sys.stdout.fileno(), b"\x1b[6n")
13
sys.stdout.flush()
14
buf = b""
15
end = time.time() + timeout
16
while time.time() < end:
17
r, _, _ = select.select([fd], [], [], 0.05)
18
if not r:
19
continue
20
buf += os.read(fd, 64)
21
m = re.search(rb"\x1b\[(\d+);(\d+)R", buf)
22
if m:
23
return int(m.group(1)), int(m.group(2))
24
return None
25
26
27
def mv(r, c):
28
sys.stdout.write(f"\x1b[{r};{c}H")
29
30
31
def put(r, c, s):
32
mv(r, c)
33
sys.stdout.write(s)
34
35
36
def clamp(v, lo, hi):
37
return max(lo, min(hi, v))
38
39
40
def terminal_size():
41
try:
42
size = os.get_terminal_size(sys.stdin.fileno())
43
return size.lines, size.columns
44
except OSError:
45
return 24, 80
46
47
48
def main():
49
if not sys.stdin.isatty() or not sys.stdout.isatty():
50
print("ERROR: CPRs aren't supported here (stdin/stdout is not a TTY).")
51
return 1
52
53
fd = sys.stdin.fileno()
54
old = termios.tcgetattr(fd)
55
56
tty.setcbreak(fd)
57
h, w = 20, 40
58
59
try:
60
first_cpr = read_cpr(fd)
61
if first_cpr is None:
62
print("ERROR: CPRs aren't supported by this terminal.")
63
return 1
64
initial_row, initial_col = first_cpr
65
66
# Reserve room below existing output so animation stays "after" prior lines.
67
reserve_rows = h + 4
68
sys.stdout.write("\n" * reserve_rows)
69
sys.stdout.flush()
70
71
# Anchor the drawing region inside the newly created area.
72
second_cpr = read_cpr(fd)
73
if second_cpr is None:
74
print("ERROR: CPRs aren't supported by this terminal.")
75
return 1
76
draw_row, _ = second_cpr
77
78
rows, cols = terminal_size()
79
max_row = max(1, rows - (h + 3))
80
max_col = max(1, cols - (w + 2))
81
r0 = clamp(draw_row - (h + 3), 1, max_row)
82
c0 = clamp(1, 1, max_col)
83
84
sys.stdout.write("\x1b[?25l") # hide cursor
85
86
# Double-walled box border
87
put(r0, c0, "╔" + "═" * w + "╗")
88
put(r0 + h + 1, c0, "╚" + "═" * w + "╝")
89
for i in range(1, h + 1):
90
put(r0 + i, c0, "║")
91
put(r0 + i, c0 + w + 1, "║")
92
93
# Random starting point and direction inside the grid.
94
x = random.randint(1, w)
95
y = random.randint(1, h)
96
dx = random.choice([-1, 1])
97
dy = random.choice([-1, 1])
98
99
# Trail grows from tiny dot -> rings -> filled circle -> ball.
100
# All share the ball's cycling color so they're clearly visible.
101
TRAIL_CHARS = ["·", "∙", "∘", "○", "◎", "◉"] # oldest -> newest
102
BALL = "●"
103
TRAIL_LEN = len(TRAIL_CHARS)
104
trail = [] # list of (row, col), oldest first
105
106
for t in range(300):
107
# Erase the position falling off the back of the trail
108
if len(trail) == TRAIL_LEN:
109
er, ec = trail[0]
110
put(r0 + er, c0 + ec, " ")
111
trail.pop(0)
112
color = 31 + (t % 6)
113
# Redraw trail: '.' far back, 'o' close to ball, all in ball's color
114
for i, (tr, tc) in enumerate(trail):
115
put(r0 + tr, c0 + tc, f"\x1b[{color}m{TRAIL_CHARS[i]}\x1b[0m")
116
put(r0 + y, c0 + x, f"\x1b[{color}m{BALL}\x1b[0m")
117
put(
118
r0 + h + 3,
119
c0,
120
f"grid={h}x{w} initial=({initial_row},{initial_col}) anchor=({r0},{c0}) ball=({y},{x}) vel=({dy:+d},{dx:+d})",
121
)
122
sys.stdout.flush()
123
time.sleep(0.03)
124
125
trail.append((y, x))
126
bounced = False
127
128
if x + dx < 1 or x + dx > w:
129
dx *= -1
130
bounced = True
131
# Jitter: occasionally perturb the orthogonal axis on wall hit.
132
if random.random() < 0.35:
133
dy *= -1
134
135
if y + dy < 1 or y + dy > h:
136
dy *= -1
137
bounced = True
138
# Jitter: occasionally perturb the orthogonal axis on wall hit.
139
if random.random() < 0.35:
140
dx *= -1
141
142
x += dx
143
y += dy
144
145
if bounced and random.random() < 0.4:
146
# Small random post-bounce offset for less deterministic paths.
147
x += random.choice([-1, 0, 1])
148
y += random.choice([-1, 0, 1])
149
x = clamp(x, 1, w)
150
y = clamp(y, 1, h)
151
return 0
152
finally:
153
sys.stdout.write("\x1b[0m\x1b[?25h\n") # reset + show cursor
154
sys.stdout.flush()
155
termios.tcsetattr(fd, termios.TCSADRAIN, old)
156
157
158
if __name__ == "__main__":
159
raise SystemExit(main())
160
161