// TDD: Snake core — pure functions, no Vela APIs. const assert = require('node:assert/strict'); const snake = require('../src/common/snake.js'); // initial snake: head first, heading right, length 3 const init = snake.initialSnake(); assert.equal(init.length, 3, 'starts length 3'); assert.deepEqual(init[0], { x: 6, y: 7 }, 'head at start cell'); assert.deepEqual(init[1], { x: 5, y: 7 }, 'body behind head'); assert.deepEqual(init[2], { x: 4, y: 7 }, 'tail'); // move right into empty cell: alive, no food, length unchanged, tail advances let r = snake.step(init, { x: 1, y: 0 }, { x: 10, y: 10 }, 14); assert.equal(r.alive, true); assert.equal(r.ate, false); assert.equal(r.snake.length, 3); assert.deepEqual(r.snake[0], { x: 7, y: 7 }, 'head moved right'); assert.deepEqual(r.snake[2], { x: 5, y: 7 }, 'tail advanced'); // eat food: length grows by 1, tail retained r = snake.step(r.snake, { x: 1, y: 0 }, { x: 8, y: 7 }, 14); assert.equal(r.ate, true, 'ate the food ahead'); assert.equal(r.snake.length, 4, 'grew by one'); assert.deepEqual(r.snake[0], { x: 8, y: 7 }); // wall collision: move left wall r = snake.step([{ x: 0, y: 5 }, { x: 1, y: 5 }], { x: -1, y: 0 }, { x: 3, y: 3 }, 14); assert.equal(r.alive, false, 'hit left wall'); r = snake.step([{ x: 13, y: 5 }, { x: 12, y: 5 }], { x: 1, y: 0 }, { x: 3, y: 3 }, 14); assert.equal(r.alive, false, 'hit right wall'); r = snake.step([{ x: 5, y: 0 }, { x: 5, y: 1 }], { x: 0, y: -1 }, { x: 3, y: 3 }, 14); assert.equal(r.alive, false, 'hit top wall'); r = snake.step([{ x: 5, y: 13 }, { x: 5, y: 12 }], { x: 0, y: 1 }, { x: 3, y: 3 }, 14); assert.equal(r.alive, false, 'hit bottom wall'); // self collision: run into own body const loop = [{ x: 5, y: 5 }, { x: 5, y: 6 }, { x: 6, y: 6 }, { x: 6, y: 5 }]; r = snake.step(loop, { x: 0, y: -1 }, { x: 3, y: 3 }, 14); // head 5,5 -> 5,4? no, dir -y -> 5,4 empty assert.equal(r.alive, true, 'moving away from body is safe'); // head at 5,5 move up into 5,4 (empty), then try to move into own neck is blocked by dir reversal // direct: build snake where head's next cell is body const sb = [{ x: 5, y: 5 }, { x: 4, y: 5 }, { x: 4, y: 4 }, { x: 5, y: 4 }]; r = snake.step(sb, { x: 0, y: 1 }, { x: 9, y: 9 }, 14); // 5,5 -> 5,6 empty assert.equal(r.alive, true); r = snake.step([{ x: 5, y: 5 }, { x: 4, y: 5 }, { x: 4, y: 4 }, { x: 5, y: 4 }, { x: 6, y: 4 }, { x: 6, y: 5 }], { x: 0, y: 1 }, { x: 9, y: 9 }, 14); assert.equal(r.alive, true, 'moving into vacating tail cell is allowed'); r = snake.step([{ x: 5, y: 5 }, { x: 5, y: 6 }, { x: 4, y: 6 }, { x: 4, y: 5 }], { x: 0, y: 1 }, { x: 9, y: 9 }, 14); // 5,5 -> 5,6 which is a middle body cell assert.equal(r.alive, false, 'run into body'); // direction reversal guard assert.deepEqual(snake.turn({ x: 1, y: 0 }, { x: -1, y: 0 }), { x: 1, y: 0 }, 'cannot reverse into itself'); assert.deepEqual(snake.turn({ x: 1, y: 0 }, { x: 0, y: 1 }), { x: 0, y: 1 }, 'turn down allowed'); assert.deepEqual(snake.turn({ x: 1, y: 0 }, { x: 1, y: 0 }), { x: 1, y: 0 }); // crown rotation: rotate direction ±90° (right->down->left->up, clockwise) assert.deepEqual(snake.rotateDir({ x: 1, y: 0 }, true), { x: 0, y: 1 }, 'clockwise right->down'); assert.deepEqual(snake.rotateDir({ x: 0, y: 1 }, true), { x: -1, y: 0 }, 'clockwise down->left'); assert.deepEqual(snake.rotateDir({ x: -1, y: 0 }, true), { x: 0, y: -1 }, 'clockwise left->up'); assert.deepEqual(snake.rotateDir({ x: 0, y: -1 }, true), { x: 1, y: 0 }, 'clockwise up->right (wrap)'); assert.deepEqual(snake.rotateDir({ x: 1, y: 0 }, false), { x: 0, y: -1 }, 'counterclockwise right->up'); assert.deepEqual(snake.rotateDir({ x: 0, y: -1 }, false), { x: -1, y: 0 }, 'counterclockwise up->left'); // spawnFood lands on an empty cell for (let i = 0; i < 50; i++) { const f = snake.spawnFood([{ x: 0, y: 0 }], 14); assert.ok(f.x >= 0 && f.x < 14 && f.y >= 0 && f.y < 14, 'in bounds'); assert.ok(!(f.x === 0 && f.y === 0), 'not on snake'); } console.log('ALL SNAKE LOGIC PASS');