Untitled
unknown
plain_text
6 days ago
11 kB
3
Indexable
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>Google‑style Snake — Single File</title> <style> :root{--bg:#0f1724;--panel:#0b1220;--accent:#26de81;--muted:#94a3b8;--danger:#ff3b30} *{box-sizing:border-box;font-family:Inter,ui-sans-serif,system-ui,Segoe UI,Roboto,"Helvetica Neue",Arial} html,body{height:100%;margin:0;background:linear-gradient(180deg,var(--bg),#071024);color:#e6eef8} .wrap{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px} .card{background:rgba(255,255,255,0.03);border-radius:12px;padding:18px;max-width:900px;width:100%;display:grid;grid-template-columns:1fr 320px;gap:16px;box-shadow:0 10px 30px rgba(2,6,23,0.6)} header{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between} h1{font-size:18px;margin:0} .board-wrap{display:flex;flex-direction:column;gap:12px;align-items:center} canvas{background:linear-gradient(180deg,#071428 0%, #052033 100%);image-rendering:pixelated;border-radius:8px} .controls{display:flex;gap:8px;flex-wrap:wrap} .btn{background:transparent;border:1px solid rgba(255,255,255,0.06);padding:8px 10px;border-radius:8px;cursor:pointer;color:var(--muted)} .btn.primary{border-color:var(--accent);color:var(--accent)} .stats{background:rgba(255,255,255,0.02);padding:12px;border-radius:8px} .row{display:flex;justify-content:space-between;gap:12px} label{font-size:12px;color:var(--muted)} input[type=range]{width:100%} .small{font-size:13px;color:var(--muted)} .footer{font-size:12px;color:var(--muted);grid-column:1/-1;text-align:center;margin-top:4px} @media(max-width:880px){.card{grid-template-columns:1fr;}} </style> </head> <body> <div class="wrap"> <div class="card"> <header> <h1>Google‑style Snake — single file</h1> <div class="small">Use arrow keys / WASD. Touch: swipe. Space to pause.</div> </header> <div class="board-wrap"> <canvas id="game" width="480" height="480"></canvas> <div class="controls"> <button id="startBtn" class="btn primary">Start</button> <button id="pauseBtn" class="btn">Pause</button> <button id="resetBtn" class="btn">Reset</button> <div style="min-width:180px;"> <label>Speed <span id="spdLabel">8</span></label> <input id="speed" type="range" min="4" max="18" value="8"> </div> <div style="min-width:120px;"> <label>Grid size</label> <select id="gridSz" class="btn" style="padding:6px 8px"> <option value="12">12x12</option> <option value="16" selected>16x16</option> <option value="20">20x20</option> <option value="24">24x24</option> </select> </div> </div> </div> <aside class="stats"> <div class="row"><div>Score</div><div id="score">0</div></div> <div class="row"><div>Highscore</div><div id="highscore">0</div></div> <div style="height:12px"></div> <div class="small">Controls: Arrow keys / WASD. Press space to pause. Tap and swipe on mobile.</div> </aside> <div class="footer">Built for you — open this file in any modern browser to play. Want features added? Ask!</div> </div> </div> <script> // Simple single-file Snake game (() => { const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); let grid = parseInt(document.getElementById('gridSz').value); // number of cells per row let cellSize = Math.floor(canvas.width / grid); let speed = Number(document.getElementById('speed').value); const spdLabel = document.getElementById('spdLabel'); const startBtn = document.getElementById('startBtn'); const pauseBtn = document.getElementById('pauseBtn'); const resetBtn = document.getElementById('resetBtn'); const scoreEl = document.getElementById('score'); const highEl = document.getElementById('highscore'); const speedInput = document.getElementById('speed'); const gridSelect = document.getElementById('gridSz'); let frameInterval = 1000 / speed; // ms per move let lastTime = 0; let running = false; let paused = false; // Game state let snake, dir, nextDir, food, score; // Touch handling let touchStart = null; function resetState() { grid = parseInt(gridSelect.value); cellSize = Math.floor(canvas.width / grid); speed = Number(speedInput.value); frameInterval = 1000 / speed; const startX = Math.floor(grid/2); const startY = Math.floor(grid/2); snake = [ {x: startX, y: startY}, {x: startX-1, y: startY}, {x: startX-2, y: startY} ]; dir = {x:1,y:0}; nextDir = {x:1,y:0}; placeFood(); score = 0; scoreEl.textContent = score; highEl.textContent = localStorage.getItem('snake_high') || 0; } function placeFood(){ // choose random empty cell const taken = new Set(snake.map(s=>s.x+','+s.y)); let tries = 0; do{ food = {x: Math.floor(Math.random()*grid), y: Math.floor(Math.random()*grid)}; tries++; if(tries>1000) break; } while(taken.has(food.x+','+food.y)); } function step(){ // move snake dir = nextDir; // apply queued direction const head = {x: snake[0].x + dir.x, y: snake[0].y + dir.y}; // wrap around edges (classic Google snake behavior uses walls optionally; we wrap here) head.x = (head.x + grid) % grid; head.y = (head.y + grid) % grid; // collision with self? if(snake.some(s => s.x===head.x && s.y===head.y)){ gameOver(); return; } snake.unshift(head); // eat food? if(head.x===food.x && head.y===food.y){ score += 1; scoreEl.textContent = score; placeFood(); } else { snake.pop(); } } function gameOver(){ running = false; paused = false; startBtn.textContent = 'Start'; // update highscore const prev = Number(localStorage.getItem('snake_high') || 0); if(score>prev) localStorage.setItem('snake_high', score); highEl.textContent = localStorage.getItem('snake_high'); // flash border canvas.animate([{boxShadow:'0 0 0 0 rgba(255,0,0,0.0)'},{boxShadow:'0 0 0 8px rgba(255,0,0,0.18)'}],{duration:400,iterations:1}); } function draw(){ // clear ctx.clearRect(0,0,canvas.width,canvas.height); // background grid ctx.fillStyle = '#041025'; ctx.fillRect(0,0,canvas.width,canvas.height); // draw food drawCell(food.x, food.y, '#ff3b30'); // draw snake for(let i=snake.length-1;i>=0;i--){ const s = snake[i]; const pct = i/snake.length; // head brighter drawCell(s.x, s.y, i===0? '#26de81' : `rgba(38,222,129,${0.5 + pct*0.5})`); } // score overlay (nice brushed look) ctx.fillStyle='rgba(255,255,255,0.03)'; ctx.fillRect(6,6,120,34); ctx.fillStyle='#cfe9d7'; ctx.font='14px Inter, Arial'; ctx.fillText('Score: '+score, 14, 28); } function drawCell(x,y,fill){ const pad = Math.max(1, Math.floor(cellSize*0.06)); const px = x*cellSize + pad; const py = y*cellSize + pad; const size = Math.max(2, cellSize - pad*2); ctx.fillStyle = fill; roundRect(ctx,px,py,size,size,Math.max(2,cellSize*0.12)); ctx.fill(); } function roundRect(ctx,x,y,w,h,r){ ctx.beginPath(); ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath(); } function loop(ts){ if(!running) return; if(paused){ lastTime = ts; requestAnimationFrame(loop); return; } if(ts - lastTime >= frameInterval){ step(); draw(); lastTime = ts; } requestAnimationFrame(loop); } // Input handling const keyMap = { ArrowUp: {x:0,y:-1}, ArrowDown:{x:0,y:1}, ArrowLeft:{x:-1,y:0}, ArrowRight:{x:1,y:0}, w:{x:0,y:-1}, s:{x:0,y:1}, a:{x:-1,y:0}, d:{x:1,y:0} }; window.addEventListener('keydown', e =>{ if(e.code==='Space'){ togglePause(); e.preventDefault(); return; } const k = e.key; if(keyMap[k]){ const nd = keyMap[k]; // prevent reversing if(nd.x === -dir.x && nd.y === -dir.y) return; nextDir = nd; } }); // Touch swipe support canvas.addEventListener('touchstart', (ev)=>{ if(ev.touches.length>1) return; const t = ev.touches[0]; touchStart = {x:t.clientX, y:t.clientY}; }); canvas.addEventListener('touchmove',(ev)=>{ ev.preventDefault(); }, {passive:false}); canvas.addEventListener('touchend',(ev)=>{ if(!touchStart) return; const t = ev.changedTouches[0]; const dx = t.clientX - touchStart.x; const dy = t.clientY - touchStart.y; if(Math.abs(dx)>30 || Math.abs(dy)>30){ if(Math.abs(dx)>Math.abs(dy)){ // left/right nextDir = dx>0? {x:1,y:0} : {x:-1,y:0}; } else { nextDir = dy>0? {x:0,y:1} : {x:0,y:-1}; } } touchStart = null; }); // Buttons startBtn.addEventListener('click', ()=>{ if(!running){ running = true; paused = false; startBtn.textContent = 'Running'; lastTime = performance.now(); requestAnimationFrame(loop); } }); pauseBtn.addEventListener('click', togglePause); resetBtn.addEventListener('click', ()=>{ resetState(); draw(); }); function togglePause(){ if(!running) return; paused = !paused; pauseBtn.textContent = paused? 'Resume' : 'Pause'; } // speed and grid controls speedInput.addEventListener('input', ()=>{ speed = Number(speedInput.value); frameInterval = 1000 / speed; spdLabel.textContent = speed; }); gridSelect.addEventListener('change', ()=>{ resetState(); draw(); }); // resizing canvas on device pixel ratio to make it sharp function adjustCanvas(){ const ratio = window.devicePixelRatio || 1; canvas.width = 480 * ratio; canvas.height = 480 * ratio; canvas.style.width = '480px'; canvas.style.height = '480px'; ctx.setTransform(ratio,0,0,ratio,0,0); cellSize = Math.floor(480 / grid); draw(); } window.addEventListener('resize', adjustCanvas); // init resetState(); adjustCanvas(); draw(); })(); </script> </body> </html>
Editor is loading...
Leave a Comment