【ゲーム】JavaScript:07 ポーカー対戦ゲーム

 このゲームは、プレイヤーとコンピュータがそれぞれ5枚ずつ手札を持ち、ワンペアやストレート、フラッシュなどのポーカー役を競うシンプルな対戦型カードゲームです。手札を配ってから一度だけ任意の枚数を交換し、最終手役を比較して勝敗を決定します。

遊び方と操作方法

 「🃏 ゲーム開始」ボタンを押すとカードが配られ、プレイヤーの手札が白抜きで、コンピュータの手札は裏向き(🂠)で表示されます。

  • カードをクリックすると選択状態(青背景)になり、交換可能なカードを指定できます。
  • 「♻️ カード交換」ボタンで選択したカードを山札から同数引き直せます(一度だけ)。
  • 交換後は「🔍 結果を見る」ボタンでコンピュータの手札を表向きにし、役を判定して勝敗を表示。
  • 「🔄 ゲームをリセット」でいつでも初期状態に戻せます。

ルール

  1. 5枚配られた手札から好きな枚数だけ交換可能(交換は1回限り)。
  2. 交換後、両者の手役(ロイヤルストレートフラッシュ~ハイカード)を比べ、強いほうが勝利。
  3. 同じ手役なら「引き分け」。役の強さは「ロイヤル… > ストレートフラッシュ > … > ハイカード」の順。

🎮ゲームプレイ

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

07 ポーカー対戦ゲーム

素材のダウンロード

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

poker_bg.png

ゲーム画面イメージ

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

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>🃏 ポーカー対戦ゲーム 🃏</title>
  <style>
    /* 全体スタイル */
    body {
      margin: 0;
      padding: 0;
      font-family: 'Arial', sans-serif;
      text-align: center;
      background: url('poker_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;
      text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
    }
    h1 { font-size: 2.5em; margin: 20px 0; }
    h2 { font-size: 1.8em; margin: 20px 0; }

    /* カードコンテナ */
    .cards {
      display: flex;
      justify-content: center;
      margin: 20px;
    }
    /* カードの見た目 */
    .card {
      width: 80px;
      height: 120px;
      border-radius: 10px;
      border: 2px solid #fff;
      margin: 5px;
      font-size: 20px;
      line-height: 120px;
      font-weight: bold;
      text-align: center;
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
      background-color: white;
      color: black;
      transition: transform 0.2s ease, background-color 0.2s ease;
      cursor: pointer;
    }
    .card.red { color: red; }
    .card.selected { background-color: lightblue; }
    .card.hidden {
      background-color: gray;
      color: gray;
      border: 2px dashed #fff;
      cursor: default;
    }

    /* ボタン共通 */
    button {
      padding: 10px 20px;
      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 ease, transform 0.2s ease;
      margin: 0 5px;
    }
    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;
    }
    /* リセットボタン横並び */
    #resetButton {
      display: none;
      margin-left: 10px;
    }

    /* 結果表示 */
    #result {
      font-size: 24px;
      margin-top: 20px;
      padding: 10px 20px;
      background: rgba(0, 0, 0, 0.6);
      display: inline-block;
      border-radius: 5px;
      text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
      animation: fadeIn 1s ease;
    }
    @keyframes fadeIn {
      from { opacity: 0; }
      to { opacity: 1; }
    }
  </style>
