статистика на экране смерти

This commit is contained in:
azykov@mail.ru
2026-09-05 11:58:44 +03:00
parent 0c374e3ef8
commit f07602b533
5 changed files with 60 additions and 2 deletions
+10
View File
@@ -376,6 +376,16 @@
padding: 0 20px;
}
.game-over-overlay__stats {
font-size: 11px;
color: var(--text);
text-align: center;
line-height: 1.8;
display: flex;
flex-direction: column;
gap: 4px;
}
@keyframes chop-shake {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
+6
View File
@@ -56,6 +56,12 @@ const GameOverOverlay = observer(function GameOverOverlay({
<div className="game-over-overlay__message">
Вы прожили {store.day} {dayWord(store.day)}, но вас поймали кролики
</div>
<div className="game-over-overlay__stats">
<div>Пройдено: {Math.round(store.character.distanceTraveled)} px</div>
<div>Срублено: {store.treesChoppedCount} деревьев, {store.bushesChoppedCount} кустов</div>
<div>Поймано кроликов: {store.rabbitsCaughtCount}</div>
<div>Заработано монет: {store.totalCoinsEarned.toFixed(1)}</div>
</div>
<button
type="button"
className="menu-btn"
+30 -1
View File
@@ -69,6 +69,15 @@ export class GameStore {
bloodMoonBannerTimer = 0;
upgradeLevels: Record<UpgradeId, number> = { ...DEFAULT_UPGRADE_LEVELS };
// This life's run-summary stats, shown on the death screen — reset to 0 on a fresh game,
// restored from the save when continuing one (see initialize()).
treesChoppedCount = 0;
bushesChoppedCount = 0;
rabbitsCaughtCount = 0;
/** Cumulative coins earned from converting resources — unlike inventory.coins, this never goes
* down when coins are spent on upgrades. */
totalCoinsEarned = 0;
private coinProgress = 0;
private saveProgress = 0;
private lastTimestamp: number | null = null;
@@ -257,7 +266,7 @@ export class GameStore {
this.upgradeLevels = { ...DEFAULT_UPGRADE_LEVELS, ...save?.upgradeLevels };
if (save?.character && save.nodes && save.resourceDrops) {
this.character = new Character(save.character.x, save.character.y, save.characterHp ?? this.characterMaxHp);
this.character = new Character(save.character.x, save.character.y, save.characterHp ?? this.characterMaxHp, save.distanceTraveled ?? 0);
this.nodes = save.nodes.map((node) => ResourceNode.restore(node));
this.resourceDrops = save.resourceDrops.map((drop) => ResourceDrop.restore(drop));
} else {
@@ -266,6 +275,13 @@ export class GameStore {
this.resourceDrops = [];
}
// This life's run-summary stats — restored from the save when continuing a life, reset to 0
// for a fresh one (matching the character branch above, since both represent the same life).
this.treesChoppedCount = save?.treesChoppedCount ?? 0;
this.bushesChoppedCount = save?.bushesChoppedCount ?? 0;
this.rabbitsCaughtCount = save?.rabbitsCaughtCount ?? 0;
this.totalCoinsEarned = save?.totalCoinsEarned ?? 0;
// Rabbits are restored/spawned independently of the world-state branch above so a save from
// before rabbits existed still loads fine — it just gets a freshly spawned batch of them.
this.rabbits = save?.rabbits
@@ -312,6 +328,11 @@ export class GameStore {
day: this.day,
hour: this.hour,
characterHp: this.character.hp,
distanceTraveled: this.character.distanceTraveled,
treesChoppedCount: this.treesChoppedCount,
bushesChoppedCount: this.bushesChoppedCount,
rabbitsCaughtCount: this.rabbitsCaughtCount,
totalCoinsEarned: this.totalCoinsEarned,
});
}
@@ -585,6 +606,10 @@ export class GameStore {
this.nodes = this.nodes.filter((n) => n.id !== node.id);
this.heldNodeId = null;
this.character.resetChopProgress();
if (node.kind === 'tree')
this.treesChoppedCount += 1;
else
this.bushesChoppedCount += 1;
break;
}
}
@@ -631,6 +656,7 @@ export class GameStore {
this.heldRabbitId = null;
this.character.resetAttackProgress();
}
this.rabbitsCaughtCount += 1;
this.ejectDrop(rabbit, 'meat');
}
@@ -697,14 +723,17 @@ export class GameStore {
if (this.inventory.logs > 0) {
this.inventory.logs -= 1;
this.inventory.coins += this.coinsPerLog;
this.totalCoinsEarned += this.coinsPerLog;
}
if (this.inventory.twigs > 0) {
this.inventory.twigs -= 1;
this.inventory.coins += this.coinsPerTwig;
this.totalCoinsEarned += this.coinsPerTwig;
}
if (this.inventory.meat > 0) {
this.inventory.meat -= 1;
this.inventory.coins += this.coinsPerMeat;
this.totalCoinsEarned += this.coinsPerMeat;
}
}
}
+5 -1
View File
@@ -9,15 +9,18 @@ export class Character {
x: number;
y: number;
hp: number;
/** Total px moved so far this life — an odometer for the death-screen run summary. */
distanceTraveled: number;
private chopProgress = 0;
private attackProgress = 0;
private regenProgress = 0;
constructor(x: number, y: number, hp: number) {
constructor(x: number, y: number, hp: number, distanceTraveled = 0) {
this.x = x;
this.y = y;
this.hp = hp;
this.distanceTraveled = distanceTraveled;
makeAutoObservable(this, {}, { autoBind: true });
}
@@ -36,6 +39,7 @@ export class Character {
const ratio = step / distance;
this.x += dx * ratio;
this.y += dy * ratio;
this.distanceTraveled += step;
return distance - step;
}
+9
View File
@@ -20,6 +20,15 @@ export interface SaveData {
hour?: number;
/** Absent on saves from before the character had hp, which default to a full characterMaxHp. */
characterHp?: number;
/** This life's run-summary stats (shown on the death screen) — absent on older saves or a
* fresh game, which default to 0. */
distanceTraveled?: number;
treesChoppedCount?: number;
bushesChoppedCount?: number;
rabbitsCaughtCount?: number;
/** Cumulative coins earned from converting resources — unlike inventory.coins, this never goes
* down when coins are spent on upgrades. */
totalCoinsEarned?: number;
}
function isValidSave(value: unknown): value is SaveData {