cli-arcade 2026.2.0__py3-none-any.whl → 2026.2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-arcade
3
- Version: 2026.2.0
3
+ Version: 2026.2.1
4
4
  Summary: Collection of terminal CLI games
5
5
  Home-page: https://github.com/Bro-Code-Technologies/cli-arcade/tree/main/windows
6
6
  Author: Bro Code Technologies LLC
@@ -96,3 +96,6 @@ All notable changes to this project will be documented in this file.
96
96
  - Byte Bouncer & Star Ship: floor + off-screen right wall; left-column bug fixed.
97
97
  - `ptk`: default-attribute/color emission fix; add helper to exit alt screen.
98
98
  - Fixed 180 movement glitch in Star Ship.
99
+
100
+ ### 2026.2.1
101
+ - Updated key mapping to work with Linux and macOS terminals.
@@ -1,29 +1,37 @@
1
1
  cli.py,sha256=sf70SoYU89X2b5CENYvqC7z8KntRadlV3IQHJXrgkw0,19567
2
- cli_arcade-2026.2.0.dist-info/licenses/LICENSE,sha256=1PLSNFyGPi9COkVEeNYcUTuRLqGxzMLLTah05tByD4s,1099
2
+ cli_arcade-2026.2.1.dist-info/licenses/LICENSE,sha256=1PLSNFyGPi9COkVEeNYcUTuRLqGxzMLLTah05tByD4s,1099
3
3
  game_classes/__init__.py,sha256=A0o4vBUktOiONToIJb0hACalszBnFS9ls9WXPn3-KH0,28
4
4
  game_classes/game_base.py,sha256=8OaMHwPE6KKhwRCbUoI0Kypf2CN6RVxze_zdqaw7HhE,3499
5
5
  game_classes/highscores.py,sha256=dDK-7lFXSMwIqExXGp6_-7QfQz6CIf12lJ22poeCdh4,4061
6
6
  game_classes/menu.py,sha256=wx-dITSozG1VCfyFeFKgooRXEijQygDCTBRvoKnd7-8,2798
7
- game_classes/ptk.py,sha256=CpbE7KJ3HXTRuaeMJSIMEfGzcwjmPMtURjyHPOFWVoY,8119
7
+ game_classes/ptk.py,sha256=v7vXIJVaJUc5JdDtUSyD6QLDJnybXY_ueDEatRqAFuI,12977
8
8
  game_classes/tools.py,sha256=sZR4m8NSil4UhmZ2qDsl1iEi1hZMwUPVBFR5hSmiEBc,5106
9
+ game_classes/__pycache__/__init__.cpython-312.pyc,sha256=R0jJUxI2JIcXR4r34VwLgaPHHUCRZBwegLXmlZ2n2LM,197
9
10
  game_classes/__pycache__/__init__.cpython-313.pyc,sha256=zSl9ESMUQn5Vo_XXAA8DD7lpFHOtOwBruGvCOJ5qeuo,192
11
+ game_classes/__pycache__/game_base.cpython-312.pyc,sha256=oeqYvfMbSDgwzZ3x2-lnuOVHSo2QWZQgbbYU9j3A8qY,6765
10
12
  game_classes/__pycache__/game_base.cpython-313.pyc,sha256=NKAgCBXxM7nZ8mt6wFy1MxUW6DK7EBhqXiLr1sVmtiA,6977
13
+ game_classes/__pycache__/highscores.cpython-312.pyc,sha256=uaBo2z_TbERpOR1V-7oiVyt9iNu2Q1-WtMRuUb5FTY8,6942
11
14
  game_classes/__pycache__/highscores.cpython-313.pyc,sha256=0rYMYs6NOzT-f00Me20F-46kUR_x8ZXk2jngO5amLy4,7080
15
+ game_classes/__pycache__/menu.cpython-312.pyc,sha256=fQ-AFhkA97IM3aauTx1J8huAXEcwELuc8QzO589o7ms,4424
12
16
  game_classes/__pycache__/menu.cpython-313.pyc,sha256=urqSfGlJJdfTKI1kqXOT0oPXjo1Ny9MyYA8QroVQHzQ,4488
