Compare commits
3 commits
177e66c626
...
0396ec83c6
| Author | SHA1 | Date | |
|---|---|---|---|
| 0396ec83c6 | |||
| d87088eff0 | |||
| 66671eda8b |
1 changed files with 210 additions and 79 deletions
|
|
@ -15,8 +15,8 @@ class Config:
|
|||
speed: float = 1.0 # global speed multiplier (TICK = 0.1 / speed)
|
||||
no_shark: bool = False
|
||||
no_ship: bool = False
|
||||
bloody: bool = False # shark eats fish with gore splats
|
||||
vegan: bool = False # disable predators (shark, big_fish, fishhook)
|
||||
bloody: bool = False # massacre: all predators eat fish with blood splats
|
||||
vegan: bool = False # peaceful: no predators (shark, big_fish, fishhook)
|
||||
any_key: bool = False # any key exits
|
||||
|
||||
# ─── Terminal control ────────────────────────────────────────────────────────
|
||||
|
|
@ -32,6 +32,17 @@ def _fg(r, g, b): return f"\033[38;2;{r};{g};{b}m"
|
|||
def _go(row, col): return f"\033[{row+1};{col+1}H"
|
||||
def term_size(): s = shutil.get_terminal_size(); return s.lines, s.columns
|
||||
|
||||
def _write_all(fd: int, data: bytes) -> None:
|
||||
"""Write all bytes to fd, retrying on partial writes (non-blocking TTY)."""
|
||||
mv = memoryview(data)
|
||||
offset = 0
|
||||
while offset < len(mv):
|
||||
try:
|
||||
n = os.write(fd, mv[offset:])
|
||||
offset += n
|
||||
except BlockingIOError:
|
||||
time.sleep(0.001)
|
||||
|
||||
|
||||
# ─── Color palette ───────────────────────────────────────────────────────────
|
||||
# RGB values matching our Homebrew init_pair fix (xterm-256 slots 16-231):
|
||||
|
|
@ -100,43 +111,68 @@ class Canvas:
|
|||
self._ck[row][col] = ck
|
||||
|
||||
def put_sprite(self, y: int, x: int, shape: List[str],
|
||||
mask: Optional[List[str]], default_color: Optional[str]):
|
||||
"""Draw a sprite. Spaces and '?' characters are transparent."""
|
||||
mask: Optional[List[str]], default_color: Optional[str],
|
||||
opaque: bool = False):
|
||||
"""Draw a sprite.
|
||||
'?' is always transparent.
|
||||
' ' is transparent when opaque=False (background decorations).
|
||||
When opaque=True: only INTERIOR spaces (between the first and last
|
||||
visible char of each line) are drawn as black background.
|
||||
Leading/trailing spaces stay transparent so shapes don't leave
|
||||
black blocks outside their visible outline.
|
||||
"""
|
||||
rows, cols = self.rows, self.cols
|
||||
for dr, line in enumerate(shape):
|
||||
row = y + dr
|
||||
if row < 0 or row >= rows:
|
||||
continue
|
||||
mline = (mask[dr] if dr < len(mask) else '') if mask else ''
|
||||
if opaque:
|
||||
nsp = [i for i, c in enumerate(line) if c not in (' ', '?')]
|
||||
interior_start = nsp[0] if nsp else -1
|
||||
interior_end = nsp[-1] if nsp else -1
|
||||
for dc, ch in enumerate(line):
|
||||
if ch in (' ', '?'):
|
||||
if ch == '?':
|
||||
continue
|
||||
col = x + dc
|
||||
if 0 <= col < cols:
|
||||
mk = mline[dc] if dc < len(mline) else ' '
|
||||
ck = mk if mk in _C else default_color
|
||||
self._ch[row][col] = ch
|
||||
self._ck[row][col] = ck
|
||||
if ch == ' ':
|
||||
if opaque and interior_start <= dc <= interior_end:
|
||||
self._ch[row][col] = ' '
|
||||
self._ck[row][col] = None
|
||||
else:
|
||||
mk = mline[dc] if dc < len(mline) else ' '
|
||||
ck = mk if mk in _C else default_color
|
||||
self._ch[row][col] = ch
|
||||
self._ck[row][col] = ck
|
||||
|
||||
def render(self) -> str:
|
||||
# \033[H homes the cursor; every cell is rewritten below so \033[2J is
|
||||
# not needed and would push content into the scrollback on every frame.
|
||||
# The very last cell (bottom-right corner) is intentionally skipped:
|
||||
# writing to it would trigger auto-wrap and scroll the terminal.
|
||||
last_row = self.rows - 1
|
||||
last_col = self.cols - 1
|
||||
# \033[H homes the cursor; every cell is rewritten so \033[2J is not
|
||||
# needed (it would push content into the scrollback on every frame).
|
||||
#
|
||||
# The last column of EVERY row is intentionally skipped (not just the
|
||||
# last row). Writing to the last column triggers "pending-wrap" state.
|
||||
# In iTerm2, cursor-position commands (_go) do NOT clear pending-wrap,
|
||||
# so the first character of the next row triggers a wrap, placing it one
|
||||
# row too low. This cascades: row r+1 lands at r+2, row r+2 at r+3, …
|
||||
# Content below the waterline shifts off-screen; skipped rows show stale
|
||||
# characters. Staying one column short prevents pending-wrap entirely.
|
||||
col_limit = self.cols - 1
|
||||
parts = ["\033[H", RESET]
|
||||
sentinel = object()
|
||||
last_ck: object = sentinel
|
||||
for r in range(self.rows):
|
||||
parts.append(_go(r, 0))
|
||||
col_limit = last_col if r == last_row else self.cols
|
||||
for c in range(col_limit):
|
||||
ck = self._ck[r][c]
|
||||
if ck is not last_ck:
|
||||
parts.append(RESET if ck is None else _esc(ck))
|
||||
last_ck = ck
|
||||
parts.append(self._ch[r][c])
|
||||
# Erase the last column with the default background (no cursor
|
||||
# advance → no pending-wrap; handles content left by wider frames).
|
||||
parts.append(RESET + "\033[K")
|
||||
last_ck = sentinel
|
||||
parts.append(RESET)
|
||||
return ''.join(parts)
|
||||
|
||||
|
|
@ -174,7 +210,8 @@ class Entity:
|
|||
death_cb: Optional[Callable] = None,
|
||||
die_time: Optional[float] = None,
|
||||
die_frame: Optional[int] = None,
|
||||
entity_type: str = ''):
|
||||
entity_type: str = '',
|
||||
opaque: bool = True):
|
||||
self.frames = frames
|
||||
self.masks = masks
|
||||
self.x = float(x)
|
||||
|
|
@ -186,6 +223,7 @@ class Entity:
|
|||
self.die_time = die_time
|
||||
self._die_frame = die_frame
|
||||
self.entity_type = entity_type
|
||||
self.opaque = opaque
|
||||
self.curr_frame = 0.0
|
||||
self.alive = True
|
||||
self.height = len(frames[0]) if frames else 0
|
||||
|
|
@ -245,29 +283,10 @@ class Entity:
|
|||
canvas.put_sprite(int(self.y), int(self.x),
|
||||
self.frames[fi],
|
||||
self.masks[fi] if self.masks and fi < len(self.masks) else None,
|
||||
self.default_color)
|
||||
self.default_color,
|
||||
self.opaque)
|
||||
|
||||
|
||||
class HookEntity(Entity):
|
||||
"""Fishhook: descends to 75% depth, then retracts."""
|
||||
|
||||
def __init__(self, *args, target_y: int, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._target_y = target_y
|
||||
self._retracting = False
|
||||
|
||||
def _move(self):
|
||||
if not self._retracting:
|
||||
if int(self.y) >= self._target_y:
|
||||
self._retracting = True
|
||||
self.cb_args[1] = -1 # reverse: go up
|
||||
else:
|
||||
self.y += 1
|
||||
else:
|
||||
self.y -= 1
|
||||
if int(self.y) < -10:
|
||||
self.alive = False
|
||||
|
||||
|
||||
class CrabEntity(Entity):
|
||||
"""Crab that walks out from the castle gate and then returns."""
|
||||
|
|
@ -306,7 +325,7 @@ class Aquarium:
|
|||
self.canvas = Canvas(rows, cols)
|
||||
# wl = first row of the wave area, proportional like Perl's int($lines/3).
|
||||
# wl+4 is the first fully-underwater row (4 wave rows below wl).
|
||||
self.wl = max(3, rows // 3)
|
||||
self.wl = max(3, rows // 3 - 3)
|
||||
self.entities: List[Entity] = []
|
||||
|
||||
fns = [self.add_whale, self.add_monster, self.add_swan,
|
||||
|
|
@ -338,7 +357,7 @@ class Aquarium:
|
|||
|
||||
def resize(self, rows: int, cols: int):
|
||||
self.rows, self.cols = rows, cols
|
||||
self.wl = max(3, rows // 3)
|
||||
self.wl = max(3, rows // 3 - 3)
|
||||
self.canvas.resize(rows, cols)
|
||||
self._cs_state = 0
|
||||
self._cs_portcullis = None
|
||||
|
|
@ -351,8 +370,8 @@ class Aquarium:
|
|||
random.choice(self._random_fns)(dead)
|
||||
|
||||
def step(self):
|
||||
if self.cfg.bloody:
|
||||
self._check_predation()
|
||||
self._check_predation()
|
||||
self._check_fishing()
|
||||
self.entities = [e for e in self.entities if e.update(self)]
|
||||
self._update_crab_scene()
|
||||
|
||||
|
|
@ -380,6 +399,7 @@ class Aquarium:
|
|||
cb_args=[0, 0, 0],
|
||||
default_color='c',
|
||||
die_offscreen=False,
|
||||
opaque=False,
|
||||
))
|
||||
|
||||
# ── Castle ───────────────────────────────────────────────────────────────
|
||||
|
|
@ -421,6 +441,7 @@ class Aquarium:
|
|||
cb_args=[0, 0, 0],
|
||||
default_color='k',
|
||||
die_offscreen=False,
|
||||
opaque=False,
|
||||
))
|
||||
|
||||
# ── Seaweed ──────────────────────────────────────────────────────────────
|
||||
|
|
@ -454,6 +475,7 @@ class Aquarium:
|
|||
die_offscreen=False,
|
||||
death_cb=lambda e, aq: aq.add_seaweed(e),
|
||||
die_time=die_t,
|
||||
opaque=False,
|
||||
))
|
||||
|
||||
# ── Bubbles ──────────────────────────────────────────────────────────────
|
||||
|
|
@ -473,7 +495,8 @@ class Aquarium:
|
|||
# ── Fish ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def add_all_fish(self):
|
||||
count = max(8, (self.rows - 9) * self.cols // 200)
|
||||
underwater_rows = max(1, self.rows - (self.wl + 4))
|
||||
count = max(6, underwater_rows * self.cols // 320)
|
||||
for _ in range(count):
|
||||
self.add_fish(None)
|
||||
|
||||
|
|
@ -833,7 +856,7 @@ yywwwyyyyyyyyyyyyyyyyyyyy
|
|||
self._add(Entity(
|
||||
frames=[shape_r if going_right else shape_l],
|
||||
masks=[mask_r if going_right else mask_l],
|
||||
x=x, y=0,
|
||||
x=x, y=self.wl - 5,
|
||||
cb_args=[speed, 0, 0],
|
||||
default_color='W',
|
||||
die_offscreen=True,
|
||||
|
|
@ -859,6 +882,7 @@ yywwwyyyyyyyyyyyyyyyyyyyy
|
|||
C C
|
||||
CCCCCCC
|
||||
C C C
|
||||
|
||||
BBBBBBB
|
||||
BB BB
|
||||
B B BWB B
|
||||
|
|
@ -868,6 +892,7 @@ BBBBB BBBB
|
|||
C C
|
||||
CCCCCCC
|
||||
C C C
|
||||
|
||||
BBBBBBB
|
||||
BB BB
|
||||
B BWB B B
|
||||
|
|
@ -890,21 +915,28 @@ BBBB BBBBB
|
|||
mask_str = mask_r_str if going_right else mask_l_str
|
||||
x = -18 if going_right else self.cols - 2
|
||||
|
||||
# 4 body lines; all frames are 8 rows: 3 spout + 1 separator + 4 body.
|
||||
# This keeps the body at the same rows (4-7) in every frame so the
|
||||
# whale doesn't jump, and the mask aligns correctly.
|
||||
body_lines = _p(body_str)
|
||||
mask_lines = _p(mask_str)
|
||||
|
||||
frames, masks = [], []
|
||||
# 5 frames without spout
|
||||
# 5 frames without spout: 4 empty rows + 4 body rows
|
||||
for _ in range(5):
|
||||
frames.append(_p('\n\n\n' + body_str))
|
||||
masks.append(_p(mask_str))
|
||||
# 7 frames with animated spout
|
||||
frames.append(['', '', '', ''] + body_lines)
|
||||
masks.append(mask_lines)
|
||||
# 7 frames with animated spout: spout padded to 3 rows, then separator, then body
|
||||
for spout in spouts:
|
||||
sp_lines = spout.strip('\n').split('\n')
|
||||
aligned = '\n'.join(' ' * spout_align + l for l in sp_lines)
|
||||
frames.append(_p(aligned + '\n' + body_str))
|
||||
masks.append(_p(mask_str))
|
||||
sp_lines = [' ' * spout_align + l for l in spout.strip('\n').split('\n')]
|
||||
while len(sp_lines) < 3:
|
||||
sp_lines = [''] + sp_lines
|
||||
frames.append(sp_lines + [''] + body_lines)
|
||||
masks.append(mask_lines)
|
||||
|
||||
self._add(Entity(
|
||||
frames=frames, masks=masks,
|
||||
x=x, y=0,
|
||||
x=x, y=self.wl - 7,
|
||||
cb_args=[speed, 0, 0, 1],
|
||||
default_color='W',
|
||||
die_offscreen=True,
|
||||
|
|
@ -988,7 +1020,7 @@ BBBB BBBBB
|
|||
|
||||
self._add(Entity(
|
||||
frames=shapes, masks=[mask] * 4,
|
||||
x=x, y=2,
|
||||
x=x, y=self.wl - 4,
|
||||
cb_args=[speed, 0, 0, 0.25],
|
||||
default_color='G',
|
||||
die_offscreen=True,
|
||||
|
|
@ -1077,6 +1109,7 @@ BBBB BBBBB
|
|||
default_color='Y',
|
||||
die_offscreen=True,
|
||||
death_cb=lambda e, aq: aq.random_object(e),
|
||||
entity_type='big_fish',
|
||||
))
|
||||
|
||||
# ── Ducks ────────────────────────────────────────────────────────────────
|
||||
|
|
@ -1242,7 +1275,7 @@ yy
|
|||
|
||||
self._add(Entity(
|
||||
frames=[shape], masks=[mask],
|
||||
x=x, y=1,
|
||||
x=x, y=self.wl - 6,
|
||||
cb_args=[speed, 0, 0, 0.25],
|
||||
default_color='W',
|
||||
die_offscreen=True,
|
||||
|
|
@ -1252,7 +1285,7 @@ yy
|
|||
# ── Fishhook ─────────────────────────────────────────────────────────────
|
||||
|
||||
def add_fishhook(self, dead=None):
|
||||
hook_shape = _p(r"""
|
||||
hook_part = _p(r"""
|
||||
o
|
||||
||
|
||||
||
|
||||
|
|
@ -1260,18 +1293,36 @@ yy
|
|||
\__//
|
||||
`--'
|
||||
""")
|
||||
x = random.randint(10, max(11, self.cols - 20))
|
||||
target_y = int(self.rows * 0.75)
|
||||
cord = ' |' # '|' at col 7, aligns with 'o'
|
||||
cord_mask = ' w' # cord is white; hook uses default_color ('y')
|
||||
x = random.randint(10, max(11, self.cols - 20))
|
||||
start_d = self.wl
|
||||
max_d = int(self.rows * 0.75)
|
||||
descent = max(1, max_d - start_d)
|
||||
fadv = 0.3 # ~1 row per 3 ticks ≈ 0.3 s/row
|
||||
|
||||
e = HookEntity(
|
||||
frames=[hook_shape], masks=None,
|
||||
x=x, y=-4,
|
||||
cb_args=[0, 1, 0],
|
||||
default_color='G',
|
||||
hook_mask_lines = [''] * len(hook_part) # hook chars → default_color
|
||||
frames, fmasks = [], []
|
||||
for d in range(descent + 1):
|
||||
n = start_d + d
|
||||
frames.append([cord] * n + hook_part)
|
||||
fmasks.append([cord_mask] * n + hook_mask_lines)
|
||||
for d in range(descent - 1, -1, -1):
|
||||
n = start_d + d
|
||||
frames.append([cord] * n + hook_part)
|
||||
fmasks.append([cord_mask] * n + hook_mask_lines)
|
||||
|
||||
e = Entity(
|
||||
frames=frames, masks=fmasks,
|
||||
x=x, y=0,
|
||||
cb_args=[0, 0, 0, fadv],
|
||||
default_color='y', # hook = yellow metal
|
||||
die_offscreen=False,
|
||||
target_y=target_y,
|
||||
die_frame=int(len(frames) / fadv),
|
||||
death_cb=lambda e, aq: aq.random_object(e),
|
||||
entity_type='fishhook',
|
||||
)
|
||||
e._hook_descent = descent # needed for early retraction on catch
|
||||
self._add(e)
|
||||
|
||||
# ── Seahorse (Seepferdchen) ───────────────────────────────────────────────
|
||||
|
|
@ -1428,19 +1479,63 @@ yccw
|
|||
# ── Predation (--bloody) ─────────────────────────────────────────────────
|
||||
|
||||
def _check_predation(self):
|
||||
sharks = [e for e in self.entities
|
||||
if e.entity_type == 'shark' and e.alive]
|
||||
fish = [e for e in self.entities
|
||||
if e.entity_type == 'fish' and e.alive]
|
||||
for s in sharks:
|
||||
sx1 = int(s.x); sx2 = sx1 + s.width
|
||||
sy1 = int(s.y); sy2 = sy1 + s.height
|
||||
# Shark always eats fish; in bloody mode big_fish joins the massacre.
|
||||
predator_types = {'shark'}
|
||||
if self.cfg.bloody:
|
||||
predator_types.add('big_fish')
|
||||
predators = [e for e in self.entities
|
||||
if e.entity_type in predator_types and e.alive]
|
||||
fish = [e for e in self.entities
|
||||
if e.entity_type == 'fish' and e.alive]
|
||||
for p in predators:
|
||||
px1 = int(p.x); px2 = px1 + p.width
|
||||
py1 = int(p.y); py2 = py1 + p.height
|
||||
for f in fish:
|
||||
fx = int(f.x) + f.width // 2
|
||||
fy = int(f.y) + f.height // 2
|
||||
if sx1 <= fx <= sx2 and sy1 <= fy <= sy2:
|
||||
self.add_splat(fx, fy)
|
||||
if px1 <= fx <= px2 and py1 <= fy <= py2:
|
||||
if p.entity_type == 'shark' or self.cfg.bloody:
|
||||
self.add_splat(fx, fy)
|
||||
f.alive = False
|
||||
if f.death_cb:
|
||||
f.death_cb(f, self)
|
||||
|
||||
def _check_fishing(self):
|
||||
"""Fishhook catches fish; caught fish is dragged upward with the hook."""
|
||||
HOOK_PART_HEIGHT = 6
|
||||
hooks = [e for e in self.entities
|
||||
if e.entity_type == 'fishhook' and e.alive]
|
||||
fish = [e for e in self.entities
|
||||
if e.entity_type == 'fish' and e.alive]
|
||||
for h in hooks:
|
||||
fi = h.frame_idx
|
||||
cord_n = len(h.frames[fi]) - HOOK_PART_HEIGHT
|
||||
hook_row = int(h.y) + cord_n # row of 'o'
|
||||
hook_col = int(h.x) + 7 # 'o' at offset 7
|
||||
for f in fish:
|
||||
fy1 = int(f.y); fy2 = fy1 + f.height
|
||||
fx1 = int(f.x); fx2 = fx1 + f.width
|
||||
if fy1 <= hook_row <= fy2 and fx1 <= hook_col <= fx2:
|
||||
f.alive = False
|
||||
if f.death_cb:
|
||||
f.death_cb(f, self)
|
||||
# Jump hook to retraction phase immediately
|
||||
descent = getattr(h, '_hook_descent', None)
|
||||
if descent is not None and int(h.curr_frame) <= descent:
|
||||
h.curr_frame = float(descent)
|
||||
# Inject fish line into all remaining hook frames so it
|
||||
# is dragged upward locked to the hook (no separate entity).
|
||||
fish_raw = (f.frames[f.frame_idx] or ['><>'])[0] or '><>'
|
||||
fish_stripped = fish_raw.strip() or '><>'
|
||||
# Centre the fish under the hook's 'o' (col offset 7 in the frame)
|
||||
pad = max(0, 7 - len(fish_stripped) // 2)
|
||||
fish_line = ' ' * pad + fish_stripped
|
||||
fi = int(h.curr_frame)
|
||||
for i in range(fi, len(h.frames)):
|
||||
h.frames[i].append(fish_line)
|
||||
if h.masks and i < len(h.masks) and h.masks[i] is not None:
|
||||
h.masks[i].append('')
|
||||
break # one fish per hook per tick
|
||||
|
||||
# ── Crab scene (portcullis + crab) ────────────────────────────────────────
|
||||
|
||||
|
|
@ -1483,7 +1578,7 @@ yccw
|
|||
mask = [' yWy ', ' ']
|
||||
c = CrabEntity(
|
||||
frames=[crab_f0, crab_f1], masks=[mask, mask],
|
||||
x=float(gx), y=float(gy),
|
||||
x=float(gx), y=float(gy + 1),
|
||||
cb_args=[-1, 0, 0, 0.25],
|
||||
default_color='y',
|
||||
die_offscreen=False,
|
||||
|
|
@ -1544,6 +1639,10 @@ def main():
|
|||
help='disable all predators (shark, big fish, fishhook)')
|
||||
parser.add_argument('--any-key', action='store_true',
|
||||
help='any key press exits (default: q/Q/Esc/Ctrl-C)')
|
||||
parser.add_argument('--debug', action='store_true',
|
||||
help='show terminal size / entity count overlay')
|
||||
parser.add_argument('--ruler', action='store_true',
|
||||
help='draw numbered row ruler and exit (diagnostic)')
|
||||
args = parser.parse_args()
|
||||
|
||||
cfg = Config(
|
||||
|
|
@ -1555,8 +1654,6 @@ def main():
|
|||
any_key = args.any_key,
|
||||
)
|
||||
|
||||
rows, cols = term_size()
|
||||
aq = Aquarium(rows, cols, cfg)
|
||||
inp = Input()
|
||||
paused = False
|
||||
needs_reset = False
|
||||
|
|
@ -1568,7 +1665,34 @@ def main():
|
|||
signal.signal(signal.SIGWINCH, on_resize)
|
||||
|
||||
fd = sys.stdout.fileno()
|
||||
os.write(fd, (ALT_ON + HIDE_CUR + "\033[2J\033[H").encode())
|
||||
# Activate alternate screen first, THEN read terminal size so the
|
||||
# size reflects the actual pane dimensions (important in split panes).
|
||||
# \033[r resets any inherited scroll region to the full screen.
|
||||
_write_all(fd, (ALT_ON + HIDE_CUR + "\033[r" + "\033[2J\033[H").encode())
|
||||
rows, cols = term_size()
|
||||
|
||||
if args.ruler:
|
||||
# Draw numbered ruler for every row to verify actual terminal dimensions.
|
||||
# The last visible row number tells us the real screen height.
|
||||
buf = ["\033[H"]
|
||||
for r in range(rows):
|
||||
label = f"row {r:3d} (1-idx={r+1}) cols={cols}"
|
||||
if r == rows - 1:
|
||||
label = f"\033[7m LAST: row {r} / rows={rows} cols={cols} -- press any key \033[m"
|
||||
buf.append(f"\033[{r+1};1H\033[0m{label}")
|
||||
_write_all(fd, ''.join(buf).encode())
|
||||
try:
|
||||
while True:
|
||||
k = inp.key()
|
||||
if k is not None:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
finally:
|
||||
inp.restore()
|
||||
_write_all(fd, (ALT_OFF + SHOW_CUR).encode())
|
||||
return
|
||||
|
||||
aq = Aquarium(rows, cols, cfg)
|
||||
|
||||
try:
|
||||
TICK = 0.1 / cfg.speed
|
||||
|
|
@ -1595,7 +1719,14 @@ def main():
|
|||
if not paused:
|
||||
aq.step()
|
||||
frame = aq.render()
|
||||
os.write(fd, frame.encode())
|
||||
if args.debug:
|
||||
fish_ys = [f"{int(e.y)}" for e in aq.entities
|
||||
if e.entity_type == 'fish'][:8]
|
||||
dbg = (f" rows={aq.rows} cols={aq.cols} wl={aq.wl}"
|
||||
f" ents={len(aq.entities)}"
|
||||
f" fish_y=[{','.join(fish_ys)}] ")
|
||||
frame += f"\033[1;1H\033[0;7m{dbg}\033[m"
|
||||
_write_all(fd, frame.encode())
|
||||
|
||||
elapsed = time.monotonic() - t0
|
||||
sleep = TICK - elapsed
|
||||
|
|
@ -1606,7 +1737,7 @@ def main():
|
|||
pass
|
||||
finally:
|
||||
inp.restore()
|
||||
os.write(fd, (SHOW_CUR + ALT_OFF + RESET).encode())
|
||||
_write_all(fd, (SHOW_CUR + ALT_OFF + RESET).encode())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue