【ゲーム】JavaScript:10 四目並べ

 「四目並べ(よんもくなべ)」ゲームは、英語で「Connect Four(コネクト・フォー)」と呼ばれる、2人用のボードゲームです。7列×6行の縦長グリッドに自分の色の円盤(赤:あなた、黄:コンピュータ)を交互に落としていき、縦・横・斜めいずれかに自分の色を4つ連続させると勝利となるクラシックなパズルゲームです。

 ドラッグではなく、画面上の列をクリックするだけで円盤が最下段または既存の円盤の直上に落ちます。まず「🤖 コンピュータの強さ」プルダウンで弱い/普通/強いのいずれかを選び、あなた(🔴 赤)が最初に列をクリック。続いて自動でコンピュータ(🟡 黄)が置きます。いずれかが4連を作るか、盤面が埋まって引き分けになるまで続きます。「🔄 リセット」を押せばいつでも初期状態に戻せます。

🔷 ゲームの概要

項目内容
名前四目並べ(Connect Four)
プレイ人数2人(通常は交互に手番)
目的自分の色のコマを縦・横・斜めいずれかに4つ連続で並べること
使用する道具縦6行 × 横7列 の格子状のボードと、赤・黄などの2色のディスク型のコマ

🕹️ 遊び方

  1. ボードは最初は空っぽの状態です。
  2. プレイヤーは交互に自分のコマ(赤または黄)を上からボードに落とします
  3. コマは、下にすでにあるコマの上に積み上がるように配置されます(重力のような動き)。
  4. 先に自分のコマを4つ連続で並べたプレイヤーが勝利します。

🧩 勝利のパターン

パターン
横並び🔴🔴🔴🔴
縦並び🔴
🔴
🔴
🔴
斜め🔴
 🔴
  🔴
   🔴

⚠️ 注意点

  • 落とす場所を間違えると、相手にチャンスを与えてしまうことがあります。
  • 先に中央列を制圧すると、有利になる戦術もあります。

🎮ゲームプレイ

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

10 四目並べ

素材のダウンロード

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

connect-four_bg.png

ゲーム画面イメージ

