simplesnake / index.html
Hackavist's picture
can use tensorflow or another js machine learning library, can you create an ai that can play the snake game - Follow Up Deployment
42001ed verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Game</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script>
<style>
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.snake-head {
background-color: #4f46e5;
border-radius: 12px;
}
.snake-body {
background-color: #6366f1;
border-radius: 8px;
}
.food {
background-color: #ef4444;
border-radius: 50%;
animation: pulse 1s infinite;
}
#game-board {
touch-action: none;
}
.control-btn {
transition: all 0.2s;
}
.control-btn:active {
transform: scale(0.95);
background-color: #4f46e5;
}
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 relative">
<div class="text-center mb-6">
<h1 class="text-4xl font-bold text-indigo-400 mb-2">Snake Game</h1>
<div class="flex justify-center gap-8 items-center">
<div class="text-xl">
<span class="text-gray-400">Score:</span>
<span id="score" class="font-bold text-indigo-300">0</span>
</div>
<div class="text-xl">
<span class="text-gray-400">High Score:</span>
<span id="high-score" class="font-bold text-indigo-300">0</span>
</div>
</div>
</div>
<div id="game-container" class="relative">
<div id="game-board" class="bg-gray-800 border-2 border-indigo-500 rounded-lg"></div>
<!-- Game Over Screen -->
<div id="game-over" class="hidden absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center rounded-lg">
<h2 class="text-4xl font-bold text-red-500 mb-4">Game Over!</h2>
<div class="text-xl mb-6">
Final Score: <span id="final-score" class="font-bold text-indigo-300">0</span>
</div>
<button id="restart-btn" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-2 px-6 rounded-full transition">
Play Again
</button>
</div>
<!-- Start Screen -->
<div id="start-screen" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center rounded-lg">
<h2 class="text-4xl font-bold text-indigo-400 mb-6">Snake Game</h2>
<p class="text-gray-300 mb-8 text-center max-w-md px-4">
Use arrow keys or swipe to control the snake. Eat the food (red dots) to grow and earn points!
</p>
<button id="start-btn" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 px-8 rounded-full text-lg transition">
Start Game
</button>
</div>
</div>
<!-- AI Controls -->
<div class="mt-6 flex justify-center gap-4">
<button id="ai-btn" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-2 px-6 rounded-full transition">
AI Player
</button>
<button id="human-btn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-6 rounded-full transition">
Human Player
</button>
</div>
<!-- Mobile Controls -->
<div id="mobile-controls" class="mt-6 grid grid-cols-3 gap-2 sm:hidden">
<div class="col-start-2">
<button id="up-btn" class="control-btn bg-gray-700 hover:bg-gray-600 w-16 h-16 rounded-full flex items-center justify-center">
</button>
</div>
<div class="col-span-3 flex justify-center gap-4 mt-2">
<button id="left-btn" class="control-btn bg-gray-700 hover:bg-gray-600 w-16 h-16 rounded-full flex items-center justify-center">
</button>
<button id="down-btn" class="control-btn bg-gray-700 hover:bg-gray-600 w-16 h-16 rounded-full flex items-center justify-center">
</button>
<button id="right-btn" class="control-btn bg-gray-700 hover:bg-gray-600 w-16 h-16 rounded-full flex items-center justify-center">
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game configuration
const GRID_SIZE = 20;
const CELL_SIZE = 20;
const GAME_SPEED = 150; // ms
// Game elements
const gameBoard = document.getElementById('game-board');
const scoreDisplay = document.getElementById('score');
const highScoreDisplay = document.getElementById('high-score');
const startScreen = document.getElementById('start-screen');
const gameOverScreen = document.getElementById('game-over');
const finalScoreDisplay = document.getElementById('final-score');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
// Mobile controls
const upBtn = document.getElementById('up-btn');
const downBtn = document.getElementById('down-btn');
const leftBtn = document.getElementById('left-btn');
const rightBtn = document.getElementById('right-btn');
// Game state
let snake = [];
let direction = 'right';
let nextDirection = 'right';
let food = {};
let score = 0;
let highScore = localStorage.getItem('snakeHighScore') || 0;
let gameInterval;
let isPaused = false;
let gameRunning = false;
// Initialize game board
function initGameBoard() {
gameBoard.innerHTML = '';
gameBoard.style.display = 'grid';
gameBoard.style.gridTemplateColumns = `repeat(${GRID_SIZE}, ${CELL_SIZE}px)`;
gameBoard.style.gridTemplateRows = `repeat(${GRID_SIZE}, ${CELL_SIZE}px)`;
gameBoard.style.width = `${GRID_SIZE * CELL_SIZE}px`;
gameBoard.style.height = `${GRID_SIZE * CELL_SIZE}px`;
highScoreDisplay.textContent = highScore;
}
// Start game
function startGame() {
// Reset game state
snake = [
{x: 5, y: 10},
{x: 4, y: 10},
{x: 3, y: 10}
];
direction = 'right';
nextDirection = 'right';
score = 0;
scoreDisplay.textContent = '0';
// Hide start screen
startScreen.classList.add('hidden');
gameOverScreen.classList.add('hidden');
// Generate first food
generateFood();
// Draw initial state
draw();
// Start game loop
gameRunning = true;
gameInterval = setInterval(gameLoop, GAME_SPEED);
}
// Game loop
function gameLoop() {
if (isPaused) return;
// Update snake direction
direction = nextDirection;
// Move snake
const head = { ...snake[0] };
switch (direction) {
case 'up':
head.y -= 1;
break;
case 'down':
head.y += 1;
break;
case 'left':
head.x -= 1;
break;
case 'right':
head.x += 1;
break;
}
// Check for collisions
if (
head.x < 0 || head.x >= GRID_SIZE ||
head.y < 0 || head.y >= GRID_SIZE ||
snake.some(segment => segment.x === head.x && segment.y === head.y)
) {
gameOver();
return;
}
// Add new head
snake.unshift(head);
// Check if food was eaten
if (head.x === food.x && head.y === food.y) {
score += 10;
scoreDisplay.textContent = score;
generateFood();
} else {
// Remove tail if no food was eaten
snake.pop();
}
// Redraw game
draw();
}
// Draw game state
function draw() {
gameBoard.innerHTML = '';
// Draw snake
snake.forEach((segment, index) => {
const segmentElement = document.createElement('div');
segmentElement.style.gridColumn = segment.x + 1;
segmentElement.style.gridRow = segment.y + 1;
segmentElement.className = index === 0 ? 'snake-head' : 'snake-body';
gameBoard.appendChild(segmentElement);
});
// Draw food
const foodElement = document.createElement('div');
foodElement.style.gridColumn = food.x + 1;
foodElement.style.gridRow = food.y + 1;
foodElement.className = 'food';
gameBoard.appendChild(foodElement);
}
// Generate food at random position
function generateFood() {
// Find all empty cells
const emptyCells = [];
for (let y = 0; y < GRID_SIZE; y++) {
for (let x = 0; x < GRID_SIZE; x++) {
if (!snake.some(segment => segment.x === x && segment.y === y)) {
emptyCells.push({x, y});
}
}
}
// Place food in random empty cell
if (emptyCells.length > 0) {
const randomIndex = Math.floor(Math.random() * emptyCells.length);
food = emptyCells[randomIndex];
}
}
// Game over
function gameOver() {
clearInterval(gameInterval);
gameRunning = false;
// Update high score if needed
if (score > highScore) {
highScore = score;
localStorage.setItem('snakeHighScore', highScore);
highScoreDisplay.textContent = highScore;
}
// Show game over screen
finalScoreDisplay.textContent = score;
gameOverScreen.classList.remove('hidden');
}
// Handle keyboard input
function handleKeyDown(e) {
if (!gameRunning) return;
// Prevent key repeating
if (e.key === 'ArrowUp' || e.key === 'ArrowDown' ||
e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
e.preventDefault();
}
switch (e.key) {
case 'ArrowUp':
if (direction !== 'down') nextDirection = 'up';
break;
case 'ArrowDown':
if (direction !== 'up') nextDirection = 'down';
break;
case 'ArrowLeft':
if (direction !== 'right') nextDirection = 'left';
break;
case 'ArrowRight':
if (direction !== 'left') nextDirection = 'right';
break;
case ' ':
isPaused = !isPaused;
break;
}
}
// Touch controls for mobile
let touchStartX = 0;
let touchStartY = 0;
function handleTouchStart(e) {
touchStartX = e.changedTouches[0].screenX;
touchStartY = e.changedTouches[0].screenY;
}
function handleTouchEnd(e) {
if (!gameRunning) return;
const touchEndX = e.changedTouches[0].screenX;
const touchEndY = e.changedTouches[0].screenY;
const dx = touchEndX - touchStartX;
const dy = touchEndY - touchStartY;
// Determine swipe direction based on greatest change
if (Math.abs(dx) > Math.abs(dy)) {
if (dx > 0 && direction !== 'left') {
nextDirection = 'right';
} else if (dx < 0 && direction !== 'right') {
nextDirection = 'left';
}
} else {
if (dy > 0 && direction !== 'up') {
nextDirection = 'down';
} else if (dy < 0 && direction !== 'down') {
nextDirection = 'up';
}
}
}
// Event listeners
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
document.addEventListener('keydown', handleKeyDown);
gameBoard.addEventListener('touchstart', handleTouchStart, {passive: true});
gameBoard.addEventListener('touchend', handleTouchEnd, {passive: true});
// Mobile control buttons
upBtn.addEventListener('touchstart', () => { if (direction !== 'down') nextDirection = 'up'; });
downBtn.addEventListener('touchstart', () => { if (direction !== 'up') nextDirection = 'down'; });
leftBtn.addEventListener('touchstart', () => { if (direction !== 'right') nextDirection = 'left'; });
rightBtn.addEventListener('touchstart', () => { if (direction !== 'left') nextDirection = 'right'; });
// Prevent default touch behavior
[upBtn, downBtn, leftBtn, rightBtn].forEach(btn => {
btn.addEventListener('touchend', (e) => e.preventDefault());
});
// Simple AI decision making
function aiMove() {
if (!gameRunning) return;
const head = snake[0];
const nextHead = { ...head };
// Check all possible directions
const possibleDirections = ['up', 'down', 'left', 'right'];
const validDirections = possibleDirections.filter(dir => {
// Don't reverse direction
if ((dir === 'up' && direction === 'down') ||
(dir === 'down' && direction === 'up') ||
(dir === 'left' && direction === 'right') ||
(dir === 'right' && direction === 'left')) {
return false;
}
// Check if direction is valid (not hitting wall or self)
let testHead = {...head};
switch(dir) {
case 'up': testHead.y--; break;
case 'down': testHead.y++; break;
case 'left': testHead.x--; break;
case 'right': testHead.x++; break;
}
// Check boundaries
if (testHead.x < 0 || testHead.x >= GRID_SIZE ||
testHead.y < 0 || testHead.y >= GRID_SIZE) {
return false;
}
// Check collision with self
if (snake.some(segment => segment.x === testHead.x && segment.y === testHead.y)) {
return false;
}
return true;
});
// If no valid directions (shouldn't happen in normal play), keep current direction
if (validDirections.length === 0) return;
// Simple goal: move towards food
const dx = food.x - head.x;
const dy = food.y - head.y;
// Prefer directions that move towards food
validDirections.sort((a, b) => {
let scoreA = 0, scoreB = 0;
// Test next position for direction a
let testA = {...head};
switch(a) {
case 'up': testA.y--; break;
case 'down': testA.y++; break;
case 'left': testA.x--; break;
case 'right': testA.x++; break;
}
// Test next position for direction b
let testB = {...head};
switch(b) {
case 'up': testB.y--; break;
case 'down': testB.y++; break;
case 'left': testB.x--; break;
case 'right': testB.x++; break;
}
// Score based on distance to food
scoreA = Math.abs(food.x - testA.x) + Math.abs(food.y - testA.y);
scoreB = Math.abs(food.x - testB.x) + Math.abs(food.y - testB.y);
return scoreA - scoreB;
});
// Choose the best direction (shortest distance to food)
nextDirection = validDirections[0];
}
// Initialize game
initGameBoard();
// AI/Human mode toggle
let aiMode = false;
const aiBtn = document.getElementById('ai-btn');
const humanBtn = document.getElementById('human-btn');
aiBtn.addEventListener('click', () => {
aiMode = true;
aiBtn.classList.add('bg-indigo-700');
humanBtn.classList.remove('bg-indigo-700');
if (!gameRunning) startGame();
});
humanBtn.addEventListener('click', () => {
aiMode = false;
humanBtn.classList.add('bg-indigo-700');
aiBtn.classList.remove('bg-indigo-700');
});
// Modify game loop to include AI movement
const originalGameLoop = gameLoop;
gameLoop = function() {
if (aiMode) {
aiMove();
}
originalGameLoop();
};
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Hackavist/simplesnake" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>