13
- game_classes/__pycache__/ptk.cpython-313.pyc,sha256=ZTwAGo3hlFRhk5rNbLiUBOMmR_odse9v_XTkHXdjxpw,14412
17
+ game_classes/__pycache__/ptk.cpython-312.pyc,sha256=h5ShTY7g8zjU1d3bJZkbcdtJGN6CVvB1IIDL7WNozOk,18602
18
+ game_classes/__pycache__/ptk.cpython-313.pyc,sha256=oSVDS5Cfugqn-cCsLjdoB7upFo6CgYSC5PwVaufS0hI,19240
14
19
  game_classes/__pycache__/ptk_curses.cpython-313.pyc,sha256=XqhJ78tqyT0HFd5UV_1_IrGl-W5PuW4WyJve5RiY3uo,13820
15
20
  game_classes/__pycache__/ptk_game_base.cpython-313.pyc,sha256=tRzGtoCErAHsUpoEWVjQN4PQ0Gw5N9RW3nJHJW2IT24,6915
16
21
  game_classes/__pycache__/ptk_menu.cpython-313.pyc,sha256=jyBSuLVxNm7ueGjAIKhd7D_ZEFpwLv9U1iim_ElClHk,2448
17
22
  game_classes/__pycache__/ptk_tools.cpython-313.pyc,sha256=Y-lBpAMlt8HfgQEio4isDGpwXclYUsyzs9wGoeGJkmE,5061
23
+ game_classes/__pycache__/tools.cpython-312.pyc,sha256=X_qDsJLFe5_Y29l4MlH_RuM46dOxvunIUTyBsvYj85o,7273
18
24
  game_classes/__pycache__/tools.cpython-313.pyc,sha256=0MS9Cai_D0LaIQ6w6c6BLSJ45XvnUr4BjHSQdFYnQ7Y,7316
19
25
  games/__init__.py,sha256=Rg46lnLj_sezpnXaQTS1NXX5OaRhR_EWpNTyYUxycj4,21
20
26
  games/byte_bouncer/__init__.py,sha256=8hxIHRyUPB-X6vJXh1432ytKjOcfIzCbdpO5mmWA7iQ,33
21
27
  games/byte_bouncer/game.py,sha256=EpTzObLNF_cGfB-xRkUlqCwYt7A7IGk8nENkOdqoV3c,8877
22
28
  games/byte_bouncer/__pycache__/byte_bouncer.cpython-313.pyc,sha256=JSTB6-_KCukAAVaZz9TpJ3V4gif_M3n_L5X1KOviSJ8,24605
29
+ games/byte_bouncer/__pycache__/game.cpython-312.pyc,sha256=ITr1oNdZ7TQiDAw_suY1bFHQaiydXB_I7-TMMJMN5h8,13709
23
30
  games/byte_bouncer/__pycache__/game.cpython-313.pyc,sha256=0H66QPFncbxvKVlMRPKEXjJDYtw1pPAw5I9sz3kWdIA,13965
24
31
  games/byte_bouncer/__pycache__/highscores.cpython-313.pyc,sha256=g3FozKpWGDVsTVETNpb6lRHcKA3ZIsQio8Kh7KCGVCI,3125
25
32
  games/star_ship/__init__.py,sha256=X8AdhoccmNVu6yl7MalsxIsMxuEsddRdlvEQmhZMD0A,30
26
33
  games/star_ship/game.py,sha256=aN3LVh-kaYppAy-J259Y6XROT5pvEAFDjGd1404oWBw,10992
34
+ games/star_ship/__pycache__/game.cpython-312.pyc,sha256=lOScasiJ9VaNlCua45m_jejQPetUQGROhCxlV_aKi1I,15712
27
35
  games/star_ship/__pycache__/game.cpython-313.pyc,sha256=hWqwV12a1VF-_Lc8Uzd7ono4ZN3gNJ4RQ-gK69OayVw,15922
28
36
  games/star_ship/__pycache__/highscores.cpython-313.pyc,sha256=nHbEOTWeX0U9D47K2EP5S5uDPI0WW52Eg7h_NesG1OU,3148
29
37
  games/star_ship/__pycache__/nibbles.cpython-313.pyc,sha256=Yv2P-GwEc30zA24aO7k0hkk1AOja6K85AFvmjLIffqQ,20510
@@ -31,11 +39,12 @@ games/star_ship/__pycache__/snek.cpython-313.pyc,sha256=3Mwyy6vjGFRmNyAWSqV5JRil
31
39
  games/star_ship/__pycache__/star_ship.cpython-313.pyc,sha256=9gJt10MTzVTSwsc4I16TDZTaAXclfhfGTzgfYgZNOKo,28021
32
40
  games/terminal_tumble/__init__.py,sha256=axTd7hCgnLRNU8QS73NnQY3q5xjIb3ZYQORJ9Lz8l_E,36
