首页 / 资讯中心 / 文章详情

终端打砖块游戏与Claude任务进度联动:Python+curses实战

终端打砖块游戏与Claude任务进度联动:Python+curses实战 ★ FEATURED ARTICLE
上周三下午我把一个“重构认证模块”的任务丢给 Claude 之后就进入了那种最磨人的状态盯着终端看日志一条一条刷不知道它下一次回话是什么时候。等到第十分钟我开始在脑子里数砖墙——不是比喻是我真的想玩打砖块了。这个念头后来长成了一个周末项目在终端里跑一个打砖块小游戏同时让 Claude 在后台干活。它不是什么花哨的 GUI 游戏就是一个用字符拼出来的、能真的把球打来打去的 Python 脚本再加上一层与任务进度联动的逻辑。Claude 每完成一个子任务进度文件就更新一次游戏里的砖块就随之少一块我这边则用球板把其余砖块一个个敲掉谁先清完谁赢。这篇文章适合两类人一类是天天跟 AI 命令行工具打交道、等任务等到发慌的开发者另一类是纯粹想在终端里折腾点好玩东西的 TUI 爱好者。我会从终端画布原理讲到打砖块的物理和手感再讲怎么把任务进度和游戏状态串起来最后是实测一周踩过的坑。代码都是 Python curses几十行就能跑起来。1. 为什么把游戏塞进终端而不是开个窗口1.1 终端游戏到底图什么直接开个网页版的打砖块不好吗当然好但有几个现实问题。第一当我通过 SSH 连着一台服务器的时候网页游戏帮不上忙服务器上只有一坨黑底白字的终端。第二项目跑到一半你不太想切换到浏览器去刷新一个高亮闪烁的页面这会打断注意力还会让同事以为你在摸鱼刷网页。终端里的小游戏反而理直气壮——看起来就像在跑某个命令行工具。更关键的一点是“上下文不切换”。玩终端游戏的时候我的眼睛始终没有离开命令行环境余光能扫到 Claude 的输出。网页游戏就不行切出去之后经常会忘了切回来等想起来的时候任务早跑完了白等。终端游戏本质上是一种“盯盘辅助工具”它把等待 AI 干活这件事从被动发呆变成了主动参与。1.2 选型对比四套方案我为什么用 curses做终端游戏市面上流行好几套路子我列一下实测后的感受方案语言特点适合场景cursesPython标准库自带跨 Unix 平台Windows 需补包快速原型、脚本小游戏裸 ANSI 转义任意语言零依赖但光标管理、颜色管理全靠自己单文件小玩具、炫技richPython渲染文本漂亮但实时刷新偏重日志输出、进度条类工具ratatuiRust性能好状态管理严谨正式点的 TUI 工具我最终选了 Python curses。原因很朴素Python 写起来快curses 把终端初始化、光标移动、颜色设置、键盘输入这些脏活全包了我在家里用 macOS 能跑连到服务器上也能跑。curses 唯一的麻烦是 Windows 上要装一个windows-curses包装完之后行为也基本一致。输入这块curses 的getch()支持方向键这在裸 ANSI 方案里是要自己解析的省了很多事。2. 终端画布原理ANSI 转义序列就是一套打印指令2.1 终端是怎么“画画”的很多人以为终端只能打印文本其实终端的本质是一个字符网格每一个格子都可以单独设置前景色和背景色。你只要记住一个核心概念终端的“画笔”就是光标发出去的一切都是“把光标挪到某个位置然后打印字符”。控制光标位置和颜色靠的是 ANSI 转义序列形式是一串以ESC也就是\033开头的指令。比如# 先清屏再把光标挪到第 5 行第 10 列画一个红色方块 print(\033[2J, end) print(\033[5;10H, end) print(\033[41m \033[0m, end)\033[2J是清屏\033[5;10H是把光标移到第 5 行第 10 列\033[41m是设置红色背景末尾的\033[0m是重置所有样式。这个套路就是所有终端游戏的底层原理每一次刷新本质上都是“清屏 把画面里的每个元素挪到新位置重新打印一遍”。如果只用裸 ANSI你要自己维护整个画面的坐标和状态写起来很累。curses 的价值就在这里它把“光标移动、颜色、清屏、刷新”封装成了函数还帮你处理了终端尺寸变化、特殊按键这些事情。2.2 curses 帮你干了哪些脏活一个最小的 curses 程序长这样import curses import time def main(stdscr): curses.curs_set(0) # 隐藏光标 stdscr.nodelay(True) # getch 不阻塞 stdscr.timeout(16) # 最多等 16ms约 60 帧 h, w stdscr.getmaxyx() px w // 2 # 球板位置 while True: key stdscr.getch() if key ord(q): break if key curses.KEY_LEFT: px max(0, px - 2) if key curses.KEY_RIGHT: px min(w - 8, px 2) stdscr.erase() stdscr.addstr(h - 2, px, * 8) stdscr.refresh() time.sleep(0.016) curses.wrapper(main)curses.wrapper(main)负责进入和退出终端图形模式就算程序崩了也会把终端恢复正常这个很重要。nodelay(True)让getch()在没有按键时立刻返回-1配合timeout(16)让游戏循环以固定节奏跑起来。erase()清空画面画完东西后refresh()一次性把整个缓冲区推到屏幕上。2.3 双缓冲与刷新策略我第一次写的时候有个明显问题画面闪烁。原因是每帧都清屏、画、刷新而清屏和画之间隔着一点时间终端会先闪成空白再出现画面。curses 的解法是把画面先画到一块离屏缓冲区里再用refresh()一次性提交这就是经典的双缓冲。如果画面里有大量元素还可以用curses.newpad建一个比屏幕还大的画布只把需要看的部分渲染出来。打砖块这种简单游戏用不着 paderase()refresh()就够了。重要的一点是别在addstr和refresh之间做耗时操作比如读网络文件那会明显拉低帧率。3. 打砖块的物理与手感从球速到反弹角度的调优3.1 游戏循环的四种职责任何终端游戏循环体都是四件事读输入、更新状态、绘制画面、按节奏休眠。打砖块的状态尤其简单无非是球的坐标、球板坐标、砖块列表外加一个计分器。球的运动本质上是坐标的线性变化bx vx * speed by vy * speedvx和vy是单位方向向量speed是每帧移动的格数。碰到左右墙就把vx取反碰到天花板就把vy取反球掉出屏幕底部就扣一条命。这个逻辑几乎没有难度真正麻烦的是“碰撞判定”和“手感”。3.2 球的反弹角度中间平直两边刁钻如果球碰到球板永远是垂直向上反弹游戏十几秒就无聊了。经典的街机做法是根据球打在球板的哪个位置来决定反弹角度打在中间接近垂直打在边缘就飞向斜侧。换算成代码是这样的import math def reflect(hit_pos, paddle_w): # hit_pos 是球碰到球板时相对球板左边缘的偏移 ratio hit_pos / paddle_w # 0 到 1 angle (ratio - 0.5) * (2 * math.pi / 3) # 映射到 -60° ~ 60° vx math.sin(angle) vy -math.cos(angle) mag math.hypot(vx, vy) return vx / mag, vy / mag这个函数是整场游戏手感的灵魂。angle的范围我调了很久最初用 ±45°结果球总是走太斜的路线经常一两个来回就漏掉后来放宽到 ±60°局面立刻活了。再配合一个技巧球板在移动时给球一个微小的水平速度加成这样球不会永远在几条固定轨迹上打转。3.3 砖块碰撞先分清撞的是横边还是竖边砖块碰撞最容易出错的地方是搞不清该翻转哪个方向。简单粗暴的做法是碰到砖块就同时翻转vx和vy这会让球的轨迹变得不可控。正确的思路是先判断球是从水平方向撞进砖块还是从垂直方向撞进去def brick_hit(bx, by, brick): x0, y0, x1, y1 brick if not (x0 bx x1 and y0 by y1): return None # 比较球心到四条边的距离哪边近就视为从哪边撞入 dx min(bx - x0, x1 - bx) dy min(by - y0, y1 - by) return x if dx dy else y返回x就翻转vx返回y就翻转vy。这个判定其实是个近似解但对字符网格来说足够准确。砖块的排列我用 6 行、每行 10 个每个砖块占 6 个字符宽度砖块之间留一个空字符避免碰撞判定时两个砖块连成一片。3.4 手感调优的三板斧第一是速度递增。每打掉 4 块砖speed涨 0.1但设一个上限比如 3.0。没有递增的游戏打到一半会犯困递增太快又会让连续反弹失控我最后用的是“温和递增 硬上限”的组合。第二是防止穿模。当球速很快时球一帧可能移动 2 格以上这就可能直接“跳过”一块只有 1 格厚的砖块。我的做法是把一次移动拆成多个小步每步最多移动 0.5 格分别做碰撞检测。游戏规模不大这个开销可以忽略。第三是输得体面。三颗球掉光才判负每次掉球给一个 1 秒的停顿和屏幕中央的提示。别小看这个停顿它给了玩家调整呼吸的节奏让手感接近真正的街机游戏。4. 让 Claude 的进度变成砖块任务联动与胜负判定4.1 进程编排让 Claude 在后台跑游戏在前台跑这里要解决的是“一个终端怎么同时干两件事”。最简单粗暴的方案是开两个终端窗口左边跑 Claude右边跑游戏。但我更推荐 tmux 分屏一套会话里左看任务、右玩游戏随时切换注意力tmux new-session -d -s claude-work bash run_steps.sh /tmp/claude_progress.json tmux split-window -h python tbreak.py --progress /tmp/claude_progress.json tmux attach -t claude-work不过分屏还是“人肉联动”你得自己用眼睛看任务走到哪一步。更好的做法是让游戏自动感知 Claude 的进度。4.2 进度文件协议一个 JSON 文件连接两边我的方案是让任务脚本把进度写进一个 JSON 文件游戏每帧读取一次。写文件时最重要的一课是“原子写”直接往目标文件写内容游戏可能读到写了一半的半个 JSON。正确做法是先写临时文件再mv覆盖#!/usr/bin/env bash set -u PROGRESS_FILE/tmp/claude_progress.json rm -f $PROGRESS_FILE steps( 梳理认证模块现有调用链输出文件清单 给登录接口补充超时与重试逻辑 重构 token 刷新流程 为改动补充单元测试 跑全量测试并输出变更总结 ) total${#steps[]} for ((i0; itotal; i)); do current${steps[$i]} prompt任务进度 $((i1))/${total}请完成${current}。完成后只回复 DONE。 claude -p $prompt || echo WARN: step $i 执行失败 done_count$((i1)) printf {done: %d, total: %d, current: %s} \ $done_count $total $current $PROGRESS_FILE.tmp mv $PROGRESS_FILE.tmp $PROGRESS_FILE done printf {done: %d, total: %d, current: %s} \ $total $total 全部完成 $PROGRESS_FILE.tmp mv $PROGRESS_FILE.tmp $PROGRESS_FILE游戏这边每帧读取import json def read_progress(path): try: with open(path, r, encodingutf-8) as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {done: 0, total: 0, current: } pg read_progress(/tmp/claude_progress.json) done pg.get(done, 0) total pg.get(total, 0)读取可能失败所以except里返回默认值很重要游戏不能因为任务脚本还没开始写文件就崩溃。我还在画面顶部把current字段打印出来这样游戏界面本身就是一个任务监视器看砖块的同时能看到 Claude 正在做哪一步。4.3 和 Claude 赛跑谁先清完谁赢这一步是最有乐趣的设计。游戏初始化时生成total块砖索引小于done的砖块直接显示为“已被任务清掉”的灰色状态。玩家要打的是剩下的砖块而 Claude 每完成一步灰色砖块就多一块玩家的目标是在 Claude 清完之前把自己的砖块全部敲掉。胜负判定有三个分支玩家球掉光三次直接判负和 Claude 进度无关Claude 的done先到达total游戏弹出“Claude 赢了回去干活吧”玩家先清完全部砖块显示“你赢了但任务可能还在跑”。这个“赛跑”机制让等待过程变成了真正的游戏任务越顺砖块消失越快你就得加快手速任务卡住砖块半天不动你反而可以慢慢打。它把一个不可控的等待过程变成了一个和 AI 互动的节奏。我实测下来任务耗时长短对心态的影响明显变小了。4.4 不想要联动怎么办如果你只是想在等任务的时候单纯打砖块完全可以关掉进度联动游戏退化成普通街机模式。我在实现里留了一个--entertainment参数传进去就忽略进度文件砖块固定为 60 块Claude 的进展只在屏幕角落用一行小字显示。两种模式用同一个游戏内核只是砖块生成的方式不同。5. 实测一周的体验与那些终端游戏坑5.1 一周实测等待焦虑真的变成了游戏节奏我把这套东西实际用了一周跑了三个不同类型的任务一次代码重构、一次文档生成、一次数据清洗脚本的长时间运行。文档生成任务最久跑了四十多分钟期间我大概打了几十局。最大的感受是等待 AI 干活这件事从“焦虑的盯盘”变成了“有节奏的互动”。砖块还剩多少、Claude 跑到哪一步余光一扫就知道不需要一直盯着屏幕刷新。不过也有副作用。砖块多的时候会打到忘我好几次任务早就完成了我还沉浸在连续反弹的节奏里直到系统提示音响起才意识到。所以我给游戏加了两个机制一是 Claude 写完全部任务时终端会响铃用到beep()二是游戏界面顶部始终显示当前任务步数加粗高亮。这样就算你打上头了也有一个强提示把你拉回来。5.2 终端游戏的经典坑坑表现解决办法终端窗口尺寸变化画面错位、addstr 越界报错监听KEY_RESIZE然后重新读取getmaxyx()并重排砖块SSH 下颜色丢失颜色全部变成默认色连服务器时设置TERMxterm-256color或者检测到 8 色模式时降级配色Windows 上方向键失灵方向键返回奇怪字符安装windows-curses并支持 WASD 作为备用键位球速过快穿模球穿过砖块不掉头分步子移动每步不超过 0.5 格逐段做碰撞检测忘记恢复终端游戏退出后终端花掉始终用curses.wrapper启动异常退出也能恢复最让我意外的是 CPU 占用。我一开始想当然地觉得一个字符游戏不会费什么电实测在笔记本上跑time.sleep(0.016)的循环CPU 大约占 3% 到 5%基本可以忽略。但如果你把sleep去掉循环变成纯忙等CPU 直接拉满笔记本风扇就开始响了。所以定时休眠不是可选项是必须项。5.3 还能怎么玩老板键、难度曲线、声音开关这一周里我顺手加了几个让同事直呼“离谱”的功能。第一个是“老板键”按b键游戏画面立刻消失显示成一个假的日志滚动界面内容就取自 Claude 的真实输出再按一次b切回游戏。这个功能在办公室真的很实用。第二个是难度曲线调整。我把砖块按行分层越靠下的砖块分数越高、颜色越亮玩家的目标也从“清完所有砖”变成了“优先抢高分砖块”这样和 Claude 赛跑时策略性更强。第三个是声音的克制使用。curses 自带的beep()在终端里会发出蜂鸣声用好了是任务完成提示用多了就是办公室公敌。我把它做成开关默认只在“Claude 全部完成”和“玩家输掉”时响一声。最后说点实在的。如果你也想搞一个直接按上面的思路做就行最难的部分其实不是物理也不是渲染而是想清楚“什么时候让游戏结束”。游戏结束的时机设计好了等待 AI 干活这段碎片时间就会变成一天里难得的专注小游戏时间设计不好它就是一个让你忘记正事的摸鱼工具。我自己现在的习惯是大任务启动后先打两局热身等进度过半就收手专心等 Claude 收尾。砖块能不能全清不重要重要的是你别把自己给玩进去了。
阅读完成 · 觉得有帮助?
咨询建站