Skip to content

huijiayu/wizard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ§™ Wizard Card Game

A real-time multiplayer web app for the Wizard card game by Ken Fisher (1984). Players create or join rooms, bid on tricks, and play through a full game β€” with live score tracking and end-game stats.

Table of Contents


Game Rules

Wizard is a trick-taking game for 3–6 players. The deck has 60 cards: the standard 52 plus 4 Wizards and 4 Jesters.

Rounds

The number of rounds depends on the player count:

Players Rounds
3 20
4 15
5 12
6 10

In round N, each player is dealt N cards. The dealer rotates each round.

Trump

After dealing, the top remaining card is flipped to determine trump:

  • Standard card β†’ that suit is trump
  • Jester β†’ no trump this round
  • Wizard β†’ the dealer chooses any suit as trump
  • Last round β†’ all cards are dealt; no trump card

Bidding

Starting left of the dealer, each player bids how many tricks they expect to win. There is no restriction on the total bids β€” they may or may not equal the number of tricks available.

Playing Tricks

  • The player left of the dealer leads the first trick
  • Players must follow the led suit if possible
  • Wizards and Jesters may be played at any time, even if you can follow suit
  • If the led card is a Jester, the next non-Jester card sets the led suit

Trick winner (in priority order):

  1. The first Wizard played wins
  2. The highest trump card wins (if any trump was played)
  3. The highest card of the led suit wins
  4. If all cards are Jesters, the first Jester played wins

The winner of each trick leads the next one.

Scoring

Result Points
Exact bid +20 + (10 Γ— tricks won)
Wrong bid βˆ’10 Γ— |tricks won βˆ’ bid|

Scores can go negative.

Examples:

  • Bid 2, win 2 β†’ +40
  • Bid 3, win 1 β†’ βˆ’20
  • Bid 0, win 0 β†’ +20

Winning

The player with the highest cumulative score after all rounds wins.


Project Structure

wizard/
β”œβ”€β”€ package.json              # Root workspace (npm workspaces)
β”œβ”€β”€ server/                   # Node.js + Express + Socket.io backend
β”‚   β”œβ”€β”€ index.js              # Server entry point
β”‚   └── src/
β”‚       β”œβ”€β”€ constants.js      # GamePhase enum, error codes
β”‚       β”œβ”€β”€ deck.js           # 60-card deck builder + Fisher-Yates shuffle
β”‚       β”œβ”€β”€ trickResolver.js  # Trick winner resolution logic
β”‚       β”œβ”€β”€ scoring.js        # Round scoring + bid validation
β”‚       β”œβ”€β”€ statsTracker.js   # Per-player and global stat accumulation
β”‚       β”œβ”€β”€ GameEngine.js     # Pure game state machine
β”‚       β”œβ”€β”€ RoomManager.js    # In-memory room registry
β”‚       β”œβ”€β”€ GameRoom.js       # Socket event handler per room
β”‚       └── __tests__/        # Vitest unit tests
β”œβ”€β”€ client/                   # React + Vite + Tailwind CSS frontend
β”‚   └── src/
β”‚       β”œβ”€β”€ App.jsx           # Phase-based screen router
β”‚       β”œβ”€β”€ socket.js         # Singleton Socket.io client
β”‚       β”œβ”€β”€ store/            # Zustand global store
β”‚       β”œβ”€β”€ hooks/            # useSocket (eventβ†’store), useGameState
β”‚       β”œβ”€β”€ components/
β”‚       β”‚   β”œβ”€β”€ screens/      # Lobby, WaitingRoom, GameScreen, EndScreen
β”‚       β”‚   β”œβ”€β”€ game/         # PlayerHand, TrickArea, BidPanel, TrumpPicker, …
β”‚       β”‚   └── ui/           # Button, Modal, RoomCodeDisplay
β”‚       β”œβ”€β”€ utils/            # cardHelpers (labels, colors, legal-play check)
β”‚       └── __tests__/        # Vitest unit tests
└── e2e/                      # Playwright end-to-end tests
    β”œβ”€β”€ playwright.config.ts
    └── tests/
        β”œβ”€β”€ lobby.spec.ts     # Room creation, joining, validation
        β”œβ”€β”€ game-flow.spec.ts # 3-player game: hand display, bidding, trick play
        └── end-screen.spec.ts# Final scores, stats tab, round history

Getting Started

Prerequisites

  • Node.js v18 or later
  • npm v8 or later

Install dependencies

npm install

Run in development

npm run dev

This starts both servers concurrently:

  • Server β†’ http://localhost:3001
  • Client β†’ http://localhost:5173