33
41
  games/terminal_tumble/game.py,sha256=UbtrMS4wi5MiIMHfreP51VlrHHlfxEPJpFmsLgdUgWs,15633
42
+ games/terminal_tumble/__pycache__/game.cpython-312.pyc,sha256=44n4iwM1rIjtMyeyITib1ewnGqOcI8p-npdpSErJQUs,23684
34
43
  games/terminal_tumble/__pycache__/game.cpython-313.pyc,sha256=DrjmLzJYY5QoYXghGh-ORn26Yax9Gmd6fSCZGdgYbRQ,24073
35
44
  games/terminal_tumble/__pycache__/highscores.cpython-313.pyc,sha256=UPnU9Zq88q2TZq1qXyo8e2Y_PdOgwvu5mncIkunFah8,3154
36
45
  games/terminal_tumble/__pycache__/terminal_tumble.cpython-313.pyc,sha256=7oLpmmkYjpLYRC0bjRoEkHbIUNq7FZVJToGoja94m0o,38016
37
- cli_arcade-2026.2.0.dist-info/METADATA,sha256=dBo3dzuaIBHOOOKkyi8NMZw7OacdGshIWdDEtMKZ5Ik,3229
38
- cli_arcade-2026.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
- cli_arcade-2026.2.0.dist-info/entry_points.txt,sha256=nNElhZv4nM_lrOYpvnOChtJn1XsWwQr8sI344flzzTQ,56
40
- cli_arcade-2026.2.0.dist-info/top_level.txt,sha256=gTfYlz7gHYgPY0ugfJivukCOkNzmOisNR6VuJjAXPF4,23
41
- cli_arcade-2026.2.0.dist-info/RECORD,,
46
+ cli_arcade-2026.2.1.dist-info/METADATA,sha256=zD5GPdMYROphM5_bhvgOHKbA_kTOdBe65-l6UZWSQOg,3308
47
+ cli_arcade-2026.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
48
+ cli_arcade-2026.2.1.dist-info/entry_points.txt,sha256=nNElhZv4nM_lrOYpvnOChtJn1XsWwQr8sI344flzzTQ,56
49
+ cli_arcade-2026.2.1.dist-info/top_level.txt,sha256=gTfYlz7gHYgPY0ugfJivukCOkNzmOisNR6VuJjAXPF4,23
50
+ cli_arcade-2026.2.1.dist-info/RECORD,,
game_classes/ptk.py CHANGED
@@ -5,8 +5,24 @@ import threading
5
5
  import queue
6
6
  import time
7
7
 
8
- from prompt_toolkit.input import create_input
9
- from prompt_toolkit.keys import Keys
8
+ _HAS_PROMPT_TOOLKIT = True
9
+ try:
10
+ from prompt_toolkit.input import create_input
11
+ from prompt_toolkit.keys import Keys
12
+ except Exception:
13
+ create_input = None
14
+ Keys = None
15
+ _HAS_PROMPT_TOOLKIT = False
16
+ import select
17
+
18
+ _HAS_TERMIOS = True
19
+ try:
20
+ import termios
21
+ import tty
22
+ except Exception:
23
+ termios = None
24
+ tty = None
25
+ _HAS_TERMIOS = False
10
26
 
11
27
  # basic color constants (match curses style usage)
12
28
  COLOR_BLACK = 0
@@ -92,10 +108,29 @@ class _Screen:
92
108
  self._use_msvcrt = os.name == "nt"
93
109
  self._input = None
94
110
  self._thread = None
111
+ self._posix_fd = None
112
+ self._orig_term_attrs = None
95
113
  if not self._use_msvcrt:
96
- self._input = create_input()
97
- self._thread = threading.Thread(target=self._reader, daemon=True)
98
- self._thread.start()
114
+ # Prefer a simple termios-based reader on POSIX for raw key capture
115
+ if _HAS_TERMIOS:
116
+ try:
117
+ self._posix_fd = sys.stdin.fileno()
118
+ self._orig_term_attrs = termios.tcgetattr(self._posix_fd)
119
+ tty.setcbreak(self._posix_fd)
120
+ self._thread = threading.Thread(target=self._posix_reader, daemon=True)
121
+ self._thread.start()
122
+ except Exception:
123
+ # fall back to prompt_toolkit if termios fails
124
+ self._posix_fd = None
125
+ self._orig_term_attrs = None
126
+ if self._posix_fd is None and _HAS_PROMPT_TOOLKIT and create_input is not None:
127
+ try:
128
+ self._input = create_input()
129
+ self._thread = threading.Thread(target=self._reader, daemon=True)
130
+ self._thread.start()
131
+ except Exception as e:
132
+ self._input = None
133
+ sys.stderr.write(f"[ptk] create_input failed: {e}\n")
99
134
  self._timeout = 0.0
