pike/val Public Public: anyone can read this; nobody can change it.
forked from benjivers/val
workbenchthis machine
Files d9bf83f2db4463f3e78b5c70a7dc50007df057db
| 1 | /** |
| 2 | * Step and slide, on a grid written out here rather than the real map, so |
| 3 | * the cases are the ones being argued about: a wall, an inner corner, and a |
| 4 | * body already pressed against both. |
| 5 | */ |
| 6 | |
| 7 | import { expect, test } from "bun:test"; |
| 8 | import { BODY_R, circleBlocked, moveSpeed, RUN, slideCircle } from "./glide.ts"; |
| 9 | import { moveCircle } from "./map.ts"; |
| 10 | |
| 11 | const TILE = 80; |
| 12 | // A room with solid walls, open from (1,1) to (3,3). |
| 13 | const room = ["#####", "#...#", "#...#", "#...#", "#####"]; |
| 14 | const solid = (col: number, row: number): boolean => |
| 15 | col < 0 || row < 0 || row >= room.length || col >= room[0]!.length || room[row]![col] === "#"; |
| 16 | |
| 17 | const slide = (x: number, y: number, dx: number, dy: number) => |
| 18 | slideCircle(x, y, dx, dy, BODY_R, solid, TILE); |
| 19 | |
| 20 | test("an unobstructed step is the step it was asked for", () => { |
| 21 | const to = slide(200, 200, 12, -7); |
| 22 | expect(to.x).toBeCloseTo(212, 9); |
| 23 | expect(to.y).toBeCloseTo(193, 9); |
| 24 | expect(slide(200, 200, 0, 0)).toEqual({ x: 200, y: 200 }); |
| 25 | }); |
| 26 | |
| 27 | test("a wall keeps the motion along it: running into it still slides", () => { |
| 28 | // Hard against the right-hand wall (x = 320 - 16), running down and right. |
| 29 | const to = slide(300, 160, 10, 10); |
| 30 | expect(to.y).toBeCloseTo(170, 6); // the free axis goes through whole |
| 31 | expect(to.x).toBeLessThanOrEqual(304); |
| 32 | expect(to.x).toBeGreaterThan(300); // and the blocked one creeps up to the wall |
| 33 | expect(circleBlocked(to.x, to.y, BODY_R, solid, TILE)).toBe(false); |
| 34 | }); |
| 35 | |
| 36 | test("a corner is one place, whichever axis is resolved first", () => { |
| 37 | // The bottom-right inner corner of the room: both walls at once. |
| 38 | const diagonal = slide(280, 280, 40, 40); |
| 39 | // Getting there one axis at a time, either order, lands in the same place. |
| 40 | const alongX = slide(280, 280, 40, 0); |
| 41 | const xThenY = slide(alongX.x, alongX.y, 0, 40); |
| 42 | const alongY = slide(280, 280, 0, 40); |
| 43 | const yThenX = slide(alongY.x, alongY.y, 40, 0); |
| 44 | expect(xThenY.x).toBeCloseTo(diagonal.x, 3); |
| 45 | expect(xThenY.y).toBeCloseTo(diagonal.y, 3); |
| 46 | expect(yThenX.x).toBeCloseTo(diagonal.x, 3); |
| 47 | expect(yThenX.y).toBeCloseTo(diagonal.y, 3); |
| 48 | // And it is the corner: a body's radius from both walls, inside them. |
| 49 | expect(diagonal.x).toBeGreaterThan(320 - BODY_R - 1.5); |
| 50 | expect(diagonal.x).toBeLessThanOrEqual(320 - BODY_R); |
| 51 | expect(diagonal.y).toBeGreaterThan(320 - BODY_R - 1.5); |
| 52 | expect(diagonal.y).toBeLessThanOrEqual(320 - BODY_R); |
| 53 | }); |
| 54 | |
| 55 | test("a body held against a wall stops moving instead of alternating", () => { |
| 56 | // This is the skip: the old resolution snapped to the tile edge on the |
| 57 | // blocked tick and moved freely on the next, so a player leaning on a |
| 58 | // wall produced a position that changed every tick with no input change. |
| 59 | let at = { x: 300, y: 200 }; |
| 60 | const seen: number[] = []; |
| 61 | for (let i = 0; i < 12; i++) { |
| 62 | at = slide(at.x, at.y, 8.3, 0); |
| 63 | seen.push(at.x); |
| 64 | } |
| 65 | const settled = seen.slice(4); |
| 66 | expect(Math.max(...settled) - Math.min(...settled)).toBeLessThan(0.05); |
| 67 | expect(Math.max(...settled)).toBeLessThanOrEqual(320 - BODY_R); |
| 68 | }); |
| 69 | |
| 70 | test("a corner is a fixed point: pressed into it, nothing moves at all", () => { |
| 71 | let at = { x: 300, y: 300 }; |
| 72 | for (let i = 0; i < 6; i++) at = slide(at.x, at.y, 8.3, 8.3); |
| 73 | const settled = slide(at.x, at.y, 8.3, 8.3); |
| 74 | expect(settled.x).toBeCloseTo(at.x, 6); |
| 75 | expect(settled.y).toBeCloseTo(at.y, 6); |
| 76 | }); |
| 77 | |
| 78 | test("a step never ends inside a wall, from anywhere in the room", () => { |
| 79 | for (let x = 96; x < 320 - 16; x += 7) |
| 80 | for (let y = 96; y < 320 - 16; y += 7) |
| 81 | for (const [dx, dy] of [ |
| 82 | [30, 0], |
| 83 | [-30, 0], |
| 84 | [0, 30], |
| 85 | [0, -30], |
| 86 | [24, 24], |
| 87 | [-24, 24], |
| 88 | [24, -24], |
| 89 | [-24, -24], |
| 90 | ] as const) { |
| 91 | const to = slide(x, y, dx, dy); |
| 92 | expect(circleBlocked(to.x, to.y, BODY_R, solid, TILE)).toBe(false); |
| 93 | } |
| 94 | }); |
| 95 | |
| 96 | test("the real map moves by the same maths", () => { |
| 97 | // Mid, walking east into whatever is there: the map's own binding. |
| 98 | const to = moveCircle(1200, 640, 9, 0, BODY_R); |
| 99 | expect(to.x).toBeGreaterThanOrEqual(1200); |
| 100 | expect(to.x).toBeLessThanOrEqual(1209); |
| 101 | expect(to.y).toBe(640); |
| 102 | }); |
| 103 | |
| 104 | test("speed is the one table both sides read", () => { |
| 105 | expect(moveSpeed({})).toBe(RUN); |
| 106 | expect(moveSpeed({ haste: true })).toBeCloseTo(RUN * 1.35, 9); |
| 107 | expect(moveSpeed({ slow: true })).toBeCloseTo(RUN * 0.55, 9); |
| 108 | expect(moveSpeed({ heavy: true })).toBeCloseTo(RUN * 0.86, 9); |
| 109 | expect(moveSpeed({ stopped: true, haste: true })).toBe(0); |
| 110 | }); |