JavaScript»ゲーム制作»clickイベント トランプをめくる(横一列)

ファイル名: js-game/quest_00728.html

下のプログラムをうつして、横一列よこいちれつ表示ひょうじされたトランプカードのうちクリックされたカードがおもてを向くようにしてください。画像がぞうは下のリンクからダウンロードできます。

画像をダウンロード

ソースコード

class Card {

  constructor(image, num, mark) {
    this.image = image;
    this.num = num;
    this.mark = mark;
    this.x = 0;
    this.y = 0;
    this.opened = false; // 最初は裏向き
  }

  // このカードの表示位置(左上頂点)を指定したx, yに設定する
  setLocation(x, y) {
    this.x = x;
    this.y = y;
  }

  // x, yで指定された点がこのカードの表示領域内であれば、カードを開いた状態にする
  openIfContains(x, y) {
    if (x > this.x && x < this.x + 32 && y > this.y && y < this.y + 64) {
      this.opened = true;
    }
  }

  update() {
    // 特に自動的に変化するデータはない
  }

  render(context) {
    const trimX = this.opened ? this.num * 32 : 448;
    const trimY = this.opened ? this.mark * 64 : 128;
    context.drawImage(this.image, trimX, trimY, 32, 64, this.x, this.y, 32, 64);
  }
}

// 52枚のCardオブジェクトがはいったデッキを表すクラス
class CardDeck {

  constructor(image) {
    this.cards = [];
    for (let num = 0; num < 13; num++) {
      for (let mark = 0; mark < 4; mark++) {
        this.cards.push(new Card(image, num, mark));
      }
    }
  }

  // デッキ内のCardをシャッフルする
  shuffle() {
    for (let i = this.cards.length - 1; i > 0; i--) {
      const r = Math.floor(Math.random() * (i + 1));
      const tmp = this.cards[i];
      this.cards[i] = this.cards[r];
      this.cards[r] = tmp;
    }
  }

  // カードデッキから、nで指定した枚数のCardオブジェクトを配列で取り出す
  pop(n) {
    const cards = [];
    for (let i = 0; i < n; i++) {
      cards.push(this.cards.pop());
    }
    return cards;
  }
}

const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");

const image = new Image();
image.src = "cards.png";
image.addEventListener("load", () => {

  const deck = new CardDeck(image);
  deck.shuffle();
  const cards = deck.pop(5);

  cards.forEach((card, i) => {
    card.setLocation(i * 48 + 48, 128);
  });

  canvas.addEventListener("click", (e) => {
    const rect = e.target.getBoundingClientRect();
    const x = e.clientX - rect.left - 1;
    const y = e.clientY - rect.top - 1;
    cards.forEach(card => card.openIfContains(x, y));
  });

  const FPS = 20;
  const frameTime = 1 / FPS;
  let prevTimestamp = 0;

  const update = (timestamp) => {

    const elapsed = (timestamp - prevTimestamp) / 1000;
    if (elapsed <= frameTime) {
      requestAnimationFrame(update);
      return;
    }

    prevTimestamp = timestamp;

    cards.forEach(card => card.update());
    context.clearRect(0, 0, 320, 320);
    cards.forEach(card => card.render(context));
    requestAnimationFrame(update);
  };

  update();
});

解説

カード1枚1枚をあらわすCardクラスと、カードをひとまとめにしたデッキをあらわすCardDeckの2つを使います。

Cardクラスは「トランプをめくる(一枚)」と似ていますが、表示位置を設定するsetLocationメソッドが追加され、containsメソッドとopenメソッドをまとめてopenIfContainsメソッドになりました。

CardDeckクラスはCardクラスから作ったりオブジェクトを配列cardsに入れます。配列cardsの各要素にはCardクラスのプロパティとメソッドが引きつがれています。

ページが読み込まれたとき、新しくデッキを作り、シャッフルし、カードを5枚取り出し、並べます。(73~81行目)

クリックしたとき、クリックされた点のキャンバス上の座標を計算し、対応するカードのopenedプロパティが表状態になります。(83~88行目)

20フレームごとにキャンバスの画像は更新され、今の状態が反映されます。(90~110行目)