feat(ui): hide GCG export in honest-AI games
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m1s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m1s
A vs_ai game is throwaway practice, so its finished history header no longer
offers the 📤 GCG export (an empty slot keeps the comms icon pinned right).
Docs note the AI exclusion; UI_DESIGN also records that confirming a resign
reveals the full board (closes the history drawer, zooms out).
This commit is contained in:
+4
-3
@@ -224,9 +224,10 @@ barred from feedback (a role, not a full account block) sees the send control di
|
||||
|
||||
### History & statistics
|
||||
Finished games are archived in a dictionary-independent form and exportable to
|
||||
GCG; the export is offered **only once a game is finished** (exporting a live game
|
||||
would leak the move journal), and the client shares the `.gcg` file where the
|
||||
platform supports it, otherwise downloads it. Statistics (durable accounts only):
|
||||
GCG; the export is offered **only once a game is finished**, and never for an
|
||||
honest-AI practice game (a live game's export would leak the move journal; an AI
|
||||
game is throwaway). The client shares the `.gcg` file where the platform supports
|
||||
it, otherwise downloads it. Statistics (durable accounts only):
|
||||
wins, losses, draws, max points in a game, and max points for a single move (the
|
||||
best play, which already includes every word it formed plus the all-tiles bonus).
|
||||
A game that can no longer be continued — because a rule changed and an earlier
|
||||
|
||||
@@ -228,9 +228,10 @@ UTC), суточного окна отсутствия (away; сетка по 10
|
||||
|
||||
### История и статистика
|
||||
Завершённые партии архивируются в независимом от словаря виде и экспортируются
|
||||
в GCG; экспорт доступен **только после завершения партии** (экспорт идущей партии
|
||||
раскрыл бы журнал ходов), и клиент делится файлом `.gcg` там, где платформа это
|
||||
поддерживает, иначе скачивает его. Статистика (только у постоянных аккаунтов):
|
||||
в GCG; экспорт доступен **только после завершения партии** и никогда — для
|
||||
тренировочной партии с ИИ (экспорт идущей партии раскрыл бы журнал ходов, а партия
|
||||
с ИИ одноразовая). Клиент делится файлом `.gcg` там, где платформа это поддерживает,
|
||||
иначе скачивает его. Статистика (только у постоянных аккаунтов):
|
||||
победы, поражения, ничьи, макс. очков за партию и макс. очков за один ход (лучший
|
||||
ход, уже включающий все образованные им слова и бонус за все фишки).
|
||||
Партия, которую больше нельзя продолжить — из-за изменения правил более ранний ход
|
||||
|
||||
+4
-2
@@ -300,9 +300,11 @@ enabled on the first, uncached load) and flip in place when an event refreshes t
|
||||
Safari.
|
||||
- **History / GCG**: the in-game slide-down history lays each move out in a per-seat grid
|
||||
(the word(s) and the move score, no running total); *Export GCG* (the 📤 in the history
|
||||
header) shares or downloads the `.gcg` file and appears only once the game is finished.
|
||||
header) shares or downloads the `.gcg` file and appears only once the game is finished — and
|
||||
never in an honest-AI game (throwaway practice). Confirming a resign reveals the full board:
|
||||
it closes the history drawer (portrait) and zooms the board out.
|
||||
- **Finished game**: the board keeps no last-word highlight and no zoom; the history header
|
||||
offers *Export GCG* (not *Drop game*) and the comms hub hides the 🔎 *Dictionary* tab; and
|
||||
offers *Export GCG* (not *Drop game*; an AI game offers neither) and the comms hub hides the 🔎 *Dictionary* tab; and
|
||||
the footer (rack + tab bar) is **drawn but inert** (greyed, non-interactive) rather than
|
||||
hidden, so the layout does not jump. Chat send / nudge are the ⬆️ / 🛎️ icons. The input
|
||||
row is turn-driven: on your turn the message field shows until your one allowed message is
|
||||
|
||||
@@ -59,6 +59,29 @@ test('AI game: 🤖 opponent, no wait, chat disabled, dictionary still works', a
|
||||
await expect(page.locator('.check input')).toBeEnabled();
|
||||
});
|
||||
|
||||
// GCG export is pointless for a practice AI game, so the finished AI game hides the 📤 export
|
||||
// (the comms hub stays). Start an AI game, resign it, reopen the history and check the header.
|
||||
test('AI game: no GCG export offered after it ends', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: /guest/i }).click();
|
||||
await page.getByRole('button', { name: /New/ }).click();
|
||||
await page.locator('.variant').first().click(); // AI is the default opponent
|
||||
await page.getByRole('button', { name: /Start game/i }).click();
|
||||
await expect(page.locator('.scoreboard').getByText('🤖')).toBeVisible();
|
||||
|
||||
// Resign the practice game from the history header.
|
||||
await page.locator('.scoreboard').click();
|
||||
await page.getByRole('button', { name: 'Drop game' }).click();
|
||||
await page.locator('button.danger').click();
|
||||
await expect(page.locator('.status .over')).toBeVisible();
|
||||
|
||||
// Reopen the history (resigning auto-closed it): the finished AI game offers no GCG export,
|
||||
// while the comms hub stays reachable.
|
||||
await page.locator('.scoreboard').click();
|
||||
await expect(page.getByRole('button', { name: 'Export GCG' })).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: 'Chat' })).toBeVisible();
|
||||
});
|
||||
|
||||
// The opponent_joined push is best-effort and never replayed, so a join that lands while the live
|
||||
// stream is down is lost. The waiting game screen recovers it without a push: a poll while the
|
||||
// stream is down, and a refetch on stream reconnect. The __stream hook (lib/app.svelte.ts) drops /
|
||||
|
||||
@@ -986,7 +986,13 @@
|
||||
{#if view}
|
||||
<div class="hhead">
|
||||
{#if gameOver}
|
||||
<button class="hicon" onclick={exportGcg} aria-label={t('game.exportGcg')}>📤</button>
|
||||
{#if view.game.vsAi}
|
||||
<!-- GCG export is not offered for a practice AI game; an empty slot keeps the comms
|
||||
icon pinned right (the header is space-between). -->
|
||||
<span aria-hidden="true"></span>
|
||||
{:else}
|
||||
<button class="hicon" onclick={exportGcg} aria-label={t('game.exportGcg')}>📤</button>
|
||||
{/if}
|
||||
{:else}
|
||||
<button class="hicon" onclick={() => (resignOpen = true)} disabled={waitingForOpponent} aria-label={t('game.dropGame')}>🏁</button>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user