【ゲーム】JavaScript:11 ブラックジャック

 「🃏 ブラックジャック 🃏 」は、プレイヤー(あなた)とディーラー(コンピュータ)がそれぞれ最初に2枚ずつカードを受け取り、合計点が21を超えないように“ヒット(追加でカードを引く)”や“スタンド(ここで手を止める)”を選択して競うカードゲームです。目標は、ディーラーを上回る合計点(ただし21以下)を作ること。

遊び方と操作方法

  1. 「🎲 ゲーム開始」を押すと、プレイヤー/ディーラーに2枚ずつ配牌され、ディーラーの1枚目は裏向きで表示されます。
  2. 「💥 ヒット」を押すとプレイヤーは山札から1枚カードを引きます。合計が21を超えればバスト(敗北)となります。
  3. 「✋ スタンド」を押すとプレイヤーのターン終了。ディーラーは合計17点未満なら自動でカードを引き続け、17以上で止まります。
  4. 両者が止まったら点数を比較し、21を超えなかったほうが勝利。引き分けもあります。
  5. 最後に「🔄 リセット」で初期状態に戻り、何度でもプレイ可能です。

ルール

  • 数字カードは数字通り、J/Q/Kは10点、Aは1点または11点として自動判定。
  • プレイヤーが21を超えた時点でバスト(即敗北)。
  • スタンド後のディーラーは合計17点以上になるまでヒット。
  • 両者とも21以下なら点数の高いほうが勝利、同点は引き分け。

🎮ゲームプレイ

以下のリンク先から実際にプレイできます。

11 ブラックジャック

素材のダウンロード

以下のリンクから使用する素材をダウンロードできます。

blackjack_bg.png

ゲーム画面イメージ

プログラム全文(blackjack.html)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>🃏 ブラックジャック 🃏</title>
  <style>
    /* --- 全体スタイル --- */
    body {
      margin: 0;
      padding: 0;
      font-family: 'Arial', sans-serif;
      text-align: center;
      /* 背景画像を blackjack_bg.png に変更 */
      background: url('blackjack_bg.png') no-repeat center center fixed;
      background-size: cover;
      color: white;
    }
    /* タイトル・見出し */
    h1, h2 {
      display: inline-block;
      background: rgba(0,0,0,0.6);
      padding: 10px 20px;
      border-radius: 8px;
      margin: 20px 0;
      text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
    }
    h1 { font-size: 2.5em; }
    h2 { font-size: 1.8em; }

    /* カード表示コンテナ */
    .cards {
      display: flex;
      justify-content: center;
      margin: 10px;
    }
    /* カード1枚分 */
    .card {
      width: 80px;
      height: 120px;
      margin: 5px;
      border-radius: 10px;
      background-color: white;
      color: black;
      font-size: 20px;
      line-height: 120px;
      font-weight: bold;
      text-align: center;
      box-shadow: 0 4px 8px rgba(0,0,0,0.3);
    }
    .card.red { color: red; }
    .card.hidden {
      background-color: gray;
      color: gray;
      border: 2px dashed #fff;
    }

    /* ボタン */
    button {
      padding: 10px 20px;
      margin: 5px;
      font-size: 16px;
      font-weight: bold;
      color: white;
      background-color: #ff5733;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      box-shadow: 0 4px 6px rgba(0,0,0,0.3);
      transition: background-color 0.2s, transform 0.2s;
    }
    button:hover:not(:disabled) {
      background-color: #ff2e00;
      transform: scale(1.1);
    }
    button:disabled {
      background-color: grey !important;
      cursor: not-allowed;
      opacity: 0.6;
      transform: none !important;
    }

    /* 結果表示 */
    #message {
      display: inline-block;
      margin: 20px;
      padding: 10px 20px;
      background: rgba(0,0,0,0.6);
      border-radius: 5px;
      font-size: 1.2em;
      text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
      min-width: 200px;
    }
  </style>
