diff --git a/.vscode/launch.json b/.vscode/launch.json
index 8da11a4..d7f6a59 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -4,8 +4,8 @@
{
"type": "firefox",
"request": "launch",
- "name": "Debug App",
- "url": "http://localhost:3000",
+ "name": "Firefox Debug",
+ "url": "http://localhost:5173",
"webRoot": "${workspaceFolder}/ai_animation",
"sourceMapPathOverrides": {
"webpack:///./src/*": "${webRoot}/*"
diff --git a/ai_animation/package-lock.json b/ai_animation/package-lock.json
index e05252b..a7b727c 100644
--- a/ai_animation/package-lock.json
+++ b/ai_animation/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@tweenjs/tween.js": "^25.0.0",
"@types/three": "^0.174.0",
+ "chart.js": "^4.4.9",
"three": "^0.174.0",
"zod": "^3.24.2"
},
@@ -443,6 +444,12 @@
"node": ">=18"
}
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "license": "MIT"
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz",
@@ -760,6 +767,18 @@
"integrity": "sha512-p97I8XEC1h04esklFqyIH+UhFrUcj8/1/vBWgc6lAK4jMJc+KbhUy8D4dquHYztFj6pHLqGcp/P1xvBBF4r3DA==",
"license": "BSD-3-Clause"
},
+ "node_modules/chart.js": {
+ "version": "4.4.9",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
+ "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
diff --git a/ai_animation/package.json b/ai_animation/package.json
index 7cd97a0..c050e88 100644
--- a/ai_animation/package.json
+++ b/ai_animation/package.json
@@ -16,6 +16,7 @@
"dependencies": {
"@tweenjs/tween.js": "^25.0.0",
"@types/three": "^0.174.0",
+ "chart.js": "^4.4.9",
"three": "^0.174.0",
"zod": "^3.24.2"
},
diff --git a/ai_animation/src/components/relationshipChart.ts b/ai_animation/src/components/relationshipChart.ts
deleted file mode 100644
index 0e17fd1..0000000
--- a/ai_animation/src/components/relationshipChart.ts
+++ /dev/null
@@ -1,273 +0,0 @@
-import { PowerENUM } from "../types/map";
-import { GameSchemaType } from "../types/gameState";
-
-// Relationship value mapping
-const RELATIONSHIP_VALUES = {
- "Enemy": -2,
- "Unfriendly": -1,
- "Neutral": 0,
- "Friendly": 1,
- "Ally": 2,
- // Add lowercase versions for case-insensitive matching
- "enemy": -2,
- "unfriendly": -1,
- "neutral": 0,
- "friendly": 1,
- "ally": 2
-};
-/**
- * Render the relationship history chart view
- * @param container The container element
- * @param gameData The current game data
- * @param currentPhaseIndex The current phase index
- * @param currentPlayerPower The power the current player is controlling
- */
-export function renderRelationshipHistoryChartView(
- container: HTMLElement,
- gameData: GameSchemaType,
- currentPhaseIndex: number,
- currentPlayerPower: PowerENUM
-): void {
- // Create header and description
- const header = document.createElement('div');
- header.innerHTML = `Diplomatic Relations (${currentPlayerPower})`;
- container.appendChild(header);
-
- // Prepare data for the chart
- const relationshipHistory = [];
- const otherPowers = new Set();
-
- // Iterate through all phases to collect relationship data
- for (let i = 0; i < gameData.phases.length; i++) {
- const phase = gameData.phases[i];
- const phaseData: any = {
- phaseName: phase.name,
- phaseIndex: i
- };
-
- // Check if agent_relationships exists and has data for current player
- if (phase.agent_relationships &&
- phase.agent_relationships[currentPlayerPower]) {
-
- console.log(`Phase ${i} (${phase.name}): Found relationships for ${currentPlayerPower}`,
- phase.agent_relationships[currentPlayerPower]);
-
- const relationships = phase.agent_relationships[currentPlayerPower];
-
- for (const [power, relation] of Object.entries(relationships)) {
- if (power !== currentPlayerPower) {
- // Convert relationship string to numeric value
- let relationValue = RELATIONSHIP_VALUES[relation as keyof typeof RELATIONSHIP_VALUES];
-
- // Default to neutral if the relationship string is not recognized
- if (relationValue === undefined) {
- relationValue = 0;
- console.warn(`Unknown relationship value: ${relation}, defaulting to Neutral (0)`);
- }
-
- console.log(` Relationship ${currentPlayerPower} -> ${power}: ${relation} (${relationValue})`);
-
- phaseData[power] = relationValue;
- otherPowers.add(power);
- }
- }
- }
-
- relationshipHistory.push(phaseData);
- }
-
- console.log("Collected relationship history:", relationshipHistory);
- console.log("Other powers found:", Array.from(otherPowers));
-
- // Convert otherPowers Set to Array for easier iteration
- const powers = Array.from(otherPowers);
-
- // Create SVG element
- const svgWidth = container.clientWidth;
- const svgHeight = 150;
- const margin = { top: 10, right: 10, bottom: 20, left: 25 };
- const width = svgWidth - margin.left - margin.right;
- const height = svgHeight - margin.top - margin.bottom;
-
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- svg.setAttribute("width", "100%");
- svg.setAttribute("height", `${svgHeight}px`);
- svg.setAttribute("viewBox", `0 0 100% ${svgHeight}`);
- svg.style.overflow = "visible";
-
- // Create SVG group for the chart content with margins
- const chart = document.createElementNS("http://www.w3.org/2000/svg", "g");
- chart.setAttribute("transform", `translate(${margin.left},${margin.top})`);
- svg.appendChild(chart);
-
- // Create scales
- // X scale: map phase index to x position
- const xScale = (index: number) => {
- const denominator = Math.max(1, relationshipHistory.length - 1); // Avoid division by zero
- return margin.left + (index / denominator) * width;
- };
-
- // Y scale: map relationship value (-2 to 2) to y position
- const yScale = (value: number) => margin.top + height / 2 - (value / 2) * (height / 2);
-
- // Draw axes
- // X-axis (middle, represents neutral)
- const xAxis = document.createElementNS("http://www.w3.org/2000/svg", "line");
- xAxis.setAttribute("x1", `${margin.left}`);
- xAxis.setAttribute("y1", `${yScale(0)}`);
- xAxis.setAttribute("x2", `${margin.left + width}`);
- xAxis.setAttribute("y2", `${yScale(0)}`);
- xAxis.setAttribute("stroke", "#8d5a2b");
- xAxis.setAttribute("stroke-width", "1");
- chart.appendChild(xAxis);
-
- // Y-axis
- const yAxis = document.createElementNS("http://www.w3.org/2000/svg", "line");
- yAxis.setAttribute("x1", `${margin.left}`);
- yAxis.setAttribute("y1", `${margin.top}`);
- yAxis.setAttribute("x2", `${margin.left}`);
- yAxis.setAttribute("y2", `${margin.top + height}`);
- yAxis.setAttribute("stroke", "#8d5a2b");
- yAxis.setAttribute("stroke-width", "1");
- chart.appendChild(yAxis);
-
- // Y-axis ticks and labels
- const yTicks = [-2, -1, 0, 1, 2];
- const yTickLabels = ["Enemy", "Unfriendly", "Neutral", "Friendly", "Ally"];
-
- for (let i = 0; i < yTicks.length; i++) {
- const tick = yTicks[i];
- const label = yTickLabels[i];
-
- // Tick line
- const tickLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
- tickLine.setAttribute("x1", `${margin.left - 5}`);
- tickLine.setAttribute("y1", `${yScale(tick)}`);
- tickLine.setAttribute("x2", `${margin.left}`);
- tickLine.setAttribute("y2", `${yScale(tick)}`);
- tickLine.setAttribute("stroke", "#8d5a2b");
- tickLine.setAttribute("stroke-width", "1");
- chart.appendChild(tickLine);
-
- // Tick label
- const tickLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
- tickLabel.setAttribute("x", `${margin.left - 8}`);
- tickLabel.setAttribute("y", `${yScale(tick) + 4}`);
- tickLabel.setAttribute("text-anchor", "end");
- tickLabel.setAttribute("font-size", "9");
- tickLabel.setAttribute("fill", "#3b2c02");
- tickLabel.textContent = label;
- chart.appendChild(tickLabel);
- }
-
- // Draw horizontal grid lines
- for (const tick of yTicks) {
- const gridLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
- gridLine.setAttribute("x1", `${margin.left}`);
- gridLine.setAttribute("y1", `${yScale(tick)}`);
- gridLine.setAttribute("x2", `${margin.left + width}`);
- gridLine.setAttribute("y2", `${yScale(tick)}`);
- gridLine.setAttribute("stroke", "#d3bf96");
- gridLine.setAttribute("stroke-width", "0.5");
- gridLine.setAttribute("stroke-dasharray", "3,3");
- chart.appendChild(gridLine);
- }
-
- // Draw lines for each power
- for (const power of powers) {
- console.log(`Drawing line for power: ${power}`);
-
- // Create path for this power
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
-
- // Generate path data
- let pathData = "";
- let hasData = false;
- let dataPoints = 0;
-
- for (let i = 0; i < relationshipHistory.length; i++) {
- if (relationshipHistory[i][power] !== undefined) {
- const relationValue = relationshipHistory[i][power];
- const x = xScale(i);
- const y = yScale(relationValue);
-
- console.log(` Point ${i}: (${x}, ${y}) for value ${relationValue}`);
- dataPoints++;
-
- if (!hasData) {
- pathData += `M ${x} ${y}`;
- hasData = true;
- } else {
- pathData += ` L ${x} ${y}`;
- }
- }
- }
-
- console.log(` Total data points for ${power}: ${dataPoints}, has data: ${hasData}`);
- console.log(` Path data: ${pathData.length > 100 ? pathData.substring(0, 100) + '...' : pathData}`);
-
- if (hasData) {
- path.setAttribute("d", pathData);
- path.setAttribute("stroke", POWER_COLORS[power] || "#000000");
- path.setAttribute("stroke-width", "2");
- path.setAttribute("fill", "none");
- chart.appendChild(path);
- console.log(` Added path to chart for ${power}`);
- } else {
- console.log(` No path data for ${power}, not adding to chart`);
- }
- }
-
- // Add a vertical line to indicate current phase
- const currentPhaseX = xScale(currentPhaseIndex);
- const currentPhaseLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
- currentPhaseLine.setAttribute("x1", `${currentPhaseX}`);
- currentPhaseLine.setAttribute("y1", `${margin.top}`);
- currentPhaseLine.setAttribute("x2", `${currentPhaseX}`);
- currentPhaseLine.setAttribute("y2", `${margin.top + height}`);
- currentPhaseLine.setAttribute("stroke", "#000000");
- currentPhaseLine.setAttribute("stroke-width", "1");
- currentPhaseLine.setAttribute("stroke-dasharray", "3,3");
- chart.appendChild(currentPhaseLine);
-
- // Add legend
- const legendGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
- legendGroup.setAttribute("transform", `translate(${margin.left}, ${margin.top + height + 10})`);
-
- let legendX = 0;
- const legendItemWidth = width / powers.length;
-
- for (const power of powers) {
- // Legend color box
- const legendBox = document.createElementNS("http://www.w3.org/2000/svg", "rect");
- legendBox.setAttribute("x", `${legendX}`);
- legendBox.setAttribute("y", "0");
- legendBox.setAttribute("width", "10");
- legendBox.setAttribute("height", "10");
- legendBox.setAttribute("fill", POWER_COLORS[power] || "#000000");
- legendGroup.appendChild(legendBox);
-
- // Legend text
- const legendText = document.createElementNS("http://www.w3.org/2000/svg", "text");
- legendText.setAttribute("x", `${legendX + 15}`);
- legendText.setAttribute("y", "8");
- legendText.setAttribute("font-size", "9");
- legendText.setAttribute("fill", "#3b2c02");
- legendText.textContent = power;
- legendGroup.appendChild(legendText);
-
- legendX += legendItemWidth;
- }
-
- chart.appendChild(legendGroup);
-
- // Add the SVG to the container
- container.appendChild(svg);
-
- // Add phase info
- const phaseInfo = document.createElement('div');
- phaseInfo.style.fontSize = '12px';
- phaseInfo.style.marginTop = '5px';
- phaseInfo.innerHTML = `Current phase: ${gameData.phases[currentPhaseIndex].name}`;
- container.appendChild(phaseInfo);
-}
diff --git a/ai_animation/src/components/rotatingDisplay.ts b/ai_animation/src/components/rotatingDisplay.ts
index b268dee..d1f8849 100644
--- a/ai_animation/src/components/rotatingDisplay.ts
+++ b/ai_animation/src/components/rotatingDisplay.ts
@@ -1,6 +1,7 @@
import { gameState } from "../gameState";
import { PowerENUM } from "../types/map";
import { GameSchemaType } from "../types/gameState";
+import { Chart } from "chart.js";
// Enum for the different display types
export enum DisplayType {
@@ -237,6 +238,7 @@ function renderCurrentStandingsView(
container.innerHTML = html;
}
+
/**
* Render the supply center history chart view
* @param container The container element
@@ -438,12 +440,6 @@ function renderSCHistoryChartView(
// Add the SVG to the container
container.appendChild(svg);
- // Add phase info
- const phaseInfo = document.createElement('div');
- phaseInfo.style.fontSize = '12px';
- phaseInfo.style.marginTop = '5px';
- phaseInfo.innerHTML = `Current phase: ${gameData.phases[currentPhaseIndex].name}`;
- container.appendChild(phaseInfo);
}
/**
@@ -480,9 +476,6 @@ export function renderRelationshipHistoryChartView(
if (phase.agent_relationships &&
phase.agent_relationships[currentPlayerPower]) {
- console.log(`Phase ${i} (${phase.name}): Found relationships for ${currentPlayerPower}`,
- phase.agent_relationships[currentPlayerPower]);
-
const relationships = phase.agent_relationships[currentPlayerPower];
for (const [power, relation] of Object.entries(relationships)) {
@@ -496,7 +489,6 @@ export function renderRelationshipHistoryChartView(
console.warn(`Unknown relationship value: ${relation}, defaulting to Neutral (0)`);
}
- console.log(` Relationship ${currentPlayerPower} -> ${power}: ${relation} (${relationValue})`);
phaseData[power] = relationValue;
otherPowers.add(power);
@@ -507,8 +499,6 @@ export function renderRelationshipHistoryChartView(
relationshipHistory.push(phaseData);
}
- console.log("Collected relationship history:", relationshipHistory);
- console.log("Other powers found:", Array.from(otherPowers));
// Convert otherPowers Set to Array for easier iteration
const powers = Array.from(otherPowers);
@@ -606,7 +596,6 @@ export function renderRelationshipHistoryChartView(
// Draw lines for each power
for (const power of powers) {
- console.log(`Drawing line for power: ${power}`);
// Create path for this power
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
@@ -622,7 +611,6 @@ export function renderRelationshipHistoryChartView(
const x = xScale(i);
const y = yScale(relationValue);
- console.log(` Point ${i}: (${x}, ${y}) for value ${relationValue}`);
dataPoints++;
if (!hasData) {
@@ -634,8 +622,6 @@ export function renderRelationshipHistoryChartView(
}
}
- console.log(` Total data points for ${power}: ${dataPoints}, has data: ${hasData}`);
- console.log(` Path data: ${pathData.length > 100 ? pathData.substring(0, 100) + '...' : pathData}`);
if (hasData) {
path.setAttribute("d", pathData);
@@ -643,7 +629,6 @@ export function renderRelationshipHistoryChartView(
path.setAttribute("stroke-width", "2");
path.setAttribute("fill", "none");
chart.appendChild(path);
- console.log(` Added path to chart for ${power}`);
} else {
console.log(` No path data for ${power}, not adding to chart`);
}
@@ -695,10 +680,4 @@ export function renderRelationshipHistoryChartView(
// Add the SVG to the container
container.appendChild(svg);
- // Add phase info
- const phaseInfo = document.createElement('div');
- phaseInfo.style.fontSize = '12px';
- phaseInfo.style.marginTop = '5px';
- phaseInfo.innerHTML = `Current phase: ${gameData.phases[currentPhaseIndex].name}`;
- container.appendChild(phaseInfo);
}
diff --git a/ai_animation/src/domElements/relationshipPopup.ts b/ai_animation/src/domElements/relationshipPopup.ts
index 4737234..7f4db0a 100644
--- a/ai_animation/src/domElements/relationshipPopup.ts
+++ b/ai_animation/src/domElements/relationshipPopup.ts
@@ -17,17 +17,17 @@ export function initRelationshipPopup(): void {
if (!document.getElementById('relationship-popup-container')) {
createRelationshipPopupElements();
}
-
+
// Get references to the created elements
relationshipPopupContainer = document.getElementById('relationship-popup-container');
relationshipContent = document.getElementById('relationship-content');
closeButton = document.getElementById('relationship-close-btn');
-
+
// Add event listeners
if (closeButton) {
closeButton.addEventListener('click', hideRelationshipPopup);
}
-
+
// Add click handler for the relationships button
if (relationshipsBtn) {
relationshipsBtn.addEventListener('click', toggleRelationshipPopup);
@@ -41,29 +41,29 @@ function createRelationshipPopupElements(): void {
const container = document.createElement('div');
container.id = 'relationship-popup-container';
container.className = 'relationship-popup-container';
-
+
// Create header
const header = document.createElement('div');
header.className = 'relationship-header';
-
+
const title = document.createElement('h2');
title.textContent = 'Diplomatic Relations';
header.appendChild(title);
-
+
const closeBtn = document.createElement('button');
closeBtn.id = 'relationship-close-btn';
closeBtn.textContent = '×';
closeBtn.title = 'Close Relationships Chart';
header.appendChild(closeBtn);
-
+
container.appendChild(header);
-
+
// Create content container
const content = document.createElement('div');
content.id = 'relationship-content';
content.className = 'relationship-content';
container.appendChild(content);
-
+
// Add to document
document.body.appendChild(container);
}
@@ -87,7 +87,7 @@ export function toggleRelationshipPopup(): void {
export function showRelationshipPopup(): void {
if (relationshipPopupContainer && relationshipContent) {
relationshipPopupContainer.classList.add('visible');
-
+
// Only render if we have game data
if (gameState.gameData) {
renderRelationshipChart();
@@ -111,22 +111,20 @@ export function hideRelationshipPopup(): void {
*/
function renderRelationshipChart(): void {
if (!relationshipContent || !gameState.gameData) return;
-
+
// Clear current content
relationshipContent.innerHTML = '';
-
+
// Get a list of powers that have relationship data
const powersWithRelationships = new Set();
-
+
// Check all phases for relationships
if (gameState.gameData && gameState.gameData.phases) {
// Debug what relationship data we have
- console.log("Checking for relationship data in game:", gameState.gameData.phases.length, "phases");
-
+
let hasRelationshipData = false;
for (const phase of gameState.gameData.phases) {
if (phase.agent_relationships) {
- console.log("Found relationship data in phase:", phase.name, phase.agent_relationships);
hasRelationshipData = true;
// Add powers that have relationship data defined
Object.keys(phase.agent_relationships).forEach(power => {
@@ -134,85 +132,85 @@ function renderRelationshipChart(): void {
});
}
}
-
+
if (!hasRelationshipData) {
console.log("No relationship data found in any phase");
}
}
-
+
// Create a container for each power's relationship chart
for (const power of Object.values(PowerENUM)) {
// Skip any non-string values
if (typeof power !== 'string') continue;
-
+
// Check if this power has relationship data
if (powersWithRelationships.has(power)) {
const powerContainer = document.createElement('div');
powerContainer.className = `power-relationship-container power-${power.toLowerCase()}`;
-
+
const powerHeader = document.createElement('h3');
powerHeader.className = `power-${power.toLowerCase()}`;
powerHeader.textContent = power;
powerContainer.appendChild(powerHeader);
-
+
const chartContainer = document.createElement('div');
chartContainer.className = 'relationship-chart-container';
-
+
// Use the existing chart rendering function
renderRelationshipHistoryChartView(
- chartContainer,
- gameState.gameData,
+ chartContainer,
+ gameState.gameData,
gameState.phaseIndex,
power as PowerENUM
);
-
+
powerContainer.appendChild(chartContainer);
relationshipContent.appendChild(powerContainer);
}
}
-
- // If no powers have relationship data, create some sample data for visualization
+
+ // If no powers have relationship data, create message to say so
if (powersWithRelationships.size === 0) {
- console.log("No relationship data found in game, creating sample data for visualization");
-
+ console.log("No relationship data found in game");
+
// Create sample relationship data for all powers in the game
const allPowers = new Set();
-
+
// Find all powers from units and centers
if (gameState.gameData && gameState.gameData.phases && gameState.gameData.phases.length > 0) {
const currentPhase = gameState.gameData.phases[gameState.phaseIndex];
-
+
if (currentPhase.state?.units) {
Object.keys(currentPhase.state.units).forEach(power => allPowers.add(power));
}
-
+
if (currentPhase.state?.centers) {
Object.keys(currentPhase.state.centers).forEach(power => allPowers.add(power));
}
-
+
// Only proceed if we found some powers
if (allPowers.size > 0) {
console.log(`Found ${allPowers.size} powers in game, creating sample relationships`);
-
+
// For each power, create a container and chart
for (const power of allPowers) {
const powerContainer = document.createElement('div');
powerContainer.className = `power-relationship-container power-${power.toLowerCase()}`;
-
+
const powerHeader = document.createElement('h3');
powerHeader.className = `power-${power.toLowerCase()}`;
powerHeader.textContent = power;
powerContainer.appendChild(powerHeader);
-
+
const chartContainer = document.createElement('div');
chartContainer.className = 'relationship-chart-container';
-
+
// Create a message about sample data
const sampleMessage = document.createElement('div');
sampleMessage.className = 'sample-data-message';
sampleMessage.innerHTML = `Note: No relationship data found for ${power}.
This chart will display when relationship data is available.`;
-
+
chartContainer.appendChild(sampleMessage);
powerContainer.appendChild(chartContainer);
relationshipContent.appendChild(powerContainer);
@@ -238,8 +236,8 @@ function renderRelationshipChart(): void {
* Update the relationship popup when game data changes
*/
export function updateRelationshipPopup(): void {
- if (relationshipPopupContainer &&
- relationshipPopupContainer.classList.contains('visible')) {
+ if (relationshipPopupContainer &&
+ relationshipPopupContainer.classList.contains('visible')) {
renderRelationshipChart();
}
-}
\ No newline at end of file
+}
diff --git a/ai_animation/src/gameState.ts b/ai_animation/src/gameState.ts
index 7fe55b4..a4f2a74 100644
--- a/ai_animation/src/gameState.ts
+++ b/ai_animation/src/gameState.ts
@@ -1,4 +1,6 @@
import * as THREE from "three"
+import { updateRotatingDisplay } from "./components/rotatingDisplay";
+import { updateRelationshipPopup } from "./domElements/relationshipPopup";
import { type CoordinateData, CoordinateDataSchema, PowerENUM } from "./types/map"
import type { GameSchemaType } from "./types/gameState";
import { GameSchema } from "./types/gameState";
@@ -8,6 +10,7 @@ import { logger } from "./logger";
import { OrbitControls } from "three/examples/jsm/Addons.js";
import { displayInitialPhase } from "./phase";
import { Tween, Group as TweenGroup } from "@tweenjs/tween.js";
+import { hideStandingsBoard, } from "./domElements/standingsBoard";
//FIXME: This whole file is a mess. Need to organize and format
@@ -28,6 +31,7 @@ function getRandomPower(): PowerENUM {
class GameState {
boardState: CoordinateData
+ gameId: number
gameData: GameSchemaType
phaseIndex: number
boardName: string
@@ -63,6 +67,7 @@ class GameState {
this.phaseIndex = 0
this.boardName = boardName
this.currentPower = getRandomPower()
+ this.gameId = 1
// State locks
this.isSpeaking = false
this.isPlaying = false
@@ -165,7 +170,7 @@ class GameState {
});
})
}
-
+
/**
* Check if a power is present in the current game
* @param power The power to check
@@ -175,26 +180,79 @@ class GameState {
if (!this.gameData || !this.gameData.phases || this.phaseIndex < 0 || this.phaseIndex >= this.gameData.phases.length) {
return false;
}
-
+
const currentPhase = this.gameData.phases[this.phaseIndex];
-
+
// Check if power has units or centers in the current phase
if (currentPhase.state?.units && power in currentPhase.state.units) {
return true;
}
-
+
if (currentPhase.state?.centers && power in currentPhase.state.centers) {
return true;
}
-
+
// Check if power has relationships defined
if (currentPhase.agent_relationships && power in currentPhase.agent_relationships) {
return true;
}
-
+
return false;
}
+
+ /*
+ * Given a gameId, load that game's state into the GameState Object
+ */
+ loadGameFile = (gameId: number) => {
+
+ // Clear any data that was already on the board, including messages, units, animations, etc.
+ //clearGameData();
+
+ // Path to the default game file
+ const gameFilePath = `./default_game${gameId}.json`;
+
+ fetch(gameFilePath)
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`Failed to load default game file: ${response.status}`);
+ }
+
+ // Check content type to avoid HTML errors
+ const contentType = response.headers.get('content-type');
+ if (contentType && contentType.includes('text/html')) {
+ throw new Error('Received HTML instead of JSON. Check the file path.');
+ }
+
+ return response.text();
+ })
+ .then(data => {
+ // Check for HTML content as a fallback
+ if (data.trim().startsWith(' {
+ console.log("Default game file loaded and parsed successfully");
+ // Explicitly hide standings board after loading game
+ hideStandingsBoard();
+ // Update rotating display and relationship popup with game data
+ if (this.gameData) {
+ updateRotatingDisplay(this.gameData, this.phaseIndex, this.currentPower);
+ updateRelationshipPopup();
+ }
+ })
+ .catch(error => {
+ // Use console.error instead of logger.log to avoid updating the info panel
+ console.error(`Error loading game ${gameFilePath}: ${error.message}`);
+ });
+ }
initScene = () => {
+ if (mapView === null) {
+ throw Error("Cannot find mapView element, unable to continue.")
+ }
this.scene.background = new THREE.Color(0x87CEEB);
// Camera
@@ -222,4 +280,5 @@ class GameState {
}
}
+
export let gameState = new GameState(AvailableMaps.STANDARD);
diff --git a/ai_animation/src/main.ts b/ai_animation/src/main.ts
index 10789bc..d2ddb52 100644
--- a/ai_animation/src/main.ts
+++ b/ai_animation/src/main.ts
@@ -2,7 +2,7 @@ import * as THREE from "three";
import "./style.css"
import { initMap } from "./map/create";
import { createAnimationsForNextPhase as createAnimationsForNextPhase } from "./units/animate";
-import { gameState } from "./gameState";
+import { gameState, loadGameFile } from "./gameState";
import { logger } from "./logger";
import { loadBtn, prevBtn, nextBtn, speedSelector, fileInput, playBtn, mapView, loadGameBtnFunction } from "./domElements";
import { updateChatWindows } from "./domElements/chatWindows";
@@ -61,7 +61,7 @@ function initScene() {
// Load default game file if in debug mode
if (isDebugMode || isStreamingMode) {
- loadDefaultGameFile();
+ gameState.loadGameFile(0);
}
if (isStreamingMode) {
setTimeout(() => {
@@ -196,66 +196,21 @@ function onWindowResize() {
gameState.renderer.setSize(mapView.clientWidth, mapView.clientHeight);
}
-// Load a default game if we're running debug
-function loadDefaultGameFile() {
- console.log("Loading default game file for debug mode...");
- // Path to the default game file
- const defaultGameFilePath = './default_game2.json';
-
- fetch(defaultGameFilePath)
- .then(response => {
- if (!response.ok) {
- throw new Error(`Failed to load default game file: ${response.status}`);
- }
-
- // Check content type to avoid HTML errors
- const contentType = response.headers.get('content-type');
- if (contentType && contentType.includes('text/html')) {
- throw new Error('Received HTML instead of JSON. Check the file path.');
- }
-
- return response.text();
- })
- .then(data => {
- // Check for HTML content as a fallback
- if (data.trim().startsWith(' {
- console.log("Default game file loaded and parsed successfully");
- // Explicitly hide standings board after loading game
- hideStandingsBoard();
- // Update rotating display and relationship popup with game data
- if (gameState.gameData) {
- updateRotatingDisplay(gameState.gameData, gameState.phaseIndex, gameState.currentPower);
- updateRelationshipPopup();
- }
- })
- .catch(error => {
- console.error("Error loading default game file:", error);
- // Use console.error instead of logger.log to avoid updating the info panel
- console.error(`Error loading default game: ${error.message}`);
-
- // Fallback - tell user to drag & drop a file but don't update the info panel
- console.log('Please load a game file using the "Load Game" button.');
- });
-}
// --- PLAYBACK CONTROLS ---
function togglePlayback() {
- if (!gameState.gameData || gameState.gameData.phases.length <= 1) return;
+ // If the game doesn't have any data, or there are no phases, return;
+ if (!gameState.gameData || gameState.gameData.phases.length <= 0) {
+ alert("This game file appears to be broken. Please reload the page and load a different game.")
+ throw Error("Bad gameState, exiting.")
+ };
- // NEW: If we're speaking, don't allow toggling playback
+ // TODO: Likely not how we want to handle the speaking section of this.
+ // Should be able to pause the other elements while we're speaking
if (gameState.isSpeaking) return;
- // Pause the camera animation
-
gameState.isPlaying = !gameState.isPlaying;
if (gameState.isPlaying) {
diff --git a/ai_animation/src/phase.ts b/ai_animation/src/phase.ts
index 24e4188..431642c 100644
--- a/ai_animation/src/phase.ts
+++ b/ai_animation/src/phase.ts
@@ -10,6 +10,7 @@ import { speakSummary } from "./speech";
import { config } from "./config";
+
/**
* Unified function to display a phase with proper transitions
* Handles both initial display and animated transitions between phases
@@ -17,8 +18,13 @@ import { config } from "./config";
*/
export function displayPhase(skipMessages = false) {
let index = gameState.phaseIndex
+ if (index >= gameState.gameData.phases.length) {
+ displayFinalPhase()
+ logger.log("Displayed final phase, moving to next game.")
+ loadGamefile(gameState.gameId + 1)
+ }
if (!gameState.gameData || !gameState.gameData.phases ||
- index < 0 || index >= gameState.gameData.phases.length) {
+ index < 0) {
logger.log("Invalid phase index.");
return;
}
@@ -164,6 +170,10 @@ export function advanceToNextPhase() {
}
}
+function displayFinalPhase() {
+ // Stub for doing anything on the final phase of a game.
+}
+
/**
* Internal helper to handle the actual phase advancement
*/
@@ -180,8 +190,11 @@ function moveToNextPhase() {
// Advance the phase index
if (gameState.gameData && gameState.phaseIndex >= gameState.gameData.phases.length - 1) {
- gameState.phaseIndex = 0;
- logger.log("Reached end of game, looping back to start");
+ logger.log("Reached end of game, Moving to next in 5 seconds");
+ setTimeout(() => {
+ gameState.loadGameFile(gameState.gameId + 1), 5000
+ })
+
} else {
gameState.phaseIndex++;
}