Merge branch 'm4/yut' into codex/owner/grp_discord_joke-app
# Conflicts: # apps/web/src/games/registry.ts # apps/web/src/lib/catalog-ui.ts # packages/games/src/index.ts # packages/shared/src/catalog.ts
This commit is contained in:
@@ -25,6 +25,8 @@ interface Plan {
|
||||
moves?: string[][];
|
||||
/** Card games: custom driver over all pages (host first). */
|
||||
drive?: (pages: Page[]) => Promise<void>;
|
||||
/** Host changes game options in the lobby before starting. */
|
||||
lobby?: (host: Page) => Promise<void>;
|
||||
}
|
||||
|
||||
/** Card games: whoever has the turn presses the first available safe action (check/call/…) until `rounds` actions were taken. */
|
||||
@@ -53,7 +55,73 @@ async function finishHand(pages: Page[]) {
|
||||
for (const p of pages) await p.getByText('이번 판 결과').first().waitFor({ timeout: 10_000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 윷놀이: one UI step for whoever can act on `p` — press the throw button, or pick a result chip →
|
||||
* a glowing piece (or the waiting-piece tray) → "이동하기" (the two-step move confirmation).
|
||||
*/
|
||||
async function yutStep(p: Page): Promise<boolean> {
|
||||
const throwBtn = p.getByRole('button', { name: /윷 던지기|한 번 더 던지기/ });
|
||||
if ((await throwBtn.count()) && (await throwBtn.isEnabled().catch(() => false))) {
|
||||
return throwBtn.click({ timeout: 1500 }).then(() => true, () => false);
|
||||
}
|
||||
const chip = p.locator('.yut-chip:not([disabled])').first();
|
||||
if (!(await chip.count())) return false;
|
||||
try {
|
||||
await chip.click({ timeout: 1500 });
|
||||
const src = p.locator('g.yut-hit[tabindex="0"], .yut-tray-btn.can').first();
|
||||
await src.waitFor({ timeout: 2000 });
|
||||
// Keyboard: Enter on a piece shows where it lands, Enter again on the same piece moves it.
|
||||
const h = await src.elementHandle({ timeout: 1000 });
|
||||
await h!.focus();
|
||||
await p.keyboard.press('Enter');
|
||||
await p.getByRole('button', { name: /^이동하기/ }).waitFor({ timeout: 1500 });
|
||||
await h!.focus();
|
||||
await p.keyboard.press('Enter');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const PLANS: Record<string, Plan> = {
|
||||
yut: {
|
||||
// 말 2개로 줄여서 PC 방장 + 휴대폰 친구가 실제 화면 조작으로 끝까지 둔다(중간 화면도 저장).
|
||||
lobby: async (host) => {
|
||||
await host.getByRole('button', { name: '2개', exact: true }).click();
|
||||
await host.getByRole('button', { name: '설정 저장' }).click();
|
||||
await host.getByText(/말 2개/).first().waitFor();
|
||||
},
|
||||
drive: async (pages) => {
|
||||
const [host, guest] = pages as [Page, Page];
|
||||
let actions = 0;
|
||||
let shot = false;
|
||||
const until = Date.now() + 300_000;
|
||||
const over = () => host.getByText(/이겼어요|아쉽게 졌어요/).count();
|
||||
while (Date.now() < until && !(await over())) {
|
||||
let acted = false;
|
||||
for (const p of pages) {
|
||||
if (await yutStep(p)) {
|
||||
acted = true;
|
||||
actions++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (actions >= 16 && !shot) {
|
||||
shot = true;
|
||||
await host.waitForTimeout(400);
|
||||
await host.screenshot({ path: join(SHOTS, 'yut-mid-pc.png') });
|
||||
await guest.screenshot({ path: join(SHOTS, 'yut-mid-phone.png'), fullPage: true });
|
||||
await guest.emulateMedia({ colorScheme: 'dark' });
|
||||
await guest.screenshot({ path: join(SHOTS, 'yut-mid-phone-dark.png') });
|
||||
await guest.emulateMedia({ colorScheme: 'light' });
|
||||
}
|
||||
if (!acted) await host.waitForTimeout(120);
|
||||
}
|
||||
if (!(await over())) throw new Error(`game did not finish (${actions} UI actions)`);
|
||||
await guest.getByText(/이겼어요|아쉽게 졌어요/).waitFor();
|
||||
console.log(` yut: finished after ${actions} UI actions`);
|
||||
},
|
||||
},
|
||||
seotda: {
|
||||
// 3 players, default 2장 섯다 (1장씩 두 번): everyone checks both betting rounds → showdown.
|
||||
players: 3,
|
||||
@@ -211,6 +279,7 @@ for (const id of games) {
|
||||
await g.getByRole('button', { name: '이 이름으로 입장하기' }).click();
|
||||
await host.getByLabel('자리').getByText(nick, { exact: true }).waitFor();
|
||||
}
|
||||
if (plan.lobby) await plan.lobby(host);
|
||||
await host.screenshot({ path: join(SHOTS, `${id}-0-lobby.png`) });
|
||||
await host.getByRole('button', { name: '▶ 게임 시작' }).click();
|
||||
await host.getByRole('button', { name: '시작하기' }).click();
|
||||
|
||||
Reference in New Issue
Block a user