Content
# Tool List
## 🎣 Text Fishing · A Deterministic Game for AI
A **single-file, zero-dependency, deterministic** text fishing game - designed specifically **for your AI companion to play**.
Buy bait → Cast a line → Catch various fish by rarity → Sell fish for points → Unlock new fishing spots → Collect a fish encyclopedia.
55 types of fish, 11 fishing spots, four seasons, drift bottles/chests/treasures, rarity ceremonies... Catch one rod at a time and see how your AI manages and gets excited about catching a legendary fish.
> **It only provides gameplay logic (engine). How to connect it to your AI is up to you** - the section "Connecting to Your AI" provides three sketches of how to do it.
> ### 📦 Two versions, download separately (see [Releases](https://github.com/tutusagi/ai-fishing-game/releases))
> - **v2.0 · Complete Diving Version** (`main`, this document): Added a complete diving gameplay on top of the lite version - treasure map fragments unlock diving spots, 22 underwater exclusive fish (with capture feel), diving atmosphere by location × season, 14 underwater encounters, 21 treasures, underwater treasure chest, oxygen bottle package.
> - **v1.2 · Lite Version** (`v1.2-lite` branch): Only includes operation/token optimization (batch overlay instructions / status bar JSON / fishing spot discovery count / continuous fishing prompts) + 6 lucky events, **no diving**, lighter.
---
## What is this / Why is it "for AI"
Ordinary text games are for humans. This engine is designed for **AI players**:
- **Deterministic**: Built-in mulberry32 PRNG, state all serialized into save files. **Same seed + same sequence of commands = reproducible results**. Easy to review, test, share the same game.
- **Blind play**: Can let AI play **without spoilers** - it doesn't know what fish there are, where rare fish are, or what the probability is, all discovered by casting a line.
- **Independent save**: Game state exists in disk files, **not in dialogue context**. AI's conversation is cleared, and fishing progress won't be lost.
- **Token saving**: Supports a "continuous fishing N lines" and returns a summary (精彩留全文, 杂鱼折叠成清点); can also use `;` or line breaks to string multiple instructions into one batch and run them at once (buy bait → cast a line, change location → cast a line). Each return appends a compact 📊 status bar JSON, AI can see it and don't have to check status repeatedly. No need to burn context for one line at a time.
---
## What's in the repository
| File | What | How to use |
|---|---|---|
| **`engine.py`** | **Readable engine source code**. Only uses two interfaces: `cmd("command")` returns result text, `new_game(seed)` restarts a game. | Want to understand / change values / add fish or locations, read this. You can also directly `import engine` as a library. |
| **`fishing.py`** | **Blind play version**. Engine is packaged into a file, hidden, only exposing `cmd()`/`new_game()`. | Want AI to play **without spoilers**, give it this file (it can't read fish/ probability, only discover by casting). |
| `build_blind.py` | A small script to regenerate `fishing.py` from `engine.py`. | If you changed `engine.py`, run `python build_blind.py` to keep blind play version up-to-date. |
| `tool-schema.json` | JSON Schema for `play_fishing` tool (for function call connection). | For reference when connecting AI with function calls. |
| `examples/` | Connection example. | Follow the example. |
> `engine.py` and `fishing.py` are **the same game**, but `fishing.py` hides the engine to prevent spoilers. Choose one or keep both.
---
## Quick Start (play a few rounds yourself)
Requires Python 3.8+.
```python
import engine # or import fishing (blind play version, same interface)
print(engine.cmd("help")) # see rules
print(engine.cmd("status")) # see current state
print(engine.cmd("cast")) # cast a line
print(engine.cmd("cast 10")) # continuous fishing 10 lines (only returns a summary)
print(engine.cmd("cast 20 stop=rare")) # continuous fishing 20 lines, stop when rare fish caught
print(engine.cmd("buy basic_worm 10; cast 10")) # multiple instructions in one batch, run at once
print(engine.new_game(2024)) # restart game with seed 2024
```
> **Windows users**: If terminal prints appear garbled or `UnicodeEncodeError`, set terminal to UTF-8:
> - set environment variable `PYTHONUTF8=1` and run (recommended);
> - or run `chcp 65001` in terminal;
> - or add `import sys; sys.stdout.reconfigure(encoding="utf-8")` in code.
> (The file itself is UTF-8, engine is fine, this is just terminal display issue.)
> Any input is safe: `cmd("...")` only returns a **提示文字** for invalid commands, won't crash; save file read/write issues will be **explicitly told** in return, won't pretend it's fine.
---
## Gameplay
- **Buy bait → Cast a line (cast)**: Casting is core. Catch fish by rarity probability.
- **Each location + season has different fish**: Want to collect encyclopedia, need to `goto` change location, notice season (season advances with casting lines).
- **Sell fish / sell treasure (sell)** for points, points used to buy better bait, **unlock new fishing spots**.
- **Encounter** drift bottles (collect notes), chests (need keys or spend points), treasures.
- **Lucky moments**: Small probability triggers when catching fish - split fishing hook (one line three fish), turn stone into gold (value × 3), fishing boom (next few fish doubled), river god blessing (several lines no bait consumption), once-in-a-lifetime rising tide (record-breaking big fish), pearl in an oyster (dig out treasure).
- **Diving expedition (dive)**: Diving is late-game content - **unlock first diving spot, then oxygen bottle available in store**. Buy oxygen bottle (buy 5 bottles 20% off, 10 bottles 30% off) then `dive` for an expedition: bring N bottles oxygen as depth budget, catch underwater fish (not caught on surface), return with **expedition settlement** (catch/treasure/new discovery/oxygen consumption/net worth). Encounter **"great ruins" pause** you to `choose` decisions - each option **consumes different oxygen** (not enough oxygen to give up), lead to different fish/treasure/story. `surface` can float up at any time. Each dive has a **local seasonal situation** (water temperature/insight/need protective clothing) at the top.
- **Unlock diving spots (treasure map fragments)**: Each fishing spot's diving requires unlocking - **fishing on surface** randomly get "treasure map fragments", collect 3~5 pieces (by water depth) automatically form treasure map, unlock diving here. Fragments can also be opened from **drift bottles, chests**; lucky enough to get **rare complete treasure map**, directly unlock a diving spot.
- **Underwater encounters**: Diving has small probability to encounter 14 kinds of underwater wonders (coral reef / mermaid palace / sunken ship graveyard / whale fall / mermaid nest / dragon palace / lost clock tower…), pick up treasures, ancient relics, oxygen or chests (not encountered on surface).
- **Collect encyclopedia**: First catch of a fish will be recorded in encyclopedia (selling it won't lose record), first discovery also has extra points reward.
Start: 200 points + basic worm × 5, in "Moonlight Pond" (and "Reed River" already unlocked).
## Command List (pass to `cmd("...")`)
| Command | Effect |
|---|---|
| `help` | see rules |
| `status` | points / location / season / bait / encyclopedia progress |
| `shop` | see available baits |
| `buy <bait_id> [quantity]` | buy bait, e.g. `buy glow_bait 2`; buy oxygen bottle `buy oxygen 5` |
| `cast [bait_id] [times] [stop=new,rare,event]` | cast a line. No bait = use cheapest; with times = continuous fishing N lines (1~20); `stop=` meet new (new)/rare (rare)/event (event=drift bottle·chest·treasure·underwater encounter) stop, can comma separate multiple |
| `dive [with several bottles] [stop=...]` | start diving expedition (first `buy oxygen`). With N bottles oxygen dive underwater catch fish; encounter great ruins pause |
| `choose <choice>` | make decision at great ruins (each option consumes different oxygen; no choice = re-view options) |
| `surface` | actively end expedition, float up |
| `goto` | **no parameter = list all fishing spots** (price / this season how many unseen fish, including single legendary) |
| `goto <location_id>` | go to location (if not unlocked, spend points unlock) |
| `inventory` | fishing basket + items + chests to open |
| `sell <instance_id> \| sell all \| sell species <fish_id> \| sell item <item_id>` | sell fish / sell treasure for points |
| `open <chest_uid>` | open caught chest |
| `encyclopedia` | encyclopedia collection progress |
| `look <id or name>` | detailed view fish / location / bait / season / item (fish not caught display ???) |
| `A; B; C` (`;` or line breaks connect) | put multiple instructions in one batch, execute in order (up to 8), e.g. `buy basic_worm 10; cast 10`, `goto reed_river; cast 8 stop=new` |
### Continuous fishing save token (key point)
AI one line at a time will repeatedly burn context. Use `cast <times>` continuous fishing:
```
cast 10 # continuous fishing 10 lines
cast glow_bait 15 stop=rare # use glow bait continuous fishing 15 lines, stop when rare fish caught
```
Return is **a summary**: new species / rare / event / season change these **exciting moments keep full text**, repeated common fish and empty military **folded into one line**. Continuous fishing put **≈2N trips** into **≈2 trips**.
### Multiple instructions in one batch (batch)
Use `;` or line breaks put multiple different instructions in one batch, execute in order (up to 8), common "buy bait → cast a line" / "change location → cast a line":
```
buy basic_worm 10; cast 10 # buy 10 worms, then continuous fishing 10 lines
goto reed_river; cast 8 stop=new # change to Reed River, continuous fishing 8 lines, stop when new species caught
```
Each paragraph with **▶ instruction** title, output in order. If an error occurs, only affect that instruction, not interrupt the following.
### Status bar JSON (attached every time)
Every `cmd()` return **end with a line** compact machine-readable status bar, AI see it enough, no need to `status`:
```
📊 {"pts": 270, "loc": "Reed River", "sea": "Spring", "turn": 6, "enc": "5/55", "bait": {"basic_worm": 2}, "hold": 6}
```
`pts` points · `loc/sea` current location/season · `turn` turn · `enc` encyclopedia progress · `bait` remaining bait · `hold` unsold fish count (have chest to open more `chest`).
## Save & Deterministic
- State exists in **same directory** as script `fishing_save.json`. Delete it = start from scratch.
- Deterministic: mulberry32 PRNG, random state serialized into save file. **Same seed + same command sequence → consistent results**. Default seed `0x9e3779b9`.
- Want multiple players each have a game: give each player an independent working directory (each has its own `fishing_save.json`).
---
## Connect to Your AI (three connection methods, choose one)
Engine only provides gameplay logic, **how to let your AI call it, up to you**. Three common methods:
### ① AI has code execution (easiest)
ChatGPT code interpreter / Claude code execution / agent with sandbox:
give it **`fishing.py`** (blind play version), let it:
```python
import fishing
print(fishing.cmd("status"))
# then decide next step based on return text, loop `cmd("cast")`/`cmd("buy ...")`/`cmd("goto ...")`
```
see `examples/play_with_code_interpreter.md`.
### ② Function call / Tool use
register `play_fishing` tool from `tool-schema.json`. Your tool function receives structured parameters, convert to command string call `engine.cmd()`, feed return text back to model:
```python
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()) # great ruins decision
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\" buy oxygen bottle
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
```
**More convenient alternative**: no structured parameters, register a tool with only one string parameter `command`, function directly `return engine.cmd(command)`. Model write `"cast 10 stop=rare"` instruction - this string connection **naturally support multiple instructions**, model write `"buy basic_worm 10; cast 10"` run at once. (Structured connection support `batch`, convert `steps` into instructions string, use `"; "` connect once `engine.cmd()`).
### ③ Write a loop yourself
most simple: model output instruction → you `engine.cmd(instruction)` → put return text back to dialogue → model decide next step → loop.
---
## Blind play instructions (prevent spoilers)
Want AI like real players **discover by casting** instead of know which fish / probability:
- give it **`fishing.py`** (engine hidden in packaged data), not `engine.py`.
- tell it: only call `cmd()`, don't decode / read file packaged data.
> Honestly: packaging is encoding, not encryption, if determined to see, model can crack with one line code. Blind play rely on **cooperation**. Formal play model follow instructions will not spoilers.
---
## Content Scale
55 kinds of surface fish (common / rare / epic / legendary / mythological 6 levels) + 22 kinds of underwater fish (with capture feel) + underwater atmosphere sentence (by location × season) + 14 kinds of underwater encounters (5 with great ruins) + 25 treasures + 8 great ruins exclusive fish · 11 fishing spots (2 free + 9 200~800 points unlock) · 4 seasons (every 20 lines advance a season) · 3 kinds of bait + oxygen bottle diving · drift bottle / chest / treasure events · 6 kinds of lucky random events · items and points economy · encyclopedia collection · rare level ceremony broadcast + location atmosphere / personality sentence.
## Modification / Extension
All content (fish / location / bait / season / events / items) are `engine.py` top pure data table, add content only modify data, no logic change.
modify `engine.py`, if you use blind play version:
```bash
python build_blind.py # from engine.py regenerate fishing.py
```
`fishing.py` content is `engine.py` base64, two files always consistent, not diverge.
## License
MIT, see `LICENSE`. Use / modify / connect to you and your AI happy fishing.
Connection Info
You Might Also Like
No recommendations available