Repterra Web — full game: base building, power grid, taming & breeding, aquatic raiders, day/night, save/load
- Isometric canvas RTS vs dinosaur waves (fan demake of Repterra) - Economy: houses/taxes, farms, foresters, quarries; colonist staffing - Power grid: generators extend build range; brownout + recovery - Defense: walls/gates, watchtowers (AA), cannon towers (ground-only) - Taming: Primal Pen + Tamers collar weakened dinos; pets obey commands - Breeding: tamed pairs incubate eggs at the pen; hatchlings grow up - 7 dino species incl. flying Pteranodons and lake-raiding Suchomimus - Telegraphed waves with direction arrows; day-15 final horde; 3 difficulties - Day/night cycle, fog of war, minimap, synth audio, 1x-3x speeds - Save/Load/Continue + dawn autosave (full JSON state snapshots) - Tests: 80-assertion headless suite, browser boot + E2E, balance harness
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
||||
/* Browser boot test: loads the game, starts a match, screenshots it. */
|
||||
'use strict';
|
||||
const { chromium } = require('/tmp/node_modules/playwright-core');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({
|
||||
executablePath: '/root/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell',
|
||||
args: ['--no-sandbox', '--disable-gpu'],
|
||||
});
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 860 } });
|
||||
|
||||
const errors = [];
|
||||
page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
|
||||
page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message));
|
||||
|
||||
await page.goto('http://127.0.0.1:8933/', { waitUntil: 'load' });
|
||||
await page.waitForTimeout(1200);
|
||||
await page.screenshot({ path: '/root/dinorts/shots/01-menu.png' });
|
||||
console.log('menu loaded');
|
||||
|
||||
// start normal difficulty
|
||||
await page.click('[data-diff="normal"]');
|
||||
await page.waitForTimeout(2500);
|
||||
await page.screenshot({ path: '/root/dinorts/shots/02-start.png' });
|
||||
const st1 = await page.evaluate(() => {
|
||||
const s = RTS.sim.state();
|
||||
return { day: s.day, dinos: s.dinos.length, units: s.units.length, blds: s.buildings.length, res: { ...s.res } };
|
||||
});
|
||||
console.log('game state:', JSON.stringify(st1));
|
||||
|
||||
// select HQ by clicking near center of screen
|
||||
await page.mouse.click(720, 430);
|
||||
await page.waitForTimeout(400);
|
||||
await page.screenshot({ path: '/root/dinorts/shots/03-selected.png' });
|
||||
|
||||
// place a house via the palette (target the button by its label, not position)
|
||||
await page.evaluate(() => {
|
||||
const btns = [...document.querySelectorAll('.palbtn')];
|
||||
const house = btns.find(b => b.textContent.includes('House'));
|
||||
house.click();
|
||||
});
|
||||
await page.waitForTimeout(200);
|
||||
// click a spot anchored to the HQ (world is randomly generated each run)
|
||||
for (const [dx, dy] of [[4, 1], [-4, 2], [3, -4], [-3, -4], [5, 4]]) {
|
||||
const pt = await page.evaluate(([dx2, dy2]) => {
|
||||
const hq = RTS.sim.hq();
|
||||
return RTS.render.worldToScreen(hq.x + dx2, hq.y + dy2);
|
||||
}, [dx, dy]);
|
||||
await page.mouse.click(pt.x, pt.y);
|
||||
await page.waitForTimeout(250);
|
||||
const done = await page.evaluate(() => RTS.sim.state().buildings.some(b => b.defId === 'house'));
|
||||
if (done) break;
|
||||
}
|
||||
const st2 = await page.evaluate(() => {
|
||||
const s = RTS.sim.state();
|
||||
return { blds: s.buildings.map(b => b.defId), gold: Math.floor(s.res.gold) };
|
||||
});
|
||||
console.log('after house placement:', JSON.stringify(st2));
|
||||
await page.keyboard.press('Escape');
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// fast-forward 30s of game time at 3x
|
||||
await page.keyboard.press('3'); // no-op key (speed buttons are UI) — use UI button instead
|
||||
await page.click('#topbtns .tbtn:nth-child(3)'); // 2×
|
||||
await page.waitForTimeout(9000);
|
||||
const st3 = await page.evaluate(() => {
|
||||
const s = RTS.sim.state();
|
||||
return { time: Math.floor(s.time), kills: s.stats.kills, dinos: s.dinos.length };
|
||||
});
|
||||
console.log('after fast-forward:', JSON.stringify(st3));
|
||||
await page.screenshot({ path: '/root/dinorts/shots/04-later.png' });
|
||||
|
||||
// pause overlay (Save/Load buttons visible)
|
||||
await page.keyboard.press('Space');
|
||||
await page.waitForTimeout(300);
|
||||
const pauseBtns = await page.evaluate(() => ({
|
||||
save: !!document.getElementById('savebtn'),
|
||||
loadDisabled: document.getElementById('loadbtn') && document.getElementById('loadbtn').disabled,
|
||||
}));
|
||||
console.log('pause menu:', JSON.stringify(pauseBtns));
|
||||
await page.screenshot({ path: '/root/dinorts/shots/05-paused.png' });
|
||||
await page.keyboard.press('Space');
|
||||
|
||||
// ---- save → reload → continue from the main menu ----
|
||||
const saved = await page.evaluate(() => RTS.storage.save(true) && RTS.storage.has());
|
||||
console.log('saved to localStorage:', saved);
|
||||
if (!saved) throw new Error('save failed');
|
||||
await page.reload();
|
||||
await page.waitForTimeout(700);
|
||||
const contVisible = await page.evaluate(() => {
|
||||
const b = document.getElementById('continueBtn');
|
||||
return b && b.style.display !== 'none';
|
||||
});
|
||||
if (!contVisible) throw new Error('Continue button not shown despite existing save');
|
||||
await page.click('#continueBtn');
|
||||
await page.waitForTimeout(600);
|
||||
const st4 = await page.evaluate(() => {
|
||||
const s = RTS.sim.state();
|
||||
return { day: s.day, time: Math.floor(s.time), blds: s.buildings.length };
|
||||
});
|
||||
if (!(st4.time > 5)) throw new Error('loaded colony did not restore progress: ' + JSON.stringify(st4));
|
||||
console.log('after continue:', JSON.stringify(st4));
|
||||
|
||||
console.log('console errors:', errors.length ? errors : 'none');
|
||||
await browser.close();
|
||||
if (errors.length) process.exit(2);
|
||||
console.log('BROWSER BOOT TEST PASSED ✔');
|
||||
})().catch(e => { console.error('TEST CRASH:', e); process.exit(1); });
|
||||
Reference in New Issue
Block a user