Giáo trình này dùng chính mã nguồn của Oggy Adventure (game đi cảnh 2D chạy trên trình duyệt) để dạy khái niệm làm game và design pattern (mẫu thiết kế). Bạn sẽ đọc code thật của một game hoàn chỉnh, chạy thí nghiệm nhỏ, rồi tự viết thêm tính năng.
Dành cho ai: sinh viên đại học đã biết JavaScript cơ bản: biến, hàm, mảng, object, vòng lặp, callback, DOM
(document.getElementById, addEventListener). Giáo trình không dạy lại cú pháp JS. Nó dạy những thứ sách JS thường bỏ qua:
vì sao game cần game loop (vòng lặp game), làm sao nhân vật nhảy đúng như nhau trên màn 60 Hz và 144 Hz, vì sao một
module âm thanh có thể "nghe" người chơi nhảy mà không ai gọi nó...
Vì sao học bằng game này:
- Không build step, không thư viện ngoài. Mở
index.htmllà chạy. Mọi dòng code đều do dự án tự viết, không có "hộp đen". - Đủ lớn để giống dự án thật (khoảng 70 file JS, 38 màn, 4 trùm, nhạc tổng hợp, lưu trữ, bảng xếp hạng, test tự động), nhưng chia module rõ ràng nên đọc từng phần được.
- Code đã được chú thích để dạy học: mỗi file có khối đầu file ghi vai trò, khái niệm, bài học liên quan; trong code có các nhãn
💡 Khái niệm:,⚠️ Bẫy:,🧪 Thử:(xem mục 6).
Mục lục #
- Lộ trình 14 bài
- Chuẩn bị: chạy game và mở DevTools
- Dùng VS Code: gợi ý kiểu với JSDoc và
@ts-check - Quy tắc làm bài tập
- Mỗi bài học gồm những gì
- Cách đọc code có chú thích
- Bảng thuật ngữ nhanh
- Sự cố thường gặp
- Tài liệu liên quan
1. Lộ trình 14 bài #
Thời lượng là ước tính cho một sinh viên trung bình, đã gồm đọc bài, đọc code, làm thí nghiệm 🧪 và bài tập. Tổng cộng khoảng 36 giờ, tức chừng 6–8 tuần nếu học 5 giờ mỗi tuần.
| Bài | Nội dung | Khái niệm chính | Code minh hoạ chính | Thời lượng | Cần học trước |
|---|---|---|---|---|---|
| 00 | Bắt đầu | Chạy game qua file://, cấu trúc thư mục, đọc code theo thứ tự <script>, chạy smoke test |
index.html, src/core/main.js |
1 giờ | JS cơ bản, dùng terminal và Git ở mức cơ bản |
| 01 | Module pattern: IIFE và namespace | IIFE, namespace window.OG, 'use strict', thứ tự load, cô lập lỗi module, "stub rồi ghi đè" |
src/core/namespace.js, src/game/default-scenes.js |
1,5 giờ | 00 |
| 02 | Game loop và delta time | requestAnimationFrame, delta time, kẹp dt, tách update/draw, fixed step để test |
src/core/loop.js (file mẫu), src/game/particles.js |
2 giờ | 01 |
| 03 | Xử lý input | Giữ phím vs vừa bấm, phím logic, đếm nguồn giữ phím, endFrame, coyote time, jump buffer |
src/core/input.js, src/game/player.js |
2 giờ | 02 |
| 04 | Vật lý và va chạm | Trọng lực, tích phân Euler, AABB, one-way platform, xét giẫm bằng đáy frame trước, hitbox vs hurtbox | src/core/physics.js, src/game/level.js |
3 giờ | 02, 03 |
| 05 | State machine và scene | FSM, scene stack go/push/pop, vòng đời enter/exit/resume, drawBelow, pha trong scene |
src/core/scenes.js, src/game/game.js |
2,5 giờ | 02 |
| 06 | Event bus (observer) | Publish–subscribe, loose coupling, hằng tên event, huỷ đăng ký và memory leak, wildcard | src/core/events.js (file mẫu), src/audio/audio.js, src/world/quests.js |
2 giờ | 01 |
| 07 | Vẽ bằng Canvas | Vẽ procedural, transform, save/restore, hoạt ảnh theo thời gian, squash & stretch, cache canvas phụ, LRU |
src/gfx/sprites.js, src/gfx/decor.js |
4 giờ | 02 |
| 08 | Camera và parallax | Toạ độ thế giới ↔ màn hình, camera theo nhân vật, rung màn hình, parallax nhiều lớp, culling | src/core/camera.js, src/gfx/scenery.js |
2,5 giờ | 04, 07 |
| 09 | AI kẻ địch và trùm | AI tuần tra, registry + strategy, boss state machine, telegraph, phase theo máu, điều chỉnh độ khó | src/game/enemies.js, src/bosses/bosses.js, src/bosses/deedee.js |
4 giờ | 04, 05 |
| 10 | Âm thanh với Web Audio | AudioContext, autoplay policy, oscillator + envelope, noise, GainNode, sequencer lookahead, MIDI → Hz |
src/audio/audio.js, src/audio/music.js |
3 giờ | 06 |
| 11 | Lưu trữ và migrate dữ liệu | localStorage + try/catch, giá trị mặc định, deep merge, migrate bản cũ, dữ liệu hỏng |
src/core/save.js, src/game/progress.js |
2 giờ | 01 |
| 12 | Thiết kế hướng dữ liệu (data-driven) | Registry, gộp theo id, compile, validate, hook mở rộng; thực hành thêm màn / câu đố / thẻ / hành trình | src/core/journeys.js, src/journeys/home/, src/game/level.js |
4 giờ | 04, 05, 06 |
| 13 | Kiểm thử và CI | Smoke test Node + DOM giả, vm, điều khiển bằng OG.Loop.step, kiểm tra bất biến, GitHub Actions, JSDoc + tsc |
tests/smoke.js, tests/fake-dom.js, .github/workflows/pages.yml |
3 giờ | 02, 06 |
Đi theo thứ tự nào? #
Bài 00–02 là bắt buộc và nên học đúng thứ tự. Từ bài 03 trở đi, bạn có thể đi theo mũi tên "cần học trước":
flowchart LR b00["00 Bắt đầu"] --> b01["01 Module"] --> b02["02 Game loop"] b02 --> b03["03 Input"] --> b04["04 Vật lý"] b02 --> b04 b02 --> b05["05 State machine"] b01 --> b06["06 Event bus"] b02 --> b07["07 Canvas"] b04 --> b08["08 Camera"] b07 --> b08 b04 --> b09["09 AI & trùm"] b05 --> b09 b06 --> b10["10 Web Audio"] b01 --> b11["11 Lưu trữ"] b04 --> b12["12 Data-driven"] b05 --> b12 b06 --> b12 b02 --> b13["13 Kiểm thử & CI"] b06 --> b13
Gợi ý ba hướng học nếu không đủ thời gian làm hết:
- Gameplay (khoảng 15 giờ): 00 → 01 → 02 → 03 → 04 → 05 → 09.
- Đồ hoạ và âm thanh (khoảng 15 giờ): 00 → 01 → 02 → 06 → 07 → 08 → 10.
- Kiến trúc phần mềm (khoảng 16 giờ): 00 → 01 → 02 → 05 → 06 → 11 → 12 → 13.
2. Chuẩn bị: chạy game và mở DevTools #
2.1 Cần cài gì #
| Công cụ | Dùng để | Bắt buộc? |
|---|---|---|
| Trình duyệt Chrome, Edge hoặc Firefox bản mới | Chạy game, mở DevTools | Có |
| Git | Lấy code, tạo nhánh làm bài tập | Có |
| Node.js 20 trở lên (CI dùng Node 22) | Chạy smoke test node tests/smoke.js |
Có (từ bài 00) |
| VS Code | Đọc code, gợi ý kiểu, nhảy tới định nghĩa | Khuyên dùng |
| Python 3 | Chạy server tĩnh python -m http.server |
Không (có cách khác) |
Dự án không có package.json và không cần npm install. Node chỉ dùng để chạy test.
2.2 Chạy game #
git clone <địa chỉ repo của lớp> oggy-adventure
cd oggy-adventureCách 1: mở trực tiếp. Nhấp đúp index.html. Game chạy qua file:// vì không dùng ES modules (bài 01 giải thích lý do).
Cách 2: server tĩnh (khuyên dùng khi học). Một số trình duyệt chặn localStorage với file://, nên tiến độ không được lưu.
Chạy một trong hai lệnh rồi mở http://localhost:8080:
python -m http.server 8080
# hoặc (cần mạng lần đầu để tải gói serve)
npx serve -l 8080 .Dừng server bằng Ctrl + C trong cửa sổ terminal đã mở nó.
Chạy smoke test (kiểm thử tự động chơi hết game, mất khoảng 20–60 giây tuỳ máy):
node tests/smoke.js # ổn thì in "XANH — không có lỗi runtime." và thoát mã 0; có lỗi thì thoát mã 1
node tests/smoke.js --verbose # in từng bước và số lần phát mỗi event
node --check src/core/loop.js # chỉ kiểm tra cú pháp một file
node tests/check-doc-links.js # kiểm tra mọi link tương đối trong docs/learn/ (in "SẠCH" nếu không có link gãy)2.3 Mở DevTools #
DevTools (công cụ cho nhà phát triển có sẵn trong trình duyệt) là "phòng thí nghiệm" chính của giáo trình.
| Hệ điều hành | Phím tắt |
|---|---|
| Windows / Linux | F12 hoặc Ctrl + Shift + I (mở thẳng Console: Ctrl + Shift + J) |
| macOS | Cmd + Option + I (Console: Cmd + Option + J) |
Các tab bạn sẽ dùng:
| Tab | Dùng trong giáo trình để |
|---|---|
| Console | Gõ lệnh trực tiếp vào game (mọi module nằm trong biến toàn cục OG), xem lỗi màu đỏ |
| Sources (Firefox: Debugger) | Mở file trong src/, đặt breakpoint (điểm dừng), chạy từng dòng. Thử đặt breakpoint trong hàm tick của src/core/loop.js |
| Performance | Ghi lại vài giây chơi để xem mỗi frame tốn bao nhiêu ms (bài 02, 07) |
| Application (Firefox: Storage) | Xem Local Storage → khoá oggyAdventure.save.v1: dữ liệu lưu của game (bài 11) |
Lệnh Console hay dùng (gõ khi game đang chạy):
OG.Scenes.stackNames() // scene stack hiện tại, vd ["level", "pause"]
OG.Loop.fps // FPS ước lượng
OG.Config.GRAVITY = 600 // trọng lực thấp: nhảy như trên Mặt Trăng (bài 04)
OG.Config.DEBUG = true // OG.Events in mọi event ra Console (bài 06)
var off = OG.Events.on('*', function (name, payload) { console.log(name, payload); });
off(); // huỷ đăng ký listener ở dòng trên
OG.Sprites.setCacheEnabled(false) // tắt cache khung hình rồi xem OG.Loop.fps (bài 07)
OG.Save.data // toàn bộ dữ liệu lưu (bài 11)
OG.Journeys.validate('home') // kiểm tra dữ liệu hành trình (bài 12)⚠️ Thay đổi gõ trong Console chỉ sống tới khi tải lại trang (F5). Muốn thay đổi lâu dài thì sửa file trong
src/.
⚠️ Gõ Console khi đang chơi: game tự tạm dừng và xoá phím. Bấm chuột vào DevTools để gõ lệnh thì cửa sổ game mất focus (phát event
blur; có thể xảy ra cả khi DevTools gắn liền cửa sổ). Lúc đó hai việc xảy ra:
src/core/main.js(hàmautoPause) push scenepausenếu đang ởlevel/boss→ stack thành["level", "pause"].src/core/input.jsgọiOG.Input.reset()→ mọi phím đang giữ bị nhả. Bấm "Tiếp tục" thìOG.Scenes.pop()lại gọiOG.Input.reset()thêm lần nữa.Hậu quả: lệnh như
OG.Input.setKey('right', true)gõ thẳng vào Console sẽ không làm Oggy tự chạy (phím bị xoá khi bạn bấm "Tiếp tục"), và thí nghiệm "giữ phím rồi làm gì đó" không làm được. Đây là hành vi cố ý của game (bài 03 mục 4, bài 05), không phải lỗi của bạn. Cách làm thí nghiệm:
Hẹn giờ: bọc lệnh trong
setTimeout(..., 3000), nhấn Enter, rồi trong vòng 3 giây bấm "Tiếp tục" trên màn hình game (bấm vào game để trang có lại focus). Lệnh chạy khi bạn đã quay lại chơi:jssetTimeout(function () { OG.Input.setKey('right', true); }, 3000); // dừng: OG.Input.setKey('right', false)Tách DevTools ra cửa sổ riêng (menu ⋮ của DevTools → Dock side → Undock) cũng vẫn làm game mất focus, nên vẫn cần hẹn giờ.
Lệnh chỉ đọc trạng thái (
OG.Scenes.stackNames(),OG.Loop.fps,OG.Sprites.cacheStats()) hay đổi cấu hình (OG.Config.GRAVITY = 600) thì không bị ảnh hưởng: gõ xong bấm "Tiếp tục" là được.
3. Dùng VS Code: gợi ý kiểu với JSDoc và @ts-check #
Mở cả thư mục dự án trong VS Code (File → Open Folder...), không mở từng file lẻ, để VS Code đọc được jsconfig.json.
JSDoc là comment dạng /** ... */ mô tả kiểu tham số và giá trị trả về. VS Code dùng nó để gợi ý code và báo lỗi kiểu,
dù code vẫn là JavaScript thuần (không cần TypeScript, không cần build).
jsconfig.json: đặtcheckJs: false, nghĩa là chỉ file có dòng đầu// @ts-checkmới bị kiểm tra kiểu. Đây là cách bật kiểm tra từng file một (opt-in) để dự án lớn chuyển dần sang có kiểu mà không bị ngập lỗi.src/types.d.ts: các kiểu dùng chung (AABB,Scene,LevelDef,BossInstance,ThemeDef...). Trong file JS, bạn viết/** @type {Player} */ var p = st.player;để VS Code biếtpcó những field nào.
Thao tác nên tập ngay ở bài 00:
| Thao tác | Phím tắt (Windows / macOS) |
|---|---|
| Xem JSDoc của hàm | rê chuột lên tên hàm |
| Nhảy tới định nghĩa | F12 hoặc Ctrl/Cmd + Click |
| Tìm mọi chỗ dùng | Shift + F12 |
Tìm trong toàn dự án (vd tên event 'item:collect') |
Ctrl + Shift + F / Cmd + Shift + F |
| Mở nhanh file theo tên | Ctrl + P / Cmd + P |
| Xem lỗi kiểu | tab Problems (Ctrl + Shift + M / Cmd + Shift + M) |
Kiểm tra kiểu cho cả dự án bằng dòng lệnh (không in gì và mã thoát 0 nghĩa là sạch):
npx -y -p [email protected] tsc -p jsconfig.json --noEmitLệnh này tải TypeScript tạm thời để kiểm tra, không thêm thư viện nào vào dự án và không sinh file nào.
Extension VS Code tuỳ chọn (không bắt buộc, dự án không phụ thuộc vào chúng):
- Một extension xem trước Mermaid cho Markdown, để thấy sơ đồ trong
ARCHITECTURE-DIAGRAMS.mdngay trong VS Code. - Một extension "live server" để chạy server tĩnh thay cho
python -m http.server.
4. Quy tắc làm bài tập #
4.1 Mỗi bài tập một nhánh Git riêng #
Không làm bài tập thẳng trên nhánh chính (master). Mỗi bài tập là một nhánh (branch) riêng để dễ nộp, dễ chấm và dễ bỏ đi làm lại.
# 1. Bắt đầu từ nhánh chính sạch sẽ, mới nhất
git switch master
git pull
git status # phải báo "nothing to commit, working tree clean"
# 2. Tạo nhánh cho bài tập. Quy ước tên: bt/<số bài>-<tên ngắn>-<mã sinh viên>
git switch -c bt/04-nhay-cao-21520001
# 3. Làm bài, kiểm tra thường xuyên
node --check src/game/player.js # mỗi file vừa sửa
node tests/smoke.js # trước mỗi commit: phải XANH
# 4. Commit nhỏ, thông điệp nói rõ làm gì
git add src/game/player.js
git commit -m "bt04: thêm nhảy cao khi giữ phím lâu hơn"
# 5. Nộp: đẩy nhánh lên và tạo Pull Request (hoặc theo cách giảng viên yêu cầu)
git push -u origin bt/04-nhay-cao-21520001Muốn làm lại từ đầu thì quay về master và tạo nhánh mới. Không cần xoá code đã viết.
4.2 Luật chung khi sửa code #
- Smoke test phải XANH trước khi nộp. Test đỏ thì đọc dòng báo lỗi đầu tiên, sửa xong mới nộp.
- Giữ ràng buộc của dự án: không thêm build step, không thêm thư viện ngoài, không dùng
import/export, game vẫn phải chạy khi mởindex.htmlquafile://. - Không đổi API công khai trừ khi đề bài yêu cầu: tên hàm
OG.X.fn, tên scene, chuỗi tên event trongOG.EV, định dạng dữ liệu lưu và dữ liệu màn. Có nhiều nơi phụ thuộc vào chúng (xemARCHITECTURE-DIAGRAMS.md). - File mới phải có thẻ
<script>trongindex.htmlđúng vị trí theo thứ tự phụ thuộc (bài 00, 01), và có khối đầu file theoCODE_STYLE.mdmục 2. - Comment theo
CODE_STYLE.md: tiếng Việt có dấu, giải thích tại sao chứ không lặp lại cái gì, ghi đơn vị (giây, px, px/giây). Font chữ dùngOG.font(...)/OG.FONT; không gõ tên "font cấm" ghi ởCODE_STYLE.mdmục 0 (smoke test quét cả comment và sẽ báo đỏ). - Thí nghiệm 🧪 là để thử rồi hoàn lại. Sau khi thử, chạy
git restore <file>(hoặcgit diffđể xem mình đã đổi gì) để nhánh bài tập chỉ chứa phần bài làm. - Ghi lại điều đã học: trong mô tả Pull Request, viết 3–5 câu: đã đổi gì, vì sao chọn cách đó, đã kiểm tra thế nào.
5. Mỗi bài học gồm những gì #
Mỗi file bài NN-*.md có cùng một khung:
| Mục | Nội dung |
|---|---|
| 🎯 Mục tiêu | Học xong bài bạn làm được gì |
| 🧠 Khái niệm | Giải thích ý tưởng, có ví dụ code ngắn hoặc sơ đồ |
| 🔍 Đọc code | Dẫn tới đúng file, đúng hàm trong src/, theo thứ tự nên đọc |
| 🧪 Thử ngay | Đổi một con số hoặc gõ một lệnh Console rồi quan sát |
| ✍️ Bài tập | Từ dễ (đổi tham số) đến khó (viết tính năng mới), làm trên nhánh riêng (mục 4) |
| 📚 Đọc thêm | Tài liệu tham khảo bên ngoài |
| ➡️ Bài tiếp theo | Liên kết sang bài kế |
Lời giải của bài tập nằm trong solutions/, cùng tên file với bài học (vd solutions/03-input.md).
Hãy tự làm trước, chỉ mở lời giải để so sánh sau khi smoke test của bạn đã XANH.
6. Cách đọc code có chú thích #
6.1 Khối đầu file #
Mọi file trong src/ mở đầu bằng một khối giới thiệu (định dạng đầy đủ ở CODE_STYLE.md mục 2):
/* ============================================================================
* OG.Loop — VÒNG LẶP GAME (game loop)
* ----------------------------------------------------------------------------
* Vai trò: module này làm gì trong game, ai gọi nó, khi nào
* Khái niệm: các khái niệm / pattern file này minh hoạ
* Bài học: bài học liên quan trong docs/learn/
* Phụ thuộc: module khác mà file này dùng (đọc lúc LOAD hay lúc CHẠY)
* Event: phát: ... · nghe: ...
* API công khai: các hàm gắn vào OG.*
* Đọc tiếp: file nên đọc tiếp theo
* ========================================================================== */Đọc dòng Vai trò và Đọc tiếp trước. Chỉ cần vậy là đủ để đi hết dự án theo một mạch.
Hai file mẫu đã áp dụng chuẩn đầy đủ, nên đọc đầu tiên: src/core/loop.js và src/core/events.js.
6.2 Ba nhãn trong code #
| Nhãn | Ý nghĩa | Bạn nên làm gì |
|---|---|---|
💡 Khái niệm: |
Dòng code bên dưới minh hoạ một khái niệm làm game hoặc một pattern | Đọc kỹ, đối chiếu với bài học |
⚠️ Bẫy: |
Lỗi dễ mắc ở chỗ này và hậu quả của nó | Nhớ lấy; nhiều câu tự kiểm tra lấy từ đây |
🧪 Thử: |
Một thay đổi an toàn để quan sát (đổi số, bật cờ, gõ Console) | Làm thử, rồi hoàn lại bằng git restore |
Tìm mọi nhãn trong dự án:
git grep -n "🧪 Thử" -- src # mọi thí nghiệm
git grep -n "⚠️ Bẫy" -- src/core # mọi bẫy trong engine6.3 Thứ tự đọc dự án gợi ý #
index.html: xem danh sách<script>(đó là bản đồ của cả dự án).src/core/namespace.js→src/core/events.js→src/core/loop.js→src/core/scenes.js→src/core/main.js.src/game/level.js→src/game/player.js→src/core/physics.js: một frame gameplay diễn ra thế nào.- Sau đó đọc theo bài học bạn đang học.
Sơ đồ module, scene, event và vòng đời frame: ARCHITECTURE-DIAGRAMS.md.
Muốn đào sâu hơn (mô hình dữ liệu, class diagram, sequence diagram từng luồng, state machine của boss/quiz/audio, sơ đồ triển khai và 10 quyết định thiết kế): docs/design/.
7. Bảng thuật ngữ nhanh #
| Thuật ngữ | Nghĩa | Bài |
|---|---|---|
| frame | Một lần tính toán và vẽ lại toàn bộ màn hình (khoảng 60 lần/giây) | 02 |
| game loop | Vòng lặp gọi update rồi draw mỗi frame | 02 |
| delta time (dt) | Khoảng thời gian giữa 2 frame, đơn vị giây | 02 |
| IIFE | Hàm tự gọi ngay khi nạp, tạo phạm vi riêng cho mỗi file | 01 |
| namespace | Object chứa các module, ở đây là window.OG |
01 |
| scene | Một "màn hình" của game: menu, bản đồ, màn chơi... | 05 |
| FSM (finite state machine) | Máy hữu hạn trạng thái: tại mỗi lúc chỉ ở một trạng thái, chuyển theo luật | 05, 09 |
| event bus / observer | Kênh sự kiện: bên phát không cần biết bên nghe là ai | 06 |
| AABB | Hình chữ nhật thẳng trục dùng để xét va chạm | 04 |
| hitbox / hurtbox | Trong dự án này: hitbox là khung va chạm thật của vật (dùng để giẫm, đỡ, nhặt); hurtbox là vùng của kẻ địch làm người chơi bị đau, nhỏ hơn hitbox để game dễ tha thứ (OG.Enemies.hurtbox, OG.Difficulty.enemyHurtbox). ⚠️ Game đối kháng thường dùng nghĩa khác (hitbox = vùng gây sát thương, hurtbox = vùng nhận sát thương); khi đọc tài liệu bên ngoài nhớ kiểm tra lại |
04, 09 |
| one-way platform | Bục chỉ đỡ khi rơi từ trên xuống, nhảy xuyên từ dưới lên được | 04 |
| coyote time | Cho phép nhảy thêm một chút sau khi đã rời mép bục | 03 |
| procedural drawing | Vẽ bằng code (path, hình học) thay vì dùng file ảnh | 07 |
| parallax | Lớp nền xa cuộn chậm hơn lớp gần, tạo cảm giác chiều sâu | 08 |
| culling | Bỏ qua không vẽ những thứ nằm ngoài màn hình | 08 |
| telegraph | Báo hiệu trước đòn đánh để người chơi kịp né | 09 |
| autoplay policy | Trình duyệt chỉ cho phát âm thanh sau thao tác đầu tiên của người dùng | 10 |
| migrate | Chuyển dữ liệu lưu từ định dạng cũ sang định dạng mới | 11 |
| data-driven | Nội dung (màn, câu đố) là dữ liệu; engine chỉ đọc dữ liệu | 12 |
| smoke test | Test chạy nhanh toàn bộ luồng để bắt lỗi lớn | 13 |
| CI (continuous integration) | Máy chủ tự chạy test mỗi lần push code | 13 |
8. Sự cố thường gặp #
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Màn hình đen hoặc đứng hình | Lỗi JS khi nạp file | Mở Console, đọc dòng đỏ đầu tiên (có tên file và số dòng) |
| Không có tiếng | Trình duyệt chặn âm thanh trước thao tác đầu tiên (autoplay policy) | Bấm một phím hoặc chạm vào game một lần (bài 10) |
| Tiến độ không được lưu | localStorage bị chặn khi mở qua file:// |
Chạy qua server tĩnh (mục 2.2) |
| Sửa code nhưng game không đổi | Trình duyệt dùng bản cũ trong cache | Tải lại cứng: Ctrl + Shift + R / Cmd + Shift + R |
| File mới không chạy | Chưa thêm thẻ <script> vào index.html, hoặc đặt sai thứ tự |
Xem bài 00 và 01 |
| Smoke test đỏ vì font | Comment hoặc code có tên font cấm | Đọc CODE_STYLE.md mục 0, dùng OG.font(...) |
| Smoke test đỏ "ctx.save/restore lệch" | Hàm vẽ thiếu một ctx.restore() |
Mỗi save() phải có đúng một restore() (bài 07) |
9. Tài liệu liên quan #
ARCHITECTURE-DIAGRAMS.md: sơ đồ Mermaid về module, scene, event, vòng đời frame, cây thư mục.docs/design/: 60 sơ đồ thiết kế chi tiết — mô hình dữ liệu, class diagram, sequence diagram, state machine, component & deployment, và các bản ghi quyết định thiết kế (ADR).CODE_STYLE.md: quy ước comment, JSDoc, refactor; bắt buộc đọc trước khi nộp bài tập đầu tiên.docs/ARCHITECTURE.md: hợp đồng API v1 (thứ tự load, toạ độ, scene, event, API từng module).docs/STORY.md: hợp đồng v2 (dữ liệu hành trình, độ khó, truyện, thẻ, câu đố).src/types.d.ts: kiểu dữ liệu dùng chung cho JSDoc.src/<thư mục>/INTEGRATION.md(vdsrc/gfx/INTEGRATION.md): ghi chú tích hợp của từng thư mục. Dự án được viết song song bởi nhiều nhóm, mỗi nhóm giữ một thư mục; file này ghi việc nhóm đó cần nhóm Tích hợp làm ở ngoài thư mục (thêm thẻ<script>, gắn hook, payload event mới). Khi bài tập bảo "ghi vàoINTEGRATION.mdcủa module", hãy viết theo cùng kiểu.README.mdở thư mục gốc: giới thiệu game, cách chơi, deploy.