100
135
  self._buffer = []
101
136
  self._attrs = []
@@ -126,9 +161,81 @@ class _Screen:
126
161
  try:
127
162
  for key in self._input.read_keys():
128
163
  self._queue.put(key)
129
- except Exception:
164
+ except Exception as e:
165
+ # log and continue — don't let the thread die silently
166
+ sys.stderr.write(f"[ptk] reader exception: {e}\n")
130
167
  time.sleep(0.01)
131
168
 
169
+ def _posix_reader(self):
170
+ if not _HAS_TERMIOS:
171
+ return
172
+ fd = self._posix_fd
173
+ buf = b""
174
+ while not self._stop.is_set():
175
+ try:
176
+ r, _, _ = select.select([fd], [], [], 0.1)
177
+ if not r:
178
+ continue
179
+ chunk = os.read(fd, 32)
180
+ if not chunk:
181
+ continue
182
+ buf += chunk
183
+ # process buffer for known sequences
184
+ while buf:
185
+ # single-byte control checks
186
+ if buf.startswith(b"\x1b"):
187
+ # escape sequences: try to consume common sequences
188
+ if buf.startswith(b"\x1b[A"):
189
+ self._queue.put(KEY_UP)
190
+ buf = buf[3:]
191
+ continue
192
+ if buf.startswith(b"\x1b[B"):
193
+ self._queue.put(KEY_DOWN)
194
+ buf = buf[3:]
195
+ continue
196
+ if buf.startswith(b"\x1b[C"):
197
+ self._queue.put(KEY_RIGHT)
198
+ buf = buf[3:]
199
+ continue
200
+ if buf.startswith(b"\x1b[D"):
201
+ self._queue.put(KEY_LEFT)
202
+ buf = buf[3:]
203
+ continue
204
+ # PageUp/PageDown common sequences
205
+ if buf.startswith(b"\x1b[5~"):
206
+ self._queue.put(KEY_PPAGE)
207
+ buf = buf[4:]
208
+ continue
209
+ if buf.startswith(b"\x1b[6~"):
210
+ self._queue.put(KEY_NPAGE)
211
+ buf = buf[4:]
212
+ continue
213
+ # unknown escape: drop single ESC
214
+ self._queue.put(27)
215
+ buf = buf[1:]
216
+ continue
217
+ # newline / carriage return
218
+ if buf[0] in (10, 13):
219
+ self._queue.put(10)
220
+ buf = buf[1:]
221
+ continue
222
+ # backspace (DEL or BS)
223
+ if buf[0] in (8, 127):
224
+ self._queue.put(KEY_BACKSPACE)
225
+ buf = buf[1:]
226
+ continue
227
+ # regular printable character
228
+ ch = buf[0]
229
+ if 32 <= ch <= 126:
230
+ self._queue.put(ch)
231
+ buf = buf[1:]
232
+ continue
233
+ # unhandled byte: drop
234
+ buf = buf[1:]
235
+ except Exception as e:
236
+ sys.stderr.write(f"[ptk] posix_reader exception: {e}\n")
237
+ time.sleep(0.01)
238
+
132
239
  def stop(self):
133
240
  self._stop.set()
134
241
  try:
@@ -136,6 +243,12 @@ class _Screen:
136
243
  self._input.close()
137
244
  except Exception:
138
245
  pass
246
+ # restore termios attrs if we changed them
247
+ try:
248
+ if self._orig_term_attrs is not None and _HAS_TERMIOS:
249
+ termios.tcsetattr(self._posix_fd, termios.TCSANOW, self._orig_term_attrs)
250
+ except Exception:
251
+ pass
139
252
 
140
253
  def nodelay(self, _flag=True):
141
254
  return None
@@ -221,6 +334,9 @@ class _Screen:
221
334
  key = self._queue.get(timeout=self._timeout)
222
335
  except Exception:
223
336
  return -1
337
+ # key may be an int from posix reader or a keypress object from prompt_toolkit
338
+ if isinstance(key, int):
339
+ return key
224
340
  return _map_keypress(key)
225
341
 
226
342