From fa02a6e93e2c4d6a02e4291ddc4895ecd62b0e5a Mon Sep 17 00:00:00 2001 From: root Date: Sat, 7 Mar 2026 13:35:00 +0000 Subject: [PATCH] feat: add findNearestWalkable method to GameMap Adds a method that searches outward from a center position in Manhattan distance rings to find the nearest walkable tile within a given radius. Returns null if no walkable tile is found. Includes 5 tests. Co-Authored-By: Claude Opus 4.6 --- server/src/map/GameMap.ts | 19 +++++++++ server/src/map/__tests__/GameMap.test.ts | 49 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 server/src/map/__tests__/GameMap.test.ts diff --git a/server/src/map/GameMap.ts b/server/src/map/GameMap.ts index 8c44a55..6fb6768 100644 --- a/server/src/map/GameMap.ts +++ b/server/src/map/GameMap.ts @@ -45,6 +45,25 @@ export class GameMap { }); } + findNearestWalkable(centerX: number, centerY: number, radius: number): Position | null { + if (this.isWalkable(centerX, centerY)) { + return { x: centerX, y: centerY }; + } + for (let dist = 1; dist <= radius; dist++) { + for (let dx = -dist; dx <= dist; dx++) { + const dyRange = dist - Math.abs(dx); + for (const dy of [-dyRange, dyRange]) { + const x = centerX + dx; + const y = centerY + dy; + if (this.isWalkable(x, y)) { + return { x, y }; + } + } + } + } + return null; + } + getRandomWalkable(): Position { let x: number, y: number; do { diff --git a/server/src/map/__tests__/GameMap.test.ts b/server/src/map/__tests__/GameMap.test.ts new file mode 100644 index 0000000..20f25c4 --- /dev/null +++ b/server/src/map/__tests__/GameMap.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect } from 'vitest'; +import { GameMap } from '../GameMap.js'; + +describe('GameMap.findNearestWalkable', () => { + it('returns the exact tile if it is walkable', () => { + const map = new GameMap(10, 10); + const result = map.findNearestWalkable(5, 5, 3); + expect(result).toEqual({ x: 5, y: 5 }); + }); + + it('returns a nearby walkable tile if center is obstacle', () => { + const map = new GameMap(10, 10); + map.setObstacle(5, 5); + const result = map.findNearestWalkable(5, 5, 3); + expect(result).not.toBeNull(); + const dx = Math.abs(result!.x - 5); + const dy = Math.abs(result!.y - 5); + expect(dx + dy).toBeLessThanOrEqual(3); + expect(map.isWalkable(result!.x, result!.y)).toBe(true); + }); + + it('returns null if no walkable tile within radius', () => { + const map = new GameMap(5, 5); + for (let x = 1; x <= 3; x++) { + for (let y = 1; y <= 3; y++) { + map.setObstacle(x, y); + } + } + const result = map.findNearestWalkable(2, 2, 1); + expect(result).toBeNull(); + }); + + it('returns null for out-of-bounds center', () => { + const map = new GameMap(10, 10); + const result = map.findNearestWalkable(-5, -5, 1); + expect(result).toBeNull(); + }); + + it('clamps search to map boundaries', () => { + const map = new GameMap(10, 10); + map.setObstacle(0, 0); + const result = map.findNearestWalkable(0, 0, 3); + expect(result).not.toBeNull(); + expect(result!.x).toBeGreaterThanOrEqual(0); + expect(result!.y).toBeGreaterThanOrEqual(0); + expect(result!.x).toBeLessThan(10); + expect(result!.y).toBeLessThan(10); + }); +});