Memory Game
A polished memory card game built as a learning project in the Apps Team: a Next.js / React front-end in TypeScript, wired to a NestJS backend, complete with a live timer, a leaderboard, light/dark theming and a playful horror twist.
Overview
The task was to build a classic memory game: a 4×4 grid of 16 cards (8 unique images, each duplicated and shuffled). Players flip two cards at a time; matching pairs stay open, mismatches flip back after a short delay. When every pair is found, the player submits their time to a leaderboard. On top of the required scope I added a light/dark theme system, background music and sound effects, and a time-based “creepy” mode with an ending scene.
Key Features
Hook-driven game logic
The board lives entirely in React state via useState, useEffect and useRef: flipping, match-checking, board locking and win detection.
Timer & scoring
A live timer starts on the first flip; the final time becomes the score posted to the backend and shown on a leaderboard sorted by fastest time.
Backend integration
Card images and scores are fetched from and posted to a NestJS API via axios, with the base URL read from an environment variable.
Light / dark theming
The UI reacts to the OS colour preference, swapping card art, backgrounds and music for each mode.
Sound & atmosphere
Background tracks, match/win sounds and a timed “Sonic.exe / Mario.exe” twist with a video ending scene.
Clean config & CI
Secrets kept out of code through NEXT_PUBLIC_* env vars and GitLab CI with Secret Detection.
How It Was Built
- Scaffolding: created an empty NestJS app and a hover-animated card grid with the title.
- Wiring: connected front-end and back-end, then added the click / flip handling.
- Gameplay: built background cards, a working timer and the match logic.
- Leaderboard: linked the score to the backend and rendered a sorted ranking.
- Polish: finished the game, extracted the backend URL into an environment variable.
- Atmosphere: added themed sounds and the ending scene; resolved a merge conflict along the way.
Daily commits and feature branches kept the whole progress traceable.
What I Learned
This project sharpened my ability to model non-trivial UI state in React with hooks, keep components typed in TypeScript, consume a REST backend cleanly, and externalise configuration through environment variables. It also reinforced disciplined version control: small, descriptive commits and resolving conflicts on a feature branch.