diff --git a/ai_animation/src/components/endScreenModal.ts b/ai_animation/src/components/endScreenModal.ts
deleted file mode 100644
index 7f03568..0000000
--- a/ai_animation/src/components/endScreenModal.ts
+++ /dev/null
@@ -1,293 +0,0 @@
-import { gameState } from '../gameState';
-
-interface EndScreenModalOptions {
- totalGamesPlayed: number;
- onRestart?: () => void;
-}
-
-let endScreenModalOverlay: HTMLElement | null = null;
-
-/**
- * Shows an end screen modal when all games have been completed
- * @param options Configuration for the end screen modal
- */
-export function showEndScreenModal(options: EndScreenModalOptions): void {
- const { totalGamesPlayed, onRestart } = options;
-
- // Close any existing modal
- closeEndScreenModal();
-
- // Create overlay
- endScreenModalOverlay = createEndScreenOverlay();
-
- // Create modal container
- const modalContainer = createEndScreenContainer(totalGamesPlayed);
-
- // Add close button if not in playing mode
- if (!gameState.isPlaying) {
- const closeButton = createCloseButton();
- modalContainer.appendChild(closeButton);
- }
-
- // Add to overlay
- endScreenModalOverlay.appendChild(modalContainer);
- document.body.appendChild(endScreenModalOverlay);
-
- // Set up event listeners
- setupEventListeners(onRestart);
-
- // Auto-restart after 5 seconds
- setTimeout(() => {
- closeEndScreenModal();
- onRestart?.();
- }, 5000);
-}
-
-/**
- * Closes the end screen modal
- */
-export function closeEndScreenModal(): void {
- if (endScreenModalOverlay) {
- endScreenModalOverlay.classList.add('fade-out');
- setTimeout(() => {
- if (endScreenModalOverlay?.parentNode) {
- endScreenModalOverlay.parentNode.removeChild(endScreenModalOverlay);
- }
- endScreenModalOverlay = null;
- }, 300);
- }
-}
-
-/**
- * Creates the main overlay element
- */
-function createEndScreenOverlay(): HTMLElement {
- const overlay = document.createElement('div');
- overlay.className = 'end-screen-modal-overlay';
- overlay.style.cssText = `
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.9);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 2000;
- opacity: 0;
- transition: opacity 0.5s ease;
- `;
-
- // Add fade-out class styles
- const style = document.createElement('style');
- style.textContent = `
- .end-screen-modal-overlay.fade-out {
- opacity: 0 !important;
- transition: opacity 0.3s ease;
- }
- `;
- document.head.appendChild(style);
-
- // Trigger fade in
- setTimeout(() => overlay.style.opacity = '1', 10);
-
- return overlay;
-}
-
-/**
- * Creates the main end screen container
- */
-function createEndScreenContainer(totalGamesPlayed: number): HTMLElement {
- const container = document.createElement('div');
- container.className = 'end-screen-modal-container';
- container.style.cssText = `
- background: radial-gradient(ellipse at center, #2a1f1f 0%, #1a1015 100%);
- border: 5px solid #8b7355;
- border-radius: 12px;
- box-shadow: 0 0 40px rgba(255, 215, 0, 0.6);
- width: 90%;
- max-width: 600px;
- position: relative;
- padding: 40px;
- text-align: center;
- font-family: "Book Antiqua", Palatino, serif;
- color: #f0e6d2;
- animation: endScreenGlow 3s ease-in-out infinite alternate;
- `;
-
- // Add glow animation
- const style = document.createElement('style');
- style.textContent = `
- @keyframes endScreenGlow {
- 0% { box-shadow: 0 0 40px rgba(255, 215, 0, 0.6); }
- 100% { box-shadow: 0 0 60px rgba(255, 215, 0, 0.8); }
- }
- `;
- document.head.appendChild(style);
-
- // Crown/completion icon
- const crownElement = document.createElement('div');
- crownElement.textContent = '👑';
- crownElement.style.cssText = `
- font-size: 64px;
- margin-bottom: 20px;
- animation: float 3s ease-in-out infinite;
- `;
-
- // Add float animation
- const floatStyle = document.createElement('style');
- floatStyle.textContent = `
- @keyframes float {
- 0%, 100% { transform: translateY(0px); }
- 50% { transform: translateY(-10px); }
- }
- `;
- document.head.appendChild(floatStyle);
-
- // Title
- const titleElement = document.createElement('h1');
- titleElement.textContent = 'SERIES COMPLETE!';
- titleElement.style.cssText = `
- margin: 0 0 20px 0;
- color: #ffd700;
- font-size: 42px;
- font-weight: bold;
- text-shadow: 3px 3px 6px rgba(0,0,0,0.7);
- letter-spacing: 2px;
- `;
-
- // Completion message
- const messageElement = document.createElement('h2');
- messageElement.textContent = `All ${totalGamesPlayed} games completed!`;
- messageElement.style.cssText = `
- margin: 0 0 30px 0;
- color: #f0e6d2;
- font-size: 24px;
- font-weight: normal;
- text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
- `;
-
- // Restart countdown
- const countdownElement = document.createElement('div');
- countdownElement.style.cssText = `
- background: linear-gradient(90deg, #4a4a4a 0%, #2a2a2a 100%);
- color: #ffd700;
- padding: 15px 25px;
- border-radius: 25px;
- display: inline-block;
- font-size: 18px;
- font-weight: bold;
- margin-bottom: 20px;
- border: 2px solid #8b7355;
- box-shadow: 0 4px 8px rgba(0,0,0,0.4);
- `;
-
- // Animate countdown
- let countdown = 5;
- countdownElement.textContent = `Restarting series in ${countdown} seconds...`;
-
- const countdownInterval = setInterval(() => {
- countdown--;
- if (countdown > 0) {
- countdownElement.textContent = `Restarting series in ${countdown} seconds...`;
- } else {
- countdownElement.textContent = 'Restarting now...';
- clearInterval(countdownInterval);
- }
- }, 1000);
-
- // Thank you message
- const thankYouElement = document.createElement('div');
- thankYouElement.textContent = 'Thank you for watching the AI Diplomacy series!';
- thankYouElement.style.cssText = `
- color: #c4b998;
- font-size: 16px;
- font-style: italic;
- margin-top: 20px;
- `;
-
- // Assemble container
- container.appendChild(crownElement);
- container.appendChild(titleElement);
- container.appendChild(messageElement);
- container.appendChild(countdownElement);
- container.appendChild(thankYouElement);
-
- return container;
-}
-
-/**
- * Creates a close button
- */
-function createCloseButton(): HTMLElement {
- const button = document.createElement('button');
- button.textContent = '×';
- button.className = 'end-screen-close-button';
- button.style.cssText = `
- position: absolute;
- top: 15px;
- right: 20px;
- background: none;
- border: none;
- font-size: 40px;
- color: #8b7355;
- cursor: pointer;
- padding: 0;
- width: 40px;
- height: 40px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 50%;
- transition: all 0.2s ease;
- `;
-
- button.addEventListener('mouseenter', () => {
- button.style.color = '#ffd700';
- button.style.transform = 'scale(1.1)';
- button.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
- });
-
- button.addEventListener('mouseleave', () => {
- button.style.color = '#8b7355';
- button.style.transform = 'scale(1)';
- button.style.backgroundColor = 'transparent';
- });
-
- return button;
-}
-
-/**
- * Sets up event listeners for the end screen modal
- */
-function setupEventListeners(onRestart?: () => void): void {
- if (!endScreenModalOverlay) return;
-
- const closeButton = endScreenModalOverlay.querySelector('.end-screen-close-button');
- const handleClose = () => {
- closeEndScreenModal();
- onRestart?.();
- };
-
- // Close button click (only if not in playing mode)
- if (!gameState.isPlaying) {
- closeButton?.addEventListener('click', handleClose);
-
- // Escape key
- const handleKeydown = (e: KeyboardEvent) => {
- if (e.key === 'Escape') {
- handleClose();
- document.removeEventListener('keydown', handleKeydown);
- }
- };
- document.addEventListener('keydown', handleKeydown);
-
- // Click outside to close
- endScreenModalOverlay.addEventListener('click', (e) => {
- if (e.target === endScreenModalOverlay) {
- handleClose();
- }
- });
- }
-}
\ No newline at end of file
diff --git a/ai_animation/src/components/rotatingDisplay.ts b/ai_animation/src/components/rotatingDisplay.ts
index 8325072..66b7d0b 100644
--- a/ai_animation/src/components/rotatingDisplay.ts
+++ b/ai_animation/src/components/rotatingDisplay.ts
@@ -79,9 +79,9 @@ function rotateToNextDisplay(): void {
// Determine next display type
switch (currentDisplayType) {
- // case DisplayType.SC_HISTORY_CHART:
- // currentDisplayType = DisplayType.RELATIONSHIP_HISTORY_CHART;
- // break;
+ case DisplayType.SC_HISTORY_CHART:
+ currentDisplayType = DisplayType.RELATIONSHIP_HISTORY_CHART;
+ break;
case DisplayType.RELATIONSHIP_HISTORY_CHART:
currentDisplayType = DisplayType.SC_HISTORY_CHART;
break;
@@ -155,6 +155,9 @@ function renderSCHistoryChartView(
container: HTMLElement,
gameData: GameSchemaType
): void {
+
+ // TODO: This likely needs to not be a custom rendered svg. There are plenty of charting libraries to use to do this instead.
+ //
// Create header
const header = document.createElement('div');
header.innerHTML = `Supply Center History`;
diff --git a/ai_animation/src/components/victoryModal.ts b/ai_animation/src/components/victoryModal.ts
index e438f5a..828a795 100644
--- a/ai_animation/src/components/victoryModal.ts
+++ b/ai_animation/src/components/victoryModal.ts
@@ -40,13 +40,6 @@ export function showVictoryModal(options: VictoryModalOptions): void {
// Set up event listeners
setupEventListeners(onClose);
- // Auto-dismiss in playing mode after delay
- if (gameState.isPlaying) {
- setTimeout(() => {
- closeVictoryModal();
- onClose?.();
- }, 5000); // 5 second display in playing mode
- }
}
/**
@@ -55,12 +48,10 @@ export function showVictoryModal(options: VictoryModalOptions): void {
export function closeVictoryModal(): void {
if (victoryModalOverlay) {
victoryModalOverlay.classList.add('fade-out');
- setTimeout(() => {
- if (victoryModalOverlay?.parentNode) {
- victoryModalOverlay.parentNode.removeChild(victoryModalOverlay);
- }
- victoryModalOverlay = null;
- }, 300);
+ if (victoryModalOverlay?.parentNode) {
+ victoryModalOverlay.parentNode.removeChild(victoryModalOverlay);
+ }
+ victoryModalOverlay = null;
}
}
@@ -249,7 +240,7 @@ function createStandingsElement(finalStandings: Array<{ power: string; centers:
const standingItem = document.createElement('div');
const medal = index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : `${index + 1}.`;
const displayName = getPowerDisplayName(entry.power as PowerENUM);
-
+
standingItem.innerHTML = `
${medal}
${displayName}
@@ -346,4 +337,4 @@ function setupEventListeners(onClose?: () => void): void {
}
});
}
-}
\ No newline at end of file
+}
diff --git a/ai_animation/src/config.ts b/ai_animation/src/config.ts
index a0fb831..3f19f25 100644
--- a/ai_animation/src/config.ts
+++ b/ai_animation/src/config.ts
@@ -4,7 +4,7 @@
export const config = {
// Default speed in milliseconds for animations and transitions
playbackSpeed: 500,
-
+ victoryModalDisplayMs: 10000,
// Streaming mode specific timing
get streamingPlaybackSpeed(): number {
const isStreamingMode = import.meta.env.MODE === 'production'
diff --git a/ai_animation/src/gameState.ts b/ai_animation/src/gameState.ts
index d79884f..37abf5a 100644
--- a/ai_animation/src/gameState.ts
+++ b/ai_animation/src/gameState.ts
@@ -2,6 +2,8 @@ import * as THREE from "three"
import { updateRotatingDisplay } from "./components/rotatingDisplay";
import { type CoordinateData, CoordinateDataSchema, PowerENUM } from "./types/map"
import type { GameSchemaType } from "./types/gameState";
+import { debugMenuInstance } from "./debug/debugMenu.ts"
+import { config } from "./config.ts"
import { GameSchema } from "./types/gameState";
import { prevBtn, nextBtn, playBtn, speedSelector, mapView, updateGameIdDisplay } from "./domElements";
import { createChatWindows } from "./domElements/chatWindows";
@@ -11,6 +13,7 @@ import { displayInitialPhase, togglePlayback } from "./phase";
import { Tween, Group as TweenGroup } from "@tweenjs/tween.js";
import { MomentsDataSchema, Moment, NormalizedMomentsData } from "./types/moments";
import { updateLeaderboard } from "./components/leaderboard";
+import { closeVictoryModal } from "./components/victoryModal.ts";
//FIXME: This whole file is a mess. Need to organize and format
@@ -123,7 +126,7 @@ class GameState {
this.phaseIndex = 0
this.boardName = boardName
this.currentPower = null;
- this.gameId = 12
+ this.gameId = 16
this.momentsData = null; // Initialize as null, will be loaded later
// State locks
@@ -226,6 +229,7 @@ class GameState {
// Update game ID display
updateGameIdDisplay();
+
})
resolve()
} else {
@@ -300,16 +304,19 @@ class GameState {
/*
* Loads the next game in the order, reseting the board and gameState
*/
- loadNextGame = () => {
+ loadNextGame = (setPlayback: boolean = false) => {
+
let gameId = this.gameId + 1
let contPlaying = false
- if (this.isPlaying) {
+ if (setPlayback || this.isPlaying) {
contPlaying = true
}
this.loadGameFile(gameId).then(() => {
-
+ gameState.gameId = gameId
if (contPlaying) {
- togglePlayback(true)
+ setTimeout(() => {
+ togglePlayback(true)
+ }, config.victoryModalDisplayMs)
}
}).catch(() => {
console.warn("caught error trying to advance game. Setting gameId to 0 and restarting...")
@@ -317,7 +324,7 @@ class GameState {
if (contPlaying) {
togglePlayback(true)
}
- })
+ }).finally(closeVictoryModal)
}
@@ -348,6 +355,9 @@ class GameState {
this.gameId = gameId
updateGameIdDisplay();
updateLeaderboard();
+ if (config.isDebugMode) {
+ debugMenuInstance.updateTools()
+ }
resolve()
}
})
diff --git a/ai_animation/src/main.ts b/ai_animation/src/main.ts
index 7964051..a6b3795 100644
--- a/ai_animation/src/main.ts
+++ b/ai_animation/src/main.ts
@@ -2,18 +2,15 @@ import * as THREE from "three";
import "./style.css"
import { initMap } from "./map/create";
import { gameState } from "./gameState";
-import { logger } from "./logger";
import { loadBtn, prevBtn, nextBtn, speedSelector, fileInput, playBtn, mapView, loadGameBtnFunction } from "./domElements";
-import { updateChatWindows } from "./domElements/chatWindows";
-import { displayPhaseWithAnimation, advanceToNextPhase, resetToPhase, nextPhase, previousPhase, togglePlayback, _setPhase } from "./phase";
import { config } from "./config";
import { Tween, Group, Easing } from "@tweenjs/tween.js";
-import { initRotatingDisplay, updateRotatingDisplay } from "./components/rotatingDisplay";
-import { closeTwoPowerConversation, showTwoPowerConversation } from "./components/twoPowerConversation";
-import { PowerENUM } from "./types/map";
+import { initRotatingDisplay, } from "./components/rotatingDisplay";
import { debugMenuInstance } from "./debug/debugMenu";
import { initializeBackgroundAudio, startBackgroundAudio } from "./backgroundAudio";
import { updateLeaderboard } from "./components/leaderboard";
+import { _setPhase } from "./phase";
+import { togglePlayback } from "./phase";
//TODO: Create a function that finds a suitable unit location within a given polygon, for placing units better
// Currently the location for label, unit, and SC are all the same manually picked location
@@ -161,7 +158,6 @@ function animate() {
// FIXME: This is a dumb patch for us not being able to find the unit we expect to find.
// We should instead bee figuring out why units aren't where we expect them to be when the engine has said that is a valid move
nextPhase()
- gameState.nextPhaseScheduled;
}
}, config.effectivePlaybackSpeed);
}
diff --git a/ai_animation/src/phase.ts b/ai_animation/src/phase.ts
index a5062fd..44cc2a7 100644
--- a/ai_animation/src/phase.ts
+++ b/ai_animation/src/phase.ts
@@ -20,7 +20,14 @@ const MOMENT_DISPLAY_TIMEOUT_MS = config.isDebugMode || config.isInstantMode ? 1
// FIXME: Going to previous phases is borked. Units do not animate properly, map doesn't update.
export function _setPhase(phaseIndex: number) {
- console.log(`[Phase] _setPhase called with index: ${phaseIndex}`);
+ if (phaseIndex < 0) {
+ throw new Error(`Provided invalid phaseIndex, cannot setPhase to ${phaseIndex} - game has ${gameState.gameData.phases.length} phases`)
+ }
+ if (phaseIndex >= gameState.gameData.phases.length - 1) {
+ gameState.phaseIndex = gameState.gameData.phases.length - 1
+ displayFinalPhase()
+ return
+ }
// Store the old phase index at the very beginning
const oldPhaseIndex = gameState.phaseIndex;
@@ -29,10 +36,9 @@ export function _setPhase(phaseIndex: number) {
debugMenuInstance.updateTools()
}
const gameLength = gameState.gameData.phases.length
+
+
// Validate that the phaseIndex is within the bounds of the game length.
- if (phaseIndex >= gameLength || phaseIndex < 0) {
- throw new Error(`Provided invalid phaseIndex, cannot setPhase to ${phaseIndex} - game has ${gameState.gameData.phases.length} phases`)
- }
if (phaseIndex - gameState.phaseIndex != 1) {
// We're moving more than one Phase forward, or any number of phases backward, to do so clear the board and reInit the units on the correct phase
gameState.unitAnimations = [];
@@ -56,12 +62,7 @@ export function _setPhase(phaseIndex: number) {
console.log(`Moving to phase ${gameState.gameData.phases[gameState.phaseIndex].name}`);
}
- if (phaseIndex === gameLength - 1) {
- displayFinalPhase()
- } else {
- displayPhase()
- }
- gameState.nextPhaseScheduled = false;
+ displayPhase()
}
// Finally, update the gameState with the current phaseIndex
@@ -235,6 +236,8 @@ export function displayPhase(skipMessages = false) {
logger.log("No animations for this phase transition");
gameState.messagesPlaying = false;
}
+ gameState.nextPhaseScheduled = false;
+
}
/**
@@ -242,7 +245,6 @@ export function displayPhase(skipMessages = false) {
* Used when first loading a game
*/
export function displayInitialPhase() {
- gameState.phaseIndex = 0;
initUnits(0);
displayPhase(true);
}
@@ -316,8 +318,6 @@ export function advanceToNextPhase() {
nextPhase();
}
- // Reset the nextPhaseScheduled flag to allow scheduling the next phase
- gameState.nextPhaseScheduled = false;
}
function displayFinalPhase() {
@@ -360,27 +360,11 @@ function displayFinalPhase() {
winner,
maxCenters,
finalStandings,
- onClose: () => {
- // Only proceed to next game if in playing mode
- if (gameState.isPlaying) {
- gameState.loadNextGame();
- }
- }
});
- //setTimeout(closeVictoryModal, 10000)
+ setTimeout(() => {
+ gameState.loadNextGame(true)
+ }, config.victoryModalDisplayMs)
- // Log the victory
- logger.log(`Victory! ${winner} wins the game with ${maxCenters} supply centers.`);
-
- // Display final standings in console
- console.log("Final Standings:");
- finalStandings.forEach((entry, index) => {
- const medal = index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : " ";
- console.log(`${medal} ${entry.power}: ${entry.centers} centers`);
- });
-
- // Show victory in info panel
- logger.updateInfoPanel(`🏆 ${winner} VICTORIOUS! 🏆\n\nFinal Score: ${maxCenters} supply centers\n\nCheck console for full standings.`);
} else {
logger.log("Could not determine game winner");
}
diff --git a/ai_animation/tsconfig.json b/ai_animation/tsconfig.json
index 3bba997..9a53325 100644
--- a/ai_animation/tsconfig.json
+++ b/ai_animation/tsconfig.json
@@ -9,7 +9,7 @@
"DOM.Iterable"
],
// Turn the below off later, just for now we're not full TS yet
- "noCheck": true,
+ "noCheck": false,
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",