miya3333/DiscoveringNumbersGame
0
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 