fuzzyworld

explodorama / files / test / path.test.ts

path.test.ts

Blame: the commit that last changed each line · file · history

82adee18 2026-10-20 Pavel Safronov1import { describe, expect, it } from 'vitest'
e6bb7c42 2026-10-25 Pavel Safronov2import { autoMoveAction, autoRoute, playerPath } from '../src/sim/path'
82adee18 2026-10-20 Pavel Safronov3import { board, v } from './helpers'
4
5describe('auto-move path', () => {
6 it('is the shortest walk round walls and crates, ending at the destination', () => {
7 const s = board(`
8 #######
9 #P.+..#
10 #.#.#.#
11 #.....#
12 #######`)
13 expect(playerPath(s, v(5, 1))).toEqual([v(1, 2), v(1, 3), v(2, 3), v(3, 3), v(4, 3), v(5, 3), v(5, 2), v(5, 1)])
14 })
15
16 it('goes round bombs', () => {
17 const s = board(`
18 #####
19 #P3.#
20 #...#
21 #####`)
22 expect(playerPath(s, v(3, 1))).toEqual([v(1, 2), v(2, 2), v(3, 2), v(3, 1)])
23 })
24
25 it('is null when the destination is walled off, blocked, or where the player stands', () => {
26 const s = board(`
27 ######
28 #P.+.#
29 ######`)
30 expect(playerPath(s, v(4, 1))).toBeNull()
31 expect(playerPath(s, v(3, 1))).toBeNull()
32 expect(playerPath(s, v(1, 1))).toBeNull()
33 })
34
52d68b59 2026-10-24 Pavel Safronov35 it('goes round an open exit unless the exit is the destination', () => {
36 const s = board(`
37 ######
38 #PE..#
39 #....#
40 ######`)
41 const path = playerPath(s, v(4, 1))!
42 expect(path).toHaveLength(5)
43 expect(path).not.toContainEqual(v(2, 1))
44 expect(path.at(-1)).toEqual(v(4, 1))
45 expect(playerPath(s, v(2, 1))).toEqual([v(2, 1)])
46 })
47
48 it('is null when the only way is over the open exit', () => {
49 const s = board(`
50 ######
51 #PE..#
52 ######`)
53 expect(playerPath(s, v(4, 1))).toBeNull()
54 })
55
56 it('crosses a locked exit, which doesn\'t end the level', () => {
57 const s = board(`
58 ######
59 #PE.B#
60 ######`)
61 expect(playerPath(s, v(3, 1))).toEqual([v(2, 1), v(3, 1)])
62 })
63
82adee18 2026-10-20 Pavel Safronov64 it('goes through enemies, which will have moved by then', () => {
65 const s = board(`
66 ######
67 #PB..#
68 ######`)
69 expect(playerPath(s, v(4, 1))).toEqual([v(2, 1), v(3, 1), v(4, 1)])
70 })
71})
28670c3e 2026-10-24 Pavel Safronov72
73describe('auto-move: a step, a Run or a Teleport (user request, 2026-10-23)', () => {
74 const always = () => true
75 const room = () =>
76 board(`
77 ##########
78 #P.......#
79 #.#.#.#.##
80 #........#
81 ##########`)
82
83 it('a tile next to you is a step, even with Run and Teleport', () => {
84 const s = room()
85 s.powers.run = true
fcf44f32 2026-10-25 Pavel Safronov86 s.charge = { kind: 'teleport', left: 3 }
28670c3e 2026-10-24 Pavel Safronov87 expect(autoMoveAction(s, v(2, 1), always)).toEqual({ id: 'move', target: v(2, 1) })
88 })
89
fcf44f32 2026-10-25 Pavel Safronov90 it('2 tiles away: two steps without Run, one run with it; never a Teleport, a charge (2026-10-25)', () => {
28670c3e 2026-10-24 Pavel Safronov91 const s = room()
92 expect(autoMoveAction(s, v(3, 1), always)).toEqual({ id: 'move', target: v(2, 1) })
93 s.powers.run = true
94 expect(autoMoveAction(s, v(3, 1), always)).toEqual({ id: 'run', target: v(3, 1) })
fcf44f32 2026-10-25 Pavel Safronov95 s.charge = { kind: 'teleport', left: 3 }
96 expect(autoMoveAction(s, v(3, 1), always)).toEqual({ id: 'run', target: v(3, 1) })
28670c3e 2026-10-24 Pavel Safronov97 })
98
99 it('2 tiles away round a corner, Run alone is no help: a step', () => {
100 const s = board(`
101 #####
102 #P..#
103 #...#
104 #####`)
105 s.powers.run = true
106 expect(autoMoveAction(s, v(2, 2), always)?.id).toBe('move')
107 })
108
109 it('further with Run only: runs where the way goes straight', () => {
110 const s = room()
111 s.powers.run = true
112 expect(autoMoveAction(s, v(8, 1), always)).toEqual({ id: 'run', target: v(3, 1) })
113 })
114
115 it('never lands on an unsafe tile: picks the next best', () => {
116 const s = room()
fcf44f32 2026-10-25 Pavel Safronov117 s.powers.run = true
118 const unsafe = (p: { x: number; y: number }) => !(p.x === 3 && p.y === 1)
119 expect(autoMoveAction(s, v(8, 1), unsafe)).toEqual({ id: 'move', target: v(2, 1) })
28670c3e 2026-10-24 Pavel Safronov120 })
121})
e6bb7c42 2026-10-25 Pavel Safronov122
123describe('autoRoute: where auto-move lands', () => {
124 it('a step on every tile without Run; every other tile with it (user report, 2026-10-25)', () => {
125 const s = board(`
126 #########
127 #P......#
128 #########`)
129 expect(autoRoute(s, v(7, 1), () => true)).toEqual([v(2, 1), v(3, 1), v(4, 1), v(5, 1), v(6, 1), v(7, 1)])
130 s.powers.run = true
131 expect(autoRoute(s, v(7, 1), () => true)).toEqual([v(3, 1), v(5, 1), v(7, 1)])
132 })
133
134 it('empty when the first move isn’t safe', () => {
135 const s = board(`
136 ######
137 #P...#
138 ######`)
139 expect(autoRoute(s, v(4, 1), () => false)).toEqual([])
140 })
141})
142
143describe('auto-move with Diagonal (user report, 2026-10-25)', () => {
144 it('cuts corners with diagonal steps, and takes one as its move', () => {
145 const s = board(`
146 ######
147 #P...#
148 #....#
149 #....#
150 ######`)
151 expect(playerPath(s, v(4, 3))).toHaveLength(5)
152 s.powers.diagonal = true
153 expect(playerPath(s, v(4, 3))).toHaveLength(3)
154 // Three moves, not five: one of them diagonal.
155 expect(autoRoute(s, v(4, 3), () => true)).toHaveLength(3)
156 const open = board(`
157 ####
158 #P.#
159 #..#
160 ####`)
161 open.powers.diagonal = true
162 expect(autoMoveAction(open, v(2, 2), () => true)).toEqual({ id: 'diagonal', target: v(2, 2) })
163 })
164})