Крестики-Нолики: Ты vs Бот
by TurboGamer88304 lines8.6 KB
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Крестики-Нолики: Ты vs Бот</title>
<style>
body { margin: 0; background: #0a0a12; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: Arial; overflow: hidden; touch-action: none; }
canvas { background: #1a1a2e; border: 2px solid #e94560; border-radius: 8px; box-shadow: 0 0 25px rgba(233,69,96,0.4); touch-action: none; }
#hud { font-size: 16px; margin-bottom: 8px; color: #e94560; font-weight: bold; text-align: center; text-shadow: 0 0 8px #e94560; }
#restart { position: absolute; bottom: 20px; padding: 12px 30px; background: #e94560; color: #fff; border: none; border-radius: 10px; font-size: 18px; font-weight: bold; cursor: pointer; box-shadow: 0 0 15px rgba(233,69,96,0.6); }
</style>
</head>
<body>
<div id="hud">Ты: ❍ | Бот: ✕</div>
<canvas id="game" width="360" height="360"></canvas>
<button id="restart">🔄 Новая игра</button>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');
const SIZE = 120;
const CELL = SIZE / 3;
let board = ['', '', '', '', '', '', '', '', ''];
let gameOver = false;
let winner = null; // 'player', 'bot', 'draw'
let playerTurn = true;
let winLine = null;
let botThinking = false;
let animTime = 0;
// --- ЗВУК ---
let audioCtx = null;
function initAudio() {
if (audioCtx) return;
try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {}
}
function playSound(freq, dur, type, vol) {
if (!audioCtx) return;
try {
const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
o.type = type || 'sine'; o.frequency.value = freq;
g.gain.setValueAtTime(vol || 0.08, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + dur);
o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + dur);
} catch(e) {}
}
function sfxPlace() { playSound(600, 0.1, 'sine', 0.08); }
function sfxWin() { playSound(523, 0.15, 'sine', 0.12); setTimeout(() => playSound(659, 0.15, 'sine', 0.12), 150); setTimeout(() => playSound(784, 0.3, 'sine', 0.12), 300); }
function sfxLose() { playSound(300, 0.3, 'sawtooth', 0.12); setTimeout(() => playSound(200, 0.4, 'sawtooth', 0.1), 200); }
function sfxDraw() { playSound(400, 0.2, 'triangle', 0.1); }
function sfxClick() { playSound(800, 0.05, 'square', 0.06); }
// --- ЛОГИКА ---
function checkWinner() {
const lines = [
[0,1,2],[3,4,5],[6,7,8],
[0,3,6],[1,4,7],[2,5,8],
[0,4,8],[2,4,6]
];
for (let line of lines) {
const [a,b,c] = line;
if (board[a] && board[a] === board[b] && board[a] === board[c]) {
winLine = line;
return board[a] === 'O' ? 'player' : 'bot';
}
}
if (board.every(c => c !== '')) return 'draw';
return null;
}
function botMove() {
if (gameOver) return;
botThinking = true;
setTimeout(() => {
// Простой ИИ: выиграть, блокировать, центр, угол, любая
const empty = board.map((c, i) => c === '' ? i : null).filter(i => i !== null);
if (empty.length === 0) return;
// 1. Выиграть
for (let i of empty) {
board[i] = 'X';
if (checkWinner() === 'bot') { placeBot(i); return; }
board[i] = '';
}
// 2. Блокировать
for (let i of empty) {
board[i] = 'O';
if (checkWinner() === 'player') { board[i] = ''; placeBot(i); return; }
board[i] = '';
}
// 3. Центр
if (board[4] === '') { placeBot(4); return; }
// 4. Угол
const corners = [0,2,6,8].filter(i => board[i] === '');
if (corners.length > 0) { placeBot(corners[Math.floor(Math.random() * corners.length)]); return; }
// 5. Любая
placeBot(empty[Math.floor(Math.random() * empty.length)]);
}, 400);
}
function placeBot(i) {
board[i] = 'X';
botThinking = false;
sfxPlace();
const w = checkWinner();
if (w) { endGame(w); return; }
playerTurn = true;
updateHUD();
}
function placePlayer(i) {
if (gameOver || !playerTurn || board[i] !== '') return;
board[i] = 'O';
sfxPlace();
const w = checkWinner();
if (w) { endGame(w); return; }
playerTurn = false;
updateHUD();
botMove();
}
function endGame(w) {
gameOver = true;
winner = w;
if (w === 'player') { sfxWin(); }
else if (w === 'bot') { sfxLose(); }
else { sfxDraw(); }
updateHUD();
}
function updateHUD() {
if (winner === 'player') hud.textContent = '🎉 Ты победил!';
else if (winner === 'bot') hud.textContent = '🤖 Бот победил!';
else if (winner === 'draw') hud.textContent = '🤝 Ничья!';
else hud.textContent = playerTurn ? 'Твой ход (❍)' : 'Бот думает...';
}
function restart() {
board = ['', '', '', '', '', '', '', '', ''];
gameOver = false;
winner = null;
playerTurn = true;
winLine = null;
botThinking = false;
sfxClick();
updateHUD();
}
// --- РИСОВАНИЕ ---
function drawGrid() {
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 4;
ctx.shadowColor = '#e94560';
ctx.shadowBlur = 10;
for (let i = 1; i < 3; i++) {
ctx.beginPath();
ctx.moveTo(i * CELL, 10);
ctx.lineTo(i * CELL, SIZE - 10);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(10, i * CELL);
ctx.lineTo(SIZE - 10, i * CELL);
ctx.stroke();
}
ctx.shadowBlur = 0;
}
function drawX(cx, cy) {
ctx.strokeStyle = '#3498db';
ctx.lineWidth = 6;
ctx.lineCap = 'round';
ctx.shadowColor = '#3498db';
ctx.shadowBlur = 12;
const s = CELL * 0.3;
ctx.beginPath();
ctx.moveTo(cx - s, cy - s);
ctx.lineTo(cx + s, cy + s);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx + s, cy - s);
ctx.lineTo(cx - s, cy + s);
ctx.stroke();
ctx.shadowBlur = 0;
}
function drawO(cx, cy, progress = 1) {
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 6;
ctx.lineCap = 'round';
ctx.shadowColor = '#e94560';
ctx.shadowBlur = 12;
const s = CELL * 0.3;
ctx.beginPath();
ctx.arc(cx, cy, s, -Math.PI/2, -Math.PI/2 + Math.PI * 2 * progress);
ctx.stroke();
ctx.shadowBlur = 0;
}
function drawWinLine() {
if (!winLine) return;
const [a,b,c] = winLine;
const ax = (a % 3) * CELL + CELL/2;
const ay = Math.floor(a / 3) * CELL + CELL/2;
const cx = (c % 3) * CELL + CELL/2;
const cy = Math.floor(c / 3) * CELL + CELL/2;
ctx.strokeStyle = '#ffd700';
ctx.lineWidth = 8;
ctx.lineCap = 'round';
ctx.shadowColor = '#ffd700';
ctx.shadowBlur = 20;
ctx.beginPath();
ctx.moveTo(ax, ay);
ctx.lineTo(cx, cy);
ctx.stroke();
ctx.shadowBlur = 0;
}
function draw() {
animTime += 0.05;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#1a1a2e');
grad.addColorStop(1, '#2a2a4e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGrid();
for (let i = 0; i < 9; i++) {
const cx = (i % 3) * CELL + CELL/2;
const cy = Math.floor(i / 3) * CELL + CELL/2;
if (board[i] === 'X') {
drawX(cx, cy);
} else if (board[i] === 'O') {
drawO(cx, cy, 1);
}
}
drawWinLine();
if (!gameOver && !botThinking) {
// Подсказка — куда можно ходить
for (let i = 0; i < 9; i++) {
if (board[i] === '') {
const cx = (i % 3) * CELL + CELL/2;
const cy = Math.floor(i / 3) * CELL + CELL/2;
ctx.fillStyle = 'rgba(255,255,255,0.05)';
ctx.beginPath();
ctx.arc(cx, cy, CELL * 0.35, 0, Math.PI * 2);
ctx.fill();
}
}
}
requestAnimationFrame(draw);
}
// --- УПРАВЛЕНИЕ ---
function getPos(e) {
const rect = canvas.getBoundingClientRect();
const t = e.touches ? e.touches[0] : e;
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
return {
x: (t.clientX - rect.left) * scaleX,
y: (t.clientY - rect.top) * scaleY
};
}
function handleClick(x, y) {
initAudio();
if (gameOver) return;
const col = Math.floor(x / CELL);
const row = Math.floor(y / CELL);
if (col < 0 || col > 2 || row < 0 || row > 2) return;
const idx = row * 3 + col;
placePlayer(idx);
}
canvas.addEventListener('touchstart', e => {
e.preventDefault();
const p = getPos(e);
handleClick(p.x, p.y);
}, { passive: false });
canvas.addEventListener('mousedown', e => {
e.preventDefault();
const p = getPos(e);
handleClick(p.x, p.y);
});
document.getElementById('restart').addEventListener('click', () => {
initAudio();
restart();
});
// Запуск
updateHUD();
draw();
</script>
</body>
</html>Game Source: Крестики-Нолики: Ты vs Бот
Creator: TurboGamer88
Libraries: none
Complexity: complex (304 lines, 8.6 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: vs-turbogamer88" to link back to the original. Then publish at arcadelab.ai/publish.