import Phaser from "phaser"; import type { ClickerDefinition, ClickerUpgrade, ClickerProgress, GameResult } from "../types"; const COLORS = { bg: 0x14162a, target: 0x4f46e5, row: 0x23264a, rowOwned: 0x0d3d38, rowDisabled: 0x1a1c33, }; interface UpgradeRow { bg: Phaser.GameObjects.Rectangle; label: Phaser.GameObjects.Text; upgrade: ClickerUpgrade; index: number; } export class ClickerScene extends Phaser.Scene { private definition!: ClickerDefinition; private onFinish?: (result: GameResult) => void; private onProgress?: (state: ClickerProgress) => void; private score = 0; private pointsPerClick = 1; private pointsPerSecond = 0; private ownedUpgrades = new Set(); private scoreText!: Phaser.GameObjects.Text; private upgradeRows: UpgradeRow[] = []; private finished = false; constructor() { super("ClickerScene"); } create() { this.definition = this.registry.get("definition"); this.onFinish = this.registry.get("onFinish"); this.onProgress = this.registry.get("onProgress"); const initialState = this.registry.get("initialState") as ClickerProgress | undefined; const validUpgradeIndices = (initialState?.ownedUpgradeIndices ?? []).filter( (i) => i >= 0 && i < this.definition.upgrades.length, ); this.score = initialState?.score ?? 0; this.pointsPerClick = initialState?.pointsPerClick ?? this.definition.startingPointsPerClick; this.pointsPerSecond = initialState?.pointsPerSecond ?? 0; this.ownedUpgrades = new Set(validUpgradeIndices); this.upgradeRows = []; this.finished = false; this.cameras.main.setBackgroundColor(COLORS.bg); this.scoreText = this.add .text(480, 30, "", { fontFamily: "sans-serif", fontSize: "24px", color: "#eef0fb", align: "center" }) .setOrigin(0.5, 0); const target = this.add.circle(480, 200, 90, COLORS.target).setInteractive({ useHandCursor: true }); const targetLabel = this.add .text(480, 200, this.definition.targetLabel, { fontFamily: "sans-serif", fontSize: "20px", color: "#ffffff" }) .setOrigin(0.5); target.on("pointerdown", () => { if (this.finished) return; this.score += this.pointsPerClick; this.tweens.add({ targets: [target, targetLabel], scale: 0.92, duration: 60, yoyo: true }); this.refresh(); }); this.definition.upgrades.forEach((upgrade, index) => { const y = 340 + index * 54; const bg = this.add .rectangle(480, y, 700, 44, COLORS.row) .setStrokeStyle(1, 0x4f46e5) .setInteractive({ useHandCursor: true }); const label = this.add .text(480, y, "", { fontFamily: "sans-serif", fontSize: "15px", color: "#eef0fb" }) .setOrigin(0.5); bg.on("pointerdown", () => this.buyUpgrade(index)); this.upgradeRows.push({ bg, label, upgrade, index }); }); this.time.addEvent({ delay: 1000, loop: true, callback: () => { if (this.finished) return; this.score += this.pointsPerSecond; this.refresh(); }, }); this.refresh(); } private buyUpgrade(index: number) { if (this.finished || this.ownedUpgrades.has(index)) return; const upgrade = this.definition.upgrades[index]; if (this.score < upgrade.cost) return; this.score -= upgrade.cost; this.pointsPerClick += upgrade.pointsPerClickBonus; this.pointsPerSecond += upgrade.pointsPerSecondBonus; this.ownedUpgrades.add(index); this.refresh(); } private refresh() { this.onProgress?.({ score: this.score, pointsPerClick: this.pointsPerClick, pointsPerSecond: this.pointsPerSecond, ownedUpgradeIndices: Array.from(this.ownedUpgrades), }); this.scoreText.setText( `${Math.floor(this.score)} / ${this.definition.targetScore}\n+${this.pointsPerClick}/клик +${this.pointsPerSecond}/сек`, ); this.upgradeRows.forEach(({ bg, label, upgrade, index }) => { const owned = this.ownedUpgrades.has(index); const affordable = this.score >= upgrade.cost; label.setText(owned ? `${upgrade.name} — куплено` : `${upgrade.name} — ${upgrade.cost}`); bg.setFillStyle(owned ? COLORS.rowOwned : affordable ? COLORS.row : COLORS.rowDisabled); bg.disableInteractive(); if (!owned && affordable) bg.setInteractive({ useHandCursor: true }); }); if (!this.finished && this.score >= this.definition.targetScore) { this.finished = true; this.onFinish?.({ won: true, score: Math.floor(this.score) }); } } }