Add Snake Protocol game

This commit is contained in:
2026-09-24 10:10:54 +02:00
parent 00ad946ea4
commit b48b48811b
+185
View File
@@ -0,0 +1,185 @@
/* ============================================================
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');
})();