kgames

KGames — free keyboard games for kids
git clone https://www.keyboard.games/code/kgames.git
Log | Files | Refs | README | LICENSE

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 });