Step 1 - Project setup
4 min read
Project setup
Create the skeleton the rest of the build sits on. Nothing game-related is implemented here; the
goal is that npm run verify runs green on an empty project so every later step has a working
safety net.
Repository layout
tic-tac-toe/
index.html
package.json
tsconfig.json
vite.config.ts
vitest.config.ts
eslint.config.js
.prettierrc
.gitignore
LICENSE
README.md
public/
favicon.svg
src/
main.ts composition root, the only file that touches both UI and state
engine/
types.ts Player, Cell, Board, GameStatus, GameState, InvalidMoveError
constants.ts BOARD_SIZE, CELL_COUNT, WIN_LINES
game.ts createGame, applyMove, undoLastMove, availableMoves, evaluateBoard
game.test.ts
ai/
types.ts Difficulty, MoveChooser
random.ts easy strategy
heuristic.ts medium strategy
minimax.ts hard strategy with alpha-beta
choose-move.ts difficulty -> strategy dispatch
ai.test.ts
ui/
board-view.ts renders the grid, emits cell selections
status-view.ts status line and the aria-live region
controls-view.ts mode, difficulty, starting mark, new game, reset scores
scoreboard-view.ts
keyboard.ts roving focus and arrow-key navigation
strings.ts every user-visible string
state/
app-state.ts AppState shape and reducers
storage.ts localStorage read/write with schema versioning
storage.test.ts
styles/
tokens.css design tokens as custom properties
layout.css
board.css
controls.css
index.css imports the above, in this order
Create every directory now, with the files as stubs where they are not yet implemented. A stub is an empty module with a TSDoc comment naming what it will contain — not a placeholder implementation that will be silently forgotten.
Tooling
Use Vite with the vanilla-TypeScript template as the dev server and bundler, and Vitest for tests. Node 20 or newer.
tsconfig.json must set at minimum:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"verbatimModuleSyntax": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"]
},
"include": ["src"]
}
noUncheckedIndexedAccess is deliberate: it forces the engine to handle board[i] being possibly
undefined, which catches off-by-one bugs at compile time.
ESLint runs with the recommended TypeScript rules plus a ban on any, non-null assertions and
console.log (console.error is allowed). Prettier: 2-space indent, single quotes, semicolons,
100-character print width.
npm scripts
| Script | Command | Purpose |
|---|---|---|
dev |
vite |
Dev server on port 5173 with hot reload |
build |
tsc --noEmit && vite build |
Type-check, then produce dist/ |
preview |
vite preview |
Serve the production build locally |
test |
vitest run |
Run the suite once |
test:watch |
vitest |
Watch mode |
lint |
eslint src |
Lint |
format |
prettier --write . |
Format |
verify |
npm run lint && npm run build && npm run test |
The gate used by every later step |
index.html
A single document with a lang="en" attribute, a viewport meta tag, a descriptive <title>, a
theme-color meta tag, and one <div id="app"></div> plus <script type="module" src="/src/main.ts">.
No inline styles and no inline scripts — a strict CSP must be possible without changes.
Add this meta tag so the page cannot be embedded or sniffed into another type:
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'none'">
If Vite's dev server needs a looser policy in development, keep the strict policy in the production build and document the difference in a comment.
Housekeeping
.gitignorecoversnode_modules/,dist/,coverage/,.DS_Store,*.local.LICENSEis the MIT licence text with the current year.public/favicon.svgis a simple two-colour X-and-O mark drawn with SVG paths, no raster fallback.- The repository
README.md(distinct from this specification) explains install, dev, build and test in under 30 lines.
Acceptance criteria
-
npm installcompletes with zero runtime dependencies inpackage.json(dependenciesis empty or absent). - Every directory and file in the layout above exists; stub modules carry a TSDoc comment describing their future contents.
-
npm run devserves a page athttp://localhost:5173that renders without console errors. -
npm run verifyexits 0 on the empty project. -
tsc --noEmitpasses withstrictandnoUncheckedIndexedAccessenabled. - ESLint reports zero errors and zero warnings;
anyand!assertions are rule-blocked. -
npm run buildproduces adist/folder whose total size is under 100 kB uncompressed. -
index.htmlcontains no inline script or style, and declares the CSP meta tag. -
LICENSEcontains the MIT licence.
Discussion
0 commentsNo comments yet. Start the discussion.