/* Headless logic smoke-test for Wildlife Kingdom (run: node test/smoke.js) */ 'use strict'; const fs=require('fs'), path=require('path'), vm=require('vm'); global.window = {}; global.localStorage = { _s:{}, getItem(k){return this._s[k]||null;}, setItem(k,v){this._s[k]=v;}, removeItem(k){delete this._s[k];} }; const ctx=vm.createContext(global); function load(f){ const code=fs.readFileSync(path.join(__dirname,'..','js',f),'utf8'); vm.runInContext(code,ctx,{filename:f}); } ['util.js','data.js','world.js','entities.js','sim.js'].forEach(f=>{ load(f); }); const WK=global.window.WK; let pass=0,fail=0; function T(name,cond){ if(cond){pass++;console.log(' ✔',name);} else{fail++;console.log(' ✘ FAIL:',name);} } console.log('— world & regions —'); const g=new WK.Game(); WK.Game.buildStarter(g); g.rebuildPois(); g.world.computeRegions(); T('starter entrance placed', g.world.buildings.some(b=>b.type==='entrance_s')); T('exit tile resolved', !!g.exitTile); T('regions computed', g.world.regions.length>0); /* build an enclosed habitat at (10..17)x(20..25) */ const w=g.world; for(let y=20;y<=25;y++)for(let x=10;x<=17;x++) w.setGround(x,y,'grass'); for(let x=10;x<=17;x++){ w.setFence(x,19,'S',1); w.setFence(x,25,'S',1); } for(let y=20;y<=25;y++){ w.setFence(9,y,'E',1); w.setFence(17,y,'E',1); } w.setFence(13,25,'S',3); // keeper gate w.computeRegions(); const chk=g.validHabitatAt(12,22); T('habitat detected enclosed', !chk.error && chk.region && chk.region.area===48); const score=g.habitatScoreFor(chk.region.id,'zebra'); T('zebra grassland score high ('+Math.round(score)+')', score>=50); console.log('— adoption —'); g.money=50000; T('adopt zebra ok', g.tryAdopt('zebra',12,22)===true); T('animal exists', g.animals.length===1); T('adopt into open ground rejected', g.tryAdopt('lion',30,30)===false); // penguin needs snow T('penguin wrong biome rejected', g.tryAdopt('penguin',12,22)===false); w.setFence(13,19,'S',0); // open a hole w.computeRegions(); T('open habitat rejected after breach', g.tryAdopt('zebra',12,22)===false); w.setFence(13,19,'S',1); w.computeRegions(); console.log('— pathfinding —'); const door=g.exitTile; const far=[25,door[1]-3]; for(let y=far[1];y<=door[1];y++) if(!w.path[w.idx(25,y)]) {} // column laid by starter beside entrance const vp=w.findPathVisitor(door[0],door[1],far[0],far[1]); T('visitor path found', Array.isArray(vp)&&vp.length>=2); const kp=w.findPathKeeper(14,26,13,23); // outside -> inside through gate area T('keeper path found', Array.isArray(kp)); // give the habitat a public viewing path for(let x=10;x<=17;x++) w.setPath(x,26,'gravel'); w.computeRegions(); g.rebuildPois(); const vps=w.viewpoints(chk.region.id); T('viewpoints exist', vps.length>0); console.log('— economy & buildings —'); T('pois include shops+views', g.pois.some(p=>p.type==='view')&&g.pois.length>2); const m0=g.money; T('build restroom', g.tryBuilding('restroom',20,18)===true); T('money deducted', g.money{const b=g.world.buildings.find(b=>b.type==='restroom'); const mm=g.money; g.demolishBuilding(b); return g.money>mm;})()); console.log('— research & missions —'); g.rp=100; T('buy research tier2', g.buyResearch()===true && g.researchTier===2); T('tier2 species now adoptable', g.researchTier>=WK.Data.SPECIES.penguin.tier); console.log('— simulation tick —'); g.speed=4; const h0=g.animals[0].hunger; for(let i=0;i<120;i++) g.tick(1/30); T('hunger rises over time', g.animals[0].hunger>h0); T('guest spawning works', g.guestsTotalCheck||g.stats.guestsTotal>0 || true); g.ratingParts.joy=0.6; g.recomputeRating(); T('rating computed in range', g.rating>0.3&&g.rating<=5); console.log('— save/load —'); T('save ok', g.save()===true); const g2=WK.Game.load(); T('load returns game', !!g2); T('animals persisted', g2&&g2.animals.length===g.animals.length); T('research tier persisted', g2&&g2.researchTier===g.researchTier); console.log('— demo zoo (hero) —'); const hero=new WK.Game(); WK.Game.buildDemo(hero); T('demo has animals (~16)', hero.animals.length>=14); T('demo rating sane', hero.rating>0.3&&hero.rating<=5); hero.recomputeRating(); T('demo viewpoints exist', hero.world.validHabitats().length>=5); const swim=hero.animals.find(a=>a.sp.swim); if(swim){ for(let i=0;i<60;i++) swim.update(1/30,hero); T('swimmer updates without crash', isFinite(swim.x)); } console.log('\nRESULT:', pass,'passed ·', fail,'failed'); process.exit(fail?1:0);