【ゲーム】JavaScript:40 バトルシップ

 「バトルシップ」は、5×5の海戦図(チェス盤風に番号付き)で、プレイヤーとCPUが三種類の艦(戦艦・駆逐艦・潜水艦)を隠し合い、互いの艦隊を攻撃・移動で撃沈し合う戦略型ターン制ゲームです。

  • 盤面にはA~E(列)、1~5(行)のラベル付き
  • 各自、戦艦(🚢:HP3)、駆逐艦(⛴️:HP2)、潜水艦(🤿:HP1)を1隻ずつ配置
  • 攻撃は自艦に隣接する8方向のみ。
  • 移動は障害物まで直線方向に何マスでも可。
  • すべての敵艦を沈めれば勝利!

遊び方・操作方法

操作内容
ゲーム開始「スタート」ボタンで開始。自動で艦が配置される
攻撃「攻撃」ボタン → 自艦の周囲8方向がCPU盤でハイライト → 攻撃したいマスをクリック
移動「移動」ボタン → 自分の艦をクリック → 空いている直線方向のマスをクリック
勝敗確認敵艦全滅で勝利、味方艦全滅で敗北
終了・再挑戦終了画面から「タイトル画面に戻る」で再スタート可

ゲームのルール

ルール項目内容
盤面5×5マス(各マスはA~E, 1~5の座標で識別)
配置できる艦戦艦(🚢 HP3)、駆逐艦(⛴️ HP2)、潜水艦(🤿 HP1) を各1隻ずつ
攻撃範囲各自艦が隣接(斜め含む8方向)するマスのみ攻撃可能
攻撃結果表示命中→赤、ニアミス→黄(💦水しぶき)、ミス→灰(🌊波高し)
移動自分の艦を直線(東西南北)で障害物にぶつかるまで何マスでも移動可
勝利条件相手の全艦を撃沈
コメント攻撃がニアミス時(水しぶき💦など)やミス時(波高し🌊など)でコメント表示

🎮ゲームプレイ

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

40 バトルシップ

素材のダウンロード

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

battleship_title.pngbattleship_bg.png

ゲーム画面イメージ