</head>
<body>
  <!-- タイトル -->
  <h1>🃏 ブラックジャック 🃏</h1>

  <!-- プレイヤー -->
  <div>
    <h2>🧑 プレイヤー</h2>
    <div class="cards" id="playerCards"></div>
    <p id="playerScore"></p>
  </div>

  <!-- ディーラー -->
  <div>
    <h2>💼 ディーラー</h2>
    <div class="cards" id="dealerCards"></div>
    <p id="dealerScore"></p>
  </div>

  <!-- 操作用ボタン -->
  <div>
    <button id="dealBtn"    onclick="deal()">🎲 ゲーム開始</button>
    <button id="hitBtn"     onclick="hit()" disabled>💥 ヒット</button>
    <button id="standBtn"   onclick="stand()" disabled>✋ スタンド</button>
    <button id="resetBtn"   onclick="reset()" disabled>🔄 リセット</button>
  </div>

  <!-- メッセージ表示 -->
  <div id="message">ゲーム開始をクリックしてください</div>

  <script>
    // --- 定数・変数 ---
    const suits  = ['♠','♥','♦','♣'];
    const values = ['2','3','4','5','6','7','8','9','10','J','Q','K','A'];
    let deck = [], playerHand = [], dealerHand = [];

    // --- デッキ生成 & シャッフル ---
    function createDeck() {
      const d = [];
      for (let s of suits) {
        for (let v of values) {
          d.push({suit: s, value: v});
        }
      }
      return shuffle(d);
    }
    function shuffle(d) {
      for (let i = d.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [d[i], d[j]] = [d[j], d[i]];
      }
      return d;
    }

    // --- カード表示 ---
    function displayHand(hand, containerId, hideFirst=false) {
      const container = document.getElementById(containerId);
      container.innerHTML = '';
      hand.forEach((card, idx) => {
        const div = document.createElement('div');
        div.className = 'card';
        if (hideFirst && idx === 0) {
          div.classList.add('hidden');
          div.textContent = '🂠';
        } else {
          div.textContent = `${card.value}${card.suit}`;
          if (card.suit === '♥' || card.suit === '♦') {
            div.classList.add('red');
          }
        }
        container.appendChild(div);
      });
      document.getElementById(containerId.replace('Cards','Score'))
        .textContent = hideFirst
          ? '?'
          : `合計:${calculateValue(hand)}`;
    }

    // --- 合計点計算 (Aは1または11) ---
    function calculateValue(hand) {
      let sum = 0, aces = 0;
      hand.forEach(c => {
        if (c.value === 'A') { sum += 11; aces += 1; }
        else if (['K','Q','J'].includes(c.value)) sum += 10;
        else sum += parseInt(c.value);
      });
      while (sum > 21 && aces > 0) {
        sum -= 10;
        aces--;
      }
      return sum;
    }

    // --- ゲーム開始 ---
    function deal() {
      deck = createDeck();
      playerHand = [deck.pop(), deck.pop()];
      dealerHand = [deck.pop(), deck.pop()];
      displayHand(playerHand, 'playerCards');
      displayHand(dealerHand, 'dealerCards', true);
      document.getElementById('dealBtn').disabled  = true;
      document.getElementById('hitBtn').disabled   = false;
      document.getElementById('standBtn').disabled = false;
      document.getElementById('message').textContent = 'ヒットかスタンドを選択';
    }

    // --- ヒット ---
    function hit() {
      playerHand.push(deck.pop());
      displayHand(playerHand, 'playerCards');
      if (calculateValue(playerHand) > 21) {
        endGame();
      }
    }

    // --- スタンド ---
    function stand() {
      document.getElementById('hitBtn').disabled   = true;
      document.getElementById('standBtn').disabled = true;
      while (calculateValue(dealerHand) < 17) {
        dealerHand.push(deck.pop());
      }
      endGame();
    }

    // --- ゲーム終了判定 & 表示 ---
    function endGame() {
      displayHand(dealerHand, 'dealerCards', false);
      const pTotal = calculateValue(playerHand);
      const dTotal = calculateValue(dealerHand);
      let result = '';
      if (pTotal > 21) result = '💔 プレイヤーバスト!あなたの負け';
      else if (dTotal > 21) result = '🎉 ディーラーバスト!あなたの勝ち';
      else if (pTotal > dTotal) result = '🎉 あなたの勝ち!';
      else if (pTotal < dTotal) result = '💔 あなたの負け…';
      else result = '🤝 引き分け!';
      document.getElementById('message').textContent = result;
      document.getElementById('hitBtn').disabled   = true;
      document.getElementById('standBtn').disabled = true;
      document.getElementById('resetBtn').disabled = false;
    }

    // --- リセット ---
    function reset() {
      playerHand = [];
      dealerHand = [];
      document.getElementById('playerCards').innerHTML = '';
      document.getElementById('dealerCards').innerHTML = '';
      document.getElementById('playerScore').textContent = '';
      document.getElementById('dealerScore').textContent = '';
      document.getElementById('dealBtn').disabled  = false;
      document.getElementById('hitBtn').disabled   = true;
      document.getElementById('standBtn').disabled = true;
      document.getElementById('resetBtn').disabled = true;
      document.getElementById('message').textContent = 'ゲーム開始をクリックしてください';
    }
  </script>
