100 Days After — complete playable survival strategy game
- Seeded procedural runs (survivors, locations, weather, events) - 100-day campaign with 5 phases, scripted story beats, Day-100 finale - 8 endings incl. hidden 'First Light Again' - Data-driven events (people/world/story packs) with callback chains - Relationships, memories, grief, karma/hope systems - Tactical encounters (fight/hide/run/negotiate) with honest odds - 8 upgradeable camp buildings, 13 location templates - Canvas parallax scenes, procedural SVG portraits, weather FX - Synthesized adaptive audio (WebAudio, no assets) - Save slots + autosave (localStorage), shareable seeds - Tests: headless multi-seed simulator + browser E2E (playwright)
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { chromium } from 'playwright-core';
|
||||
// build a Day-100-ready save with node + tsx first
|
||||
const { execSync } = await import('node:child_process');
|
||||
const stateJson = execSync(`npx tsx -e "
|
||||
import { createNewGame } from './src/engine/state';
|
||||
const s = createNewGame(2024);
|
||||
s.day = 99; s.ap = 3;
|
||||
s.flags.evacPrep = 1; s.flags.heardTruth = 1; s.flags.militaryRep = 3;
|
||||
s.res.food = 40; s.res.water = 40; s.res.medicine = 6;
|
||||
console.log(JSON.stringify({ v: s.version, ts: Date.now(), state: s }));
|
||||
"`, { encoding: 'utf8', cwd: '/root/100days' }).trim();
|
||||
|
||||
const browser = await chromium.launch({ executablePath: '/root/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell', args:['--no-sandbox','--disable-gpu','--disable-dev-shm-usage'] });
|
||||
const ctx = await browser.newContext({ viewport:{width:1440,height:900} });
|
||||
await ctx.addInitScript((save) => {
|
||||
localStorage.setItem('d100_save_auto', save);
|
||||
}, stateJson);
|
||||
const page = await ctx.newPage();
|
||||
page.setDefaultTimeout(9000);
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
await page.goto('http://127.0.0.1:5199', { waitUntil:'domcontentloaded' });
|
||||
await page.waitForSelector('.title-screen');
|
||||
await page.click('text=CONTINUE');
|
||||
await page.waitForSelector('.game-layout');
|
||||
console.log('loaded day:', await page.evaluate(()=>document.querySelector('#hud-day')?.textContent));
|
||||
|
||||
// end days 99 -> 100 -> finale
|
||||
for (let i = 0; i < 10; i++) {
|
||||
// drain modals
|
||||
for (let k = 0; k < 12; k++) {
|
||||
const r = await page.evaluate(() => {
|
||||
const ov = document.querySelectorAll('.modal-root .overlay');
|
||||
const last = ov[ov.length - 1];
|
||||
if (!last) return { none: true };
|
||||
const btns = [...last.querySelectorAll('button')].filter(b => !b.disabled && b.offsetParent !== null);
|
||||
if (/DAY 100/i.test(last.querySelector('.modal-head h2')?.textContent ?? '')) {
|
||||
const muster = btns.find(b => /convoy|muster/i.test(b.textContent));
|
||||
const any = muster ?? btns[0];
|
||||
any.click();
|
||||
return { finale: true, clicked: any.textContent.slice(0, 30) };
|
||||
}
|
||||
const t = btns.find(b => /CONTINUE|STEEL/i.test(b.textContent))
|
||||
|| btns.find(b => b.classList.contains('ev-choice') && !b.classList.contains('locked'))
|
||||
|| btns.find(b => b.classList.contains('choice'));
|
||||
if (!t) return { stuck: true };
|
||||
t.click();
|
||||
return { clicked: true };
|
||||
});
|
||||
if (r.none) break;
|
||||
if (r.finale) { console.log('FINALE choice:', r.clicked); continue; }
|
||||
if (r.stuck) { console.log('STUCK on modal'); break; }
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
const ended = await page.evaluate(() => !!document.querySelector('.ending-modal'));
|
||||
if (ended) break;
|
||||
const btn = page.locator('#btn-endday');
|
||||
if (!(await btn.isEnabled())) break;
|
||||
await btn.click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
await page.waitForTimeout(800);
|
||||
const ending = await page.evaluate(() => {
|
||||
const el = document.querySelector('.ending-title');
|
||||
return el ? el.textContent : null;
|
||||
});
|
||||
await page.screenshot({ path: 'shots/ending.png' });
|
||||
console.log('ENDING SHOWN:', ending);
|
||||
console.log(errors.length ? 'PAGE ERRORS: ' + errors.join(' | ') : 'no page errors');
|
||||
await browser.close();
|
||||
if (!ending || errors.length) process.exit(1);
|
||||
console.log('FINALE TEST PASSED');
|
||||
Reference in New Issue
Block a user