Merge branch 'm5/halli-galli' into codex/owner/grp_discord_joke-app
# Conflicts: # apps/web/src/games/registry.ts # apps/web/src/lib/catalog-ui.ts # e2e/games.e2e.ts # packages/games/src/index.ts # packages/shared/src/catalog.ts
This commit is contained in:
@@ -254,6 +254,101 @@ const PLANS: Record<string, Plan> = {
|
||||
await host.screenshot({ path: join(SHOTS, 'gostop-breakdown-pc.png') });
|
||||
if (state.plays < 10) throw new Error(`only ${state.plays} cards were played`);
|
||||
console.log(` gostop: ${state.plays} plays, ${state.sheets} choice sheets (${state.picks} card picks), 고 ${state.go}, 스톱 ${state.stop}`);
|
||||
/** 할리갈리: fruit totals of the face-up tops, read from the seat labels ("펼친 카드 딸기 3개"). */
|
||||
async function halliTotals(p: Page): Promise<Record<string, number>> {
|
||||
const labels = await p.locator('.hg-seat').evaluateAll((els) => els.map((e) => e.getAttribute('aria-label') ?? ''));
|
||||
const t: Record<string, number> = { 딸기: 0, 바나나: 0, 라임: 0, 자두: 0 };
|
||||
for (const l of labels) {
|
||||
const m = /펼친 카드 (딸기|바나나|라임|자두) (\d)개/.exec(l);
|
||||
if (m) t[m[1]!]! += Number(m[2]);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
const PLANS: Record<string, Plan> = {
|
||||
'halli-galli': {
|
||||
// PC host + 2 phones. Real flips (Enter on PC, taps on phones); whenever some fruit totals exactly five a player
|
||||
// rings the bell (PC: Space, phone: tap), plus at least one deliberate wrong bell. 24+ flips through the UI.
|
||||
players: 3,
|
||||
lobby: async (host) => {
|
||||
await host.getByRole('group', { name: '뒤집기 제한 시간' }).getByRole('button', { name: '5초' }).click();
|
||||
await host.getByRole('button', { name: '설정 저장' }).click();
|
||||
await host.getByText(/뒤집기 5초/).first().waitFor();
|
||||
},
|
||||
drive: async (pages) => {
|
||||
const [host, guest] = pages as [Page, Page, Page];
|
||||
const myTurn = (p: Page) => p.evaluate(() => !!document.querySelector('.turn-status')?.textContent?.includes('내 차례예요'));
|
||||
const ended = () => host.getByRole('dialog', { name: '게임 결과' }).count();
|
||||
await host.locator('.hg-seat').first().waitFor();
|
||||
// 3-second countdown first.
|
||||
await host.waitForFunction(() => !document.querySelector('.hg-count'), null, { timeout: 8000 });
|
||||
let flips = 0;
|
||||
let rights = 0;
|
||||
let wrongs = 0;
|
||||
let lates = 0;
|
||||
let shots = 0;
|
||||
let ringer = 0;
|
||||
const until = Date.now() + 150_000;
|
||||
const bubble = (p: Page) => p.locator('.hg-bubble').first().innerText({ timeout: 2500 }).catch(() => '');
|
||||
while (Date.now() < until && !(await ended()) && (flips < 24 || rights < 1 || wrongs < 1)) {
|
||||
const totals = await halliTotals(host);
|
||||
const five = Object.values(totals).includes(5);
|
||||
const wantWrong = !five && wrongs === 0 && flips >= 4 && Object.values(totals).some((n) => n > 0);
|
||||
if (five || wantWrong) {
|
||||
const p = pages[ringer++ % pages.length]!;
|
||||
const bell = p.locator('.hg-bell');
|
||||
if (await bell.isEnabled().catch(() => false)) {
|
||||
if (p === host) {
|
||||
await host.evaluate(() => (document.activeElement as HTMLElement | null)?.blur());
|
||||
await host.keyboard.press('Space');
|
||||
} else await bell.tap();
|
||||
const text = await bubble(p);
|
||||
if (text.includes('땡')) rights++;
|
||||
else if (text.includes('삐')) wrongs++;
|
||||
else if (text.includes('늦었어요')) lates++;
|
||||
if (shots === 0 && text) {
|
||||
shots++;
|
||||
await host.screenshot({ path: join(SHOTS, 'halli-galli-bell-pc.png') });
|
||||
await guest.screenshot({ path: join(SHOTS, 'halli-galli-bell-phone.png') });
|
||||
}
|
||||
await host.waitForTimeout(400);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let flipped = false;
|
||||
for (const p of pages) {
|
||||
if (!(await myTurn(p))) continue;
|
||||
const btn = p.getByRole('button', { name: '뒤집기', exact: true });
|
||||
if (!(await btn.isEnabled().catch(() => false))) break;
|
||||
const before = await p.locator('.hg-seat[data-me="true"] .hg-meta').innerText();
|
||||
if (p === host) {
|
||||
await host.evaluate(() => (document.activeElement as HTMLElement | null)?.blur());
|
||||
await host.keyboard.press('Enter');
|
||||
} else await btn.tap();
|
||||
await p.waitForFunction((b) => document.querySelector('.hg-seat[data-me="true"] .hg-meta')?.textContent !== b, before, { timeout: 3000 }).catch(() => {});
|
||||
flips++;
|
||||
flipped = true;
|
||||
break;
|
||||
}
|
||||
if (flips === 12 && flipped && shots < 2) {
|
||||
shots = 2;
|
||||
await host.waitForTimeout(300);
|
||||
await host.screenshot({ path: join(SHOTS, 'halli-galli-mid-pc.png') });
|
||||
await guest.screenshot({ path: join(SHOTS, 'halli-galli-mid-phone.png') });
|
||||
await guest.emulateMedia({ colorScheme: 'dark' });
|
||||
await guest.waitForTimeout(400); // let the theme transition finish
|
||||
await guest.screenshot({ path: join(SHOTS, 'halli-galli-mid-phone-dark.png') });
|
||||
await guest.emulateMedia({ colorScheme: 'light' });
|
||||
await host.emulateMedia({ colorScheme: 'dark' });
|
||||
await host.waitForTimeout(400);
|
||||
await host.screenshot({ path: join(SHOTS, 'halli-galli-mid-pc-dark.png') });
|
||||
await host.emulateMedia({ colorScheme: 'light' });
|
||||
}
|
||||
await host.waitForTimeout(flipped ? 650 : 120);
|
||||
}
|
||||
console.log(` halli-galli: ${flips} UI flips, ${rights} correct bells, ${wrongs} wrong bells, ${lates} late`);
|
||||
if (!(await ended()) && (flips < 20 || wrongs < 1)) throw new Error('not enough flips / bells');
|
||||
if (rights < 1 && !(await ended())) throw new Error('no correct bell happened');
|
||||
},
|
||||
},
|
||||
rummikub: {
|
||||
|
||||
Reference in New Issue
Block a user