diff --git a/README.md b/README.md index d37b83e..557518b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ Collect `6`, then `×`, then `4` → `6 × 4 = 24` ✓ - Collect a number, an operator, a number… to build a running total. - Only your **latest** number and operator count — eat a better one to replace it. -- Hitting a wall, biting your own tail, or letting the clock expire each costs a life. +- Hitting a wall wraps the snake to the opposite side — no penalty. +- Biting your own tail or letting the clock expire costs a life. - Three lives per run. ### Progression diff --git a/public/css/style.css b/public/css/style.css index 0cff6cb..362195d 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -148,7 +148,7 @@ body{ .flash.good{background:radial-gradient(circle,rgba(120,220,150,.0),rgba(90,210,140,.35));animation:flash .5s ease;} @keyframes flash{0%{opacity:0}30%{opacity:1}100%{opacity:0}} -.ent{position:absolute;left:0;top:0;width:var(--cell);height:var(--cell);display:flex;align-items:center;justify-content:center;transition:transform .075s linear;will-change:transform;} +.ent{position:absolute;left:0;top:0;width:var(--cell);height:var(--cell);display:flex;align-items:center;justify-content:center;transition:transform .075s linear,opacity .075s linear;will-change:transform;} .ent.notrans{transition:none;} .box{width:84%;height:84%;display:flex;align-items:center;justify-content:center;font-family:'Fredoka';font-weight:600;font-size:calc(var(--cell)*.42);position:relative;} .box.num{background:var(--num-bg);color:var(--num-fg);border-radius:50%;box-shadow:0 3px 0 rgba(0,0,0,.14);} diff --git a/public/js/main.js b/public/js/main.js index 41cc07e..24e9361 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -152,6 +152,7 @@ function render() { renderRibbon({ tokens: ft, total: totalValue(), solved: isSolved(), pendingOp: curFrame().op, openGroups: G.frames.length > 1 }); renderGrid(G, cell, ft); G.noTrans = false; + G.wrapGhost = null; } function schedule() { clearTimeout(tickTimer); if (G.running) tickTimer = setTimeout(step, G.tickMs); } @@ -160,8 +161,14 @@ function step() { if (!G.running) return; G.dir = G.nextDir; const pos = nextHead(G.snake, G.dir, COLS, ROWS); - if (!pos) { crash(); schedule(); return; } if (collidesWithSelf(G.snake, pos)) { crash(); schedule(); return; } + const head = G.snake[0]; + const isWrap = Math.abs(head.x - pos.x) > 1 || Math.abs(head.y - pos.y) > 1; + G.wrapGhost = isWrap ? { + entryFrom: { x: pos.x - G.dir.x, y: pos.y - G.dir.y }, // one cell outside entry wall + exitFrom: { x: head.x, y: head.y }, // old head position + exitTo: { x: head.x + G.dir.x, y: head.y + G.dir.y }, // one cell past exit wall + } : null; const ti = G.tiles.findIndex(t => t.x === pos.x && t.y === pos.y); if (ti >= 0) { eatTile(ti, pos); schedule(); return; } G.snake = advance(G.snake, pos); @@ -313,7 +320,7 @@ function startGame() { biome: 'garden', score: 0, lives: 3, target: 10, timeLeft: 26, timerMax: 26, frames: [{ tokens: [], acc: null, op: null }], history: [], snake: [], dir: { x: 1, y: 0 }, nextDir: { x: 1, y: 0 }, tiles: [], - tickMs: 160, baseTick: 160, running: false, solved: 0, longest: 0, noTrans: true + tickMs: 160, baseTick: 160, running: false, solved: 0, longest: 0, noTrans: true, wrapGhost: null }; applyBiome('garden'); app.dataset.screen = 'game'; diff --git a/public/js/snake.js b/public/js/snake.js index 7ad1e50..cb4627e 100644 --- a/public/js/snake.js +++ b/public/js/snake.js @@ -5,11 +5,11 @@ export function createSnake(cols = COLS, rows = ROWS) { return [{ x: Math.floor(cols / 2), y: Math.floor(rows / 2) }]; } -// Returns the new head position, or null if it would leave the grid. export function nextHead(snake, dir, cols = COLS, rows = ROWS) { - const nx = snake[0].x + dir.x, ny = snake[0].y + dir.y; - if (nx < 0 || ny < 0 || nx >= cols || ny >= rows) return null; - return { x: nx, y: ny }; + return { + x: (snake[0].x + dir.x + cols) % cols, + y: (snake[0].y + dir.y + rows) % rows, + }; } // Checks all segments except the last tail cell (which will vacate on the same tick). diff --git a/public/js/ui.js b/public/js/ui.js index 510e841..0c511b9 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -78,18 +78,43 @@ export function renderGrid(G, cell, tokens) { // Body segments spell the running expression, most-recent token nearest the head. const bodyTokens = tokens.slice().reverse(); + let headHTML = null; G.snake.forEach((p, i) => { const inner = i === 0 ? `