A word-guessing game where you race to guess the hidden word before programming languages get wiped off the map — one wrong letter at a time. link here
Assembly Endgame is a Hangman-style game built with React and TypeScript. The main goal was to learn how to:
- Manage state with
useStateand derive values from it rather than storing redundant state - Lift state up and pass data down through component props with proper TypeScript typing
- Use conditional CSS class names for dynamic styling (
clsx) - Implement accessibility best practices using
aria-liveregions for screen reader support - Add visual flair with
react-confettion win conditions - Structure a React project with Vite and enforce code quality with ESLint and TypeScript
- Clone the repository:
git clone https://github.com/your-username/assembly-endgame.git
- Navigate into the project folder:
cd assembly-endgame - Install dependencies:
npm install
- Start the development server:
npm run dev
I'm using Vite to make development faster. Here are the main scripts I use:
npm run dev // Starts the project so I can see changes live.
npm run build // Prepares the project for the real world (deployment).
npm run preview // Lets me check the build version locally.aria-live behaviour wasn't what I expected - Initially used aria-live="assertive" which interrupted the screen reader mid-sentence on every keystroke. Switching to polite fixed the interruption issue, but then the announced message wasn't meaningful enough on its own - "correct" without context tells you nothing. Solved by building a dedicated AriaLiveStatus component that announces the full picture: whether the guess was correct, how many attempts remain, and the current word state letter by letter.
Derived state vs stored state - It was tempting to store isGameWon, isGameLost, and wrongGuessCount as separate useState values. The problem is that stored derived state can go out of sync with the source of truth. Instead, all game status values are computed directly from currentWord and guessedLetters on every render, two pieces of state drive the entire game.
TypeScript utility types - Omit<Language, "name"> in LanguageChips.tsx was the first time I used a utility type in practice. The component needed to spread backgroundColor and color as inline styles, but not name - using Omit made this explicit and type-safe rather than just casting to any.
All state in App.tsx, all components purely presentational - Every component receives props and emits callbacks, none own state. This makes data flow easy to trace and components straightforward to reason about in isolation.
clsx for conditional class names - Especially visible in Keyboard.tsx where a button can be default, correct, or wrong. Template literal conditionals would have been harder to read and easier to get wrong.
- Add physical keyboard support - clicking on-screen buttons works but feels unnatural for a word game
- Prevent the same word from appearing twice in a row -
getRandomWordcan return the current word again on new game - Add word length as a difficulty indicator - short and long words play very differently
- Node.js (v18+)
- A text editor (VS Code recommended)
- Basic familiarity with React and TypeScript
| No | File Name | What it does |
|---|---|---|
| 1 | index.html |
HTML shell — the single page Vite serves; holds the #root mount point and loads main.tsx |
| 2 | App.tsx |
Root component — holds all game state and derived values, composes the full UI |
| 3 | languages.ts |
Defines the list of programming languages used as the "lives" system |
| 4 | words.ts |
Exports the word bank the game randomly selects from |
| 5 | utils.ts |
Helper functions: random word picker and farewell message generator |
| 6 | index.css |
Global styles — layout, colour scheme, keyboard, chips, and accessibility utilities |
| 7 | main.tsx |
App entry point — mounts the React tree into the DOM |
| 8 | vite-env.d.ts |
Vite environment type declarations |
The game runs entirely on the client side with no backend. On load, a random word is chosen from words.ts. The player clicks letters on the on-screen keyboard; correct guesses reveal letters in the word, while incorrect ones eliminate programming languages one by one (starting from HTML and ending at Assembly). The player wins by guessing the full word before all 8 languages are lost. State is managed in App.tsx and all child components are purely presentational, receiving props and emitting callbacks.
- Found a bug? Open an issue and I'll try to fix it.
- Advice? If you have ideas for new words, languages, or game features, let me know!
Keep pull requests focused — one fix or feature per PR. Make sure the TypeScript compiler and ESLint pass (npm run build and npm run lint) before submitting. For larger changes, open an issue first to discuss the approach.
Feel free to use this for your own practice!
MIT License.