fire-truck-decor.test.js (4394B)
1 'use strict'; 2 3 const test = require('node:test'); 4 const assert = require('node:assert'); 5 const FT = require('../../games/fire-truck/lib.js'); 6 7 function makeIsland(seed) { 8 return FT.buildIsland({ width: 50, height: 50, routeCount: 50, seed: seed ?? 1 }); 9 } 10 11 test('hashCell is deterministic and in [0,1)', () => { 12 for (let i = 0; i < 200; i++) { 13 const x = (i * 7) % 50; 14 const y = (i * 13) % 50; 15 const a = FT.hashCell(x, y, 3); 16 const b = FT.hashCell(x, y, 3); 17 assert.strictEqual(a, b, 'same inputs → same output'); 18 assert.ok(a >= 0 && a < 1, `in range: ${a}`); 19 } 20 // Salt changes the value (very likely) 21 assert.notStrictEqual(FT.hashCell(4, 9, 1), FT.hashCell(4, 9, 2)); 22 }); 23 24 test('findBuildingBlocks returns disjoint rectangles covering all building cells', () => { 25 const island = makeIsland(2); 26 const blocks = FT.findBuildingBlocks(island.grid); 27 const covered = new Set(); 28 for (const b of blocks) { 29 assert.ok(b.w >= 1 && b.h >= 1, 'positive size'); 30 for (let yy = b.y; yy < b.y + b.h; yy++) { 31 for (let xx = b.x; xx < b.x + b.w; xx++) { 32 const key = xx + ',' + yy; 33 assert.ok(!covered.has(key), `no overlap at ${key}`); 34 covered.add(key); 35 assert.strictEqual(island.grid[yy][xx].type, FT.CELL_TYPES.BUILDING, 36 `block cell ${key} is a building`); 37 } 38 } 39 } 40 // Every building cell is covered by exactly one block 41 for (let y = 0; y < island.height; y++) { 42 for (let x = 0; x < island.width; x++) { 43 if (island.grid[y][x].type === FT.CELL_TYPES.BUILDING) { 44 assert.ok(covered.has(x + ',' + y), `building ${x},${y} covered`); 45 } 46 } 47 } 48 }); 49 50 test('assignParks is deterministic and keeps type building', () => { 51 const a = makeIsland(3); 52 const b = makeIsland(3); 53 FT.assignParks(a, { fraction: 0.15 }); 54 FT.assignParks(b, { fraction: 0.15 }); 55 let parkCount = 0; 56 for (let y = 0; y < a.height; y++) { 57 for (let x = 0; x < a.width; x++) { 58 const ca = a.grid[y][x]; 59 const cb = b.grid[y][x]; 60 assert.strictEqual(!!ca.park, !!cb.park, `deterministic park tag at ${x},${y}`); 61 if (ca.park) { 62 parkCount++; 63 assert.strictEqual(ca.type, FT.CELL_TYPES.BUILDING, 'park stays type building'); 64 } 65 } 66 } 67 assert.ok(parkCount > 0, 'at least one park tagged'); 68 }); 69 70 test('advanceCarPlan stays on roads and avoids immediate reversal', () => { 71 const island = makeIsland(4); 72 const rand = FT.createSeededRng(99); 73 let cell = island.roads[0]; 74 let heading = 'east'; 75 for (const card of ['N', 'E', 'S', 'W']) { 76 if (cell.exits[card]) { heading = FT.CARD_TO_HEADING[card]; break; } 77 } 78 for (let i = 0; i < 500; i++) { 79 const prev = cell; 80 const next = FT.advanceCarPlan(island.grid, cell, heading, rand); 81 assert.strictEqual(next.cell.type, FT.CELL_TYPES.ROAD, `step ${i} on road`); 82 // No immediate reversal unless the current cell is a straight/dead corridor 83 // where reversing is the only option (avoidable = >1 exit besides back). 84 const exits = ['N', 'E', 'S', 'W'].filter(c => prev.exits[c]); 85 const back = FT.OPPOSITE[heading]; 86 const forwardOptions = exits 87 .map(c => FT.CARD_TO_HEADING[c]) 88 .filter(h => h !== back); 89 if (forwardOptions.length > 0) { 90 assert.notStrictEqual(next.heading, back, `step ${i} did not reverse when avoidable`); 91 } 92 cell = next.cell; 93 heading = next.heading; 94 } 95 }); 96 97 test('sidewalkEdges flags building-adjacent edges of a road cell', () => { 98 const island = makeIsland(5); 99 // The ring road (index 2) has beach outside and buildings inside. 100 // Find a straight vertical stretch of the left ring road. 101 const cell = island.grid[25][2]; 102 assert.strictEqual(cell.type, FT.CELL_TYPES.ROAD, 'ring cell is road'); 103 const edges = FT.sidewalkEdges(island.grid, 2, 25); 104 assert.strictEqual(edges.E, true, 'east edge abuts building interior'); 105 assert.strictEqual(edges.W, false, 'west edge abuts beach, not building'); 106 }); 107 108 test('pickFireDestination never targets a park building', () => { 109 const island = makeIsland(6); 110 FT.assignParks(island, { fraction: 0.3 }); 111 const rand = FT.createSeededRng(1234); 112 for (let i = 0; i < 50; i++) { 113 const dest = FT.pickFireDestination(island, rand, {}); 114 if (!dest) continue; 115 assert.ok(!dest.buildingCell.park, `dest ${dest.buildingCell.x},${dest.buildingCell.y} is not a park`); 116 } 117 });