Open http://localhost:5173 in multiple browser tabs or windows to play with multiple players locally.

Play a game locally

  1. Open http://localhost:5173 in three browser tabs
  2. In tab 1: enter a name β†’ Create Room β†’ note the 4-character room code
  3. In tabs 2 & 3: enter a name β†’ Join Room β†’ enter the room code
  4. Back in tab 1 (the host): click Start Game
  5. Each tab shows only that player's private hand β€” play proceeds in turn order

Running Tests

Unit Tests (Vitest)

Unit tests cover all pure game logic on the server and client-side utilities.

Run all unit tests:

npm test

This runs vitest run in both the server and client workspaces.

Run server tests only:

npm test --workspace=server

Run client tests only:

npm test --workspace=client

What's tested

Server (server/src/__tests__/)

File Coverage
deck.test.js Deck composition (60 cards, 4W/4J/52 standard), suit counts, unique IDs, shuffle correctness, deal correctness
scoring.test.js Exact bid scores, over/underbid penalties, negative scores, bid validation
trickResolver.test.js All trick resolution rules: Wizard priority, first-of-multiple Wizards, highest trump, led suit, all-Jesters, Jester lead edge cases
GameEngine.test.js Legal card determination (follow suit, void, specials always legal), round dealing, full 1-card round flow, turn enforcement
statsTracker.test.js Exact bid counting, overbid/underbid tracking, exact-bid streak tracking, suit distribution, round history

Client (client/src/__tests__/)

File Coverage
cardHelpers.test.js Card labels, color classes, getLegalCards (client-side mirror of server logic)
gameStore.test.js All store mutations: room events, game lifecycle (started β†’ round β†’ bids β†’ tricks β†’ end), error state

Total: 99 unit tests


End-to-End Tests (Playwright)

E2e tests spin up the full dev server and run multi-browser scenarios.

Install Playwright browsers (first time only):

npx playwright install --only-shell chromium
# or, from the e2e workspace:
npm run test:e2e --workspace=e2e -- --help

Run all e2e tests:

npm run test:e2e

Run with a visible browser (headed mode):

cd e2e && npx playwright test --headed

Open the interactive Playwright UI:

cd e2e && npx playwright test --ui

What's tested

lobby.spec.ts

  • Creating a room shows a 4-character code
  • Create button is disabled when no name is entered
  • Invalid room code shows an error
  • Join button disabled until both name and code are filled
  • Two players see each other in the waiting room
  • Host sees the Start Game button; non-host sees a waiting message
  • Cannot start with fewer than 3 players (button disabled)

game-flow.spec.ts

  • 3 players start a game and each see their private hand (1 card in round 1)
  • Trump display is visible to all players after dealing
  • Bid panel appears for the player whose turn it is to bid
  • After all bids, the active player's cards are highlighted; playing a card updates the trick area

end-screen.spec.ts (plays a complete 20-round game)

  • Final scoreboard lists all players with cumulative scores
  • Winner is highlighted
  • Stats tab shows per-player accuracy, wizard/jester counts, and suit distribution
  • Round History tab shows all 20 rounds with bid/actual/delta per player
  • "Play Again" resets to the lobby

Note: The end-screen tests run a full 20-round game and have a 120-second timeout. They may take 60–90 seconds to complete.


Architecture

Real-time Communication

All game state lives on the server. Clients receive events and update a Zustand store. No client-to-client communication occurs directly.

Client A ──emit('playCard')──► Server ──emit('CARD_PLAYED')──► All clients

Private information (hands) is sent only to the owning socket. All other state is broadcast to the room.

Game Phase State Machine

WAITING β†’ DEALING* β†’ [TRUMP_CHOICE β†’] BIDDING
  β†’ TRICK_LEAD β†’ TRICK_FOLLOW β†’ TRICK_RESOLVE*
  β†’ (more tricks) β†’ TRICK_LEAD
  β†’ (round done) β†’ ROUND_SCORE* β†’ DEALING* (next round)
  β†’ (game over) β†’ GAME_OVER

* = server-internal transitions; clients see results via socket events.

Key Socket Events

Direction Event Description
C→S createRoom / joinRoom Lobby setup
C→S startGame Host only
S→C ROUND_STARTED Private hand delivery (per socket)
S→C TRUMP_REVEALED Broadcast trump card/suit
C→S chooseTrump Dealer picks suit after Wizard flip
C→S placeBid Active bidder submits bid
C→S playCard Active player plays a card
S→C TRICK_WON Announces winner, updates trick counts
S→C ROUND_ENDED Scores + stats broadcast
S→C GAME_ENDED Final scores, stats, winner

About

Like the card game

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors