mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-28 17:29:41 +00:00
WIP: Continuing to iterate
This commit is contained in:
parent
190686e248
commit
981dfef6a0
9 changed files with 153 additions and 402 deletions
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
|
@ -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}/*"
|
||||
|
|
|
|||
19
ai_animation/package-lock.json
generated
19
ai_animation/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 = `<strong>Diplomatic Relations</strong> <span class="power-${currentPlayerPower.toLowerCase()}">(${currentPlayerPower})</span>`;
|
||||
container.appendChild(header);
|
||||
|
||||
// Prepare data for the chart
|
||||
const relationshipHistory = [];
|
||||
const otherPowers = new Set<string>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string>();
|
||||
|
||||
|
||||
// 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<string>();
|
||||
|
||||
|
||||
// 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 = `<strong>Note:</strong> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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('<!DOCTYPE') || data.trim().startsWith('<html')) {
|
||||
throw new Error('Received HTML instead of JSON. Check the file path.');
|
||||
}
|
||||
|
||||
console.log("Loaded game file, attempting to parse...");
|
||||
return this.loadGameData(data);
|
||||
})
|
||||
.then(() => {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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('<!DOCTYPE') || data.trim().startsWith('<html')) {
|
||||
throw new Error('Received HTML instead of JSON. Check the file path.');
|
||||
}
|
||||
|
||||
console.log("Loaded game file, attempting to parse...");
|
||||
return gameState.loadGameData(data);
|
||||
})
|
||||
.then(() => {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue