Learning site for website creation

カードゲームを作成する – JS –

公開日:2021年12月26日 更新日:2021年12月28日

トランプを配り相手より大きな数を出せば勝てるゲーム。

それだけでは駆け引き要素が少ないので、ギリギリの数値で勝つほうがポイントが高くなる要素を追加してます。

https://jobtech.jp/data/game01/

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>オブジェクト</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div class="Box">
    <h1 class="mainTitle">大きな数を出したほうが勝ち</h1>

    <div class="Table">
      <div class="DealerArea">
        <h2 class="subTitle">ディーラー</h2>
        <div id="dealerHand" class="DealerArea_hand"></div>
      </div>
      <div class="PlayerArea">
        <h2 class="subTitle">プレイヤー</h2>
        <div id="playerHand" class="PlayerArea_hand"></div>
      </div>
    </div>

    <div class="MasterArea">
      <div id="start" class="Start">
        <select id="cardCount" class="Start_cardCount select"></select>
        <input class="Start_btn input_btn" type="button" value="カードを配る" onclick="master.startGame()">
      </div>
      <div class="Game">
        <input id="gameBtn" class="Game_btn input_btn" type="button" value="勝負" onclick="master.gameJudge()">
        <input id="nextBtn" class="Game_btn input_btn" type="button" value="次のゲーム" onclick="master.nextGame()">
      </div>

      <div id="result" class="result"></div>

      <div class="text">
        勝ち:<span id="win" class="point">0</span> /
        負け:<span id="loose" class="point">0</span> /
        引き分け:<span id="draw" class="point">0</span>
      </div>

      <div class="text">
        プレイヤー:<span id="plyerPoint" class="point">0</span>ポイント /
        ディーラー:<span id="dealerPoint" class="point">0</span>ポイント
      </div>

      <div class="text">
        <h3 class="subTitle">ポイントルール</h3>
        <table id="rule" class="Rule"></table>
      </div>

      <div class="Card -deck">
        <div class="Card_text">残り</div>
        <div id="deckCardLength" class="Card_num">-</div>
        <div class="Card_text">枚</div>
      </div>
    </div>
  </div>
  <script src="js/script.js"></script>
</body>
</html>

JavaScript

jsフォルダ内に「script.js」で作成

class Card {
  static cardCount = 1;
  #serialNum;
  #markNum;
  #mark;
  #num;
  #gameNum;
  constructor(markNum, gameNum) {
    let markList = [
      '♠',
      '♥',
      '♦',
      '♣',
    ];
    this.#serialNum = Card.cardCount++;
    this.#markNum = markNum;
    this.#mark = markList[markNum];
    this.#num = gameNum;

    switch (gameNum) {
      case 1:
        this.#num = 'A';
        this.#gameNum = 14;
        break;
      case 11:
        this.#num = 'J';
        this.#gameNum = 11;
        break;
      case 12:
        this.#num = 'Q';
        this.#gameNum = 12;
        break;
      case 13:
        this.#num = 'K';
        this.#gameNum = 13;
        break;
      default:
        this.#gameNum = gameNum;
    }
  }

  setBgColor(color = '#fff') {
    document.getElementById('serial' + this.#serialNum).style.backgroundColor = color;
  }

  getGameNum() {
    return this.#gameNum;
  }

  getSerialNum() {
    return this.#serialNum;
  }

  getCode() {
    let code = '<div id="serial' + this.#serialNum + '" class="Card -markNum' + this.#markNum + '">\n';
    code += '<div class="Card_mark -top">' + this.#mark + '</div>\n';
    code += '<div class="Card_num">' + this.#num + '</div>\n';
    code += '<div class="Card_mark -bottom">' + this.#mark + '</div>\n';
    code += '</div>\n';
    return code;
  }
}

class Deck {
  #deck;
  constructor() {
    this.#deck = [];
    let count = 0;
    for (let i = 0; i < 4; i++) {
      for (let j = 1; j <= 13; j++) {
        this.#deck.push(new Card(i, j));
        count++;
      }
    }
  }

