Merge branch 'm7/las-vegas' into codex/owner/grp_discord_joke-app
This commit is contained in:
114
e2e/games.e2e.ts
114
e2e/games.e2e.ts
@@ -466,6 +466,68 @@ async function qdStep(p: Page, keyboard: boolean): Promise<boolean> {
|
||||
return d === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 라스베가스: one UI step for `p` — press "주사위 굴리기", or pick a number group (every other pick prefers a group
|
||||
* marked "동점 주의" so duplicate-tie cancellations really happen) and confirm "N번 카지노에 M개 놓기".
|
||||
* The PC host's first pick uses the keyboard, its second one taps the casino card instead of the group.
|
||||
*/
|
||||
async function lasVegasStep(
|
||||
p: Page,
|
||||
phone: boolean,
|
||||
st: { rolls: number; places: number; keyboard: boolean; casinoPick: boolean; shots: Set<string> },
|
||||
): Promise<boolean> {
|
||||
const press = (l: ReturnType<Page['locator']>) => (phone ? l.tap({ timeout: 1500 }) : l.click({ timeout: 1500 }));
|
||||
const roll = p.getByRole('button', { name: /^🎲 주사위 굴리기/ });
|
||||
if ((await roll.count()) && (await roll.isEnabled().catch(() => false))) {
|
||||
if (!(await press(roll).then(() => true, () => false))) return false;
|
||||
st.rolls++;
|
||||
return true;
|
||||
}
|
||||
const groups = p.locator('.lv-group:not([disabled])');
|
||||
if (!(await groups.count())) return false;
|
||||
try {
|
||||
const tie = p.locator('.lv-group:not([disabled])[data-standing="tie"]');
|
||||
const target = st.places % 2 === 1 && (await tie.count()) ? tie.first() : groups.first();
|
||||
const confirm = p.getByRole('button', { name: /번 카지노에 \d+개 놓기$/ });
|
||||
if (!phone && !st.keyboard) {
|
||||
// Keyboard only: Enter on a number group, then Enter on the confirm button.
|
||||
await target.focus();
|
||||
await p.keyboard.press('Enter');
|
||||
await confirm.waitFor({ timeout: 1500 });
|
||||
await confirm.focus();
|
||||
await p.keyboard.press('Enter');
|
||||
st.keyboard = true;
|
||||
} else if (!phone && !st.casinoPick) {
|
||||
// Choose by tapping the highlighted casino card instead of the group button.
|
||||
await p.locator('button.lv-casino').first().click({ timeout: 1500 });
|
||||
await confirm.waitFor({ timeout: 1500 });
|
||||
st.casinoPick = true;
|
||||
await confirm.click({ timeout: 1500 });
|
||||
} else {
|
||||
await press(target);
|
||||
await confirm.waitFor({ timeout: 1500 });
|
||||
const tag = phone ? 'phone' : 'pc';
|
||||
if (st.places >= 6 && !st.shots.has(tag)) {
|
||||
// Mid-game: a number group chosen, casino highlighted with its preview tag, light + dark.
|
||||
st.shots.add(tag);
|
||||
if (phone) await p.evaluate(() => window.scrollTo(0, 0));
|
||||
await p.screenshot({ path: join(SHOTS, `las-vegas-mid-${tag}.png`) });
|
||||
if (phone) await p.screenshot({ path: join(SHOTS, `las-vegas-mid-${tag}-full.png`), fullPage: true });
|
||||
await p.emulateMedia({ colorScheme: 'dark' });
|
||||
await p.waitForTimeout(250);
|
||||
await p.screenshot({ path: join(SHOTS, `las-vegas-mid-${tag}-dark.png`) });
|
||||
await p.emulateMedia({ colorScheme: 'light' });
|
||||
}
|
||||
await press(confirm);
|
||||
}
|
||||
st.places++;
|
||||
await p.waitForFunction(() => !document.querySelector('.lv-group[aria-pressed="true"]'), null, { timeout: 5000 });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const PLANS: Record<string, Plan> = {
|
||||
rummikub: {
|
||||
// PC host + phone guest, first meld "없음" so hint plays happen early; 10 real turns through the UI.
|
||||
@@ -1269,6 +1331,58 @@ const PLANS: Record<string, Plan> = {
|
||||
console.log(` quoridor: 3 walls + 1 refused wall, ${steps} pawn moves (${jumps} jump), ${summary.split('\n').find((l) => l.includes('승리'))}`);
|
||||
},
|
||||
},
|
||||
'las-vegas': {
|
||||
// PC host + phone guest (2 players → 4 white dice each), 3 rounds via the lobby: real rolls and placements
|
||||
// through the UI until round settlements (with a duplicate-tie cancellation) and the final result appear.
|
||||
lobby: async (host) => {
|
||||
await host.getByRole('group', { name: '라운드 수' }).getByRole('button', { name: '3라운드' }).click();
|
||||
await host.getByRole('button', { name: '설정 저장' }).click();
|
||||
await host.getByText(/^3라운드 · 흰 주사위 자동/).first().waitFor();
|
||||
},
|
||||
drive: async (pages) => {
|
||||
const [host, guest] = pages as [Page, Page];
|
||||
const st = { rolls: 0, places: 0, keyboard: false, casinoPick: false, shots: new Set<string>() };
|
||||
let tieSeen = false;
|
||||
let settleShot = false;
|
||||
const over = () => host.getByRole('dialog', { name: '게임 결과' }).count();
|
||||
const until = Date.now() + 300_000;
|
||||
while (Date.now() < until && !(await over())) {
|
||||
let acted = false;
|
||||
for (const [i, p] of pages.entries()) {
|
||||
if (await lasVegasStep(p, i > 0, st)) {
|
||||
acted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!settleShot && (await host.locator('section[aria-label="1라운드 정산"]').count())) {
|
||||
settleShot = true;
|
||||
await host.waitForTimeout(700);
|
||||
await host.screenshot({ path: join(SHOTS, 'las-vegas-settle-pc.png') });
|
||||
await guest.screenshot({ path: join(SHOTS, 'las-vegas-settle-phone.png'), fullPage: true });
|
||||
}
|
||||
if (!tieSeen && (await host.locator('.lv-settle .lv-tieout').count())) tieSeen = true;
|
||||
if (!acted) await host.waitForTimeout(150);
|
||||
}
|
||||
if (!(await over())) throw new Error(`game did not finish (${JSON.stringify({ ...st, shots: [...st.shots] })})`);
|
||||
await guest.getByRole('dialog', { name: '게임 결과' }).waitFor();
|
||||
if (!tieSeen && (await host.locator('.lv-settle .lv-tieout').count())) tieSeen = true;
|
||||
await host.waitForTimeout(600);
|
||||
await host.screenshot({ path: join(SHOTS, 'las-vegas-result-pc.png') });
|
||||
await guest.screenshot({ path: join(SHOTS, 'las-vegas-result-phone.png') });
|
||||
// Close the result sheet to look at the final table on the board.
|
||||
await host.keyboard.press('Escape');
|
||||
await host.locator('section[aria-label="최종 결과"]').waitFor();
|
||||
await host.screenshot({ path: join(SHOTS, 'las-vegas-final-pc.png') });
|
||||
await host.emulateMedia({ colorScheme: 'dark' });
|
||||
await host.waitForTimeout(250);
|
||||
await host.screenshot({ path: join(SHOTS, 'las-vegas-final-pc-dark.png') });
|
||||
await host.emulateMedia({ colorScheme: 'light' });
|
||||
if (!(await host.locator('section[aria-label="3라운드 정산"]').count())) throw new Error('no settlement for round 3');
|
||||
if (!tieSeen) throw new Error('no duplicate-tie cancellation appeared in any settlement');
|
||||
if (st.rolls < 10 || st.places < 10 || !st.keyboard || !st.casinoPick) throw new Error(`too few real turns: ${JSON.stringify({ ...st, shots: [...st.shots] })}`);
|
||||
console.log(` las-vegas: ${st.rolls} rolls, ${st.places} placements, tie cancellation seen`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const games = process.argv.slice(2).length ? process.argv.slice(2) : Object.keys(PLANS);
|
||||
|
||||
Reference in New Issue
Block a user