{"_id":"@amas.nghia/todaycode-missions","name":"@amas.nghia/todaycode-missions","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@amas.nghia/todaycode-missions","version":"0.1.0","description":"Missions rulebook — implements the todaycode-game-engine Engine contract; single source of CodeClash mission rules for todaycode-be and CodeClash-fe.","license":"UNLICENSED","publishConfig":{"access":"public"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.cts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"build":"tsup","test":"jest","lint":"tsc -b --noEmit"},"dependencies":{"@amas.nghia/todaycode-game-engine":"^0.2.3"},"devDependencies":{"@types/jest":"^30.0.0","@types/node":"^22.0.0","jest":"^30.0.0","ts-jest":"^29.0.0","tsup":"^8.0.0","typescript":"^5.6.0"},"jest":{"preset":"ts-jest","testEnvironment":"node","testMatch":["**/__tests__/**/*.spec.ts"],"moduleFileExtensions":["ts","js","json"]},"gitHead":"8162ae02f93c99cc1bd9146a3571437c1379e82b","_id":"@amas.nghia/todaycode-missions@0.1.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-rGPfi2N16xww85QqpjxDoPZREbdJdeJcyHFzwqfuwDZD86tDncXO4RC6QcMhzPzN1OYBQOiqtphgBpDALv45Zg==","shasum":"f1939e8700c58dbc2fdcc0f79e7f1c2c7c930a09","tarball":"https://registry.npmjs.org/@amas.nghia/todaycode-missions/-/todaycode-missions-0.1.0.tgz","fileCount":8,"unpackedSize":116478,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC7pMSP4CFvrXFYzWQFqxKNduWGmxxnV18sML1tUS7DGwIhALHmuHR6/uNJwW+xPyJuDcVA8EU56jUQl21HanI1DNS2"}]},"_npmUser":{"name":"amas.nghia","email":"amasnghia@gmail.com"},"directories":{},"maintainers":[{"name":"amas.nghia","email":"amasnghia@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/todaycode-missions_0.1.0_1784028667415_0.22956352754050968"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-14T11:31:07.202Z","0.1.0":"2026-07-14T11:31:07.560Z","modified":"2026-07-14T11:31:07.913Z"},"maintainers":[{"name":"amas.nghia","email":"amasnghia@gmail.com"}],"description":"Missions rulebook — implements the todaycode-game-engine Engine contract; single source of CodeClash mission rules for todaycode-be and CodeClash-fe.","license":"UNLICENSED","readme":"# todaycode-missions\n\n**Quyển luật của game Missions** — 213 màn học Python theo giáo trình CodeClash\n(CS1: 61 · CS2: 78 · CS3: 74; học sinh vào vai `wizard`, viết Python vượt màn) —\nimplement `Engine` contract của\n[todaycode-game-engine](https://github.com/amas-nghia/todaycode-game-engine).\nLuậ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.\n\n> **Trạng thái: Phase P0** — MOVE/ATTACK/PICK_UP + 7 loại điều kiện thắng.\n> Xem [`P0_REVIEW.md`](P0_REVIEW.md) trước khi code tiếp.\n\nKhác farmwars một điểm quan trọng: không có bot đối kháng — \"đối thủ\" là map +\nđiều kiện thắng. Và vì code học sinh **đọc state giữa chừng**\n(`while wizard.mana >= 20:`), runner phải chạy **xen kẽ từng lệnh** chứ không gom\ncả turn.\n\n## Module nào làm gì — cho ai\n\n| Module | Làm gì | Ai dùng |\n|---|---|---|\n| `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 |\n| `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 |\n| `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 |\n| `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 |\n| `level-schema.ts` | `MissionDefinition` + `validateLevel` (throw khi thiếu goal/entities) | Người soạn 213 level JSON |\n| `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 |\n\n## Dùng thế nào — chạy một màn từ đầu đến cuối\n\n```ts\nimport { MissionsEngine, type MissionOrder } from '@amas-nghia/todaycode-missions'\nimport { findNearestEnemy, wizardView } from '@amas-nghia/todaycode-missions'\n\n// 1. Level — đây là format 213 màn sẽ được soạn\nconst level = {\n  slug: 'cs1-01', version: 1, gameSlug: 'missions',\n  definition: {\n    course: 'cs1', chapter: 1, coordinateSystem: 'grid',\n    map: { width: 6, height: 3, walls: [] },\n    wizard: { x: 0, y: 0, health: 100, mana: 100 },\n    entities: [{ kind: 'goal', x: 5, y: 0 }],\n    apiAllowlist: ['move_right'],          // runner chặn API ngoài danh sách này\n  },\n  grading: {\n    win_conditions: { mode: 'all', conditions: [\n      { type: 'reach_goal' },\n      { type: 'max_statements', max: 10 },\n    ] },\n  },\n}\n\n// 2. Init\nconst engine = new MissionsEngine()\nlet state = engine.init(level, /* seed */ 0)\n\n// 3. Runner dịch code học sinh thành orders — mỗi wizard.* = 1 order.\n//    META gửi MỘT LẦN đầu trận: số statement đếm bằng Python AST.\nconst orders: MissionOrder[] = [\n  { op: 'META', statementCount: 5 },\n  { op: 'MOVE', dir: 'right', steps: 5 },  // = wizard.move_right() ×5 hoặc move(\"right\", 5)\n]\n\n// 4. Chạy XEN KẼ: đẩy order → tick → Python đọc state qua selectors → order tiếp\nfor (const order of orders) {\n  state = engine.applyOrders(state, 0, [order])\n  const { events } = engine.tick(state, 0)   // events: 'moved', 'bump', 'attacked'… → FE vẽ\n  // Giữa các order, code Python có thể hỏi:\n  //   wizard.health          → wizardView(state)\n  //   find_nearest_enemy()   → findNearestEnemy(state)\n  if (engine.isOver(state)) break             // màn tự dừng ngay khi thắng\n}\n\n// 5. Chấm điểm\nconsole.log(engine.result(state))\n// → { over: true, passed: true, stars: 3,\n//     metrics: { statementCount: 5, ordersExecuted: 2, ... } }\n```\n\nViệc **không** thuộc package này (runner của app làm): chạy Python (Pyodide/\nsandbox), đếm statement bằng AST, chặn API theo `apiAllowlist`, giới hạn số bước\n(chống `while True:` không lối thoát).\n\n## Implement thế nào\n\n### Thêm một loại điều kiện thắng (việc thường gặp nhất, P1–P3)\n\n1. `win-conditions.ts` — type đã khai đủ 26 trong union `WinConditionType`;\n   thêm nhánh `if (cond.type === '...')` trong `evaluateCondition` (đang throw\n   `not implemented`). Cần dữ liệu mới thì thêm counter vào `MissionState`\n   (`state.ts`) và cập nhật trong `tick.ts` (vd `saidStrings` cho `say_string`).\n2. Test: 1 case pass + 1 case fail trong `__tests__/win-conditions.spec.ts`.\n3. Golden fixture: level dùng condition mới + orders + expected outcome\n   (`__tests__/fixtures/`, chạy qua determinism spec).\n4. Release: build + commit dist + tag.\n\n### Thêm một lệnh wizard mới (vd `SAY`, `CAST`)\n\n`orders.ts` (variant mới) → `tick.ts` (xử lý + event mới trong `events.ts` +\ncounter nếu cần chấm điểm) → test + fixture → **đồng bộ runner** ở app (dịch\n`wizard.say(...)` thành order, thêm vào `apiAllowlist` của các màn liên quan).\n\n### Soạn level mới\n\nTheo `MissionDefinition` (ví dụ ở trên). Nguồn nội dung 213 màn: thiết kế từng\nmàn trong curriculum (`man-choi/ch01…ch08.md` — map ASCII, starter, giải mẫu,\nwin, hints). Quy ước: `reach_goal` cần entity `goal`; boss ≥ 3 conditions;\n`validateLevel` throw nếu thiếu goal/entities.\n\n## Lộ trình\n\n| Phase | Phạm vi | Tag |\n|---|---|---|\n| **P0** ← hiện tại | MOVE/ATTACK/PICK_UP + 7 win-type | `v0.1.0` |\n| P1 | SAY/CAST + mana + đủ 14 win-type CS1 | `v0.2.0` |\n| P2 | Toạ độ xy: MOVE_TO, distanceTo, defeat_type… (CS2) | `v0.3.0` |\n| P3 | Events, collections, boss đa phase (CS3) | `v0.4.0` |\n| P4 | Tích hợp todaycode-be + CodeClash-fe | (repo consume) |\n\n## Cài & phát triển\n\n```json\n\"@amas-nghia/todaycode-missions\": \"^0.1.0\",\n\"@amas-nghia/todaycode-game-engine\": \"^0.2.1\"\n```\n\nCần `GITHUB_TOKEN` (read:packages) + `.npmrc` scope `@amas-nghia`.\n\n```bash\npnpm install && pnpm test && pnpm build && pnpm lint\n```\n\nGiữ thuần khiết (không I/O / `Math.random` / clock); mọi thay đổi `src` kèm\nfixture + `pnpm build` + **commit `dist/`** + tag semver.\n","readmeFilename":"README.md","_rev":"1-dfc4e970a77aadedb933a1d73d67a22a"}