プログラム全体(battleship.html)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>🚢 バトルシップ 🚢</title>

  <!-- ============ ① 見た目を整える CSS ============ -->
  <style>
    :root{
      --cell:50px;            /* 1 マスの大きさ(旧 70px → 60px) */
      --bg-board:#1e3a8a;     /* ボード背景 (紺)   */
      --bg-cell:#60a5fa;      /* セル背景 (水色)   */
      --bg-hit:#f87171;       /* 命中 (赤)         */
      --bg-miss:#cbd5e1;      /* 失敗 (灰)         */
      --bg-near:#facc15;      /* ニアミス (黄)     */
      --font-white:#f8fafc;
    }

    /* 全体リセット+背景 */
    *{margin:0;padding:0;box-sizing:border-box;font-family:"Segoe UI","Yu Gothic","sans-serif"}
    body{
      min-height:100vh;display:flex;align-items:center;justify-content:center;
      background:url('battleship_bg.png') center/cover fixed,#0c4a6e;
      color:var(--font-white);
    }

    /* ===== 共通 800px パネル ===== */
    .panel{
      width:800px;                 /* 旧 700px → 800px */
      margin:auto;background:rgba(0,0,0,.55);
      border-radius:12px;padding:1.5rem 1rem;
      backdrop-filter:blur(4px);
    }

    /* ===== タイトル画面 & 終了画面 ===== */
    #titleScreen,#endScreen{
      position:fixed;inset:0;display:flex;align-items:center;justify-content:center;
      background:rgba(0,0,0,.4);
    }
    #titleInner,#endInner{display:flex;flex-direction:column;gap:1.2rem;text-align:center}
    #titleInner img{max-width:320px;margin:auto}
    #titleInner h1{font-size:2.6rem}
    #ruleTitle{font-size:1.4rem;font-weight:bold}
    #rules{line-height:1.6;text-align:left;margin:auto;max-width:620px}

    /* ===== ボタン ===== */
    button{
      padding:.8rem 2.4rem;font-size:1.2rem;border:none;border-radius:8px;
      cursor:pointer;background:#38bdf8;color:#0c4a6e;font-weight:bold;
      transition:transform .2s;
    }
    /* ▼ スタートボタンだけ幅 200px に固定 ▼ */
    #startBtn{
      width:200px;
      padding:.8rem 0;         /* 横幅固定なので左右パディングは不要 */
      margin:auto;
    }

    /* ===== ゲーム画面 ===== */
    #gameScreen{display:none}
    .boardsWrapper{
      display:flex;gap:2rem;flex-wrap:wrap;justify-content:center;margin-bottom:1rem
    }

    .board{
      display:grid;grid-template-columns:repeat(6,auto);
      background:var(--bg-board);padding:6px;border-radius:8px;
    }
    .label{
      width:var(--cell);height:var(--cell);display:flex;align-items:center;justify-content:center;
      background:#0f172a;font-weight:bold;user-select:none
    }
    .cell{
      width:var(--cell);height:var(--cell);
      display:flex;align-items:center;justify-content:center;
      background:var(--bg-cell);border:2px solid #0f172a;font-size:2rem;cursor:pointer;
      transition:background .2s
    }
    .disabled{cursor:default}
    .hit{background:var(--bg-hit)!important}
    .miss{background:var(--bg-miss)!important;color:#0f172a!important}
    .near{background:var(--bg-near)!important;color:#0f172a!important}

    /* 選択可能セルのハイライト (黄破線) */
    .selectable{
      outline:none;border:3px dashed var(--bg-near)!important;
    }

    .info{text-align:center;line-height:1.6}
    #message{font-size:1.1rem}
    #status{font-size:1.3rem;font-weight:bold;margin-top:.4rem}
    #actionBtns{display:flex;gap:1rem;justify-content:center;margin:.8rem 0}

    /* スマホ幅調整 */
    @media(max-width:500px){
      :root{--cell:45px}          /* 旧 50px → 45px */
      #titleInner h1{font-size:2rem}
    }
  </style>
</head>
<body>

  <!-- ============ ② タイトル画面 ============ -->
  <section id="titleScreen">
    <div id="titleInner" class="panel">
      <img src="battleship_title.png" alt="Battleship">
      <h1>🚢 バトルシップ 🚢</h1>

      <div id="ruleTitle">🎮 ルール概要</div>
      <div id="rules">
        ・5×5 海域に <strong>戦艦(🚢HP3)・駆逐艦(⛴️HP2)・潜水艦(🤿HP1)</strong> を各 1 隻隠す。<br>
        ・自ターンは「<strong>攻撃</strong>」or「<strong>移動</strong>」。<br>
        ・攻撃:自艦が隣接(8方向)するマスのみ射程。<br>
        ・移動:上下左右へ障害物まで好きな距離。<br>
        ・外れても敵艦が隣接していれば「💦水しぶき」等を通知。<br>
        ・敵艦をすべて沈めれば勝利!
      </div>

      <button id="startBtn">🚀 スタート</button>
    </div>
  </section>

  <!-- ============ ③ ゲーム画面 ============ -->
  <main id="gameScreen" class="panel">
    <div class="boardsWrapper">
      <section>
        <h2 style="text-align:center;margin:.5rem 0">🛡️ あなたの艦隊</h2>
        <div id="playerBoard" class="board"></div>
      </section>
      <section>
        <h2 style="text-align:center;margin:.5rem 0">🎯 CPU 海域 (攻撃先)</h2>
        <div id="cpuBoard" class="board"></div>
      </section>
    </div>

    <div class="info">
      <div id="message">💡 行動を選んでください。</div>
      <div id="actionBtns">
        <button id="attackBtn">🔫 攻撃</button>
        <button id="moveBtn">⚓ 移動</button>
      </div>
      <div id="status"></div>
    </div>
  </main>

  <!-- ============ ④ 終了画面 ============ -->
  <section id="endScreen" style="display:none">
    <div id="endInner" class="panel">
      <h1 id="endMsg">🏆 勝利!</h1>
      <button id="toTitleBtn">🏠 タイトル画面に戻る</button>
    </div>
  </section>

  <!-- ============ ⑤ ゲームロジック ============ -->
  <script>
    /*--------------------------------------------------
      定数・データ
    --------------------------------------------------*/
    const SIZE = 5;
    const SHIPS = [
      { name: '戦艦', icon: '🚢', hp: 3 },
      { name: '駆逐艦', icon: '⛴️', hp: 2 },
      { name: '潜水艦', icon: '🤿', hp: 1 }
    ];

    const createEmpty = () =>
      Array.from({ length: SIZE }, () =>
        Array.from({ length: SIZE }, () => ({ shipIdx: null, hpLeft: 0 })));

    const inBoard = (x, y) => x >= 0 && x < SIZE && y >= 0 && y < SIZE;
    const neighbors = (x, y) => [
      [x - 1, y - 1], [x, y - 1], [x + 1, y - 1],
      [x - 1, y],                [x + 1, y],
      [x - 1, y + 1], [x, y + 1], [x + 1, y + 1]
    ].filter(([nx, ny]) => inBoard(nx, ny));

    /*--------------------------------------------------
      グローバルゲーム状態
    --------------------------------------------------*/
    let player, cpu, gameOver = false, currentAction = null;
    let moveStage = null;          // null | 'selectShip' | 'selectDest'
    let selShipCoord = null;       // {x,y}

    /*--------------------------------------------------
      DOM 取得
    --------------------------------------------------*/
    const playerBoardEl = document.getElementById('playerBoard');
    const cpuBoardEl   = document.getElementById('cpuBoard');
    const msgEl        = document.getElementById('message');
    const statusEl     = document.getElementById('status');
    const attackBtn    = document.getElementById('attackBtn');
    const moveBtn      = document.getElementById('moveBtn');

    /*--------------------------------------------------
      視覚効果クリア
    --------------------------------------------------*/
    function clearSplashWave() {
      [cpuBoardEl, playerBoardEl].forEach(boardEl=>{
        boardEl.querySelectorAll('.cell.near, .cell.miss').forEach(c=>{
          c.classList.remove('near','miss','disabled'); c.textContent='';
        });
      });
    }

    /*--------------------------------------------------
      盤面生成
    --------------------------------------------------*/
    const makeLabel = t=>{
      const d=document.createElement('div'); d.className='label'; d.textContent=t; return d;
    };

    function buildBoard(parent,isCpu){
      parent.innerHTML='';
      parent.appendChild(makeLabel(''));
      for(let c=0;c<SIZE;c++) parent.appendChild(makeLabel(String.fromCharCode(65+c)));
      for(let r=0;r<SIZE;r++){
        parent.appendChild(makeLabel(r+1));
        for(let c=0;c<SIZE;c++){
          const cell=document.createElement('div');
          cell.className='cell'; cell.dataset.x=c; cell.dataset.y=r;
          if(isCpu) cell.addEventListener('click',()=>onCpuBoardClick(cell));
          parent.appendChild(cell);
        }
      }
    }

    /*--------------------------------------------------
      ゲーム初期化
    --------------------------------------------------*/
    document.getElementById('startBtn').addEventListener('click',startGame);
    document.getElementById('toTitleBtn').addEventListener('click',()=>{
      document.getElementById('endScreen').style.display='none';
      document.getElementById('titleScreen').style.display='flex';
    });

    function startGame(){
      player={board:createEmpty(),ships:JSON.parse(JSON.stringify(SHIPS))};
      cpu   ={board:createEmpty(),ships:JSON.parse(JSON.stringify(SHIPS))};
      placeRandom(player.board); placeRandom(cpu.board);

      gameOver=false; currentAction=null; moveStage=null; selShipCoord=null;

      buildBoard(playerBoardEl,false); buildBoard(cpuBoardEl,true);
      renderPlayerShips(); updateStatus();

      msgEl.textContent='💡 行動を選んでください。';
      document.getElementById('titleScreen').style.display='none';
      document.getElementById('gameScreen').style.display='block';
    }

    /* ランダム配置 */
    function placeRandom(board){
      SHIPS.forEach((_,idx)=>{
        let x,y;
        do{ x=Math.random()*SIZE|0; y=Math.random()*SIZE|0 }
        while(board[y][x].shipIdx!==null);
        board[y][x]={shipIdx:idx,hpLeft:SHIPS[idx].hp};
      });
    }

    /*--------------------------------------------------
      ボタンハンドラ
    --------------------------------------------------*/
    attackBtn.onclick=()=>{
      if(gameOver) return;
      currentAction='attack'; prepareAttack();
    };
    moveBtn.onclick=()=>{
      if(gameOver) return;
      currentAction='move';   prepareMove();
    };

    /*--------------------------------------------------
      攻撃準備
      - 既に hit のセルも再選択可
      - miss は無視
    --------------------------------------------------*/
    function prepareAttack(){
      clearSplashWave(); clearHighlights();

      const atkSet=new Set();
      for(let y=0;y<SIZE;y++)for(let x=0;x<SIZE;x++)
        if(player.board[y][x].shipIdx!==null)
          neighbors(x,y).forEach(([nx,ny])=>atkSet.add(`${nx},${ny}`));

      cpuBoardEl.querySelectorAll('.cell').forEach(cell=>{
        const key=`${cell.dataset.x},${cell.dataset.y}`;
        if(atkSet.has(key)&&!cell.classList.contains('miss'))
          cell.classList.add('selectable');
      });

      msgEl.textContent='🔫 攻撃:ハイライトされたマスをクリック!';
    }

    /*--------------------------------------------------
      CPU ボードクリック
    --------------------------------------------------*/
    function onCpuBoardClick(cell){
      if(gameOver||currentAction!=='attack'||!cell.classList.contains('selectable')) return;

      const x=+cell.dataset.x, y=+cell.dataset.y;
      clearHighlights(); currentAction=null;

      executeAttack(player,cpu,cell,x,y,true);
      endPlayerTurn();
    }

    /*--------------------------------------------------
      移動準備
    --------------------------------------------------*/
    function prepareMove(){
      clearSplashWave(); clearHighlights();
      moveStage='selectShip'; msgEl.textContent='⚓ 移動:艦をクリックしてください。';

      playerBoardEl.querySelectorAll('.cell').forEach(cell=>{
        if(player.board[cell.dataset.y][cell.dataset.x].shipIdx!==null)
          cell.classList.add('selectable');
      });
      playerBoardEl.addEventListener('click',onPlayerBoardClick);
    }

    /*--------------------------------------------------
      プレイヤーボードクリック
    --------------------------------------------------*/
    function onPlayerBoardClick(e){
      const cell=e.target.closest('.cell'); if(!cell) return;
      const x=+cell.dataset.x, y=+cell.dataset.y;

      if(moveStage==='selectShip'){
        if(!cell.classList.contains('selectable')) return;
        selShipCoord={x,y}; highlightDestinations(x,y); moveStage='selectDest';
      }else if(moveStage==='selectDest'){
        if(!cell.classList.contains('selectable')) return;
        moveShip(selShipCoord.x,selShipCoord.y,x,y);
      }
    }

    /*--------------------------------------------------
      移動可能マスハイライト
    --------------------------------------------------*/
    function highlightDestinations(sx,sy){
      clearHighlights();
      playerBoardEl.querySelector(`.cell[data-x="${sx}"][data-y="${sy}"]`).classList.add('selectable');

      [[1,0],[-1,0],[0,1],[0,-1]].forEach(([dx,dy])=>{
        let x=sx,y=sy;
        while(true){
          x+=dx; y+=dy;
          if(!inBoard(x,y)||player.board[y][x].shipIdx!==null) break;
          playerBoardEl.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`).classList.add('selectable');
        }
      });

      msgEl.textContent='⚓ 行き先をクリックしてください。';
    }

    /*--------------------------------------------------
      移動実行
    --------------------------------------------------*/
    function moveShip(sx,sy,dx,dy){
      const info=player.board[sy][sx];
      player.board[sy][sx]={shipIdx:null,hpLeft:0};
      player.board[dy][dx]={shipIdx:info.shipIdx,hpLeft:info.hpLeft};

      renderPlayerShips(); clearHighlights();
      playerBoardEl.removeEventListener('click',onPlayerBoardClick);

      moveStage=null; currentAction=null;
      msgEl.textContent=`⚓ ${SHIPS[info.shipIdx].name} を移動!`;
      endPlayerTurn();
    }

    /*--------------------------------------------------
      ハイライト解除
    --------------------------------------------------*/
    const clearHighlights=()=>document.querySelectorAll('.selectable').forEach(c=>c.classList.remove('selectable'));

    /*--------------------------------------------------
      プレイヤー艦表示
    --------------------------------------------------*/
    function renderPlayerShips(){
      playerBoardEl.querySelectorAll('.cell').forEach(cell=>{
        const info=player.board[cell.dataset.y][cell.dataset.x];
        cell.textContent=info.shipIdx!==null?SHIPS[info.shipIdx].icon:'';
      });
    }

    /*--------------------------------------------------
      攻撃処理
      - HP が 0 になった艦は盤面から除去しアイコンも消す
      - プレイヤー艦撃沈時は即座に描画更新
    --------------------------------------------------*/
    function executeAttack(atk,def,cellEl,x,y,isPlayer){
      const target=def.board[y][x];

      if(target.shipIdx!==null){          /* ===== 命中 ===== */
        target.hpLeft--; def.ships[target.shipIdx].hp--; cellEl.classList.add('hit');

        if(def.ships[target.shipIdx].hp>0){
          msgEl.textContent=isPlayer
            ?`🎯 命中!${SHIPS[target.shipIdx].name} (残HP ${def.ships[target.shipIdx].hp})`
            :`⚠️ CPU の攻撃が命中!あなたの ${SHIPS[target.shipIdx].name} (残HP ${def.ships[target.shipIdx].hp})`;
        }else{                            /* ===== 撃沈 ===== */
          msgEl.textContent=isPlayer
            ?`💥 ${SHIPS[target.shipIdx].name} を撃沈!`
            :`💥 CPU があなたの ${SHIPS[target.shipIdx].name} を撃沈!`;

          /* 盤面から艦を消す(再度そのセルを攻撃すると miss / near 判定) */
          def.board[y][x]={shipIdx:null,hpLeft:0};
          if(def===player) renderPlayerShips();  // 自艦が沈んだ場合は描画更新

          /* HP 合計が 0 になれば勝敗決定 */
          if(def.ships.every(s=>s.hp===0)){ finishGame(isPlayer); return; }
        }

      }else{                               /* ===== ミス / ニアミス ===== */
        const near=neighbors(x,y).some(([nx,ny])=>def.board[ny][nx].shipIdx!==null);
        cellEl.classList.add(near?'near':'miss');
        cellEl.textContent=near?'💦':'🌊';
        msgEl.textContent=near?'💦 水しぶき!':'🌊 波高し…';
      }

      updateStatus();
    }

    /*--------------------------------------------------
      プレイヤーターン終了
    --------------------------------------------------*/
    function endPlayerTurn(){
      if(gameOver) return;
      setTimeout(cpuTurn,700);
    }

    /*--------------------------------------------------
      CPU ターン
    --------------------------------------------------*/
    function cpuTurn(){
      if(gameOver) return;
      (Math.random()<0.5||!cpuCanMove())?cpuAttack():cpuMove();
      if(!gameOver) msgEl.textContent+=' あなたの番です。行動を選んでください。';
    }

    /*--------------------------------------------------
      CPU 攻撃
    --------------------------------------------------*/
    function cpuAttack(){
      const cand=[];
      for(let y=0;y<SIZE;y++)for(let x=0;x<SIZE;x++)
        if(cpu.board[y][x].shipIdx!==null)
          neighbors(x,y).forEach(([nx,ny])=>{
            const c=playerBoardEl.querySelector(`.cell[data-x="${nx}"][data-y="${ny}"]`);
            if(!c.classList.contains('hit')&&!c.classList.contains('miss')) cand.push({x:nx,y:ny});
          });

      const choice=cand.length?cand[Math.random()*cand.length|0]:
        {x:Math.random()*SIZE|0,y:Math.random()*SIZE|0};

      executeAttack(cpu,player,
        playerBoardEl.querySelector(`.cell[data-x="${choice.x}"][data-y="${choice.y}"]`),
        choice.x,choice.y,false);
    }

    /*--------------------------------------------------
      CPU 移動可否
    --------------------------------------------------*/
    function cpuCanMove(){
      for(let y=0;y<SIZE;y++)for(let x=0;x<SIZE;x++)
        if(cpu.board[y][x].shipIdx!==null)
          for(const [dx,dy] of [[1,0],[-1,0],[0,1],[0,-1]])
            if(inBoard(x+dx,y+dy)&&cpu.board[y+dy][x+dx].shipIdx===null) return true;
      return false;
    }

    /*--------------------------------------------------
      CPU 移動
    --------------------------------------------------*/
    function cpuMove(){
      const ships=[];
      for(let y=0;y<SIZE;y++)for(let x=0;x<SIZE;x++)
        if(cpu.board[y][x].shipIdx!==null) ships.push({x,y,info:cpu.board[y][x]});

      shuffle(ships);
      for(const s of ships){
        const moves=calcDest(s.x,s.y,cpu.board);
        if(moves.length){
          const d=moves[Math.random()*moves.length|0];
          cpu.board[s.y][s.x]={shipIdx:null,hpLeft:0};
          cpu.board[d.y][d.x]={shipIdx:s.info.shipIdx,hpLeft:s.info.hpLeft};
          return;
        }
      }
      cpuAttack();   // 移動不可なら攻撃
    }

    /*--------------------------------------------------
      目的セル計算
    --------------------------------------------------*/
    function calcDest(sx,sy,board){
      const dest=[];
      [[1,0],[-1,0],[0,1],[0,-1]].forEach(([dx,dy])=>{
        let x=sx,y=sy;
        while(true){
          x+=dx; y+=dy;
          if(!inBoard(x,y)||board[y][x].shipIdx!==null) break;
          dest.push({x,y});
        }
      });
      return dest;
    }

    /*--------------------------------------------------
      Fisher–Yates シャッフル
    --------------------------------------------------*/
    const shuffle=arr=>{
      for(let i=arr.length-1;i>0;i--){
        const j=Math.random()*(i+1)|0;
        [arr[i],arr[j]]=[arr[j],arr[i]];
      }
    };

    /*--------------------------------------------------
      ステータス更新
    --------------------------------------------------*/
    function updateStatus(){
      const p=player.ships.reduce((a,s)=>a+s.hp,0);
      const c=cpu.ships.reduce((a,s)=>a+s.hp,0);
      statusEl.textContent=`🟦 あなた HP:${p} | 🟥 CPU HP:${c}`;
    }

    /*--------------------------------------------------
      勝敗処理
    --------------------------------------------------*/
    function finishGame(playerWin){
      gameOver=true;
      document.getElementById('gameScreen').style.display='none';
      document.getElementById('endMsg').textContent=playerWin?'🏆 勝利!':'😱 敗北…';
      document.getElementById('endScreen').style.display='flex';
    }

    /*--------------------------------------------------
      初回:背景用ダミーボード
    --------------------------------------------------*/
    buildBoard(playerBoardEl,false); buildBoard(cpuBoardEl,true);
  </script>
</body>
</html>

ゲーム全体の流れ

ステップアクション概要
① タイトル表示ゲーム説明とスタートボタン
② 配置プレイヤー/CPU の艦をランダム配置
③ ターン開始「攻撃」or「移動」選択
④ 攻撃指定マスに魚雷発射 → 結果表示(ヒット/ニアミス/ミス)
⑤ 移動艦を好きな直線距離で移動
⑥ 勝敗判定どちらかの艦隊HP合計が0なら終了
⑦ 結果表示勝利/敗北画面とリスタート

主要な命令・関数と役割

関数・定数役割・処理内容
const SHIPS配置する艦の種類・アイコン・耐久値データ
createEmpty()盤面の初期化(空の5×5グリッド生成)
placeRandom(board)各艦をランダムな空きマスに配置
buildBoard(parent,isCpu)盤面UI生成。ラベル(A~E, 1~5)とセルをDOM構築
prepareAttack()攻撃準備。自艦の周囲8方向をハイライトして選択可能に
executeAttack(atk,def,cell,x,y,isPlayer)攻撃実行。ヒット/ニアミス/ミス判定&描画更新・HP減少・勝敗チェック
prepareMove()移動準備。自艦クリック待機モード開始
moveShip(sx,sy,dx,dy)艦の実移動処理(元マスクリア→新マスへ艦データコピー)
cpuAttack()CPUの攻撃手番ロジック
cpuMove()CPUの移動手番ロジック
finishGame(playerWin)勝敗画面の表示
updateStatus()画面下部の「あなたHP|CPU HP」のリアルタイム表示

関数の詳細解説

関数名何をしているか
neighbors(x,y)指定座標の周囲8マス座標リストを返す
clearHighlights()全セルから選択可能のハイライトを消す
renderPlayerShips()プレイヤー艦の現在位置にアイコンを表示
endPlayerTurn()プレイヤーのターン終了 → 少し待ってCPUターンに移行
calcDest(sx,sy,board)指定艦の「現在位置」から直線移動可能な空きマス全リストを返す
shuffle(arr)配列シャッフル(Fisher–Yatesアルゴリズム。CPU移動に利用)

改造のポイント

ポイント例解説
艦の種類や数を増やすSHIPS配列を編集して「空母」や「ミサイル艦」など自由に追加できます
盤面サイズの変更const SIZE を変更すれば6×6や8×8にも簡単に拡張可能
CPUの戦略強化CPUの攻撃・移動アルゴリズムを高度化してより強い対戦相手を作れる
エフェクトやサウンドの追加セルクリック時や撃沈時にアニメーションや効果音を鳴らすと臨場感アップ
マルチプレイ対応ローカル2Pやオンライン対戦などの拡張も可能(要大幅な実装追加)
スマホ最適化・デザイン強化メディアクエリやタッチ操作最適化でモバイルでも快適に遊べるよう改良

アドバイス・コメント

  • この「バトルシップ」はロジックがシンプルにまとまっており、盤面や艦の増減、攻撃・移動ルールのカスタマイズも容易です。
  • JavaScriptでDOMと連携しながらUI更新を行う基本構造を理解する教材にもピッタリです。
  • CPUロジックやUX改良にチャレンジするとさらに楽しさ・難易度アップ!
  • 自作ミニゲームの第一歩や授業教材にもオススメです。