Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Snake Game with Admin Panel</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary: #6c5ce7; | |
| --secondary: #a29bfe; | |
| --accent: #00cec9; | |
| --dark: #2d3436; | |
| --light: #dfe6e9; | |
| --success: #00b894; | |
| --danger: #d63031; | |
| --warning: #fdcb6e; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); | |
| min-height: 100vh; | |
| color: #fff; | |
| } | |
| header { | |
| background: rgba(0, 0, 0, 0.3); | |
| backdrop-filter: blur(10px); | |
| padding: 1rem 2rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| color: var(--accent); | |
| } | |
| .logo i { | |
| font-size: 2rem; | |
| } | |
| .built-with { | |
| color: var(--secondary); | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| transition: color 0.3s; | |
| } | |
| .built-with:hover { | |
| color: var(--accent); | |
| } | |
| .nav-buttons { | |
| display: flex; | |
| gap: 1rem; | |
| } | |
| .nav-btn { | |
| padding: 0.5rem 1.5rem; | |
| border: none; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| transition: all 0.3s; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .nav-btn.game-btn { | |
| background: var(--accent); | |
| color: var(--dark); | |
| } | |
| .nav-btn.admin-btn { | |
| background: var(--primary); | |
| color: #fff; | |
| } | |
| .nav-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); | |
| } | |
| main { | |
| padding: 2rem; | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| } | |
| .game-container { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 2rem; | |
| } | |
| .game-info { | |
| display: flex; | |
| gap: 3rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .info-card { | |
| background: rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(10px); | |
| padding: 1rem 2rem; | |
| border-radius: 15px; | |
| text-align: center; | |
| min-width: 150px; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .info-card h3 { | |
| font-size: 0.9rem; | |
| color: var(--secondary); | |
| margin-bottom: 0.5rem; | |
| } | |
| .info-card .value { | |
| font-size: 2rem; | |
| font-weight: bold; | |
| color: var(--accent); | |
| } | |
| .game-board-wrapper { | |
| position: relative; | |
| } | |
| #gameCanvas { | |
| background: linear-gradient(145deg, #1a1a2e, #0f0f1a); | |
| border-radius: 15px; | |
| box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5), | |
| inset 0 0 60px rgba(108, 92, 231, 0.1); | |
| border: 3px solid var(--primary); | |
| } | |
| .game-controls { | |
| display: flex; | |
| gap: 1rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .control-btn { | |
| padding: 1rem 2rem; | |
| border: none; | |
| border-radius: 30px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| font-size: 1rem; | |
| transition: all 0.3s; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .control-btn.start { | |
| background: linear-gradient(135deg, var(--success), #00a884); | |
| color: #fff; | |
| } | |
| .control-btn.pause { | |
| background: linear-gradient(135deg, var(--warning), #e5b85c); | |
| color: var(--dark); | |
| } | |
| .control-btn.restart { | |
| background: linear-gradient(135deg, var(--danger), #b52828); | |
| color: #fff; | |
| } | |
| .control-btn:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 5px 25px rgba(0, 0, 0, 0.3); | |
| } | |
| .mobile-controls { | |
| display: none; | |
| grid-template-columns: repeat(3, 60px); | |
| grid-template-rows: repeat(3, 60px); | |
| gap: 5px; | |
| margin-top: 1rem; | |
| } | |
| .mobile-btn { | |
| background: rgba(255, 255, 255, 0.1); | |
| border: 2px solid var(--primary); | |
| border-radius: 10px; | |
| color: #fff; | |
| font-size: 1.5rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .mobile-btn:active { | |
| background: var(--primary); | |
| transform: scale(0.95); | |
| } | |
| .mobile-btn.up { grid-column: 2; grid-row: 1; } | |
| .mobile-btn.left { grid-column: 1; grid-row: 2; } | |
| .mobile-btn.right { grid-column: 3; grid-row: 2; } | |
| .mobile-btn.down { grid-column: 2; grid-row: 3; } | |
| /* Admin Panel Styles */ | |
| .admin-container { | |
| display: none; | |
| } | |
| .admin-header { | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| } | |
| .admin-header h1 { | |
| font-size: 2.5rem; | |
| color: var(--accent); | |
| margin-bottom: 0.5rem; | |
| } | |
| .admin-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); | |
| gap: 2rem; | |
| } | |
| .admin-card { | |
| background: rgba(255, 255, 255, 0.05); | |
| backdrop-filter: blur(10px); | |
| border-radius: 20px; | |
| padding: 1.5rem; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .admin-card h2 { | |
| color: var(--secondary); | |
| margin-bottom: 1.5rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-size: 1.3rem; | |
| } | |
| .setting-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| .setting-group label { | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| color: var(--light); | |
| font-weight: 500; | |
| } | |
| .setting-group input[type="range"] { | |
| width: 100%; | |
| height: 8px; | |
| border-radius: 4px; | |
| background: rgba(255, 255, 255, 0.1); | |
| outline: none; | |
| -webkit-appearance: none; | |
| } | |
| .setting-group input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: var(--accent); | |
| cursor: pointer; | |
| box-shadow: 0 2px 10px rgba(0, 206, 201, 0.5); | |
| } | |
| .setting-group input[type="number"], | |
| .setting-group input[type="text"], | |
| .setting-group select { | |
| width: 100%; | |
| padding: 0.75rem; | |
| border: 2px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 10px; | |
| background: rgba(0, 0, 0, 0.3); | |
| color: #fff; | |
| font-size: 1rem; | |
| transition: border-color 0.3s; | |
| } | |
| .setting-group input:focus, | |
| .setting-group select:focus { | |
| border-color: var(--accent); | |
| outline: none; | |
| } | |
| .color-picker-group { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .color-picker-group input[type="color"] { | |
| width: 50px; | |
| height: 50px; | |
| border: none; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| background: transparent; | |
| } | |
| .toggle-switch { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .toggle-switch input { | |
| display: none; | |
| } | |
| .toggle-slider { | |
| width: 60px; | |
| height: 30px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 15px; | |
| position: relative; | |
| cursor: pointer; | |
| transition: background 0.3s; | |
| } | |
| .toggle-slider::before { | |
| content: ''; | |
| position: absolute; | |
| width: 24px; | |
| height: 24px; | |
| background: #fff; | |
| border-radius: 50%; | |
| top: 3px; | |
| left: 3px; | |
| transition: transform 0.3s; | |
| } | |
| .toggle-switch input:checked + .toggle-slider { | |
| background: var(--accent); | |
| } | |
| .toggle-switch input:checked + .toggle-slider::before { | |
| transform: translateX(30px); | |
| } | |
| .save-btn { | |
| width: 100%; | |
| padding: 1rem; | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| border: none; | |
| border-radius: 10px; | |
| color: #fff; | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| margin-top: 1rem; | |
| } | |
| .save-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 30px rgba(108, 92, 231, 0.4); | |
| } | |
| .leaderboard { | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| .leaderboard-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 0.75rem; | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 10px; | |
| margin-bottom: 0.5rem; | |
| transition: background 0.3s; | |
| } | |
| .leaderboard-item:hover { | |
| background: rgba(255, 255, 255, 0.1); | |
| } | |
| .leaderboard-item .rank { | |
| width: 30px; | |
| height: 30px; | |
| background: var(--primary); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: bold; | |
| } | |
| .leaderboard-item .rank.gold { background: #ffd700; color: #000; } | |
| .leaderboard-item .rank.silver { background: #c0c0c0; color: #000; } | |
| .leaderboard-item .rank.bronze { background: #cd7f32; color: #000; } | |
| .leaderboard-item .name { | |
| flex: 1; | |
| margin-left: 1rem; | |
| } | |
| .leaderboard-item .score { | |
| font-weight: bold; | |
| color: var(--accent); | |
| } | |
| .clear-btn { | |
| background: var(--danger); | |
| padding: 0.5rem 1rem; | |
| border: none; | |
| border-radius: 5px; | |
| color: #fff; | |
| cursor: pointer; | |
| margin-top: 1rem; | |
| transition: all 0.3s; | |
| } | |
| .clear-btn:hover { | |
| background: #b52828; | |
| } | |
| /* Game Over Modal */ | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.8); | |
| backdrop-filter: blur(5px); | |
| z-index: 1000; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .modal-content { | |
| background: linear-gradient(145deg, #1a1a2e, #16213e); | |
| padding: 3rem; | |
| border-radius: 20px; | |
| text-align: center; | |
| border: 2px solid var(--primary); | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); | |
| animation: modalPop 0.3s ease; | |
| } | |
| @keyframes modalPop { | |
| from { | |
| transform: scale(0.8); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: scale(1); | |
| opacity: 1; | |
| } | |
| } | |
| .modal-content h2 { | |
| font-size: 2.5rem; | |
| color: var(--danger); | |
| margin-bottom: 1rem; | |
| } | |
| .modal-content .final-score { | |
| font-size: 4rem; | |
| color: var(--accent); | |
| font-weight: bold; | |
| margin: 1rem 0; | |
| } | |
| .modal-content input { | |
| padding: 0.75rem 1.5rem; | |
| border: 2px solid var(--primary); | |
| border-radius: 10px; | |
| background: rgba(0, 0, 0, 0.3); | |
| color: #fff; | |
| font-size: 1rem; | |
| margin: 1rem 0; | |
| width: 100%; | |
| max-width: 250px; | |
| } | |
| .modal-buttons { | |
| display: flex; | |
| gap: 1rem; | |
| justify-content: center; | |
| margin-top: 1.5rem; | |
| } | |
| .instructions { | |
| background: rgba(255, 255, 255, 0.05); | |
| padding: 1.5rem; | |
| border-radius: 15px; | |
| margin-top: 1rem; | |
| text-align: left; | |
| } | |
| .instructions h3 { | |
| color: var(--accent); | |
| margin-bottom: 1rem; | |
| } | |
| .instructions ul { | |
| list-style: none; | |
| } | |
| .instructions li { | |
| padding: 0.5rem 0; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .instructions li i { | |
| color: var(--primary); | |
| } | |
| @media (max-width: 768px) { | |
| header { | |
| flex-direction: column; | |
| gap: 1rem; | |
| padding: 1rem; | |
| } | |
| .game-info { | |
| gap: 1rem; | |
| } | |
| .info-card { | |
| padding: 0.75rem 1.5rem; | |
| min-width: 100px; | |
| } | |
| .info-card .value { | |
| font-size: 1.5rem; | |
| } | |
| .mobile-controls { | |
| display: grid; | |
| } | |
| .admin-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| #gameCanvas { | |
| max-width: 100%; | |
| height: auto; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .nav-buttons { | |
| flex-direction: column; | |
| width: 100%; | |
| } | |
| .nav-btn { | |
| justify-content: center; | |
| } | |
| .game-controls { | |
| flex-direction: column; | |
| width: 100%; | |
| } | |
| .control-btn { | |
| justify-content: center; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="logo"> | |
| <i class="fas fa-dragon"></i> | |
| <span>Snake Game</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with"> | |
| Built with anycoder | |
| </a> | |
| <div class="nav-buttons"> | |
| <button class="nav-btn game-btn" onclick="showGame()"> | |
| <i class="fas fa-gamepad"></i> Play Game | |
| </button> | |
| <button class="nav-btn admin-btn" onclick="showAdmin()"> | |
| <i class="fas fa-cog"></i> Admin Panel | |
| </button> | |
| </div> | |
| </header> | |
| <main> | |
| <!-- Game Container --> | |
| <div class="game-container" id="gameSection"> | |
| <div class="game-info"> | |
| <div class="info-card"> | |
| <h3><i class="fas fa-star"></i> Score</h3> | |
| <div class="value" id="currentScore">0</div> | |
| </div> | |
| <div class="info-card"> | |
| <h3><i class="fas fa-trophy"></i> High Score</h3> | |
| <div class="value" id="highScore">0</div> | |
| </div> | |
| <div class="info-card"> | |
| <h3><i class="fas fa-ruler"></i> Length</h3> | |
| <div class="value" id="snakeLength">3</div> | |
| </div> | |
| <div class="info-card"> | |
| <h3><i class="fas fa-tachometer-alt"></i> Speed</h3> | |
| <div class="value" id="currentSpeed">5</div> | |
| </div> | |
| </div> | |
| <div class="game-board-wrapper"> | |
| <canvas id="gameCanvas" width="400" height="400"></canvas> | |
| </div> | |
| <div class="game-controls"> | |
| <button class="control-btn start" id="startBtn" onclick="startGame()"> | |
| <i class="fas fa-play"></i> Start | |
| </button> | |
| <button class="control-btn pause" id="pauseBtn" onclick="togglePause()"> | |
| <i class="fas fa-pause"></i> Pause | |
| </button> | |
| <button class="control-btn restart" onclick="restartGame()"> | |
| <i class="fas fa-redo"></i> Restart | |
| </button> | |
| </div> | |
| <div class="mobile-controls"> | |
| <button class="mobile-btn up" onclick="changeDirection('up')"><i class="fas fa-arrow-up"></i></button> | |
| <button class="mobile-btn left" onclick="changeDirection('left')"><i class="fas fa-arrow-left"></i></button> | |
| <button class="mobile-btn right" onclick="changeDirection('right')"><i class="fas fa-arrow-right"></i></button> | |
| <button class="mobile-btn down" onclick="changeDirection('down')"><i class="fas fa-arrow-down"></i></button> | |
| </div> | |
| <div class="instructions"> | |
| <h3><i class="fas fa-info-circle"></i> How to Play</h3> | |
| <ul> | |
| <li><i class="fas fa-arrow-up"></i><i class="fas fa-arrow-down"></i><i class="fas fa-arrow-left"></i><i class="fas fa-arrow-right"></i> Use Arrow Keys or WASD to move</li> | |
| <li><i class="fas fa-apple-alt"></i> Eat food to grow and score points</li> | |
| <li><i class="fas fa-skull"></i> Don't hit the walls or yourself!</li> | |
| <li><i class="fas fa-bolt"></i> Speed increases as you grow</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <!-- Admin Container --> | |
| <div class="admin-container" id="adminSection"> | |
| <div class="admin-header"> | |
| <h1><i class="fas fa-cog"></i> Admin Panel</h1> | |
| <p>Customize your Snake Game experience</p> | |
| </div> | |
| <div class="admin-grid"> | |
| <!-- Game Settings --> | |
| <div class="admin-card"> | |
| <h2><i class="fas fa-sliders-h"></i> Game Settings</h2> | |
| <div class="setting-group"> | |
| <label>Initial Speed (1-10): <span id="speedValue">5</span></label> | |
| <input type="range" id="gameSpeed" min="1" max="10" value="5" oninput="updateSpeedValue()"> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Grid Size</label> | |
| <select id="gridSize"> | |
| <option value="10">Small (10x10)</option> | |
| <option value="20" selected>Medium (20x20)</option> | |
| <option value="30">Large (30x30)</option> | |
| </select> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Points per Food</label> | |
| <input type="number" id="pointsPerFood" value="10" min="1" max="100"> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Speed Increase Rate</label> | |
| <input type="range" id="speedIncrease" min="0" max="10" value="5" oninput="updateSpeedIncreaseValue()"> | |
| <span id="speedIncreaseValue">5</span> | |
| </div> | |
| <div class="setting-group toggle-switch"> | |
| <label>Wall Collision</label> | |
| <input type="checkbox" id="wallCollision" checked> | |
| <span class="toggle-slider" onclick="toggleWallCollision()"></span> | |
| </div> | |
| <button class="save-btn" onclick="saveGameSettings()"> | |
| <i class="fas fa-save"></i> Save Settings | |
| </button> | |
| </div> | |
| <!-- Appearance Settings --> | |
| <div class="admin-card"> | |
| <h2><i class="fas fa-palette"></i> Appearance</h2> | |
| <div class="setting-group"> | |
| <label>Snake Color</label> | |
| <div class="color-picker-group"> | |
| <input type="color" id="snakeColor" value="#00cec9"> | |
| <span>Primary snake color</span> | |
| </div> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Snake Head Color</label> | |
| <div class="color-picker-group"> | |
| <input type="color" id="snakeHeadColor" value="#00b894"> | |
| <span>Head highlight color</span> | |
| </div> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Food Color</label> | |
| <div class="color-picker-group"> | |
| <input type="color" id="foodColor" value="#d63031"> | |
| <span>Food item color</span> | |
| </div> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Background Color</label> | |
| <div class="color-picker-group"> | |
| <input type="color" id="bgColor" value="#1a1a2e"> | |
| <span>Game board background</span> | |
| </div> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Grid Lines Color</label> | |
| <div class="color-picker-group"> | |
| <input type="color" id="gridColor" value="#2d3436"> | |
| <span>Grid line color</span> | |
| </div> | |
| </div> | |
| <div class="setting-group toggle-switch"> | |
| <label>Show Grid Lines</label> | |
| <input type="checkbox" id="showGrid" checked> | |
| <span class="toggle-slider" onclick="toggleGrid()"></span> | |
| </div> | |
| <button class="save-btn" onclick="saveAppearanceSettings()"> | |
| <i class="fas fa-save"></i> Save Appearance | |
| </button> | |
| </div> | |
| <!-- Leaderboard --> | |
| <div class="admin-card"> | |
| <h2><i class="fas fa-trophy"></i> Leaderboard</h2> | |
| <div class="leaderboard" id="leaderboard"> | |
| <!-- Leaderboard items will be inserted here --> | |
| </div> | |
| <button class="clear-btn" onclick="clearLeaderboard()"> | |
| <i class="fas fa-trash"></i> Clear Leaderboard | |
| </button> | |
| </div> | |
| <!-- Statistics --> | |
| <div class="admin-card"> | |
| <h2><i class="fas fa-chart-bar"></i> Statistics</h2> | |
| <div class="setting-group"> | |
| <label>Total Games Played</label> | |
| <input type="text" id="totalGames" value="0" readonly> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Highest Score Ever</label> | |
| <input type="text" id="highestScore" value="0" readonly> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Total Food Eaten</label> | |
| <input type="text" id="totalFood" value="0" readonly> | |
| </div> | |
| <div class="setting-group"> | |
| <label>Average Score</label> | |
| <input type="text" id="avgScore" value="0" readonly> | |
| </div> | |
| <button class="clear-btn" onclick="resetStatistics()"> | |
| <i class="fas fa-undo"></i> Reset Statistics | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Game Over Modal --> | |
| <div class="modal" id="gameOverModal"> | |
| <div class="modal-content"> | |
| <h2><i class="fas fa-skull-crossbones"></i> Game Over!</h2> | |
| <p>Your Score</p> | |
| <div class="final-score" id="finalScore">0</div> | |
| <input type="text" id="playerName" placeholder="Enter your name" maxlength="20"> | |
| <div class="modal-buttons"> | |
| <button class="control-btn start" onclick="saveScore()"> | |
| <i class="fas fa-save"></i> Save Score | |
| </button> | |
| <button class="control-btn restart" onclick="playAgain()"> | |
| <i class="fas fa-redo"></i> Play Again | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Game Variables | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let gridSize = 20; | |
| let tileCount = canvas.width / gridSize; | |
| let snake = []; | |
| let food = { x: 0, y: 0 }; | |
| let direction = { x: 0, y: 0 }; | |
| let nextDirection = { x: 0, y: 0 }; | |
| let score = 0; | |
| let gameLoop = null; | |
| let isPaused = false; | |
| let isGameRunning = false; | |
| // Settings | |
| let settings = { | |
| speed: 5, | |
| gridSize: 20, | |
| pointsPerFood: 10, | |
| speedIncrease: 5, | |
| wallCollision: true, | |
| snakeColor: '#00cec9', | |
| snakeHeadColor: '#00b894', | |
| foodColor: '#d63031', | |
| bgColor: '#1a1a2e', | |
| gridColor: '#2d3436', | |
| showGrid: true | |
| }; | |
| // Statistics | |
| let stats = { | |
| totalGames: 0, | |
| highestScore: 0, | |
| totalFood: 0, | |
| scores: [] | |
| }; | |
| // Leaderboard | |
| let leaderboard = []; | |
| // Load saved data | |
| function loadSavedData() { | |
| const savedSettings = localStorage.getItem('snakeSettings'); | |
| const savedStats = localStorage.getItem('snakeStats'); | |
| const savedLeaderboard = localStorage.getItem('snakeLeaderboard'); | |
| if (savedSettings) settings = JSON.parse(savedSettings); | |
| if (savedStats) stats = JSON.parse(savedStats); | |
| if (savedLeaderboard) leaderboard = JSON.parse(savedLeaderboard); | |
| applySettings(); | |
| updateLeaderboard(); | |
| updateStatistics(); | |
| } | |
| // Initialize game | |
| function initGame() { | |
| tileCount = Math.floor(canvas.width / settings.gridSize); | |
| snake = [ | |
| { x: Math.floor(tileCount / 2), y: Math.floor(tileCount / 2) }, | |
| { x: Math.floor(tileCount / 2) - 1, y: Math.floor(tileCount / 2) }, | |
| { x: Math.floor(tileCount / 2) - 2, y: Math.floor(tileCount / 2) } | |
| ]; | |
| direction = { x: 0, y: 0 }; | |
| nextDirection = { x: 0, y: 0 }; | |
| score = 0; | |
| spawnFood(); | |
| updateDisplay(); | |
| draw(); | |
| } | |
| // Spawn food | |
| function spawnFood() { | |
| do { | |
| food.x = Math.floor(Math.random() * tileCount); | |
| food.y = Math.floor(Math.random() * tileCount); | |
| } while (snake.some(segment => segment.x === food.x && segment.y === food.y)); | |
| } | |
| // Draw game | |
| function draw() { | |
| // Clear canvas | |
| ctx.fillStyle = settings.bgColor; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Draw grid | |
| if (settings.showGrid) { | |
| ctx.strokeStyle = settings.gridColor; | |
| ctx.lineWidth = 0.5; | |
| for (let i = 0; i <= tileCount; i++) { | |
| ctx.beginPath(); | |
| ctx.moveTo(i * settings.gridSize, 0); | |
| ctx.lineTo(i * settings.gridSize, canvas.height); | |
| ctx.stroke(); | |
| ctx.beginPath(); | |
| ctx.moveTo(0, i * settings.gridSize); | |
| ctx.lineTo(canvas.width, i * settings.gridSize); | |
| ctx.stroke(); | |
| } | |
| } | |
| // Draw food | |
| ctx.fillStyle = settings.foodColor; | |
| ctx.beginPath(); | |
| const foodCenterX = food.x * settings.gridSize + settings.gridSize / 2; | |
| const foodCenterY = food.y * settings.gridSize + settings.gridSize / 2; | |
| ctx.arc(foodCenterX, foodCenterY, settings.gridSize / 2 - 2, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Draw food glow | |
| ctx.shadowColor = settings.foodColor; | |
| ctx.shadowBlur = 10; | |
| ctx.fill(); | |
| ctx.shadowBlur = 0; | |
| // Draw snake | |
| snake.forEach((segment, index) => { | |
| const isHead = index === 0; | |
| ctx.fillStyle = isHead ? settings.snakeHeadColor : settings.snakeColor; | |
| const x = segment.x * settings.gridSize; | |
| const y = segment.y * settings.gridSize; | |
| const size = settings.gridSize - 2; | |
| const radius = isHead ? 8 : 5; | |
| // Rounded rectangle | |
| ctx.beginPath(); | |
| ctx.roundRect(x + 1, y + 1, size, size, radius); | |
| ctx.fill(); | |
| // Draw eyes on head | |
| if (isHead) { | |
| ctx.fillStyle = '#fff'; | |
| const eyeSize = 4; | |
| const eyeOffset = 5; | |
| if (direction.x === 1) { | |
| ctx.beginPath(); | |
| ctx.arc(x + size - eyeOffset, y + eyeOffset + 2, eyeSize, 0, Math.PI * 2); | |
| ctx.arc(x + size - eyeOffset, y + size - eyeOffset - 2, eyeSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } else if (direction.x === -1) { | |
| ctx.beginPath(); | |
| ctx.arc(x + eyeOffset + 2, y + eyeOffset + 2, eyeSize, 0, Math.PI * 2); | |
| ctx.arc(x + eyeOffset + 2, y + size - eyeOffset - 2, eyeSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } else if (direction.y === -1) { | |
| ctx.beginPath(); | |
| ctx.arc(x + eyeOffset + 2, y + eyeOffset + 2, eyeSize, 0, Math.PI * 2); | |
| ctx.arc(x + size - eyeOffset - 2, y + eyeOffset + 2, eyeSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } else { | |
| ctx.beginPath(); | |
| ctx.arc(x + eyeOffset + 2, y + size - eyeOffset - 2, eyeSize, 0, Math.PI * 2); | |
| ctx.arc(x + size - eyeOffset - 2, y + size - eyeOffset - 2, eyeSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| }); | |
| } | |
| // Update game | |
| function update() { | |
| if (isPaused) return; | |
| direction = { ...nextDirection }; | |
| // Move snake | |
| const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y }; | |
| // Wall collision | |
| if (settings.wallCollision) { | |
| if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) { | |
| gameOver(); | |
| return; | |
| } | |
| } else { | |
| // Wrap around | |
| if (head.x < 0) head.x = tileCount - 1; | |
| if (head.x >= tileCount) head.x = 0; | |
| if (head.y < 0) head.y = tileCount - 1; | |
| if (head.y >= tileCount) head.y = 0; | |
| } | |
| // Self collision | |
| if (snake.some(segment => segment.x === head.x && segment.y === head.y)) { | |
| gameOver(); | |
| return; | |
| } | |
| snake.unshift(head); | |
| // Check food collision | |
| if (head.x === food.x && head.y === food.y) { | |
| score += settings.pointsPerFood; | |
| stats.totalFood++; | |
| spawnFood(); | |
| // Increase speed | |
| if (settings.speedIncrease > 0 && snake.length % 5 === 0) { | |
| const newSpeed = Math.min(settings.speed + (settings.speedIncrease / 10), 15); | |
| clearInterval(gameLoop); | |
| gameLoop = setInterval(gameStep, 1000 / (newSpeed * 2)); | |
| } | |
| } else { | |
| snake.pop(); | |
| } | |
| updateDisplay(); | |
| draw(); | |
| } | |
| // Game step | |
| function gameStep() { | |
| if (direction.x !== 0 || direction.y !== 0) { | |
| update(); | |
| } else { | |
| draw(); | |
| } | |
| } | |
| // Start game | |
| function startGame() { | |
| if (isGameRunning) return; | |
| isGameRunning = true; | |
| isPaused = false; | |
| stats.totalGames++; | |
| if (direction.x === 0 && direction.y === 0) { | |
| nextDirection = { x: 1, y: 0 }; | |
| } | |
| document.getElementById('startBtn').innerHTML = '<i class="fas fa-play"></i> Playing...'; | |
| document.getElementById('startBtn').disabled = true; | |
| gameLoop = setInterval(gameStep, 1000 / (settings.speed * 2)); | |
| } | |
| // Toggle pause | |
| function togglePause() { | |
| if (!isGameRunning) return; | |
| isPaused = !isPaused; | |
| const pauseBtn = document.getElementById('pauseBtn'); | |
| if (isPaused) { | |
| pauseBtn.innerHTML = '<i class="fas fa-play"></i> Resume'; | |
| } else { | |
| pauseBtn.innerHTML = '<i class="fas fa-pause"></i> Pause'; | |
| } | |
| } | |
| // Restart game | |
| function restartGame() { | |
| clearInterval(gameLoop); | |
| isGameRunning = false; | |
| isPaused = false; | |
| document.getElementById('startBtn').innerHTML = '<i class="fas fa-play"></i> Start'; | |
| document.getElementById('startBtn').disabled = false; | |
| document.getElementById('pauseBtn').innerHTML = '<i class="fas fa-pause"></i> Pause |