CoolFace
Apppublic

miya3333/DiscoveringNumbersGame

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
script.js310 linesDownload Raw Back to root
1(function(){2  const canvas = document.getElementById('gameCanvas');3  const ctx = canvas.getContext('2d');4  const targetSpan = document.getElementById('target');5  const startBtn = document.getElementById('startBtn');6  const messageDiv = document.getElementById('message');7  const modeRadios = document.querySelectorAll('input[name="mode"]');8  const difficultyDiv = document.getElementById('difficultySelect');9  const difficultyRadios = document.querySelectorAll('input[name="difficulty"]');10  modeRadios.forEach(radio => {11    radio.addEventListener('change', () => {12      if (document.querySelector('input[name="mode"]:checked').value === 'vsAI') {13        difficultyDiv.style.display = 'block';14      } else {15        difficultyDiv.style.display = 'none';16      }17    });18  });19  const scoresDiv = document.getElementById('scores');20  const playerScoreSpan = document.getElementById('playerScore');21  const aiScoreSpan = document.getElementById('aiScore');22  let gameMode = 'single';23  let playerScore = 0;24  let aiScore = 0;25  let aiTimeout = null;26  let aiAccuracy;27  const AI_EASY_ACCURACY = 0.3;28  const AI_HARD_ACCURACY = 0.7;29  const aiMinDelay = 500;30  const aiMaxDelay = 2000;31  let items = [];32  let remaining = [];33  let currentTarget = null;34  const count = 30;35  const fontSize = 24;36  ctx.textBaseline = 'alphabetic';37  ctx.font = fontSize + 'px sans-serif';38 39  function isOverlapping(x, y, w, h, arr) {40    return arr.some(item => {41      return x < item.x + item.width &&42             x + w > item.x &&43             y - h < item.y &&44             y > item.y - item.height;45    });46  }47 48  function initGame() {49    items = [];50    messageDiv.textContent = '';51    messageDiv.className = '';52    targetSpan.textContent = '--';53    gameMode = document.querySelector('input[name="mode"]:checked').value;54    if (gameMode === 'vsAI') {55      const difficulty = document.querySelector('input[name="difficulty"]:checked').value;56      aiAccuracy = difficulty === 'easy' ? AI_EASY_ACCURACY : AI_HARD_ACCURACY;57      playerScore = 0;58      aiScore = 0;59      playerScoreSpan.textContent = playerScore;60      aiScoreSpan.textContent = aiScore;61      scoresDiv.style.display = 'block';62    } else {63      scoresDiv.style.display = 'none';64    }65    if (aiTimeout) {66      clearTimeout(aiTimeout);67      aiTimeout = null;68    }69    for (let i = 1; i <= count; i++) {70      const text = i.toString();71      const metrics = ctx.measureText(text);72      const width = metrics.width;73      const height = fontSize;74      let x, y, attempts = 0;75      do {76        x = Math.random() * (canvas.width - width);77        y = Math.random() * (canvas.height - height) + height;78        attempts++;79        if (attempts > 1000) break;80      } while (isOverlapping(x, y, width, height, items));81      const angle = Math.random() * 2 * Math.PI;82      items.push({ num: i, x, y, width, height, angle });83    }84    remaining = items.slice();85    drawAll();86    pickNext();87  }88 89  function drawAll() {90    ctx.clearRect(0, 0, canvas.width, canvas.height);91    ctx.fillStyle = '#000';92    remaining.forEach(item => {93      ctx.save();94      const cx = item.x + item.width / 2;95      const cy = item.y - item.height / 2;96      ctx.translate(cx, cy);97      ctx.rotate(item.angle);98      ctx.fillText(item.num, -item.width / 2, item.height / 2);99      ctx.restore();100    });101  }102 103  function pickNext() {104    if (remaining.length === 0) {105      currentTarget = null;106      targetSpan.textContent = '--';107      messageDiv.className = 'clear';108      if (gameMode === 'vsAI') {109        if (aiTimeout) {110          clearTimeout(aiTimeout);111          aiTimeout = null;112        }113        let resultText = '';114        if (playerScore > aiScore) resultText = 'あなたの勝ち!';115        else if (playerScore < aiScore) resultText = 'AIの勝ち!';116        else resultText = '引き分け!';117        messageDiv.textContent = 'ゲームクリア!結果: あなた ' + playerScore + ' - AI ' + aiScore + ' ' + resultText;118      } else {119        messageDiv.textContent = 'ゲームクリア!';120      }121      return;122    }123    const idx = Math.floor(Math.random() * remaining.length);124    currentTarget = remaining[idx].num;125    targetSpan.textContent = currentTarget;126    if (gameMode === 'vsAI') {127      scheduleAIAttempt();128    }129  }130 131  function repositionItems() {132    const newItems = [];133    remaining.forEach(orig => {134      const { num, width, height } = orig;135      let x, y, attempts = 0;136      do {137        x = Math.random() * (canvas.width - width);138        y = Math.random() * (canvas.height - height) + height;139        attempts++;140        if (attempts > 1000) break;141      } while (isOverlapping(x, y, width, height, newItems));142      const angle = Math.random() * 2 * Math.PI;143      newItems.push({ num, x, y, width, height, angle });144    });145    return newItems;146  }147 148  function animateReposition(oldItems, newItems, duration, callback) {149    const startTime = performance.now();150    function animate(time) {151      const t = Math.min((time - startTime) / duration, 1);152      remaining = oldItems.map((oldItem, i) => {153        const newItem = newItems[i];154        return {155          num: oldItem.num,156          width: oldItem.width,157          height: oldItem.height,158          x: oldItem.x + (newItem.x - oldItem.x) * t,159          y: oldItem.y + (newItem.y - oldItem.y) * t,160          angle: oldItem.angle + (newItem.angle - oldItem.angle) * t161        };162      });163      drawAll();164      if (t < 1) requestAnimationFrame(animate);165      else callback();166    }167    requestAnimationFrame(animate);168  }169 170  function drawRedCircle(item) {171    const cx = item.x + item.width / 2;172    const cy = item.y - item.height / 2;173    const r = Math.max(item.width, item.height) / 2 + 5;174    ctx.strokeStyle = 'red';175    ctx.lineWidth = 3;176    ctx.beginPath();177    ctx.arc(cx, cy, r, 0, 2 * Math.PI);178    ctx.stroke();179  }180 181  function drawRedCross(item) {182    const x1 = item.x;183    const y1 = item.y - item.height;184    const x2 = item.x + item.width;185    const y2 = item.y;186    ctx.strokeStyle = 'red';187    ctx.lineWidth = 3;188    ctx.beginPath();189    ctx.moveTo(x1, y1);190    ctx.lineTo(x2, y2);191    ctx.moveTo(x1, y2);192    ctx.lineTo(x2, y1);193    ctx.stroke();194  }195  function drawBlueCircle(item) {196    const cx = item.x + item.width / 2;197    const cy = item.y - item.height / 2;198    const r = Math.max(item.width, item.height) / 2 + 5;199    ctx.strokeStyle = 'blue';200    ctx.lineWidth = 3;201    ctx.beginPath();202    ctx.arc(cx, cy, r, 0, 2 * Math.PI);203    ctx.stroke();204  }205  function drawBlueCross(item) {206    const x1 = item.x;207    const y1 = item.y - item.height;208    const x2 = item.x + item.width;209    const y2 = item.y;210    ctx.strokeStyle = 'blue';211    ctx.lineWidth = 3;212    ctx.beginPath();213    ctx.moveTo(x1, y1);214    ctx.lineTo(x2, y2);215    ctx.moveTo(x1, y2);216    ctx.lineTo(x2, y1);217    ctx.stroke();218  }219 220  function scheduleAIAttempt() {221    if (aiTimeout) clearTimeout(aiTimeout);222    const delay = aiMinDelay + Math.random() * (aiMaxDelay - aiMinDelay);223    aiTimeout = setTimeout(doAIAttempt, delay);224  }225 226  function doAIAttempt() {227    aiTimeout = null;228    if (gameMode !== 'vsAI' || currentTarget === null) return;229    const correct = Math.random() < aiAccuracy;230    if (correct) {231      const idx = remaining.findIndex(item => item.num === currentTarget);232      currentTarget = null;233      if (idx === -1) return;234      const item = remaining[idx];235      aiScore++;236      aiScoreSpan.textContent = aiScore;237      messageDiv.className = 'correct';238      messageDiv.textContent = 'AIが正解!';239      drawAll();240      drawBlueCircle(item);241      setTimeout(() => {242        remaining.splice(idx, 1);243        const oldItems = remaining.map(it => ({ ...it }));244        const newItems = repositionItems();245        animateReposition(oldItems, newItems, 1000, () => {246          remaining = newItems;247          pickNext();248        });249      }, 500);250    } else {251      if (remaining.length > 1) {252        const wrongItems = remaining.filter(item => item.num !== currentTarget);253        const wrongItem = wrongItems[Math.floor(Math.random() * wrongItems.length)];254        messageDiv.className = 'wrong';255        messageDiv.textContent = 'AIが間違えた!';256        drawAll();257        drawBlueCross(wrongItem);258        setTimeout(drawAll, 500);259      }260      scheduleAIAttempt();261    }262  }263 264  canvas.addEventListener('click', e => {265    if (currentTarget === null) return;266    const rect = canvas.getBoundingClientRect();267    const clickX = e.clientX - rect.left;268    const clickY = e.clientY - rect.top;269    for (let i = 0; i < remaining.length; i++) {270      const item = remaining[i];271      if (clickX >= item.x && clickX <= item.x + item.width &&272          clickY <= item.y && clickY >= item.y - item.height) {273        if (item.num === currentTarget) {274          currentTarget = null;275          if (gameMode === 'vsAI') {276            if (aiTimeout) {277              clearTimeout(aiTimeout);278              aiTimeout = null;279            }280            playerScore++;281            playerScoreSpan.textContent = playerScore;282          }283          messageDiv.className = 'correct';284          messageDiv.textContent = '正解!';285          drawAll();286          drawRedCircle(item);287          setTimeout(() => {288            remaining.splice(i, 1);289            const oldItems = remaining.map(it => ({ ...it }));290            const newItems = repositionItems();291            animateReposition(oldItems, newItems, 1000, () => {292              remaining = newItems;293              pickNext();294            });295          }, 500);296        } else {297          messageDiv.className = 'wrong';298          messageDiv.textContent = '違うよ!';299          drawAll();300          drawRedCross(item);301          setTimeout(drawAll, 500);302        }303        return;304      }305    }306  });307 308  startBtn.addEventListener('click', initGame);309})();310