This commit is contained in:
azykov@mail.ru
2026-09-09 11:49:24 +03:00
parent bd9b5d9aec
commit a3f2f4eeef
20 changed files with 490 additions and 70 deletions
+8 -1
View File
@@ -16,6 +16,8 @@
* Run with: npx tsx scripts/play.ts [tickSeconds]
*/
import readline from 'node:readline';
import { Analytics, makeAnalyticsGameInfo } from '../src/analytics';
import { ConsoleAnalyticsTransport } from '../src/analytics/ConsoleAnalyticsTransport';
import { bushesForDay, treesForDay } from '../src/game/constants';
import { formatClock } from '../src/game/daynight';
import { Rabbit } from '../src/game/entities/Rabbit';
@@ -33,7 +35,12 @@ const TICKS_PER_BATCH = 1000;
/** How far past the nearest rabbit to aim while fleeing it. */
const FLEE_DISTANCE = 400;
const store = new GameStore();
// Logs every analytics call to the console (see ConsoleAnalyticsTransport) instead of sending it
// anywhere — this is what actually lets `npm run play` be used to verify analytics behavior.
let store: GameStore;
const analytics = new Analytics(new ConsoleAnalyticsTransport(), () => makeAnalyticsGameInfo(store));
analytics.start();
store = new GameStore(analytics);
store.initialize(MAP_WIDTH, MAP_HEIGHT);
const startedAt = Date.now();
-57
View File
@@ -1,57 +0,0 @@
/** Headless example: plays the game for many ticks with no browser/DOM involved at all — proves
* GameStore/its entities (see src/game/entities) can run standalone, and demonstrates measuring
* how often a player would need to act (pick a new target) per unit of simulated time.
*
* Run with: npx tsx scripts/simulate.ts [ticks] [tickSeconds]
*/
import { Rabbit } from '../src/game/entities/Rabbit';
import { ResourceNode } from '../src/game/entities/ResourceNode';
import { GameStore } from '../src/game/GameStore';
const totalTicks = Number(process.argv[2] ?? 1_000_000);
const tickSeconds = Number(process.argv[3] ?? 0.1);
const mapWidth = 1200;
const mapHeight = 800;
const store = new GameStore();
store.initialize(mapWidth, mapHeight);
// A minimal bot policy standing in for a player: keep whatever target is currently held as long
// as it still exists; otherwise pick the nearest node or rabbit and hold it. Each such pick is
// counted as one "user action" — a mouse click choosing a new thing to interact with.
let actionCount = 0;
for (let i = 0; i < totalTicks; i++) {
if (store.isDead)
break;
const stillHoldingNode = store.heldNodeId !== null && store.nodes.some((node) => node.id === store.heldNodeId);
const stillHoldingRabbit = store.heldRabbitId !== null && store.rabbits.some((rabbit) => rabbit.id === store.heldRabbitId);
if (!stillHoldingNode && !stillHoldingRabbit) {
const nearestNode = ResourceNode.findNearestToPosition(store.nodes, store.character);
const nearestRabbit = Rabbit.findNearestToPosition(store.rabbits, store.character);
const nodeDistance = nearestNode ? Math.hypot(nearestNode.x - store.character.x, nearestNode.y - store.character.y) : Infinity;
const rabbitDistance = nearestRabbit ? Math.hypot(nearestRabbit.x - store.character.x, nearestRabbit.y - store.character.y) : Infinity;
if (nearestNode && nodeDistance <= rabbitDistance) {
store.holdNode(nearestNode.id);
actionCount++;
} else if (nearestRabbit) {
store.holdRabbit(nearestRabbit.id);
actionCount++;
}
}
store.tick(tickSeconds);
}
const simulatedSeconds = totalTicks * tickSeconds;
console.log(JSON.stringify({
ticks: totalTicks,
simulatedDay: Number((store.day + store.hour / 24).toFixed(2)),
isDead: store.isDead,
inventory: store.inventory,
actionCount,
actionsPerSimulatedMinute: Number((actionCount / (simulatedSeconds / 60)).toFixed(3)),
}, null, 2));