@amas.nghia/todaycode-farmwars
v0.3.1
Published
Farm Wars rulebook — implements the todaycode-game-engine Engine contract; the single source of Farm Wars rules for todaycode-be and CodeClash-fe.
Readme
todaycode-farmwars
Quyển luật duy nhất của Farm Wars — game đấu bot 1v1 (trồng cây, thu hoạch,
phá hoại trên grid 12×9, 200 turn) — implement Engine contract của
todaycode-game-engine.
Luật tính ở hai nơi (browser chạy thử + server trận chính thức) và phải ra
kết quả giống nhau bit-for-bit, nên viết đúng một lần ở đây.
Chỉ chứa luật thuần: không chạy bot, không render, không I/O, không
Math.random().
Bạn cần gì? Nhảy thẳng tới đó
| Bạn là | Đọc | |---|---| | Dev tích hợp (gọi luật từ app) | Dùng thế nào | | Dev sửa luật / thêm cơ chế | Sửa luật thế nào + bảng module | | Người viết bot | Contract với bot | | Game designer đổi chỉ số | Config — không cần đọc code |
Module nào làm gì — cho ai
| Module | Làm gì | Ai đang dùng thật |
|---|---|---|
| tick.ts (+combat/scoring/gift-rng) | Trái tim luật: applyTick(state, frame, a1, a2) — 1 bước mô phỏng (3 frame = 1 turn; di chuyển theo speed, attack x y tường minh, cây HP/ra quả, deposit, gift) | FE match-simulator + BE simulator.ts gọi mỗi frame |
| config.ts | FarmWarsConfig + defaultConfig() + resolveConfig(overrides) — mọi chỉ số balance là dữ liệu của trận, validate input rác | App truyền config từ DB/JSON vào mỗi trận |
| initial-state.ts | createInitialState(seed, config) — state turn-0 | Cả hai simulator |
| farmwars.engine.ts | FarmWarsEngine implements Engine — cổng generic; init đọc level.definition.config | Hạ tầng nào muốn chạy game không cần biết luật |
| orders.ts / wire.ts | Parse stdout bot → Actions; serialize state cho bot (giấu inventory harvester địch — tường chống gian lận) | Runner BE; FE có bản cùng semantics |
| events.ts | 20+ loại event (move, targeted_attack, tree_damaged, tree_destroy…) — client vẽ animation từ đây, không chạy lại luật | FE effect-descriptors + battle-log; BE lưu vào replay |
| replay.ts | Reducer applyFrame(state, events) — phát lại trận đã tính, không mutate | FE trang /replays/[id]/watch |
Dùng thế nào
1. Chạy một trận từ đầu đến cuối
import { createInitialState, applyTick, defaultConfig, type Actions } from 'todaycode-farmwars'
const config = defaultConfig()
let state = createInitialState(/* seed */ 42, config)
for (let turn = 1; turn <= config.maxTicks; turn++) {
// Actions của mỗi đội cho turn này — thực tế lấy từ bot (xem contract dưới);
// ở đây hardcode để minh hoạ. baseSetup chỉ có tác dụng ở turn 1.
const a1: Actions = { commands: ['right', 'right', 'stay'], giftChoice: 0,
...(turn === 1 ? { baseSetup: [1, 1, 2] as [number,number,number] } : {}) }
const a2: Actions = { commands: ['stay', 'stay', 'stay'], giftChoice: 0 }
// 1 turn = 3 frame (actor speed 1/2/3 di chuyển ở các frame khác nhau).
// applyTick MUTATE state truyền vào — muốn giữ frame bất biến cho scrubber
// thì structuredClone trước (FE làm vậy trong engine/run-turn.ts).
for (let f = 0; f < 3; f++) {
const { events } = applyTick(state, (turn - 1) * 3 + f, a1, a2)
// events → đẩy cho renderer / battle log / stats
}
}
console.log(state.team1.score, state.team2.score)Cùng (seed, config, chuỗi Actions) → cùng kết quả trên mọi máy. Bản chạy được
đầy đủ (kèm bot Python thật): CodeClash-fe/scripts/farm-wars-headless.mjs.
2. Đổi chỉ số không cần release
import { resolveConfig, createInitialState } from 'todaycode-farmwars'
// overrides lấy từ DB (app_config row `farmwars.config`) hoặc file JSON.
// Sai kiểu/thiếu field → tự fallback default, không bao giờ NaN.
const config = resolveConfig({ treeMaxHp: 3, actorStats: { worm: { atk: 4 } } })
const state = createInitialState(42, config)Qua contract generic thì nhét vào level: engine.init({ ...level,
definition: { config: overrides } }, seed). Bắt buộc: lưu snapshot config
vào match/replay — replay phát lại bằng config của NÓ, không phải config mới nhất.
3. Phát lại trận đã tính (trang xem replay)
import { applyFrame, type FarmWarsState } from 'todaycode-farmwars'
let state: FarmWarsState = resultData.keyframe
for (const f of resultData.frames) {
state = applyFrame(state, f.events) // trả state MỚI, không mô phỏng lại luật
}Contract với bot
Mỗi turn bot nhận wire state (JSON) qua stdin và in ra stdout:
<lệnh actor 0: planter> up | down | left | right | stay | attack <x> <y>
<lệnh actor 1: harvester>
<lệnh actor 2: worm>
gift <0-3> luôn có; 0 = bỏ qua
base <i> <j> <k> CHỈ turn 1 — chọn base cho từng actor (được đứng chung)Lệnh sai/thiếu tự thành stay. Wire state: my_team (đủ thông tin) +
enemy_team (giấu inventory harvester) + grid {width, height, trees} + gifts.
Luật đáng nhớ: attack tầm Manhattan ≤ 1, cooldown 1 tick; worm cắn được mọi
cây địch (quả hay không); planter tự trồng khi đứng ngoài base; harvester tự hái
cây mình có quả; deposit khi về base.
Sửa luật thế nào
| Muốn | Sửa ở đâu | Sau đó |
|---|---|---|
| Đổi chỉ số balance (HP, atk, thời gian cây…) | Không sửa code — đổi config của trận (mục 2 trên). Đổi default: constants.ts | test + release |
| Thêm loại event | events.ts (thêm variant) → phát trong tick.ts | Checklist consumer: FE effect-descriptors.ts (hiệu ứng) + battle-log.ts (log) + reducer replay.ts nếu event làm đổi state — event không ai vẽ = tàng hình (đã có bug thật: tree_damaged từng không được vẽ) |
| Đổi cơ chế (movement, combat, gift…) | tick.ts / combat.ts… | golden fixtures sẽ đỏ — cập nhật có chủ đích, đừng sửa số cho test xanh |
| Đổi giao thức bot (thêm lệnh) | orders.ts + wire.ts | đồng bộ FE: worker parse (public/farm-wars-pyodide-worker.js), helpers.py, 2 bot mẫu — lệch harness là bot chết lặng lẽ cả trận (đã xảy ra) |
Release checklist (mọi thay đổi src/):
pnpm lint && pnpm test → pnpm build → commit cả dist/ → tag v* → CI
tự publish GitHub Packages → bump version ở todaycode-be + CodeClash-fe.
Dist lệch src từng gây crash thật ở FE (v0.1.1, lỗi "reading '0'") — đừng bỏ bước build.
Cài & phát triển
"todaycode-farmwars": "npm:@amas-nghia/todaycode-farmwars@^0.2.1",
"todaycode-game-engine": "npm:@amas-nghia/todaycode-game-engine@^0.2.1"Cần GITHUB_TOKEN (read:packages) + .npmrc scope @amas-nghia.
pnpm install && pnpm test && pnpm build && pnpm lint