@amas.nghia/todaycode-missions
v0.1.0
Published
Missions rulebook — implements the todaycode-game-engine Engine contract; single source of CodeClash mission rules for todaycode-be and CodeClash-fe.
Readme
todaycode-missions
Quyển luật của game Missions — 213 màn học Python theo giáo trình CodeClash
(CS1: 61 · CS2: 78 · CS3: 74; học sinh vào vai wizard, viết Python vượt màn) —
implement Engine contract của
todaycode-game-engine.
Luật tính ở hai nơi (browser chạy thử + server chấm chính thức) nên viết một lần ở đây.
Trạng thái: Phase P0 — MOVE/ATTACK/PICK_UP + 7 loại điều kiện thắng. Xem
P0_REVIEW.mdtrước khi code tiếp.
Khác farmwars một điểm quan trọng: không có bot đối kháng — "đối thủ" là map +
điều kiện thắng. Và vì code học sinh đọc state giữa chừng
(while wizard.mana >= 20:), runner phải chạy xen kẽ từng lệnh chứ không gom
cả turn.
Module nào làm gì — cho ai
| Module | Làm gì | Ai dùng |
|---|---|---|
| missions.engine.ts | MissionsEngine implements Engine; isOver tự dừng khi thắng (kể cả học sinh viết while True:) | Runner FE/BE |
| tick.ts | Trái tim luật: MOVE (theo steps, dừng khi bump), ATTACK (Manhattan ≤ 1), PICK_UP; phát events cho animation | Runner mỗi lệnh |
| selectors.ts | Hàm chỉ đọc: findNearestEnemy (hoà → id nhỏ thắng), isPathClear, distanceTo, wizardView | Runner trả lời API quan sát của Python — FE/BE cùng gọi một bộ nên không lệch |
| win-conditions.ts | Evaluator theo nguyên văn schema giáo trình {mode, conditions[]} (26 type, P0 evaluate 7) + MissionGrader → passed/stars/metrics | Chấm cuối màn; isOver mỗi bước |
| level-schema.ts | MissionDefinition + validateLevel (throw khi thiếu goal/entities) | Người soạn 213 level JSON |
| orders.ts | MOVE / ATTACK / PICK_UP / META (META = runner gửi statementCount đếm bằng Python AST) | Runner dịch mỗi wizard.* thành 1 order |
Dùng thế nào — chạy một màn từ đầu đến cuối
import { MissionsEngine, type MissionOrder } from '@amas-nghia/todaycode-missions'
import { findNearestEnemy, wizardView } from '@amas-nghia/todaycode-missions'
// 1. Level — đây là format 213 màn sẽ được soạn
const level = {
slug: 'cs1-01', version: 1, gameSlug: 'missions',
definition: {
course: 'cs1', chapter: 1, coordinateSystem: 'grid',
map: { width: 6, height: 3, walls: [] },
wizard: { x: 0, y: 0, health: 100, mana: 100 },
entities: [{ kind: 'goal', x: 5, y: 0 }],
apiAllowlist: ['move_right'], // runner chặn API ngoài danh sách này
},
grading: {
win_conditions: { mode: 'all', conditions: [
{ type: 'reach_goal' },
{ type: 'max_statements', max: 10 },
] },
},
}
// 2. Init
const engine = new MissionsEngine()
let state = engine.init(level, /* seed */ 0)
// 3. Runner dịch code học sinh thành orders — mỗi wizard.* = 1 order.
// META gửi MỘT LẦN đầu trận: số statement đếm bằng Python AST.
const orders: MissionOrder[] = [
{ op: 'META', statementCount: 5 },
{ op: 'MOVE', dir: 'right', steps: 5 }, // = wizard.move_right() ×5 hoặc move("right", 5)
]
// 4. Chạy XEN KẼ: đẩy order → tick → Python đọc state qua selectors → order tiếp
for (const order of orders) {
state = engine.applyOrders(state, 0, [order])
const { events } = engine.tick(state, 0) // events: 'moved', 'bump', 'attacked'… → FE vẽ
// Giữa các order, code Python có thể hỏi:
// wizard.health → wizardView(state)
// find_nearest_enemy() → findNearestEnemy(state)
if (engine.isOver(state)) break // màn tự dừng ngay khi thắng
}
// 5. Chấm điểm
console.log(engine.result(state))
// → { over: true, passed: true, stars: 3,
// metrics: { statementCount: 5, ordersExecuted: 2, ... } }Việc không thuộc package này (runner của app làm): chạy Python (Pyodide/
sandbox), đếm statement bằng AST, chặn API theo apiAllowlist, giới hạn số bước
(chống while True: không lối thoát).
Implement thế nào
Thêm một loại điều kiện thắng (việc thường gặp nhất, P1–P3)
win-conditions.ts— type đã khai đủ 26 trong unionWinConditionType; thêm nhánhif (cond.type === '...')trongevaluateCondition(đang thrownot implemented). Cần dữ liệu mới thì thêm counter vàoMissionState(state.ts) và cập nhật trongtick.ts(vdsaidStringschosay_string).- Test: 1 case pass + 1 case fail trong
__tests__/win-conditions.spec.ts. - Golden fixture: level dùng condition mới + orders + expected outcome
(
__tests__/fixtures/, chạy qua determinism spec). - Release: build + commit dist + tag.
Thêm một lệnh wizard mới (vd SAY, CAST)
orders.ts (variant mới) → tick.ts (xử lý + event mới trong events.ts +
counter nếu cần chấm điểm) → test + fixture → đồng bộ runner ở app (dịch
wizard.say(...) thành order, thêm vào apiAllowlist của các màn liên quan).
Soạn level mới
Theo MissionDefinition (ví dụ ở trên). Nguồn nội dung 213 màn: thiết kế từng
màn trong curriculum (man-choi/ch01…ch08.md — map ASCII, starter, giải mẫu,
win, hints). Quy ước: reach_goal cần entity goal; boss ≥ 3 conditions;
validateLevel throw nếu thiếu goal/entities.
Lộ trình
| Phase | Phạm vi | Tag |
|---|---|---|
| P0 ← hiện tại | MOVE/ATTACK/PICK_UP + 7 win-type | v0.1.0 |
| P1 | SAY/CAST + mana + đủ 14 win-type CS1 | v0.2.0 |
| P2 | Toạ độ xy: MOVE_TO, distanceTo, defeat_type… (CS2) | v0.3.0 |
| P3 | Events, collections, boss đa phase (CS3) | v0.4.0 |
| P4 | Tích hợp todaycode-be + CodeClash-fe | (repo consume) |
Cài & phát triển
"@amas-nghia/todaycode-missions": "^0.1.0",
"@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 lintGiữ thuần khiết (không I/O / Math.random / clock); mọi thay đổi src kèm
fixture + pnpm build + commit dist/ + tag semver.
