一个单文件、零依赖、确定性的文字钓鱼游戏 —— 专门做来让你的 AI 伴侣来玩的。
买饵 → 抛竿 → 按稀有度钓上各种鱼 → 卖鱼换点数 → 解锁新水域 → 集齐图鉴。 55 种鱼、11 个钓点、四季流转、漂流瓶/宝箱/宝物、稀有度仪式感文案……一竿一竿钓下去,看你的 AI 怎么经营、怎么为一条传说鱼上钩而激动。
它只给玩法逻辑(引擎)。怎么接到你自己的 AI 上,由你配置 —— 下面「接到你的 AI 上」一节给了三种接法草图。
📦 两个版本,分开下载(见 Releases)
- v2.0 · 潜水完整版(
main,本文档):在精简版基础上加了整套潜水玩法——藏宝图碎片解锁潜水点、22 种水下专属鱼(带捕获手感)、按地点×季节的下潜氛围、14 种水下奇遇、21 件宝物、海底宝库、氧气瓶套餐。- v1.2 · 精简版(
v1.2-lite分支):只含操作/省 token 优化(batch 叠加指令 / 状态栏 JSON / 钓点待发现计数 / 连钓提示)+ 6 个幸运事件,不含潜水,更轻量。
普通文字游戏是给人玩的。这个引擎从设计上就是给 AI 玩家用的:
- 确定性:内置 mulberry32 PRNG,状态全部序列化进存档。同一个种子 + 同一串指令 = 逐位可复现的结果。便于复盘、测试、分享同一局。
- 盲玩:可以让 AI 在不剧透的前提下玩 —— 它不知道有哪些鱼、稀有鱼在哪、概率多少,全靠一竿一竿亲手发现。
- 存档独立:游戏状态存在磁盘文件里,不在对话上下文里。AI 的对话被清空,钓鱼进度也不会丢。
- 省 token:支持一次「连钓 N 竿」,只回一个汇总(精彩的留全文、杂鱼折叠成清点);还能用
;或换行把多条指令串成一批一次跑(买饵→抛竿、换地点→抛竿)。每次返回末尾附一行紧凑📊状态栏 JSON,AI 看它就够、不必反复查状态。不用一竿一条消息来回烧上下文。
| 文件 | 是什么 | 你怎么用 |
|---|---|---|
engine.py |
可读的引擎源码。对外只用两个接口:cmd("指令") 返回结果文字、new_game(seed) 重开一局。 |
想读懂 / 改数值 / 加鱼加地点,就看这个。也可以直接 import engine 当库用。 |
fishing.py |
盲玩版。引擎被打包进文件里、藏起来,只露 cmd()/new_game()。 |
想让 AI 不剧透地玩,就把这个文件给它(它读不到鱼谱/概率,只能靠抛竿发现)。 |
build_blind.py |
从 engine.py 重新生成 fishing.py 的小脚本。 |
改了 engine.py 后跑 python build_blind.py,让盲玩版跟上。 |
tool-schema.json |
play_fishing 工具的 JSON Schema(函数调用接法用)。 |
用「函数调用 / tool use」接 AI 时的参考。 |
examples/ |
接法示例。 | 照着接。 |
engine.py和fishing.py是同一个游戏,只是fishing.py把引擎藏了起来防剧透。二选一即可,或都放着。
需要 Python 3.8+。
import engine # 或 import fishing(盲玩版,接口一样)
print(engine.cmd("help")) # 看规则
print(engine.cmd("status")) # 看当前状态
print(engine.cmd("cast")) # 抛一竿
print(engine.cmd("cast 10")) # 一次连钓 10 竿(只回一个汇总)
print(engine.cmd("cast 20 stop=rare")) # 连钓 20 竿,钓到稀有就停
print(engine.cmd("buy basic_worm 10; cast 10")) # 多条指令串一批、一次跑完
print(engine.new_game(2024)) # 用种子 2024 重开一局Windows 用户:游戏里有中文和 emoji。如果终端打印出现乱码或
UnicodeEncodeError,让控制台走 UTF-8 即可,三选一:
- 设环境变量
PYTHONUTF8=1再运行(推荐);- 或在终端先执行
chcp 65001;- 或在代码开头加
import sys; sys.stdout.reconfigure(encoding="utf-8")。 (文件本身是 UTF-8、引擎没问题,这只是终端显示的事。)
任何输入都安全:
cmd("...")对乱七八糟的指令也只会返回一句提示文字,不会抛异常炸栈;存档读/写出问题(损坏 / 目录不可写)也会在返回里明确告诉你,不会假装存好了。
- 买饵 → 抛竿(cast):抛竿是核心。按稀有度概率钓上常见 / 少见 / 稀有 / 史诗 / 传说 / 神话各档的鱼。
- 每个地点 + 季节出的鱼不同:想集齐图鉴,得
goto换地点、留意季节(季节随抛竿数推进)。 - 卖鱼 / 卖宝(sell)换点数,点数用来买好饵、解锁新水域。
- 抛竿偶遇漂流瓶(收集纸条)、宝箱(要钥匙或花点数开)、宝物。
- 幸运时刻:钓到鱼时小概率触发——分裂鱼钩(一竿上三条)、点石成金(这条价值×3)、渔获热潮(接下来几条翻倍)、河神的祝福(几竿不耗饵)、千载难逢的涨潮(破纪录大鱼)、蚌中生珠(掏出财宝)。
- 潜水远征(dive):潜水是后期玩法——解锁第一个潜水点后,商店才上架氧气瓶。买氧气瓶(买 5 瓶 8 折、10 瓶 7 折)后
dive开一趟「远征」:带 N 瓶氧气当深度预算,捕获只有水下才有的鱼种(水面钓不到),上岸出一份远征结算(渔获/宝物/新发现/氧气花费/净值)。途中遇到**「大遗迹」会暂停让你choose抉择——每个选项消耗不同氧气**(氧气不够就只能放弃),通往不同的鱼种/宝物/故事。surface可随时上浮。每次下潜顶部还有一句当地当季的「下潜实况」(水温/见闻/要不要防护服)。 - 解锁潜水点(藏宝图碎片):每个钓点的潜水要先解锁——在该地水面钓鱼会随机捞到「藏宝图碎片」,集齐 3~5 块(按水域深浅)自动拼成藏宝图,解锁这里的潜水。碎片也能从漂流瓶、宝箱里开出;运气好还能开出稀有的完整藏宝图,直接解锁一处潜水点。
- 水下奇遇:潜水时小概率撞见 14 种水下奇观(珊瑚宫 / 人鱼宫殿 / 沉船墓场 / 鲸落 / 海妖巢穴 / 龙王宫阙 / 失落的钟楼…),捡到珍宝、古遗物、氧气或宝箱(水面不会遇到)。
- 集图鉴:第一次钓到某种鱼会记入图鉴(卖掉也不丢记录),首次发现还有额外点数奖励。
开局:200 点 + 普通蚯蚓×5,在「月光池塘」(和「芦苇河」已解锁)。
| 指令 | 作用 |
|---|---|
help |
看规则 |
status |
点数 / 地点 / 季节 / 鱼饵 / 图鉴进度 |
shop |
看可买鱼饵 |
buy <饵id> [数量] |
买饵,如 buy glow_bait 2;买氧气瓶 buy oxygen 5 |
cast [饵id] [次数] [stop=new,rare,event] |
抛竿。不填饵=用最便宜的;带次数=连钓 N 竿(1~20);stop= 遇新种(new)/稀有(rare)/事件(event=漂流瓶·宝箱·宝物·水下奇遇)就提前停,可逗号多选 |
dive [带几瓶] [stop=...] |
开潜水远征(先 buy oxygen)。带 N 瓶氧气下水捕水下鱼;遇大遗迹暂停 |
choose <编号> |
在大遗迹处抉择(每个选项耗不同氧气;不带编号=重看选项) |
surface |
主动结束远征、上浮上岸 |
goto |
不带参数 = 列出所有钓点(价格 / 本季还有几种没见过的鱼,含单列的传说级) |
goto <地点id> |
前往该地点(未解锁则花点数解锁) |
inventory |
渔篓 + 物品 + 待开宝箱 |
sell <实例id> | sell all | sell species <鱼id> | sell item <物品id> |
卖鱼 / 卖财宝换点数 |
open <宝箱uid> |
打开钓上来的宝箱 |
encyclopedia |
图鉴收集进度 |
look <id或中文名> |
细看鱼 / 地点 / 鱼饵 / 季节 / 物品(没钓到的鱼显示 ???) |
A; B; C(; 或换行串联) |
把多条指令排成一批、一次按序执行(最多 8 条),如 buy basic_worm 10; cast 10、goto reed_river; cast 8 stop=new |
AI 一竿一条消息会反复来回烧上下文。用 cast <次数> 一次连钓:
cast 10 # 连钓 10 竿
cast glow_bait 15 stop=rare # 用夜光饵连钓 15 竿,钓到稀有及以上就停
返回是一个汇总:新种 / 稀有 / 事件 / 换季这些精彩时刻留完整文案,重复的杂鱼和空军折叠成一行清点。一次连钓把「≈2N 次往返」压成「≈2 次」。
用 ; 或换行把多条不同指令串成一批、一次按顺序执行(最多 8 条),常见的「买饵→抛竿」「换地点→抛竿」一次搞定:
buy basic_worm 10; cast 10 # 先买 10 个蚯蚓,再连钓 10 竿
goto reed_river; cast 8 stop=new # 换到芦苇河,连钓 8 竿、钓到新种就停
每段前面带 ▶ 指令 小标题,按序输出。某条出错只影响那一条、不打断后面的。
每次 cmd() 返回的末尾都有一行紧凑机读状态栏,AI 看它就够、不必再单独 status:
📊 {"pts": 270, "loc": "芦苇河", "sea": "春", "turn": 6, "enc": "5/55", "bait": {"basic_worm": 2}, "hold": 6}
pts 点数 · loc/sea 当前地点/季节 · turn 回合 · enc 图鉴进度 · bait 余饵 · hold 未卖渔获条数(有待开宝箱时多一个 chest)。
- 状态存在和脚本同目录的
fishing_save.json。删掉它 = 从头开始。 - 确定性:mulberry32 PRNG,随机状态序列化进存档。同 seed + 同指令序列 → 结果完全一致。默认种子
0x9e3779b9。 - 想多人各自一局:给每个玩家一个独立的工作目录(各有各的
fishing_save.json)。
引擎只负责玩法逻辑,怎么让你的 AI 调用它,由你按自己的栈配置。三种常见姿势:
ChatGPT 代码解释器 / Claude 带代码执行 / 自带沙箱的 agent:
把 fishing.py(盲玩版)丢给它,让它:
import fishing
print(fishing.cmd("status"))
# 然后根据返回文字决定下一步,循环 cmd("cast")/cmd("buy ...")/cmd("goto ...")见 examples/play_with_code_interpreter.md。
把 tool-schema.json 里的 play_fishing 注册成一个工具。你的工具处理函数收到结构化参数后,转成指令字符串调 engine.cmd(),再把返回文字喂回模型:
import engine
def play_fishing(args: dict) -> str:
a = args["action"]
if a in ("cast", "dive"):
parts = [a]
if a == "cast" and args.get("bait_id"): parts.append(args["bait_id"])
if args.get("times"): parts.append(str(args["times"]))
if args.get("stop_on"): parts.append("stop=" + ",".join(args["stop_on"]))
return engine.cmd(" ".join(parts))
if a == "choose": return engine.cmd(f"choose {args.get('choice','')}".strip()) # 大遗迹抉择
if a == "surface": return engine.cmd("surface")
if a == "buy": return engine.cmd(f"buy {args.get('bait_id','')} {args.get('qty',1)}") # bait_id=\"oxygen\" 即买氧气瓶
if a == "goto": return engine.cmd(f"goto {args.get('location_id','')}".strip())
if a == "sell": return engine.cmd(f"sell {args.get('target','')}")
if a == "open": return engine.cmd(f"open {args.get('chest_uid','')}")
if a == "look": return engine.cmd(f"look {args.get('id','')}")
return engine.cmd(a) # status / shop / inventory / encyclopedia更省事的替代:不想用结构化参数,就注册一个只有一个字符串参数 command 的工具,处理函数直接 return engine.cmd(command)。模型自己写 "cast 10 stop=rare" 这种指令——这种字符串接法天然支持叠加指令,模型直接写 "buy basic_worm 10; cast 10" 就一次跑完。(结构化接法要支持 batch,就把 steps 里每步转成指令串、用 "; " 连起来传给一次 engine.cmd()。)
最朴素:模型输出一条指令 → 你 engine.cmd(指令) → 把返回文字塞回对话 → 模型决定下一步 → 循环。
想让 AI 像真玩家一样靠抛竿发现、而不是提前知道有哪些鱼 / 概率:
- 给它
fishing.py(引擎藏在打包数据里),别给engine.py。 - 告诉它:只调
cmd(),别去解码 / 读文件里那段打包数据。
坦白说:打包只是编码、不是加密,铁了心要偷看的模型一行代码就能解开。盲玩本质靠配合。正经玩的模型照着说明来就不会剧透。
55 种水面鱼(跨常见 / 少见 / 稀有 / 史诗 / 传说 / 神话 6 档)+ 22 种潜水专属水下鱼(带捕获手感)+ 潜水氛围句(按地点×季节)+ 14 种水下奇遇(含 5 个带抉择的「大遗迹」)+ 25 件宝物 + 8 条遗迹专属鱼 · 11 个钓点(2 个免费 + 9 个 200~800 点解锁)· 4 季节(每 20 竿推进一季)· 3 种鱼饵 + 氧气瓶潜水 · 漂流瓶 / 宝箱 / 宝物事件 · 6 种幸运随机事件 · 物品与点数经济 · 图鉴收集 · 稀有度仪式感播报 + 地点氛围/性格句。
所有内容(鱼 / 地点 / 鱼饵 / 季节 / 事件 / 物品)都是 engine.py 顶部的纯数据表,加内容只改数据、不动逻辑。
改完 engine.py 后,如果你用盲玩版,跑一下让它跟上:
python build_blind.py # 从 engine.py 重新生成 fishing.pyfishing.py 的内容就是 engine.py 的 base64,两份永远一致、不会分叉。
MIT,详见 LICENSE。随便用、随便改、随便接到你和你 AI 的小日子里。🎣