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++; }