/* ============================================================ SNAKE PROTOCOL — Snake, spaceport energy theme ============================================================ */ (function () { 'use strict'; const { Game, STATE } = window.DSP; const S = window.DSP_SPRITES; const W = 800, H = 600; const CELL = 20, COLS = 36, ROWS = 24; const OX = (W - COLS * CELL) / 2, OY = (H - ROWS * CELL) / 2; class SnakeProtocol extends Game { constructor(canvas) { super({ name: 'snake', title: 'SNAKE PROTOCOL', width: W, height: H, canvas, loadSteps: ['BOOTING GRID', 'CHARGING CELLS', 'SYNCING SERPENT', 'SYNCING GRID'] }); this.grid = null; this.snake = []; this.dir = { x: 1, y: 0 }; this.nextDir = { x: 1, y: 0 }; this.food = null; this.special = null; this.tickRate = 8; this.tickAcc = 0; this.grow = 0; this.slowTimer = 0; this.ghostTimer = 0; this.stars = Array.from({ length: 50 }, () => ({ x: Math.random() * W, y: Math.random() * H, s: Math.random() < 0.2 ? 2 : 1 })); } reset() { this.snake = [{ x: 8, y: 12 }, { x: 7, y: 12 }, { x: 6, y: 12 }]; this.dir = { x: 1, y: 0 }; this.nextDir = { x: 1, y: 0 }; this.grow = 0; this.slowTimer = 0; this.ghostTimer = 0; this.tickRate = 8; this.placeFood(); this.special = null; } placeFood() { let p; do { p = { x: Math.floor(Math.random() * COLS), y: Math.floor(Math.random() * ROWS) }; } while (this.snake.some(s => s.x === p.x && s.y === p.y)); this.food = p; } placeSpecial() { let p; do { p = { x: Math.floor(Math.random() * COLS), y: Math.floor(Math.random() * ROWS) }; } while (this.snake.some(s => s.x === p.x && s.y === p.y) || (this.food && this.food.x === p.x && this.food.y === p.y)); this.special = { ...p, kind: Math.random() < 0.5 ? 'bonus' : 'ghost', life: 480 }; } update() { const i = this.input; if (i.wasPressed('ArrowUp', 'KeyW') && this.dir.y === 0) this.nextDir = { x: 0, y: -1 }; else if (i.wasPressed('ArrowDown', 'KeyS') && this.dir.y === 0) this.nextDir = { x: 0, y: 1 }; else if (i.wasPressed('ArrowLeft', 'KeyA') && this.dir.x === 0) this.nextDir = { x: -1, y: 0 }; else if (i.wasPressed('ArrowRight', 'KeyD') && this.dir.x === 0) this.nextDir = { x: 1, y: 0 }; if (this.slowTimer > 0) this.slowTimer--; if (this.ghostTimer > 0) this.ghostTimer--; if (this.special) { this.special.life--; if (this.special.life <= 0) this.special = null; } const rate = this.slowTimer > 0 ? this.tickRate + 4 : this.tickRate; this.tickAcc++; if (this.tickAcc < rate) return; this.tickAcc = 0; this.dir = this.nextDir; const head = { x: this.snake[0].x + this.dir.x, y: this.snake[0].y + this.dir.y }; if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS) { window.DSP_AUDIO.sfx('bigboom'); this.particles.burst(OX + head.x * CELL + CELL / 2, OY + head.y * CELL + CELL / 2, '#ff4d6d', 20, 3, 30); this.shake.trigger(6, 20); this.lives--; if (this.lives <= 0) { this.gameOver(); return; } this.reset(); return; } if (this.ghostTimer <= 0 && this.snake.some(s => s.x === head.x && s.y === head.y)) { window.DSP_AUDIO.sfx('bigboom'); this.particles.burst(OX + head.x * CELL + CELL / 2, OY + head.y * CELL + CELL / 2, '#ff4d6d', 20, 3, 30); this.shake.trigger(6, 20); this.lives--; if (this.lives <= 0) { this.gameOver(); return; } this.reset(); return; } this.snake.unshift(head); if (this.food && head.x === this.food.x && head.y === this.food.y) { this.addScore(10); window.DSP_AUDIO.sfx('eat'); this.grow += 2; this.placeFood(); if (this.tickRate > 4 && this.score % 50 === 0) { this.tickRate--; this.level = Math.floor(this.score / 100) + 1; window.DSP_AUDIO.sfx('levelup'); } if (Math.random() < 0.25 && !this.special) this.placeSpecial(); } if (this.special && head.x === this.special.x && head.y === this.special.y) { if (this.special.kind === 'bonus') { this.addScore(50); window.DSP_AUDIO.sfx('bonus'); this.particles.burst(OX + head.x * CELL + CELL / 2, OY + head.y * CELL + CELL / 2, '#ff9d2d', 16, 3, 30); } else { this.ghostTimer = 300; window.DSP_AUDIO.sfx('powerup'); } this.special = null; } if (this.grow > 0) this.grow--; else this.snake.pop(); } render() { const ctx = this.ctx; ctx.fillStyle = '#05010d'; ctx.fillRect(0, 0, W, H); for (const s of this.stars) { ctx.fillStyle = s.s > 1 ? '#7df9ff' : '#2a2145'; ctx.fillRect(s.x, s.y, s.s, s.s); } ctx.strokeStyle = '#3a2f5b'; ctx.lineWidth = 2; ctx.strokeRect(OX - 2, OY - 2, COLS * CELL + 4, ROWS * CELL + 4); ctx.strokeStyle = 'rgba(58,47,91,0.25)'; ctx.lineWidth = 1; for (let x = 0; x <= COLS; x++) { ctx.beginPath(); ctx.moveTo(OX + x * CELL, OY); ctx.lineTo(OX + x * CELL, OY + ROWS * CELL); ctx.stroke(); } for (let y = 0; y <= ROWS; y++) { ctx.beginPath(); ctx.moveTo(OX, OY + y * CELL); ctx.lineTo(OX + COLS * CELL, OY + y * CELL); ctx.stroke(); } if (this.food) { const fx = OX + this.food.x * CELL, fy = OY + this.food.y * CELL; const pulse = 1 + Math.sin(this.time * 0.15) * 0.15; ctx.fillStyle = '#ffe14d'; ctx.fillRect(fx + CELL / 2 - 5 * pulse, fy + CELL / 2 - 5 * pulse, 10 * pulse, 10 * pulse); } if (this.special) { const sx = OX + this.special.x * CELL, sy = OY + this.special.y * CELL; const blink = this.special.life < 120 && Math.floor(this.time / 6) % 2 === 0; if (!blink) { ctx.fillStyle = this.special.kind === 'bonus' ? '#ff9d2d' : '#7df9ff'; ctx.fillRect(sx + 3, sy + 3, CELL - 6, CELL - 6); ctx.fillStyle = '#05010d'; ctx.font = '10px "Press Start 2P", monospace'; ctx.textAlign = 'center'; ctx.fillText(this.special.kind === 'bonus' ? 'B' : 'G', sx + CELL / 2, sy + CELL / 2 + 3); } } const ghost = this.ghostTimer > 0; for (let i = this.snake.length - 1; i >= 0; i--) { const s = this.snake[i]; const x = OX + s.x * CELL, y = OY + s.y * CELL; if (i === 0) { ctx.fillStyle = ghost ? 'rgba(125,249,255,0.5)' : '#7dff5e'; ctx.fillRect(x + 1, y + 1, CELL - 2, CELL - 2); ctx.fillStyle = '#05010d'; const ex = this.dir.x !== 0 ? (this.dir.x > 0 ? 12 : 4) : 6; const ey = this.dir.y !== 0 ? (this.dir.y > 0 ? 12 : 4) : 6; ctx.fillRect(x + ex, y + ey, 3, 3); ctx.fillRect(x + (CELL - 6 - ex), y + ey, 3, 3); } else { const t = i / this.snake.length; ctx.fillStyle = ghost ? `rgba(125,249,255,${0.5 - t * 0.3})` : `rgba(125,255,94,${1 - t * 0.5})`; ctx.fillRect(x + 2, y + 2, CELL - 4, CELL - 4); } } } } window.DSP_ACTIVE_GAME = new SnakeProtocol(document.getElementById('game')); window.DSP_AUDIO.playMusic('snake'); })();