Lab 1 — Ghost (Node/Express + Bootstrap): Strategy + Multiplayer

Classroom Link

Overview

In this lab you will take a working starter web app for the word game Ghost and turn it into:
1) A playable game with repeated rounds and scorekeeping
2) A two-human multiplayer mode where two browsers connect to the same server and play each other

The starter app already includes:

  • A Bootstrap front end
  • An Express/Node back end with API routes
  • A large word list loader on the server (or a fallback)
  • A placeholder computer move: the server replies with a random letter

Your work is to make the back end actually manage a full Ghost game and (in part 2) support two clients playing the same game.


Ghost rules (this version)

A fragment starts empty. Players alternate adding a single letter to the end of the fragment.

You lose the round if, after your move:
1) You complete a valid word of length ≥ N (N is configurable; default 4), OR
2) The fragment is not the prefix of any valid word (i.e., no dictionary word starts with that fragment)


Learning goals

  • Practice JavaScript in a full-stack setting
  • Understand client/server responsibilities
  • Maintain state safely on the server across multiple requests
  • Design clean JSON APIs
  • Implement game logic using a word list + prefix checking
  • Build a simple real-time multiplayer experience (2 clients)

Getting the Starter Running (Linux — Shared Server)

All students are running their projects on the same Linux server.
Because of this, you must start your server on a port assigned to you in the range:

4020–4050

In the examples below, replace 4027 with your assigned port number.


Step 1: Install dependencies

From the project directory:

make install


Step 2: Start the development server

You must specify your port when starting the server:

PORT=4027 make dev

If you see an error that mentions:

EADDRINUSE

it means that port is already being used by another process.
Choose a different port within your assigned range and try again.


Step 3: Open the app in your browser

Open the following URL, replacing the port with the one you used above:

http://10.192.145.179:4027

Important notes:

  • If you are logged into the server via SSH, your browser is running on your local machine, not on the server.
  • Do not use localhost unless the browser is running on the same machine as the server.
  • Use the server’s hostname or IP address instead.

Optional: Install a larger dictionary

The server will try these sources in order:
1) server/words.txt
2) /usr/share/dict/words
3) server/words_sample.txt (small fallback)

If you want a large list and you have outbound internet from your environment:

  • make fetch-words

Part A — Make the server run repeated rounds and keep score (Human vs Computer)

What the starter currently does

  • The server stores a single global fragment
  • When you play a letter, it appends it
  • Then the server appends a random letter
  • It does a basic rule check to see if someone lost immediately

This is not a real “game session” yet: there is no scoring, no concept of rounds for a particular user, and no strategy.

Your tasks (Part A)

A1) Create a real game/session model on the server

You need a server-side data structure that can represent:

  • Current fragment
  • Whose turn it is
  • Whether the round is over
  • Score for human and computer (across rounds)
  • Config value N (minimum word length)

Requirement: Your server must support playing multiple rounds without restarting the server.

Design hint (no code):

  • You can keep an in-memory object keyed by a gameId
  • The client can store the gameId and send it with each request
  • Or you can use cookies/sessions (optional; more advanced)

A2) Define clear API endpoints

You should end up with endpoints that support:

  • Starting a new game (returns gameId)
  • Starting a new round within an existing game
  • Submitting a move
  • Getting current state (fragment, score, status)

You may change the starter endpoints, but your UI must still function.

A3) Implement robust rule checking

After every move:

  • Check if the fragment is a completed word of length ≥ N
  • Check if the fragment is a valid prefix of at least one word

If a player loses:

  • Update score
  • Mark round complete
  • Allow starting next round without resetting score

A4) Replace “random letter” with a computer strategy

You will design your own strategy. It does not need to be perfect, but it must:

  • Only play a–z
  • Avoid illegal moves when possible
  • Prefer moves that increase the chance the human eventually loses

Technical requirement: Your strategy must use the dictionary and/or prefix logic on the server.

Examples of possible approaches (choose one):

  • “Safe move”: choose a letter that keeps the fragment as a valid prefix
  • “Look-ahead”: consider the next human move(s)
  • “Parity strategy”: aim to force the opponent to complete a word (classic Ghost heuristic)

You should explain your strategy in your README.


Part B — Two-human multiplayer mode (two browsers)

Goal

Two browsers should be able to connect to the server, join the same game, and alternate turns.

Requirements

  • The server must support at least one active 2-player game at a time (more is a bonus).
  • Two clients must see the same fragment and score.
  • The server must enforce turn-taking:
  • If it’s Player 1’s turn, Player 2’s move is rejected (HTTP 400 or 409 with JSON error).
  • The server must detect round end and update score correctly.
  • The UI should show:
  • Current fragment
  • Whose turn it is
  • Scoreboard
  • Round result message when someone loses

Suggested architecture (no code)

  • When a multiplayer game is created, it produces a gameId.
  • A second client “joins” by entering the gameId.
  • The server stores two player slots (P1 and P2).
  • Each client identifies itself as P1 or P2 (token, random secret, or server-assigned id).
  • Moves are validated against the current turn.

Polling is OK

You do NOT need WebSockets for Lab 1.
Your clients can update the UI by:

  • polling a /state endpoint every ~500–1000ms, OR
  • refreshing state after each move and after “new round”

What to turn in

1) A working implementation of Part A (single human vs computer) with:

  • repeated rounds
  • scorekeeping
  • non-random strategy

2) A working implementation of Part B (two humans) with:

  • join-by-gameId (or equivalent)
  • turn enforcement
  • shared state visible to both clients

3) A short README that includes:

  • how to run
  • your API design (endpoint list + sample JSON fields)
  • how your strategy works
  • any limitations/known issues

Grading rubric (suggested)

Correctness (40%)

  • Rule checks correct (word completion + invalid prefix)
  • Turn order enforced
  • Score updated correctly and persists across rounds

Server design (25%)

  • Clear state model
  • Clean endpoints and JSON responses
  • Proper validation and error responses

Strategy quality (15%)

  • Clearly described
  • Better than random (uses dictionary/prefix checks)
  • Doesn’t frequently self-destruct with illegal moves

Multiplayer (15%)

  • Two clients share state reliably
  • Join flow works
  • UI reflects turn/score/round outcome

Code quality (5%)

  • Reasonable structure
  • Helpful comments
  • No giant monolithic functions

Common pitfalls

  • Forgetting to lowercase/validate input letters
  • Confusing “prefix exists” with “word exists”
  • Not enforcing turn order on the server
  • Not resetting fragment correctly between rounds
  • Using global variables for fragment/score (breaks multiple games)

Extension ideas (optional)

  • Multiple concurrent games
  • “Best of 5” match structure
  • Timer per turn
  • Display the completed losing word at end of round (if applicable)
  • Host on a shared class server for cross-machine play
Scroll to Top