Files
fool-cardgame/assets/scripts/ui/MainFlow.ts
T
ftcandClaude Opus 5 4f3a26dc45 M5-M7: сцены, лобби, экран игры и цикл матча
Обе сцены собраны через Code Mode, не руками.

- loader: поднимает модули, прогревает арт карт, уходит в lobby
- lobby: обе UI-ветки в одной сцене (по ARCHITECTURE.md), матч — это
  смена видимого корня, а не загрузка сцены
- GameScreen рисует текущее состояние и доводит узлы твинами, а не
  проигрывает поток событий: узел карты живёт вместе со своей картой,
  поэтому "шестёрка ушла из руки на стол" не требует отдельного случая
- один тап = ход целиком; логика вынесена в core/rules/intent.ts
  и покрыта тестами, в том числе проверкой, что она никогда не предложит
  ход, который правила отвергнут
- арт карт переехал в assets/resources: .pac — это инструкция упаковки
  на сборке, а не рантайм-ассет, и без ссылок на кадры в сборку
  не попадало ничего. Теперь все 37 спрайтов в одной текстуре 2048x2048
- клики кнопок подписываются в коде, а не через clickEvents сцены
- строки интерфейса собраны в одном файле

Сборка web-mobile проходит чисто, все ссылки в сцене проверены.
Вживую не запускалось: браузер в этом окружении не поднимает
главный цикл движка. Подробности — в docs/TECHNICAL.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 11:30:28 +03:00

72 lines
2.0 KiB
TypeScript

import { _decorator, Component, Node } from 'cc';
import { App } from '../app/App';
import { LobbyScreen } from './lobby/LobbyScreen';
import { GameScreen, MatchOutcome } from './game/GameScreen';
const { ccclass, property } = _decorator;
/**
* Owns the two screens the lobby scene contains and moves between them.
*
* ARCHITECTURE.md puts the main game UI in the lobby scene, so a match is a
* change of visible screen rather than a scene load: no reload cost between
* games, and no way for a half-finished match to leak into a new one as long
* as `GameScreen.teardown` is honest about clearing up.
*/
@ccclass('MainFlow')
export class MainFlow extends Component {
@property(LobbyScreen)
lobby: LobbyScreen | null = null;
@property(GameScreen)
game: GameScreen | null = null;
@property(Node)
lobbyRoot: Node | null = null;
@property(Node)
gameRoot: Node | null = null;
start(): void {
if (this.lobby) {
this.lobby.onStartRequested = () => this.startMatch();
}
if (this.game) {
this.game.onFinished = (outcome) => this.finishMatch(outcome);
this.game.onQuitRequested = () => this.abandonMatch();
}
this.showLobby();
}
private showLobby(): void {
setActive(this.gameRoot, false);
setActive(this.lobbyRoot, true);
this.lobby?.refresh();
}
private startMatch(): void {
setActive(this.lobbyRoot, false);
setActive(this.gameRoot, true);
this.game?.startNewMatch();
}
private finishMatch(outcome: MatchOutcome): void {
if (App.exists) {
App.instance.recordMatch(outcome);
}
this.game?.teardown();
this.showLobby();
}
/** Leaving early still ends the match — and it counts as a loss. */
private abandonMatch(): void {
this.finishMatch('loss');
}
}
function setActive(node: Node | null, active: boolean): void {
if (node) {
node.active = active;
}
}