  shuffle() {
    for (let i = 0; i < this.#deck.length; i++) {
      let target = Math.floor(Math.random() * this.#deck.length);
      let tmp = this.#deck[i];
      this.#deck[i] = this.#deck[target];
      this.#deck[target] = tmp;
    }
    console.log(this.#deck);
  }

  getLength() {
    return this.#deck.length;
  }

  getCard() {
    let target = this.#deck.shift();
    this.displayDeckLength();
    return target;
  }

  displayDeckLength() {
    document.getElementById('deckCardLength').innerHTML = this.getLength();
  }
}

class Player {
  #hand = [];
  #discard = [];
  #point = 0;

  setHand(deck, num = 0) {
    this.#hand = [];
    for (let i = this.#hand.length; i < num; i++) {
      this.#hand.push(deck.getCard());
    }
  }

  setPoint(point = 0) {
    this.#point += point;
  }

  getHandLength() {
    return this.#hand.length;
  }

  getHandCode(bool = false) {
    let code = '<div class="Hand">\n';
    for (let i = 0; i < this.#hand.length; i++) {
      let serialNum = this.#hand[i].getSerialNum();
      if (bool === true) {
        code += '<input id="serial' + serialNum + '" class="Card_radio" type="radio" name="cardGroup" value="' + i + '">\n';
      }
      code += '<label for="serial' + serialNum + '">\n';
      code += this.#hand[i].getCode();
      code += '</label>\n';
    }
    code += '</div>\n';
    return code;
  }

  getPoint() {
    return this.#point;
  }

  selectCard(num = null) {
    let target;
    if (num === null) {
      target = this.#hand.splice(Math.floor(Math.random() * this.#hand.length), 1);
    } else {
      target = this.#hand.splice(num, 1);
    }
    this.#discard = this.#discard.concat(target);
    target[0].setBgColor('#fcc');
    return target[0].getGameNum();
  }
}

class Master {
  #gameCount = 1;
  #deck = new Deck();
  #dealer = new Player();
  #player = new Player();
  #win = 0;
  #loose = 0;
  #draw = 0;
  constructor() {
    this.#deck.shuffle();
    this.#deck.displayDeckLength();
    this.#initGame();
    Master.displayRule(3);
  }

  static displayRule(rows) {
    let count = 12;
    let code = '';
    for (let i = 0; i < rows; i++) {
      code += '<tr>';
      for (let j = 1; j <= count; j += rows) {
        code += '<th>' + (i + j) + '差で勝利</th>';
        code += '<td>' + ((count + 1) - (i + j)) + 'ポイント</td>';
      }
      code += '</tr>';
    }
    document.getElementById('rule').innerHTML = code;
  }

  #initGame() {
    if (this.#deck.getLength() > 0) {
      let code = '';
      for (let i = 1; i <= this.#deck.getLength() / 2; i++) {
        code += '<option value="' + i + '">' + i + '枚</option>\n';
      }
      document.getElementById('cardCount').innerHTML = code;
      document.getElementById('start').style.display = 'inline-block';
    } else {
      document.getElementById('start').style.display = 'none';
    }
    document.getElementById('gameBtn').style.display = 'none';
    document.getElementById('nextBtn').style.display = 'none';
  }

  startGame() {
    let cardCount = document.getElementById('cardCount').value;
    this.#dealer.setHand(this.#deck, cardCount);
    this.#player.setHand(this.#deck, cardCount);
    this.nextGame();
  }

  nextGame() {
    console.log('ゲーム回数:' + this.#gameCount);
    document.getElementById('dealerHand').innerHTML = this.#dealer.getHandCode(false);
    document.getElementById('playerHand').innerHTML = this.#player.getHandCode(true);
    document.getElementById('start').style.display = 'none';
    document.getElementById('gameBtn').style.display = 'inline-block';
    document.getElementById('nextBtn').style.display = 'none';
  }

  gameJudge() {
    let result = '';
    let resultPoint = '';
    let selectNum = null;
    let cardGroup = document.getElementsByName('cardGroup');
    for (let i = 0; i < cardGroup.length; i++) {
      if (cardGroup[i].checked) {
        selectNum = this.#player.selectCard(cardGroup[i].value);
        break;
      }
    }
    console.log('プレイヤー選択:' + selectNum);
    if (selectNum === null) {
      result = 'カードを選択してください';
    } else {
      let dealerNum = this.#dealer.selectCard();
      console.log('ディーラー選択:' + dealerNum);
      if (selectNum === dealerNum) {
        console.log('引き分け');
        result = '引き分け';
        resultPoint = 0;
        this.#draw++;
      } else if (selectNum > dealerNum) {
        console.log('勝ち');
        result = '勝ち';
        resultPoint = 13 - (selectNum - dealerNum);
        this.#win++;
        this.#player.setPoint(resultPoint);
      } else {
        console.log('負け');
        result = '負け';
        resultPoint = 13 - (dealerNum - selectNum);
        this.#loose++;
        this.#dealer.setPoint(resultPoint);
      }

      for (let i = 0; i < cardGroup.length; i++) {
        cardGroup[i].disabled = true;
      }

      document.getElementById('win').innerHTML = this.#win;
      document.getElementById('loose').innerHTML = this.#loose;
      document.getElementById('draw').innerHTML = this.#draw;
      document.getElementById('plyerPoint').innerHTML = this.#player.getPoint();
      document.getElementById('dealerPoint').innerHTML = this.#dealer.getPoint();

      if (this.#player.getHandLength() === 0) {
        this.#initGame();
      } else {
        document.getElementById('gameBtn').style.display = 'none';
        document.getElementById('nextBtn').style.display = 'inline-block';
      }
      this.#gameCount++;
    }
    document.getElementById('result').innerHTML = result + ':' + resultPoint;
  }
}

let master = new Master();

CSS

/* 基本デザイン */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
}

table {
  border: solid 1px #333;
  border-collapse: collapse;
  margin: 20px auto;
}

th {
  background-color: #fee;
  border: solid 1px #333;
  padding: 0.5em;
}

td {
  background-color: #fff;
  border: solid 1px #333;
  padding: 0.5em;
}

/* フォーム */
.input_text {
  -moz-appearance: none;
  -webkit-appearance: none;
  appearance: none;
  background: #fff;
  border: 1px solid #666;
  border-radius: 0;
  color: inherit;
  font-family: inherit;
  font-size: 1em;
  padding: 0.4em 0.8em;
}

.input_text:focus {
  border: 1px solid #4caf50;
  box-shadow: none;
  outline: none;
}

.input_btn {
  -moz-appearance: none;
  -webkit-appearance: none;
  appearance: none;
  background: #ddd;
  border: solid 2px #ccc;
  border-radius: 0;
  color: inherit;
  cursor: pointer;
  display: inline-block;
  font-size: 1em;
  padding: 0.4em 0.8em;
  text-decoration: none;
}

.input_btn:hover,
.input_btn:focus {
  background: #ccc;
  outline: none;
}

.input_btn::-moz-foucus-inner {
  border: none;
  padding: 0;
}

.select {
  -moz-appearance: none;
  -webkit-appearance: none;
  appearance: none;
  background: #fff;
  border: 1px solid #666;
  border-radius: 0;
  color: inherit;
  cursor: pointer;
  font-family: inherit;
  font-size: 1em;
  padding: 0.4em 0.8em;
}

.select::-ms-expand {
  display: none;
}

.select:focus {
  border: 1px solid #4caf50;
  box-shadow: none;
  outline: none;
}

.textarea {
  -moz-appearance: none;
  -webkit-appearance: none;
  appearance: none;
  background: #fff;
  border: 1px solid #666;
  border-radius: 0;
  color: inherit;
  font-family: inherit;
  font-size: 1em;
  height: 100px;
  padding: 0.4em 0.8em;
}

.textarea:focus {
  border: 1px solid #4caf50;
  box-shadow: none;
  outline: none;
}


/* 共通デザイン */
.btn {
  padding: 10px;
}

.result {
  font-size: 3em;
  font-weight: 700;
  margin: 20px;
}

.point {
  font-weight: 700;
  font-size: 1.5em;
  padding: 0.2em;
  color: #f00;
}

.text {
  margin: 20px;
}

/* 全体レイアウト */
.Box {
  text-align: center;
}


/* ゲームテーブル */
.DealerArea {
  background-color: #cddc39;
  padding: 10px;
}

.PlayerArea {
  background-color: #4caf50;
  padding: 10px;
}

.PlayerArea .Card {
  cursor: pointer;
}

.Hand {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
}

.Card {
  background-color: #fff;
  border: solid 3px #333;
  border-radius: 10px;
  width: 80px;
  padding: 6px;
  margin: 6px;
}

.Card.-deck {
  background-color: #144062;
  color: #fff;
  margin: 0 auto;
}

.Card.-markNum1,
.Card.-markNum2 {
  color: #f00;
}

.Card_mark.-top {
  text-align: left;
}

.Card_mark.-bottom {
  text-align: right;
}

.Card_num {
  font-size: 1.5em;
  padding: 12px 0;
  text-align: center;
}

.Card_radio {
  display: none;
}

.Card_radio:checked + label .Card {
  background-color: #fcc;
}

/* マスターエリア */
.MasterArea {
  margin: 20px;
}