Week 5 Lab Sheet — Memory Game

Web Games Development · Arrays, images & game logic · ~150 minutes

Objectives

  1. Model game pieces as objects (Card) with a draw() method.
  2. Build a deck from an array of pairs.
  3. Shuffle by repeatedly swapping two random entries.
  4. Load images with new Image() and draw them with drawImage.
  5. Hit-test a click against card rectangles.
  6. Track turn state and delay the flip-back with setTimeout.
  7. Time the game with Date and detect completion.
  8. Draw regular polygons with paths and trigonometry.

Instructions

Create a folder named Week5 and copy the photo materials into it (needed for memoryPictures.html). Create each file below, writing the code shown. Save and refresh after each change. Tick each checkpoint.

Files

1. triangle.html Objective 8

Week5\triangle.html

  1. [Obj 8] Create the file and add this scaffold code.
    <!DOCTYPE html>
    <html lang="en">
    <head><title>Triangle</title></head>
    <body>
      <canvas id="canvas" width="400" height="400"></canvas>
    
      <script>
        var ctx = document.getElementById("canvas").getContext("2d");
        var rad = 50, centerX = 200, centerY = 200, n = 3;
        var angle = (2 * Math.PI) / n;
        // TODO: draw the polygon with a path
      </script>
    </body>
    </html>
  2. [Obj 8] Draw the polygon with a path.
    ctx.beginPath();
    ctx.moveTo(centerX + rad * Math.cos(-0.5 * angle), centerY + rad * Math.sin(-0.5 * angle));
    for (var i = 1; i < n; i++) {
      ctx.lineTo(centerX + rad * Math.cos((i - 0.5) * angle), centerY + rad * Math.sin((i - 0.5) * angle));
    }
    ctx.closePath();
    ctx.fill();
Checkpoint: triangle.html draws a triangle with paths and trigonometry (Objective 8).

2. memoryPolygons.html Objectives 1, 2, 3, 5, 6