</body>
</html>

アルゴリズムの流れ

ステップ関数/命令内容
デッキ生成・シャッフルcreateDeck()shuffle()52 枚のカードを生成し、Fisher–Yates アルゴリズムでシャッフル
配牌deal()プレイヤー・ディーラーにそれぞれ2枚ずつ配り、UI を更新
合計点算出calculateValue(hand)A を 11 または 1 として最適変換し、合計点を返却
手札表示displayHand()カードを <div> 要素として描画し、ディーラーの1枚目は裏向き表示
プレイヤーヒットhit()プレイヤーの手札に1枚追加、バスト判定
プレイヤースタンドstand()ディーラーが 17 未満なら自動ヒット、終了判定
終了判定・結果表示endGame()両者の合計を比較、勝敗メッセージを表示
リセットreset()手札・UI を初期状態に戻し、再度プレイ可能に

関数の詳細

関数名機能
createDeck()4つの絵柄 × 13 の値を組み合わせて 52 枚のデッキ配列を生成
shuffle(d)Fisher–Yates で配列 d をランダムに並び替え。
displayHand(hand,id,hideFirst)手札をカード要素として描画。hideFirst=true でディーラー1枚目を裏向きに
calculateValue(hand)J/Q/K→10、A→11→1 と最適に調整しながら点数を計算
deal()デッキ生成→配牌→UI 更新→ボタン有効化
hit()プレイヤーが山札から1枚追加→UI 更新→バスト判定
stand()ヒット/スタンド操作を無効化→ディーラーロジック(17 未満でヒット)→終了判定
endGame()ディーラーの裏向きカードを表示→合計比較→メッセージ表示→操作ボタン状態更新
reset()すべての手札・スコア・メッセージをクリア→ボタン状態を初期化

このゲームの改造のポイント

  • ベット&チップ機能:プレイヤーがチップを賭け、勝敗でチップの増減を管理。
  • ブラックジャック判定:「A+10 点コンビ」時の自動ブラックジャック判定と報酬倍率を追加。
  • 複数デッキ対応:本場同様に複数デッキ(6 デッキなど)を扱うロジックを導入。
  • サイドベット:ペア/赤黒/スート一致などへのオプションベットを実装。
  • AI 強化:ディーラーではなく、基礎戦略表に基づく選択(ヒット/スタンド/ダブルダウン/スプリット)を導入。
  • UI 演出:カードめくりアニメ、チップアニメーション、効果音、勝利エフェクトなどを追加。
  • オンライン対戦:WebSocket を使った複数人同時対戦やリモート対戦モード。

 ぜひこの基本実装をベースに、自分好みのルールや演出を盛り込んで、より本格的なブラックジャック体験を作り込んでみてください!