diff --git a/frontend/src/features/games/neon-drop/engine.test.ts b/frontend/src/features/games/neon-drop/engine.test.ts index 74c8c30..99927e1 100644 --- a/frontend/src/features/games/neon-drop/engine.test.ts +++ b/frontend/src/features/games/neon-drop/engine.test.ts @@ -20,6 +20,10 @@ describe('neon drop engine', () => { it('starts with a valid piece and a complete seven-piece bag', () => { const game = createNeonDropGame(() => 0.5) expect(game.status).toBe('playing') + expect(game.board).toHaveLength(17) + expect(game.board.every((row) => row.length === NEON_DROP_COLUMNS)).toBe( + true, + ) expect(new Set([game.active.kind, ...game.queue]).size).toBe(7) expect(canPlaceNeonDropPiece(game.board, game.active)).toBe(true) }) diff --git a/frontend/src/features/games/neon-drop/engine.ts b/frontend/src/features/games/neon-drop/engine.ts index a16063b..f2bd343 100644 --- a/frontend/src/features/games/neon-drop/engine.ts +++ b/frontend/src/features/games/neon-drop/engine.ts @@ -8,7 +8,7 @@ import type { } from './types' export const NEON_DROP_COLUMNS = 10 -export const NEON_DROP_ROWS = 18 +export const NEON_DROP_ROWS = 17 export const NEON_DROP_LINES_PER_LEVEL = 8 const PIECE_KINDS: NeonDropPieceKind[] = ['I', 'J', 'L', 'O', 'S', 'T', 'Z'] diff --git a/frontend/src/features/games/snake/engine.test.ts b/frontend/src/features/games/snake/engine.test.ts index 5bfd8ff..9249716 100644 --- a/frontend/src/features/games/snake/engine.test.ts +++ b/frontend/src/features/games/snake/engine.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest' import { createSnakeGame, + SNAKE_BOARD_HEIGHT, SNAKE_BOARD_WIDTH, stepSnake, turnSnake, @@ -27,6 +28,17 @@ describe('Snake engine', () => { expect(next.body).not.toContainEqual(next.fruit) }) + it('places fruit inside the visible play area', () => { + for (const random of [() => 0, () => 1]) { + const { fruit } = createSnakeGame(random) + + expect(fruit.x).toBeGreaterThanOrEqual(0) + expect(fruit.x).toBeLessThan(SNAKE_BOARD_WIDTH) + expect(fruit.y).toBeGreaterThanOrEqual(0) + expect(fruit.y).toBeLessThan(SNAKE_BOARD_HEIGHT) + } + }) + it('ignores an immediate reverse direction', () => { const game = createSnakeGame(() => 0) @@ -46,6 +58,20 @@ describe('Snake engine', () => { expect(stepSnake(game).status).toBe('game-over') }) + it('ends the game at the lower edge of the visible play area', () => { + const game: SnakeGameState = { + ...createSnakeGame(() => 0), + body: [ + { x: 4, y: SNAKE_BOARD_HEIGHT - 1 }, + { x: 4, y: SNAKE_BOARD_HEIGHT - 2 }, + ], + direction: 'down', + pendingDirection: 'down', + } + + expect(stepSnake(game).status).toBe('game-over') + }) + it('ends the game on body collision', () => { const game: SnakeGameState = { body: [ diff --git a/frontend/src/features/games/snake/engine.ts b/frontend/src/features/games/snake/engine.ts index 2dbe883..3295806 100644 --- a/frontend/src/features/games/snake/engine.ts +++ b/frontend/src/features/games/snake/engine.ts @@ -5,7 +5,7 @@ import type { } from './types' export const SNAKE_BOARD_WIDTH = 16 -export const SNAKE_BOARD_HEIGHT = 34 +export const SNAKE_BOARD_HEIGHT = 30 const DIRECTION_VECTORS: Record = { up: { x: 0, y: -1 }, diff --git a/frontend/src/stores/phone.ts b/frontend/src/stores/phone.ts index 8e55a79..f1ec0eb 100644 --- a/frontend/src/stores/phone.ts +++ b/frontend/src/stores/phone.ts @@ -341,7 +341,7 @@ const defaultLocales: LocaleTree = { mute: 'Mute game sounds', newGame: 'New Game', score: 'Score', - swipeHint: 'Swipe the board, use the buttons, or press arrow keys / WASD', + swipeHint: 'Swipe the board or use arrow keys / WASD', unmute: 'Turn on game sounds', wonBody: 'You reached the legendary tile. Continue climbing or begin again.', wonTitle: 'You made 2048!', diff --git a/frontend/src/views/apps/MemoryApp.vue b/frontend/src/views/apps/MemoryApp.vue index 8f49ef2..c2aa588 100644 --- a/frontend/src/views/apps/MemoryApp.vue +++ b/frontend/src/views/apps/MemoryApp.vue @@ -123,8 +123,12 @@ onBeforeUnmount(() => {