miya3333's picture
Upload script.js
e3b3029 verified
(function(){
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const targetSpan = document.getElementById('target');
const startBtn = document.getElementById('startBtn');
const messageDiv = document.getElementById('message');
const modeRadios = document.querySelectorAll('input[name="mode"]');
const difficultyDiv = document.getElementById('difficultySelect');
const difficultyRadios = document.querySelectorAll('input[name="difficulty"]');
modeRadios.forEach(radio => {
radio.addEventListener('change', () => {
if (document.querySelector('input[name="mode"]:checked').value === 'vsAI') {
difficultyDiv.style.display = 'block';
} else {
difficultyDiv.style.display = 'none';
}
});
});
const scoresDiv = document.getElementById('scores');
const playerScoreSpan = document.getElementById('playerScore');
const aiScoreSpan = document.getElementById('aiScore');
let gameMode = 'single';
let playerScore = 0;
let aiScore = 0;
let aiTimeout = null;
let aiAccuracy;
const AI_EASY_ACCURACY = 0.3;
const AI_HARD_ACCURACY = 0.7;
const aiMinDelay = 500;
const aiMaxDelay = 2000;
let items = [];
let remaining = [];
let currentTarget = null;
const count = 30;
const fontSize = 24;
ctx.textBaseline = 'alphabetic';
ctx.font = fontSize + 'px sans-serif';
function isOverlapping(x, y, w, h, arr) {
return arr.some(item => {
return x < item.x + item.width &&
x + w > item.x &&
y - h < item.y &&
y > item.y - item.height;
});
}
function initGame() {
items = [];
messageDiv.textContent = '';
messageDiv.className = '';
targetSpan.textContent = '--';
gameMode = document.querySelector('input[name="mode"]:checked').value;
if (gameMode === 'vsAI') {
const difficulty = document.querySelector('input[name="difficulty"]:checked').value;
aiAccuracy = difficulty === 'easy' ? AI_EASY_ACCURACY : AI_HARD_ACCURACY;
playerScore = 0;
aiScore = 0;
playerScoreSpan.textContent = playerScore;
aiScoreSpan.textContent = aiScore;
scoresDiv.style.display = 'block';
} else {
scoresDiv.style.display = 'none';
}
if (aiTimeout) {
clearTimeout(aiTimeout);
aiTimeout = null;
}
for (let i = 1; i <= count; i++) {
const text = i.toString();
const metrics = ctx.measureText(text);
const width = metrics.width;
const height = fontSize;
let x, y, attempts = 0;
do {
x = Math.random() * (canvas.width - width);
y = Math.random() * (canvas.height - height) + height;
attempts++;
if (attempts > 1000) break;
} while (isOverlapping(x, y, width, height, items));
const angle = Math.random() * 2 * Math.PI;
items.push({ num: i, x, y, width, height, angle });
}
remaining = items.slice();
drawAll();
pickNext();
}
function drawAll() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#000';
remaining.forEach(item => {
ctx.save();
const cx = item.x + item.width / 2;
const cy = item.y - item.height / 2;
ctx.translate(cx, cy);
ctx.rotate(item.angle);
ctx.fillText(item.num, -item.width / 2, item.height / 2);
ctx.restore();
});
}
function pickNext() {
if (remaining.length === 0) {
currentTarget = null;
targetSpan.textContent = '--';
messageDiv.className = 'clear';
if (gameMode === 'vsAI') {
if (aiTimeout) {
clearTimeout(aiTimeout);
aiTimeout = null;
}
let resultText = '';
if (playerScore > aiScore) resultText = 'あなたの勝ち!';
else if (playerScore < aiScore) resultText = 'AIの勝ち!';
else resultText = '引き分け!';
messageDiv.textContent = 'ゲームクリア!結果: あなた ' + playerScore + ' - AI ' + aiScore + ' ' + resultText;
} else {
messageDiv.textContent = 'ゲームクリア!';
}
return;
}
const idx = Math.floor(Math.random() * remaining.length);
currentTarget = remaining[idx].num;
targetSpan.textContent = currentTarget;
if (gameMode === 'vsAI') {
scheduleAIAttempt();
}
}
function repositionItems() {
const newItems = [];
remaining.forEach(orig => {
const { num, width, height } = orig;
let x, y, attempts = 0;
do {
x = Math.random() * (canvas.width - width);
y = Math.random() * (canvas.height - height) + height;
attempts++;
if (attempts > 1000) break;
} while (isOverlapping(x, y, width, height, newItems));
const angle = Math.random() * 2 * Math.PI;
newItems.push({ num, x, y, width, height, angle });
});
return newItems;
}
function animateReposition(oldItems, newItems, duration, callback) {
const startTime = performance.now();
function animate(time) {
const t = Math.min((time - startTime) / duration, 1);
remaining = oldItems.map((oldItem, i) => {
const newItem = newItems[i];
return {
num: oldItem.num,
width: oldItem.width,
height: oldItem.height,
x: oldItem.x + (newItem.x - oldItem.x) * t,
y: oldItem.y + (newItem.y - oldItem.y) * t,
angle: oldItem.angle + (newItem.angle - oldItem.angle) * t
};
});
drawAll();
if (t < 1) requestAnimationFrame(animate);
else callback();
}
requestAnimationFrame(animate);
}
function drawRedCircle(item) {
const cx = item.x + item.width / 2;
const cy = item.y - item.height / 2;
const r = Math.max(item.width, item.height) / 2 + 5;
ctx.strokeStyle = 'red';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(cx, cy, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawRedCross(item) {
const x1 = item.x;
const y1 = item.y - item.height;
const x2 = item.x + item.width;
const y2 = item.y;
ctx.strokeStyle = 'red';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.moveTo(x1, y2);
ctx.lineTo(x2, y1);
ctx.stroke();
}
function drawBlueCircle(item) {
const cx = item.x + item.width / 2;
const cy = item.y - item.height / 2;
const r = Math.max(item.width, item.height) / 2 + 5;
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(cx, cy, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawBlueCross(item) {
const x1 = item.x;
const y1 = item.y - item.height;
const x2 = item.x + item.width;
const y2 = item.y;
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.moveTo(x1, y2);
ctx.lineTo(x2, y1);
ctx.stroke();
}
function scheduleAIAttempt() {
if (aiTimeout) clearTimeout(aiTimeout);
const delay = aiMinDelay + Math.random() * (aiMaxDelay - aiMinDelay);
aiTimeout = setTimeout(doAIAttempt, delay);
}
function doAIAttempt() {
aiTimeout = null;
if (gameMode !== 'vsAI' || currentTarget === null) return;
const correct = Math.random() < aiAccuracy;
if (correct) {
const idx = remaining.findIndex(item => item.num === currentTarget);
currentTarget = null;
if (idx === -1) return;
const item = remaining[idx];
aiScore++;
aiScoreSpan.textContent = aiScore;
messageDiv.className = 'correct';
messageDiv.textContent = 'AIが正解!';
drawAll();
drawBlueCircle(item);
setTimeout(() => {
remaining.splice(idx, 1);
const oldItems = remaining.map(it => ({ ...it }));
const newItems = repositionItems();
animateReposition(oldItems, newItems, 1000, () => {
remaining = newItems;
pickNext();
});
}, 500);
} else {
if (remaining.length > 1) {
const wrongItems = remaining.filter(item => item.num !== currentTarget);
const wrongItem = wrongItems[Math.floor(Math.random() * wrongItems.length)];
messageDiv.className = 'wrong';
messageDiv.textContent = 'AIが間違えた!';
drawAll();
drawBlueCross(wrongItem);
setTimeout(drawAll, 500);
}
scheduleAIAttempt();
}
}
canvas.addEventListener('click', e => {
if (currentTarget === null) return;
const rect = canvas.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const clickY = e.clientY - rect.top;
for (let i = 0; i < remaining.length; i++) {
const item = remaining[i];
if (clickX >= item.x && clickX <= item.x + item.width &&
clickY <= item.y && clickY >= item.y - item.height) {
if (item.num === currentTarget) {
currentTarget = null;
if (gameMode === 'vsAI') {
if (aiTimeout) {
clearTimeout(aiTimeout);
aiTimeout = null;
}
playerScore++;
playerScoreSpan.textContent = playerScore;
}
messageDiv.className = 'correct';
messageDiv.textContent = '正解!';
drawAll();
drawRedCircle(item);
setTimeout(() => {
remaining.splice(i, 1);
const oldItems = remaining.map(it => ({ ...it }));
const newItems = repositionItems();
animateReposition(oldItems, newItems, 1000, () => {
remaining = newItems;
pickNext();
});
}, 500);
} else {
messageDiv.className = 'wrong';
messageDiv.textContent = '違うよ!';
drawAll();
drawRedCross(item);
setTimeout(drawAll, 500);
}
return;
}
}
});
startBtn.addEventListener('click', initGame);
})();