explodorama / files / test / q75.test.ts
q75.test.ts
| 1 | import { describe, expect, it } from 'vitest' |
| 2 | import { isDeadly, predictDanger } from '../src/sim/danger' |
| 3 | import { autoMoveAction, safeStep } from '../src/sim/path' |
| 4 | import { tapMeaning } from '../src/sim/picks' |
| 5 | import { eq } from '../src/sim/state' |
| 6 | import { step } from '../src/sim/turn' |
| 7 | import type { GameState, Vec } from '../src/sim/types' |
| 8 | import { actionableTiles, cursorActions, cursorLines, farTiles, moveCursor, tileActions } from '../src/ui/padTiles' |
| 9 | import { board, v } from './helpers' |
| 10 | |
| 11 | // Q75: a coin dropped by an enemy, in a pocket (block to the right, the edge to the left), reachable by walking round. |
| 12 | const scene = (): GameState => { |
| 13 | const s = board(` |
| 14 | ..P..+B |
| 15 | $#.#+#. |
| 16 | ...+... |
| 17 | .#.#.#. |
| 18 | ..+...B |
| 19 | .#.#+#. |
| 20 | ....... |
| 21 | .#.#.#.`) |
| 22 | s.powers.rpg = true |
| 23 | s.stats.capacity = 2 |
| 24 | s.enemies[0]!.pos = v(6, 0) |
| 25 | s.enemies[0]!.plan = [v(6, 1)] |
| 26 | s.enemies[1]!.pos = v(6, 4) |
| 27 | s.enemies[1]!.plan = [v(6, 3)] |
| 28 | return s |
| 29 | } |
| 30 | const safeOf = (s: GameState) => { |
| 31 | const danger = predictDanger(s) |
| 32 | return (p: Vec) => safeStep(s, danger, p) |
| 33 | } |
| 34 | const coin = v(0, 1) |
| 35 | const above = v(0, 0) |
| 36 | |
| 37 | describe('Q75: a coin in a pocket', () => { |
| 38 | it('the logic is right: a walk, a faded tile, no deadly first step', () => { |
| 39 | const s = scene() |
| 40 | expect(tapMeaning(s, coin)).toBe('walk') |
| 41 | expect(farTiles(s, safeOf(s)).some((f) => eq(f.tile, coin))).toBe(true) |
| 42 | expect(isDeadly(s, { id: 'move', target: v(2, 1) })).toBe(false) |
| 43 | }) |
| 44 | |
| 45 | it('the RPG tile above the coin is bright, Move greyed on it', () => { |
| 46 | const s = scene() |
| 47 | expect(actionableTiles(s).some((t) => eq(t, above))).toBe(true) |
| 48 | expect(tileActions(s, above).find((a) => a.button === 'move')!.possible).toBe(false) |
| 49 | expect(tileActions(s, above).find((a) => a.button === 'bomb')!.possible).toBe(true) |
| 50 | }) |
| 51 | |
| 52 | it('d-pad Down from the RPG tile reaches the coin, with nothing picked and with the coin as destination', () => { |
| 53 | const s = scene() |
| 54 | for (const dest of [null, coin]) { |
| 55 | const tiles = [...actionableTiles(s), ...farTiles(s, safeOf(s), dest).map((f) => f.tile)] |
| 56 | expect(eq(moveCursor(tiles, above, 0, 1), coin)).toBe(true) |
| 57 | } |
| 58 | }) |
| 59 | |
| 60 | it('with a destination elsewhere, the coin is no candidate (only the destination is far)', () => { |
| 61 | const s = scene() |
| 62 | const dest = v(0, 6) |
| 63 | const far = farTiles(s, safeOf(s), dest).map((f) => f.tile) |
| 64 | expect(far.some((t) => eq(t, coin))).toBe(false) |
| 65 | }) |
| 66 | }) |
| 67 | |
| 68 | // Q75b: a tile that is both an RPG target and a walk away offers Move (the walk) and Bomb. |
| 69 | describe('Q75b: Move on a tile that is also a bomb target', () => { |
| 70 | // The RPG flies to the end of the line: a wall at column 6 makes (5,1) the target. |
| 71 | const open = (): GameState => { |
| 72 | const s = board(` |
| 73 | ....... |
| 74 | ...P..# |
| 75 | .......`) |
| 76 | s.powers.rpg = true |
| 77 | s.stats.capacity = 2 |
| 78 | s.enemies = [] |
| 79 | return s |
| 80 | } |
| 81 | const twoAway = v(5, 1) |
| 82 | const get = (s: GameState, tile: Vec, button: string) => cursorActions(s, safeOf(s), tile, null).find((a) => a.button === button)! |
| 83 | |
| 84 | it('the old behaviour: the tile is bright, Move is greyed in tileActions', () => { |
| 85 | const s = open() |
| 86 | expect(actionableTiles(s).some((t) => eq(t, twoAway))).toBe(true) |
| 87 | expect(tileActions(s, twoAway).find((a) => a.button === 'move')!.possible).toBe(false) |
| 88 | expect(tileActions(s, twoAway).find((a) => a.button === 'bomb')!.possible).toBe(true) |
| 89 | expect(farTiles(s, safeOf(s)).some((f) => eq(f.tile, twoAway))).toBe(false) |
| 90 | }) |
| 91 | |
| 92 | it('the cursor offers Bomb and a walk of 2 steps', () => { |
| 93 | const s = open() |
| 94 | const move = get(s, twoAway, 'move') |
| 95 | expect(move.possible).toBe(true) |
| 96 | expect(move.far).toBe(true) |
| 97 | expect(move.label).toBe('Walk here · 2 steps') |
| 98 | expect(get(s, twoAway, 'bomb').possible).toBe(true) |
| 99 | const lines = cursorLines(cursorActions(s, safeOf(s), twoAway, null)).map((l) => l.button) |
| 100 | expect(lines).toEqual(['move', 'bomb']) |
| 101 | }) |
| 102 | |
| 103 | it('"Keep walking" when it is the destination', () => { |
| 104 | const s = open() |
| 105 | const move = cursorActions(s, safeOf(s), twoAway, twoAway).find((a) => a.button === 'move')! |
| 106 | expect(move.label).toBe('Keep walking · 2 steps') |
| 107 | }) |
| 108 | |
| 109 | it('Move walks (a step toward it), not an RPG', () => { |
| 110 | const s = open() |
| 111 | expect(tapMeaning(s, twoAway)).toBe('walk') |
| 112 | const action = autoMoveAction(s, twoAway, safeOf(s))! |
| 113 | const next = step(s, action).state |
| 114 | expect(next.player.x).toBeGreaterThan(s.player.x) |
| 115 | expect(next.bombs.length).toBe(0) |
| 116 | }) |
| 117 | |
| 118 | it('the same for a tile 3+ away', () => { |
| 119 | const s = open() |
| 120 | s.player = v(1, 1) |
| 121 | const far = v(5, 1) |
| 122 | expect(actionableTiles(s).some((t) => eq(t, far))).toBe(true) |
| 123 | expect(get(s, far, 'bomb').possible).toBe(true) |
| 124 | expect(get(s, far, 'move').far).toBe(true) |
| 125 | expect(get(s, far, 'move').label).toBe('Walk here · 4 steps') |
| 126 | }) |
| 127 | |
| 128 | it('a plain tile next to you keeps its ordinary Move, and your own tile has no walk', () => { |
| 129 | const s = open() |
| 130 | const near = get(s, v(4, 1), 'move') |
| 131 | expect(near.possible).toBe(true) |
| 132 | expect(near.far).toBeUndefined() |
| 133 | expect(get(s, s.player, 'move').possible).toBe(false) |
| 134 | }) |
| 135 | |
| 136 | it('the coin case: the faded tile still offers only the walk', () => { |
| 137 | const s = scene() |
| 138 | const acts = cursorActions(s, safeOf(s), coin, null) |
| 139 | expect(acts.find((a) => a.button === 'move')!.far).toBe(true) |
| 140 | expect(acts.filter((a) => a.possible).length).toBe(1) |
| 141 | }) |
| 142 | }) |