Merge branch 'm4/rummikub' 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:
@@ -19,6 +19,8 @@ mkdirSync(SHOTS, { recursive: true });
|
||||
interface Plan {
|
||||
/** Total players including the host (default 2). Extra players join on phones. */
|
||||
players?: number;
|
||||
/** Host changes room options before starting (inline settings form on PC). */
|
||||
lobby?: (host: Page) => Promise<void>;
|
||||
/** Steps run before normal moves (e.g. janggi formation choice). */
|
||||
setup?: (host: Page, guest: Page) => Promise<void>;
|
||||
/** Board games: key sequences on the grid, in turn order. */
|
||||
@@ -120,6 +122,84 @@ const PLANS: Record<string, Plan> = {
|
||||
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`);
|
||||
/** 숫자 타일: whoever has the turn tries "도와줘" → "새 묶음" → "확정"; otherwise takes one tile. */
|
||||
async function rummikubTurns(pages: Page[], turns: number) {
|
||||
const stats = { plays: 0, draws: 0, undo: false };
|
||||
const myTurn = (p: Page) => p.evaluate(() => !!document.querySelector('.turn-status')?.textContent?.includes('내 차례예요'));
|
||||
const bar = (p: Page) => p.getByRole('group', { name: '내 차례 행동' });
|
||||
/** Waits until a bar button is enabled / disabled (server answered). */
|
||||
const barButton = (p: Page, name: string, enabled: boolean) =>
|
||||
p.waitForFunction(
|
||||
([n, e]) => [...document.querySelectorAll('.rk-bar button')].some((b) => b.textContent === n && b.hasAttribute('disabled') !== e),
|
||||
[name, enabled] as const,
|
||||
{ timeout: 5000 },
|
||||
);
|
||||
for (let n = 0; n < turns; n++) {
|
||||
let mover: Page | null = null;
|
||||
for (let t = 0; t < 150 && !mover; t++) {
|
||||
for (const p of pages) if (!mover && (await myTurn(p))) mover = p;
|
||||
if (!mover) await pages[0]!.waitForTimeout(100);
|
||||
}
|
||||
if (!mover) throw new Error(`nobody has the turn (turn ${n + 1})`);
|
||||
await bar(mover).waitFor();
|
||||
let played = false;
|
||||
await bar(mover).getByRole('button', { name: '도와줘' }).click();
|
||||
const picked = await mover.locator('.rk-rack button[aria-pressed="true"]').count();
|
||||
if (picked > 0) {
|
||||
// Tap one picked tile twice (deselect, select again) — the tap-to-select path.
|
||||
const first = mover.locator('.rk-rack button[aria-pressed="true"]').first();
|
||||
const label = await first.getAttribute('aria-label');
|
||||
await first.click();
|
||||
await mover.locator(`.rk-rack button[aria-label="${label}"]`).first().click();
|
||||
await bar(mover).getByRole('button', { name: '새 묶음' }).click();
|
||||
await mover.locator('.rk-set').last().waitFor();
|
||||
if (!stats.undo) {
|
||||
// Undo once (back to the turn start), then place again.
|
||||
await barButton(mover, '되돌리기', true);
|
||||
await bar(mover).getByRole('button', { name: '되돌리기' }).click();
|
||||
await barButton(mover, '처음으로', false);
|
||||
stats.undo = true;
|
||||
await bar(mover).getByRole('button', { name: '도와줘' }).click();
|
||||
await bar(mover).getByRole('button', { name: '새 묶음' }).click();
|
||||
}
|
||||
const commit = bar(mover).getByRole('button', { name: '확정' });
|
||||
await mover
|
||||
.waitForFunction(() => [...document.querySelectorAll('.rk-bar button')].some((b) => b.textContent === '확정' && b.classList.contains('btn-primary') && !b.hasAttribute('disabled')), null, { timeout: 5000 })
|
||||
.then(
|
||||
() => (played = true),
|
||||
() => {},
|
||||
);
|
||||
if (played) {
|
||||
await commit.click();
|
||||
stats.plays++;
|
||||
} else {
|
||||
await bar(mover).getByRole('button', { name: '처음으로' }).click();
|
||||
await mover.waitForTimeout(300);
|
||||
}
|
||||
}
|
||||
if (!played) {
|
||||
await bar(mover).getByRole('button', { name: /^(한 장 가져오기|넘기기)$/ }).click();
|
||||
const sheet = mover.getByRole('button', { name: /^되돌리고/ });
|
||||
if (await sheet.count()) await sheet.click();
|
||||
stats.draws++;
|
||||
}
|
||||
await mover.waitForFunction(() => !document.querySelector('.turn-status')?.textContent?.includes('내 차례예요'), null, { timeout: 5000 });
|
||||
}
|
||||
console.log(` rummikub: ${stats.plays} plays, ${stats.draws} draws, undo ${stats.undo ? 'used' : 'not used'}`);
|
||||
return stats;
|
||||
}
|
||||
|
||||
const PLANS: Record<string, Plan> = {
|
||||
rummikub: {
|
||||
// PC host + phone guest, first meld "없음" so hint plays happen early; 10 real turns through the UI.
|
||||
lobby: async (host) => {
|
||||
await host.getByRole('group', { name: '첫 등록 최소 점수' }).getByRole('button', { name: '없음' }).click();
|
||||
await host.getByRole('button', { name: '설정 저장' }).click();
|
||||
await host.getByText('첫 등록 점수 없음').first().waitFor();
|
||||
},
|
||||
drive: async (pages) => {
|
||||
const stats = await rummikubTurns(pages, 10);
|
||||
if (stats.plays + stats.draws < 10) throw new Error('not enough turns');
|
||||
},
|
||||
},
|
||||
seotda: {
|
||||
|
||||
Reference in New Issue
Block a user