Skip to content

Lab 10: Classes and Modules

In this lab, you will implement a classic card game by writing five classes in two modules. The HTML, CSS, and user interface are provided!

screenshot of the finished lab playing Crazy Eights

Background

Crazy Eights is a classic card game for two or more players. The main objective is to be the first player to get rid of all your cards. Here’s how to play:

  • Deal five or more cards to each player, and then deal one card face up to create the “discard pile”. Place the remaining cards face down to create the “draw pile”.
  • Each player takes turns placing a single card on the discard pile. The card must match the rank or suit of the previously played card, or be an eight, which is a “wild card”.
  • When players don’t have a matching card or an eight, they must draw new cards until they get one.
  • If the draw pile ever runs out, the discard pile is shuffled (except the top card) and becomes the new draw pile.
  • As soon as a player has no cards, the game ends, and all other players score penalty points for their remaining cards. Eights are worth 20, face cards are worth 10, and all others are worth their rank.

You can read https://en.wikipedia.org/wiki/Crazy_Eights for more details, but we have enough to get started.

Starter Code

Download the following files to get started:

  • main.html – The game page (do not modify).
  • eights.css – Styling and card visuals (do not modify).
  • ui.js – DOM rendering and animation (do not modify).

You will create and submit two new JavaScript module files: cards.js and game.js.

Tip

Open main.html in a browser to play the game as you go. The page will show errors in the console until both modules are fully implemented.

Tip

The provided ui.js file imports your classes like this:

import { Card, Deck, Hand } from "./cards.js";
import { Player, CrazyEights } from "./game.js";

Make sure you export every class so these imports work.

Tip

The following documentation pages may be helpful:

Module 1: cards.js

This module contains three general-purpose classes that can be used in any card game.

classDiagram
    class Card {
        +RANKS : string[]$
        +SUITS : string[]$
        +SUIT_SYMBOLS : object$
        -rank : string
        -suit : string

        +constructor(rank : string, suit : string)
        «get» rank : string
        «get» suit : string
        +matches(other : Card) boolean
        +tostring() string
    }

    class Deck {
        -cards : Card[]

        +constructor()
        «get» size : number
        «get» isEmpty : boolean
        +addCards(cards : Card[])
        +drawCard() Card
        +shuffle()
    }

    class Hand {
        -label : string
        -cards : Card[]

        +constructor(label : string)
        «get» label : string
        «get» cards : Card[]
        «get» size : number
        «get» isEmpty : boolean
        +addCard(card : Card)
        +getCard(index : Number) Card
        +removeCard(index : Number) Card
    }

    Deck --> Card : contains
    Hand --> Card : contains

The Card class

  • Static properties (shared by all Card instances):
    • RANKS — An array of 13 rank strings: ["A", "2", "3", ..., "10", "J", "Q", "K"]
    • SUITS — An array of 4 suit strings: ["clubs", "diamonds", "hearts", "spades"]
    • SUIT_SYMBOLS — An object mapping each suit name to its Unicode symbol: { clubs: "♣", diamonds: "♦", hearts: "♥", spades: "♠" }
  • Private fields: #rank and #suit, set in the constructor
  • Getters: rank and suit return the corresponding private fields
  • matches(other): Returns true if this card shares the same suit or same rank as other, or if this card’s rank is "8" (eights are wild)
  • toString(): Returns a short string like "A♠" or "10♥" (rank + suit symbol)

The Deck class

  • Private field: #cards, an array of Card objects
  • constructor(): Creates an ordered deck of 52 cards (one for every combination of Card.SUITS and Card.RANKS). The deck is not shuffled.
  • Getters: size returns the number of cards; isEmpty returns true when the deck has no cards
  • addCards(cards): Pushes all cards in the given array onto the deck (use the spread syntax with push())
  • drawCard(): Removes and returns the last card in the array (use pop())
  • shuffle(): Randomly rearranges the cards in place using the Fisher-Yates algorithm:

    for i from n-1 down to 1:
        j = random integer in [0, i]
        swap cards[i] and cards[j]
    

    Tip

    Use Math.floor(Math.random() * (i + 1)) to generate j, and use destructuring assignment to swap: [a, b] = [b, a]

The Hand class

  • Private fields: #label (a string) and #cards (an array)
  • constructor(label): Creates a hand with the given label and empty cards array
  • Getters: label, size, isEmpty work as expected; cards returns a shallow copy of the internal array (use the spread syntax: [...this.#cards])
  • addCard(card): Pushes a card onto the end of the hand
  • removeCard(index): Removes and returns the card at index (use splice())
  • getCard(index): Returns the card at index without removing it

Module 2: game.js

This module contains two classes specific to Crazy Eights.

classDiagram
    class Player {
        -name : string
        -hand : Hand

        +constructor(name : string)
        «get» name : string
        «get» hand : Hand
        +findMatch(topCard : Card) number
        +score() number
    }

    class CrazyEights {
        -deck : Deck
        -discardPile : Card[]
        -human : Player
        -computer : Player
        -currentPlayerIndex : number

        +constructor(humanName : string, computerName : string)
        «get» human : Player
        «get» computer : Player
        «get» topCard : Card
        «get» drawPileSize : number
        «get» currentPlayerIndex : number
        «get» isOver : boolean
        +drawCard() Card
        +playCard(card : Card)
        +computerTurn() object
        -reshuffle()
    }

    CrazyEights --> Player : manages
    CrazyEights --> Deck : uses
    Player --> Hand : has

The Player class

  • Private fields: #name and #hand
  • constructor(name): Sets the name and creates a new empty Hand using the name as the label
  • Getters: name returns the player’s name; hand returns the Hand object
  • findMatch(topCard): Loops through the player’s hand and returns the index of the first card that matches() the given topCard. Returns -1 if no match is found
  • score(): Returns the total penalty points for all cards remaining in the hand:

    Rank Points
    8 20
    J, Q, K 10 each
    A 1
    2–7, 9–10 Face value (use parseInt)

The CrazyEights class

  • Private fields: #deck, #discardPile (an array of Card), #human, #computer, #currentPlayerIndex
  • constructor(humanName, computerName): Sets up a new game:
    1. Create and shuffle a new Deck
    2. Create two Player objects (human and computer)
    3. Deal 5 cards to each player by calling this.#deck.drawCard() and adding each card to the player’s hand
    4. Turn one card face-up to start the discard pile (draw one more card from the deck and store it in an array)
    5. Set currentPlayerIndex to 0 (human goes first)
  • Getters:
    • human and computer return the two players
    • topCard returns the last element of the discard pile array
    • drawPileSize returns this.#deck.size
    • currentPlayerIndex returns 0 (human’s turn) or 1 (computer’s turn)
    • isOver returns true if either player’s hand is empty
  • drawCard(): If the deck is empty, call #reshuffle() first. Then draw and return one card from the deck.
  • playCard(card): Pushes the card onto the discard pile and switches the current player (toggle between 0 and 1)
  • computerTurn(): Executes the computer’s full turn:
    1. Look for a matching card in the computer’s hand using findMatch()
    2. If found, remove it from the hand and call playCard()
    3. If not found, repeatedly call drawCard() until a match is drawn. Non-matching drawn cards are added to the computer’s hand.
    4. Return an object { played: Card, drawn: Number } with the card that was played and how many cards were drawn
  • #reshuffle(): A private method that saves the top card, moves all other discard-pile cards back into the deck using addCards(), resets the discard pile to just the saved top card, and shuffles the deck

Submission

When you are finished, submit cards.js and game.js to Gradescope.