</head>
<body>
  <!-- タイトル -->
  <h1>🃏 ポーカー対戦ゲーム 🃏</h1>

  <!-- プレイヤー手札 -->
  <div>
    <h2>🧑 プレイヤーの手札 🧑</h2>
    <div class="cards" id="playerCards"></div>
  </div>

  <!-- コンピュータ手札 -->
  <div>
    <h2>💻 コンピューターの手札 💻</h2>
    <div class="cards" id="computerCards"></div>
  </div>

  <!-- 操作用ボタン -->
  <div>
    <button id="dealButton" onclick="playGame()">🃏 ゲーム開始</button>
    <button id="exchangeButton" onclick="exchangeCards()" disabled>♻️ カード交換</button>
    <button id="resultButton" onclick="finalResult()" disabled>🔍 結果を見る</button>
    <button id="resetButton" onclick="resetGame()">🔄 ゲームをリセット</button>
  </div>

  <!-- 結果表示領域 -->
  <p id="result"></p>

  <script>
    const suits = ['♠','♥','♦','♣'];
    const values = ['2','3','4','5','6','7','8','9','10','J','Q','K','A'];
    const handRanks = {
      'ロイヤルストレートフラッシュ':10,'ストレートフラッシュ':9,'フォーカード':8,
      'フルハウス':7,'フラッシュ':6,'ストレート':5,'スリーカード':4,
      'ツーペア':3,'ワンペア':2,'ハイカード':1
    };

    let deck = [], playerHand = [], computerHand = [];
    let exchangeAllowed = true;

    function getDeck(){
      const d = [];
      for(const s of suits) for(const v of values) d.push({value:v,suit:s});
      return 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 dealCards(d){ return d.splice(0,5); }
    function displayCards(hand, id, selectable=false, hidden=false){
      const c = document.getElementById(id);
      c.innerHTML = '';
      hand.forEach((card,i) => {
        const div=document.createElement('div');
        div.className='card';
        div.textContent=hidden?'🂠':`${card.value}${card.suit}`;
        if(card.suit==='♥'||card.suit==='♦') div.classList.add('red');
        if(hidden) div.classList.add('hidden');
        if(selectable) div.onclick=() => div.classList.toggle('selected');
        c.appendChild(div);
      });
    }

    function playGame(){
      deck = shuffle(getDeck());
      playerHand = dealCards(deck);
      computerHand = dealCards(deck);
      displayCards(playerHand,'playerCards',true,false);
      displayCards(computerHand,'computerCards',false,true);
      document.getElementById('dealButton').disabled = true;
      document.getElementById('exchangeButton').disabled = false;
      document.getElementById('resultButton').disabled = false;
      document.getElementById('resetButton').style.display = 'none';
      document.getElementById('result').textContent = '';
      exchangeAllowed = true;
    }

    function exchangeCards(){
      if(!exchangeAllowed) return;
      const selected = [];
      document.querySelectorAll('#playerCards .card').forEach((div,i)=>{
        if(div.classList.contains('selected')) selected.push(i);
      });
      selected.forEach(i => playerHand[i] = deck.pop());
      displayCards(playerHand,'playerCards',false,false);
      document.getElementById('exchangeButton').disabled = true;
      exchangeAllowed = false;
    }

    function getHandRank(hand){
      const suitsInHand = hand.map(c=>c.suit);
      const vals = hand.map(c=>c.value);
      const counts = {};
      vals.forEach(v=>counts[v]=(counts[v]||0)+1);
      const countVals = Object.values(counts).sort((a,b)=>b-a);
      const idxs = [...new Set(vals)].map(v=>values.indexOf(v)).sort((a,b)=>a-b);
      const isFlush = suitsInHand.every(s=>s===suitsInHand[0]);
      const isStraight = idxs.length===5 && idxs[4]-idxs[0]===4;
      const isRoyal = isStraight && idxs[0]===values.indexOf('10');
      if(isFlush&&isRoyal) return 'ロイヤルストレートフラッシュ';
      if(isFlush&&isStraight) return 'ストレートフラッシュ';
      if(countVals[0]===4) return 'フォーカード';
      if(countVals[0]===3&&countVals[1]===2) return 'フルハウス';
      if(isFlush) return 'フラッシュ';
      if(isStraight) return 'ストレート';
      if(countVals[0]===3) return 'スリーカード';
      if(countVals.filter(c=>c===2).length===2) return 'ツーペア';
      if(countVals[0]===2) return 'ワンペア';
      return 'ハイカード';
    }

    function finalResult(){
      const pR = getHandRank(playerHand);
      const cR = getHandRank(computerHand);
      displayCards(computerHand,'computerCards',false,false);
      const resElm = document.getElementById('result');
      resElm.innerHTML = `🧑 プレイヤー:<strong>${pR}</strong> vs 💻 コンピューター:<strong>${cR}</strong><br>`;
      const pS = handRanks[pR], cS = handRanks[cR];
      if(pS>cS) {
        resElm.innerHTML += '<span style="color:lightgreen;font-size:1.5em;">🎉 プレイヤーの勝ち! 🎉</span>';
      } else if(pS<cS) {
        resElm.innerHTML += '<span style="color:red;font-size:1.5em;">💔 コンピューターの勝ち! 💔</span>';
      } else {
        resElm.innerHTML += '<span style="color:yellow;font-size:1.5em;">🤝 引き分け! 🤝</span>';
      }
      document.getElementById('dealButton').disabled = true;
      document.getElementById('exchangeButton').disabled = true;
      document.getElementById('resultButton').disabled = true;
      document.getElementById('resetButton').style.display = 'inline-block';
    }

    function resetGame(){
      document.getElementById('dealButton').disabled = false;
      document.getElementById('exchangeButton').disabled = true;
      document.getElementById('resultButton').disabled = true;
      document.getElementById('resetButton').style.display = 'none';
      document.getElementById('playerCards').innerHTML = '';
      document.getElementById('computerCards').innerHTML = '';
      document.getElementById('result').textContent = '';
    }
  </script>
</body>
</html>

プログラムのアルゴリズムの流れ

ステップ主な関数・命令処理内容
1. デッキ生成・シャッフルgetDeck()shuffle()52枚のトランプを配列化し、Fisher–Yatesでシャッフル
2. ゲーム開始playGame()デッキから5枚ずつ配り(dealCards())、手札を描画(displayCards())
3. カード交換exchangeCards()選択済みカードを検出し、山札から同数を引いて更新
4. 役判定getHandRank()出現回数・絵柄・連番を判定し、役名(ストレートなど)を返す。
5. 結果表示finalResult()コンピュータ手札を開示&再描画、両者の役を比較して勝敗を表示
6. リセットresetGame()UIを初期化し、再度ゲーム開始可能な状態に戻す。
  • splice で配列からカードを取り出し、
  • pop で山札から末尾のカードを1枚ずつ引き、
  • querySelectorAlldocument.createElement でカード要素を動的に生成し、
  • classList.toggle / add / contains で選択状態や色付けを制御しています。

関数の詳細

関数名機能
getDeck()4種の絵柄 × 13種の数値を組み合わせ、トランプ52枚のオブジェクト配列を生成
shuffle(d)Fisher–Yatesアルゴリズムで配列をランダムに並び替え
dealCards(d)デッキの先頭5枚を手札として取り出し、デッキから削除
displayCards()指定された要素IDに、手札または裏向きカードを .card 要素として描画
playGame()ゲーム開始時の一連の処理:デッキ生成・シャッフル・配札・UIボタン状態切替
exchangeCards()プレイヤーが選択したカードを山札から補充し、手札を再描画
getHandRank()手札を分析し、ペア数・連続性・フラッシュ判定の結果から最適な役名を返却
finalResult()コンピュータ手札を表向きにし、手役を比較して勝敗/引き分けを結果欄に表示
resetGame()手札表示と結果欄、ボタン状態を初期化して再度プレイ可能な状態に戻す

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

  • 交換回数制限のカスタマイズ:交換を1回から最大2回に増やす、あるいは交換回数をベット制にする。
  • ベット&チップ機能:ポーカーらしく賭け金・チップを導入し、勝敗でチップの増減を行う。
  • 複数プレイヤー対応:観戦モードや3~4人対戦モードに拡張。
  • AIの強化:コンピュータの交換ロジックを、確率計算や期待値評価に基づく戦略的判断にアップグレード。
  • ブラウザ間対戦:WebSocket を使ったリアルタイムオンライン対戦対応。
  • ビジュアル演出・効果音:カードめくりアニメ、勝敗サウンド、背景エフェクトで臨場感アップ。
  • モバイル最適化:タッチ操作でカード選択・交換、レスポンシブデザイン対応。

これらを組み合わせて、より本格的なポーカーゲーム体験を実現してみてください!