Week5\memoryPolygons.html

  1. [Obj 1, 2] Create the file and add this scaffold code.
    <!DOCTYPE html>
    <html lang="en">
    <head><title>Memory game using polygons</title></head>
    <body>
      <canvas id="canvas" width="900" height="400"></canvas>
      <br />
      Click on two cards to see if you have a match.
      <form name="f">
        Number of matches: <input type="text" name="count" value="0" size="1" />
        Time taken: <input type="text" name="elapsed" value=" " size="4" /> seconds.
      </form>
    
      <script>
        var ctx, canvas1, deck = [], firstpick = true, firstcard, secondcard, matched;
        var cardrad = 30, cardwidth = 4 * cardrad, cardheight = 4 * cardrad;
        var firstsx = 30, firstsy = 50, margin = 30;
        // TODO: add the Card constructor and makedeck()
        // TODO: add shuffle(), choose() and flipback()
        function init() {
          ctx = document.getElementById("canvas").getContext("2d");
          canvas1 = document.getElementById("canvas");
          // TODO: add the click listener, make and shuffle the deck
        }
      </script>
    </body>
    </html>
  2. [Obj 1, 2] Add the Card constructor and build the deck.
    function Card(sx, sy, swidth, sheight, info) {
      this.sx = sx; this.sy = sy; this.swidth = swidth; this.sheight = sheight;
      this.info = info; this.draw = drawback;
    }
  3. [Obj 3] Shuffle by swapping entries.
    for (var nt = 0; nt < 3 * dl; nt++) {
      i = Math.floor(Math.random() * dl);
      k = Math.floor(Math.random() * dl);
      holder = deck[i].info; deck[i].info = deck[k].info; deck[k].info = holder;
    }
  4. [Obj 5] Handle a click and compare the picks.
    canvas1.addEventListener("click", choose, false);
    if (deck[i].info == deck[firstcard].info) { matched = true; }
  5. [Obj 6] Flip back non-matching cards after a delay.
    firstpick = true;
    setTimeout(flipback, 1000);
  6. [Obj 1, 5, 6] Add the colours, drawing and game functions.
    var backcolor = "rgb(128,0,128)";
    var frontbgcolor = "rgb(251,215,73)";
    var polycolor = "rgb(254,11,0)";
    var tablecolor = "rgb(255,255,255)";
    
    function drawback() {
      ctx.fillStyle = backcolor;
      ctx.fillRect(this.sx, this.sy, this.swidth, this.sheight);
    }
    function Polycard(sx, sy, rad, n) {
      this.sx = sx; this.sy = sy; this.rad = rad; this.n = n;
      this.angle = (2 * Math.PI) / n; this.draw = drawpoly;
    }
    function drawpoly() {
      ctx.fillStyle = frontbgcolor;
      ctx.fillRect(this.sx - 2 * this.rad, this.sy - 2 * this.rad, 4 * this.rad, 4 * this.rad);
      ctx.beginPath();
      ctx.fillStyle = polycolor;
      var rad = this.rad;
      ctx.moveTo(this.sx + rad * Math.cos(-0.5 * this.angle), this.sy + rad * Math.sin(-0.5 * this.angle));
      for (var i = 1; i < this.n; i++) {
        ctx.lineTo(this.sx + rad * Math.cos((i - 0.5) * this.angle), this.sy + rad * Math.sin((i - 0.5) * this.angle));
      }
      ctx.fill();
    }
    function makedeck() {
      var cx = firstsx, cy = firstsy;
      for (var i = 3; i < 9; i++) {
        deck.push(new Card(cx, cy, cardwidth, cardheight, i));
        deck.push(new Card(cx, cy + cardheight + margin, cardwidth, cardheight, i));
        cx = cx + cardwidth + margin;
      }
    }
    function choose(ev) {
      var mx = ev.pageX, my = ev.pageY, card;
      for (var i = 0; i < deck.length; i++) {
        card = deck[i];
        if (card.sx >= 0 && mx > card.sx && mx < card.sx + card.swidth && my > card.sy && my < card.sy + card.sheight) { break; }
      }
      if (i < deck.length) {
        if (firstpick) {
          firstcard = i; firstpick = false;
          new Polycard(card.sx + cardwidth * 0.5, card.sy + cardheight * 0.5, cardrad, card.info).draw();
        } else {
          secondcard = i;
          new Polycard(card.sx + cardwidth * 0.5, card.sy + cardheight * 0.5, cardrad, card.info).draw();
          matched = (card.info == deck[firstcard].info);
          if (matched) { document.f.count.value = String(1 + Number(document.f.count.value)); }
          firstpick = true;
          setTimeout(flipback, 1000);
        }
      }
    }
    function flipback() {
      if (!matched) { deck[firstcard].draw(); deck[secondcard].draw(); }
      else {
        ctx.fillStyle = tablecolor;
        ctx.fillRect(deck[secondcard].sx, deck[secondcard].sy, deck[secondcard].swidth, deck[secondcard].sheight);
        ctx.fillRect(deck[firstcard].sx, deck[firstcard].sy, deck[firstcard].swidth, deck[firstcard].sheight);
        deck[secondcard].sx = -1; deck[firstcard].sx = -1;
      }
    }
  7. [Obj 1, 2, 3] Add init() to start the game.
    ctx = document.getElementById("canvas").getContext("2d");
    canvas1 = document.getElementById("canvas");
    canvas1.addEventListener("click", choose, false);
    makedeck();
    document.f.count.value = "0";
    starttime = Date.now();
    shuffle();
Checkpoint: memoryPolygons.html shuffles, matches pairs and flips back (Objectives 1, 2, 3, 5, 6).

3. memoryPictures.html Objectives 4, 7

