kgames

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

projection.test.js (4278B)


      1 const { describe, it } = require('node:test');
      2 const assert = require('node:assert/strict');
      3 const {
      4   buildIsland,
      5   pickPromptMove,
      6 } = require('../../games/fire-truck/lib.js');
      7 
      8 const fixedRng = { pick: arr => arr[0], next: () => 0.2 };
      9 
     10 function bfsReachableRoads(grid, roads) {
     11   const seen = new Set();
     12   const queue = [roads[0]];
     13   seen.add(roads[0].x + ',' + roads[0].y);
     14   const D = { N: [0, -1], E: [1, 0], S: [0, 1], W: [-1, 0] };
     15 
     16   while (queue.length) {
     17     const cell = queue.shift();
     18     for (const dir of ['N', 'E', 'S', 'W']) {
     19       if (!cell.exits[dir]) continue;
     20       const [dx, dy] = D[dir];
     21       const nx = cell.x + dx;
     22       const ny = cell.y + dy;
     23       const key = nx + ',' + ny;
     24       if (!seen.has(key)) {
     25         seen.add(key);
     26         queue.push(grid[ny][nx]);
     27       }
     28     }
     29   }
     30   return seen.size;
     31 }
     32 
     33 describe('buildIsland()', () => {
     34   it('has fixed rings', () => {
     35     const island = buildIsland({ seed: 1 });
     36     for (let x = 0; x < 50; x++) {
     37       assert.equal(island.grid[0][x].type, 'water');
     38       assert.equal(island.grid[49][x].type, 'water');
     39     }
     40     for (let y = 0; y < 50; y++) {
     41       assert.equal(island.grid[y][0].type, 'water');
     42       assert.equal(island.grid[y][49].type, 'water');
     43     }
     44     for (let x = 1; x < 49; x++) {
     45       assert.equal(island.grid[1][x].type, 'beach');
     46       assert.equal(island.grid[48][x].type, 'beach');
     47     }
     48     for (let y = 1; y < 49; y++) {
     49       assert.equal(island.grid[y][1].type, 'beach');
     50       assert.equal(island.grid[y][48].type, 'beach');
     51     }
     52     for (let x = 2; x < 48; x++) {
     53       assert.equal(island.grid[2][x].type, 'road');
     54       assert.equal(island.grid[47][x].type, 'road');
     55     }
     56     for (let y = 2; y < 48; y++) {
     57       assert.equal(island.grid[y][2].type, 'road');
     58       assert.equal(island.grid[y][47].type, 'road');
     59     }
     60   });
     61 
     62   it('has only building or road in interior', () => {
     63     const island = buildIsland({ seed: 2 });
     64     for (let y = 3; y <= 46; y++) {
     65       for (let x = 3; x <= 46; x++) {
     66         const t = island.grid[y][x].type;
     67         assert.ok(t === 'building' || t === 'road', `expected building or road at ${x},${y}, got ${t}`);
     68       }
     69     }
     70   });
     71 
     72   it('has a fully connected road graph', () => {
     73     const island = buildIsland({ seed: 3 });
     74     const reachable = bfsReachableRoads(island.grid, island.roads);
     75     assert.equal(reachable, island.roads.length);
     76   });
     77 
     78   it('has no dead-end roads', () => {
     79     const island = buildIsland({ seed: 4 });
     80     for (const cell of island.roads) {
     81       const degree = Object.values(cell.exits).filter(Boolean).length;
     82       assert.ok(degree >= 2, `dead-end at ${cell.x},${cell.y} with degree ${degree}`);
     83     }
     84   });
     85 
     86   it('has varied intersections', () => {
     87     const island = buildIsland({ seed: 5 });
     88     let tCount = 0;
     89     let fourCount = 0;
     90     for (const cell of island.roads) {
     91       if (cell.meta.kind === 't') tCount++;
     92       if (cell.meta.kind === 'four') fourCount++;
     93     }
     94     const decisionCount = tCount + fourCount;
     95     assert.ok(decisionCount > 0, 'expected some decision cells');
     96     const tRatio = tCount / decisionCount;
     97     const fourRatio = fourCount / decisionCount;
     98     assert.ok(tRatio >= 0.55, `expected t ratio >= 0.55, got ${tRatio.toFixed(3)}`);
     99     assert.ok(fourRatio >= 0.06, `expected four-way ratio >= 0.06, got ${fourRatio.toFixed(3)}`);
    100   });
    101 
    102   it('has average road degree at least 2.0', () => {
    103     const island = buildIsland({ seed: 6 });
    104     const total = island.roads.reduce((sum, c) => sum + Object.values(c.exits).filter(Boolean).length, 0);
    105     const avg = total / island.roads.length;
    106     assert.ok(avg >= 2.0, `expected avg degree >= 2.0, got ${avg.toFixed(2)}`);
    107   });
    108 
    109   it('has correct building count', () => {
    110     const island = buildIsland({ seed: 8 });
    111     let interiorRoadCount = 0;
    112     for (let y = 3; y <= 46; y++) {
    113       for (let x = 3; x <= 46; x++) {
    114         if (island.grid[y][x].type === 'road') interiorRoadCount++;
    115       }
    116     }
    117     assert.equal(island.buildings.length + interiorRoadCount, 44 * 44);
    118   });
    119 });
    120 
    121 describe('pickPromptMove()', () => {
    122   it('always returns a legal move', () => {
    123     assert.equal(pickPromptMove(['left', 'right'], fixedRng), 'left');
    124     assert.equal(pickPromptMove(['straight'], fixedRng), 'straight');
    125   });
    126 });