プログラム全文(connect-four.html)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>🟡🔴 四目並べゲーム 🔴🟡</title>
  <style>
    /* 共通設定 */
    body {
      margin: 0;
      padding: 0;
      font-family: Arial, sans-serif;
      background: url('connect-four_bg.png') no-repeat center center fixed;
      background-size: cover;
      text-align: center;
    }

    /* 設定メニュー(不透明) */
    #settings {
      margin: 20px 0;
      font-size: 20px;
      color: #fff;
      text-shadow: 1px 1px 3px #000;
      background: #000;           /* 完全不透明の黒 */
      display: inline-block;
      padding: 8px 12px;
      border-radius: 8px;
    }
    #settings label {
      margin-right: 8px;
    }
    #settings select {
      font-size: 18px;
      padding: 6px 10px;
      border-radius: 4px;
      border: none;
    }

    /* 見出し */
    h1 {
      margin: 20px 0 10px;
      font-size: 2.5rem;
      color: #ffeb3b;
      text-shadow: 2px 2px 4px #000;
    }

    /* ボード部分だけに幅・高さと半透明背景を設定 */
    #board {
      width: 450px;                /* 7列×60px + 6間隔×5px = 450px */
      height: 385px;               /* 6行×60px + 5間隔×5px = 385px */
      display: grid;
      grid-template-columns: repeat(7, 60px);
      grid-template-rows: repeat(6, 60px);
      gap: 5px;
      margin: 0 auto;
      background: rgba(0, 0, 0, 0.6);  /* 半透明の黒背景 */
      border-radius: 12px;
    }

    /* 各セル */
    .cell {
      width: 60px;
      height: 60px;
      background-color: #004d40;
      border-radius: 10%;
      position: relative;
      cursor: pointer;
      box-shadow: inset 0 0 5px #000;
    }

    /* 円盤 */
    .disc {
      width: 50px;
      height: 50px;
      border-radius: 50%;
      position: absolute;
      top: 5px;
      left: 5px;
      border: 2px solid #333;
      box-shadow: 0 2px 4px rgba(0,0,0,0.5);
    }
    .red { background-color: #e53935; }
    .yellow { background-color: #fdd835; }

    /* メッセージエリア(不透明) */
    #message {
      margin: 20px 0;
      font-size: 20px;
      display: inline-block;
      background: #fff;           /* 完全不透明の白 */
      padding: 10px 20px;
      border-radius: 8px;
      color: #333;
    }
  </style>
</head>
<body>

  <h1>🟡🔴 四目並べゲーム 🔴🟡</h1>

  <!-- コンピュータ強さ選択 -->
  <div id="settings">
    <label for="difficulty">🤖 コンピュータの強さ:</label>
    <select id="difficulty">
      <option value="easy">弱い</option>
      <option value="medium" selected>普通</option>
      <option value="hard">強い</option>
    </select>
  </div>

  <!-- ゲーム盤表示 -->
  <div id="board"></div>

  <!-- ゲーム状況メッセージ -->
  <div id="message">🔴 あなたの番です</div>

  <script>
    const ROWS = 6, COLS = 7;
    const board = document.getElementById("board");
    const message = document.getElementById("message");
    const grid = Array.from({ length: ROWS }, () => Array(COLS).fill(null));
    let currentPlayer = 'R', gameOver = false;

    // 盤面のセルを生成
    function createBoard() {
      board.innerHTML = '';
      for (let r = 0; r < ROWS; r++) {
        for (let c = 0; c < COLS; c++) {
          const cell = document.createElement("div");
          cell.classList.add("cell");
          cell.dataset.row = r;
          cell.dataset.col = c;
          board.appendChild(cell);
        }
      }
    }

    // 指定列の最下空き行を返す
    function findEmptyRow(c) {
      for (let r = ROWS - 1; r >= 0; r--) {
        if (grid[r][c] === null) return r;
      }
      return -1;
    }

    // 4つ並んだかチェック
    function checkWin(r, c, p) {
      const dirs = [[0,1],[1,0],[1,1],[1,-1]];
      for (let [dr, dc] of dirs) {
        let cnt = 1;
        for (let i = 1; i < 4; i++) {
          const rr = r + dr*i, cc = c + dc*i;
          if (rr<0||rr>=ROWS||cc<0||cc>=COLS||grid[rr][cc]!==p) break;
          cnt++;
        }
        for (let i = 1; i < 4; i++) {
          const rr = r - dr*i, cc = c - dc*i;
          if (rr<0||rr>=ROWS||cc<0||cc>=COLS||grid[rr][cc]!==p) break;
          cnt++;
        }
        if (cnt >= 4) return true;
      }
      return false;
    }

    // 円盤を描画
    function drawDisc(r, c, p) {
      const disc = document.createElement("div");
      disc.classList.add("disc", p==='R'?'red':'yellow');
      board.children[r * COLS + c].appendChild(disc);
    }

    // メッセージ更新
    function updateMessage() {
      if (gameOver) return;
      message.textContent = currentPlayer==='R'
        ? '🔴 あなたの番です'
        : '🟡 コンピュータの番です';
    }

    // CPUの列選択
    function getCpuColumn(diff) {
      const avail = [];
      for (let c = 0; c < COLS; c++)
        if (!grid[0][c]) avail.push(c);
      if (!avail.length) return null;
      if (diff==='easy')
        return avail[Math.floor(Math.random()*avail.length)];
      // 自分の勝ち手を探す
      for (let c of avail) {
        let r = findEmptyRow(c);
        grid[r][c]='Y';
        if (checkWin(r,c,'Y')) { grid[r][c]=null; return c; }
        grid[r][c]=null;
      }
      // 相手の勝ち手阻止
      for (let c of avail) {
        let r = findEmptyRow(c);
        grid[r][c]='R';
        if (checkWin(r,c,'R')) { grid[r][c]=null; return c; }
        grid[r][c]=null;
      }
      if (diff==='hard') {
        for (let c of [3,2,4,1,5,0,6])
          if (avail.includes(c)) return c;
      }
      return avail[Math.floor(Math.random()*avail.length)];
    }

    // CPUのターン
    function cpuTurn() {
      if (gameOver) return;
      const col = getCpuColumn(document.getElementById('difficulty').value);
      if (col===null) return;
      const row = findEmptyRow(col);
      grid[row][col] = currentPlayer;
      drawDisc(row, col, currentPlayer);
      if (checkWin(row,col,currentPlayer)) { endGame(currentPlayer); return; }
      if (grid.flat().every(x=>x!==null)) { endGame(null); return; }
      currentPlayer='R'; updateMessage();
    }

    // ゲーム終了処理
    function endGame(w) {
      gameOver = true;
      message.textContent = w==='R'
        ? '👏 🔴 あなたの勝ち!'
        : w==='Y'
          ? '💻 🟡 コンピュータの勝ち!'
          : '🤝 引き分けです!';
    }

    // クリック時処理(あなたのターン)
    function handleClick(e) {
      if (currentPlayer!=='R'||gameOver) return;
      const c = +e.target.dataset.col;
      if (isNaN(c)) return;
      const r = findEmptyRow(c);
      if (r<0) return;
      grid[r][c] = currentPlayer;
      drawDisc(r, c, currentPlayer);
      if (checkWin(r,c,currentPlayer)) { endGame(currentPlayer); return; }
      if (grid.flat().every(x=>x!==null)) { endGame(null); return; }
      currentPlayer='Y'; updateMessage();
      setTimeout(cpuTurn, 600);
    }

    // 初期化
    createBoard();
    updateMessage();
    board.addEventListener("click", handleClick);
  </script>

</body>
</html>

アルゴリズムの流れ

ステップ関数/命令内容
ボード生成createBoard()7×6 の .cell を動的に生成し、data-rowdata-col を設定
落下位置検出findEmptyRow(col)指定列の最下空き行を探して返却
円盤描画drawDisc(r,c,player).disc 要素を生成し、赤または黄のクラスを付与してセルに追加
勝利判定checkWin(r,c,player)4方向(→↓↘↗)それぞれ両方向を走査し、連続する同色カウントが4以上か判定
メッセージ更新updateMessage()現ターンに応じて「あなたの番」「コンピュータの番」を表示
CPU手番cpuTurn()難易度別に勝利手/阻止手/中央優先/ランダムの順で列を選び、自分の円盤を置く
ゲーム終了endGame(winner)gameOver フラグ立て&勝敗または引き分けメッセージを表示
クリック処理handleClick(event)あなたのターンのみセルクリックを受け付け、円盤配置→判定→CPUターン呼び出し

関数の詳細

関数名役割
createBoard()HTML 上に 7 列×6 行のセルを描画し、クリックリスナを設定
findEmptyRow(c)指定列 c の最下空き行 r を返し、空きがなければ −1 を返却
drawDisc(r,c,p)セル(r,c)に `<div class="disc red
checkWin(r,c,p)最新配置位置の周囲を 4 方向で探索し、4 連が完成していれば true を返す
getCpuColumn(diff)難易度に応じ、優先的に勝利手→阻止手→中央優先→ランダムで CPU の列を選択
cpuTurn()getCpuColumn() で列を決定し、findEmptyRowdrawDisc → 勝敗判定を実行
endGame(w)gameOver=true にし、message.textContent に勝者または引き分けを表示
handleClick(e)プレイヤークリックをハンドリング。盤更新→判定→CPUターン呼び出し

改造のポイント

  • AI強化(ミニマックス法)getCpuColumn をミニマックスに置き換えて最善手を常に選択する本格AIに。
  • 連鎖ボーナス:盤面いっぱいに複数同時消去(使えれば)へのボーナス得点機能を追加。
  • ビジュアル演出:落下アニメーション、勝利時のラインハイライト、サウンドエフェクトを実装。
  • ネット対戦:WebSocket や Firebase RealtimeDB を使い、遠隔地プレイヤー同士でリアルタイム対戦。
  • 多人数拡張:8×7 といった大盤面や3人対戦モード、手番順ランダム化などバリエーション追加。
  • モバイル最適化:タッチ操作で列をタップ、レスポンシブレイアウト対応。

 この基本実装をベースに、ぜひ自分ならではの機能や演出を盛り込んで、もっと面白い「四目並べ」を作り込んでみてください!