Week5\memoryPictures.html

  1. [Obj 1, 2] Create the file and add this scaffold code (your memory game, ready for images).
    <!DOCTYPE html>
    <html lang="en">
    <head><title>Memory game using pictures</title></head>
    <body>
      <canvas id="canvas" width="900" height="400"></canvas>
    
      <script>
        var ctx, canvas1, deck = [], firstpick = true, firstcard, secondcard, matched;
        var cardwidth = 100, cardheight = 100, firstsx = 30, firstsy = 50, margin = 30;
        var starttime, count = 0;
        var pairs = [
          // TODO: add pairs of image file names
        ];
        // TODO: add the Card constructor and makedeck() that loads the images
        // TODO: add shuffle(), choose() and flipback()
        function init() {
          ctx = document.getElementById("canvas").getContext("2d");
          canvas1 = document.getElementById("canvas");
          // TODO: add the click listener, make and shuffle the deck
        }
      </script>
    </body>
    </html>
  2. [Obj 2, 4] Add the pairs of image files.
    var pairs = [
      ["anneGorge.jpg", "anneNow.jpg"],
      ["esther.jpg", "pigtailEsther.jpg"]
    ];
  3. [Obj 4] Load each image.
    var pica = new Image();
    pica.src = pairs[i][0];
  4. [Obj 4] Draw the image when a card is revealed.
    ctx.drawImage(card.img, card.sx, card.sy, card.swidth, card.sheight);
  5. [Obj 7] Time the game with Date.
    starttime = new Date();
    var seconds = Math.floor(0.5 + (now - starttime) / 1000);
  6. [Obj 1, 2, 4] Add the Card constructor, deck and drawing/game functions.
    var backcolor = "rgb(128,0,128)";
    var tablecolor = "rgb(255,255,255)";
    
    function Card(sx, sy, swidth, sheight, img, info) {
      this.sx = sx; this.sy = sy; this.swidth = swidth; this.sheight = sheight;
      this.img = img; this.info = info; this.draw = drawback;
    }
    function drawback() { ctx.fillStyle = backcolor; ctx.fillRect(this.sx, this.sy, this.swidth, this.sheight); }
    function makedeck() {
      var cx = firstsx, cy = firstsy;
      for (var i = 0; i < pairs.length; i++) {
        var pica = new Image(); pica.src = pairs[i][0];
        var picb = new Image(); picb.src = pairs[i][1];
        deck.push(new Card(cx, cy, cardwidth, cardheight, pica, i));
        deck.push(new Card(cx, cy + cardheight + margin, cardwidth, cardheight, picb, i));
        cx = cx + cardwidth + margin;
        deck[deck.length - 2].draw();
        deck[deck.length - 1].draw();
      }
    }
    function shuffle() {
      for (var nt = 0; nt < 3 * deck.length; nt++) {
        var i = Math.floor(Math.random() * deck.length), k = Math.floor(Math.random() * deck.length);
        var hi = deck[i].info, him = deck[i].img;
        deck[i].info = deck[k].info; deck[i].img = deck[k].img;
        deck[k].info = hi; deck[k].img = him;
      }
    }
    function choose(ev) {
      var mx = ev.pageX, my = ev.pageY, card;
      for (var i = 0; i < deck.length; i++) {
        card = deck[i];
        if (card.sx >= 0 && mx > card.sx && mx < card.sx + card.swidth && my > card.sy && my < card.sy + card.sheight) { break; }
      }
      if (i < deck.length) {
        if (firstpick) {
          firstcard = i; firstpick = false;
          ctx.drawImage(card.img, card.sx, card.sy, card.swidth, card.sheight);
        } else {
          secondcard = i;
          ctx.drawImage(card.img, card.sx, card.sy, card.swidth, card.sheight);
          matched = (card.info == deck[firstcard].info) && (firstcard != secondcard);
          if (matched) { count++; }
          firstpick = true;
          setTimeout(flipback, 1000);
        }
      }
    }
    function flipback() {
      if (!matched) { deck[firstcard].draw(); deck[secondcard].draw(); }
      else {
        ctx.fillStyle = tablecolor;
        ctx.fillRect(deck[secondcard].sx, deck[secondcard].sy, deck[secondcard].swidth, deck[secondcard].sheight);
        ctx.fillRect(deck[firstcard].sx, deck[firstcard].sy, deck[firstcard].swidth, deck[firstcard].sheight);
        deck[secondcard].sx = -1; deck[firstcard].sx = -1;
        if (count >= deck.length / 2) {
          var seconds = Math.floor(0.5 + (Date.now() - starttime) / 1000);
          ctx.fillStyle = tablecolor; ctx.fillRect(0, 0, 900, 400);
          ctx.fillStyle = backcolor;
          ctx.fillText("You finished in " + seconds + " secs.", 10, 100);
        }
      }
    }
  7. [Obj 1, 2, 3] Add init() to start the game.
    ctx = document.getElementById("canvas").getContext("2d");
    canvas1 = document.getElementById("canvas");
    canvas1.addEventListener("click", choose, false);
    makedeck();
    shuffle();
    ctx.font = "bold 20pt sans-serif";
    starttime = Date.now();
Checkpoint: memoryPictures.html matches photos and shows the time when finished (Objectives 4, 7).

Marking rubric

CriterionPointsScore
Polygons drawn with paths and trigonometry2
Card objects and deck built correctly3
Shuffle works2
Click hit-testing and matching logic3
Flip-back delay with setTimeout2
Images loaded and timed completion2
Runs with no console errors1
Total15

Common errors

Progress and quiz scores are also tracked on the Week 5 lab page in the class website.