mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-19 12:58:09 +00:00
Adding a GameId to allow moving through a list of games.
This commit is contained in:
parent
9314a411f9
commit
9a43be9b9c
7 changed files with 163 additions and 7 deletions
2
ai_animation/.gitignore
vendored
2
ai_animation/.gitignore
vendored
|
|
@ -25,3 +25,5 @@ dist-ssr
|
|||
|
||||
# AI things
|
||||
.claude/
|
||||
|
||||
./public/games/
|
||||
|
|
|
|||
|
|
@ -20,33 +20,43 @@ The turn animation system is built around several key components that work toget
|
|||
The turn advancement follows a carefully orchestrated sequence:
|
||||
|
||||
#### 1. Playback Initiation
|
||||
|
||||
When the user clicks Play, `togglePlayback()` is triggered, which:
|
||||
|
||||
- Sets `gameState.isPlaying = true`
|
||||
- Hides the standings board
|
||||
- Starts the camera pan animation
|
||||
- Begins message display for the current phase
|
||||
|
||||
#### 2. Message Animation Phase
|
||||
|
||||
If the current phase has messages:
|
||||
|
||||
- `updateChatWindows()` displays messages word-by-word
|
||||
- Each message appears with typing animation
|
||||
- `gameState.messagesPlaying` tracks this state
|
||||
|
||||
#### 3. Unit Animation Phase
|
||||
|
||||
Once messages complete (or if there are no messages):
|
||||
|
||||
- `displayPhaseWithAnimation()` is called
|
||||
- `createAnimationsForNextPhase()` analyzes the previous phase's orders
|
||||
- Movement tweens are created for each unit based on order results
|
||||
- Animations are added to `gameState.unitAnimations` array
|
||||
|
||||
#### 4. Animation Monitoring
|
||||
|
||||
The main `animate()` loop continuously:
|
||||
|
||||
- Updates all active unit animations
|
||||
- Filters out completed animations
|
||||
- Detects when `gameState.unitAnimations.length === 0`
|
||||
|
||||
#### 5. Phase Transition
|
||||
|
||||
When all animations complete:
|
||||
|
||||
- `advanceToNextPhase()` is scheduled with a configurable delay
|
||||
- If the phase has a summary, text-to-speech is triggered
|
||||
- After speech completes, `moveToNextPhase()` increments the phase index
|
||||
|
|
@ -125,3 +135,6 @@ This architecture ensures smooth, coordinated animations while maintaining clear
|
|||
## Game Data
|
||||
|
||||
Game data is loaded from JSON files in the `public/games/` directory. The expected format includes phases with messages, orders, and state information for each turn of the Diplomacy game.
|
||||
|
||||
--TODO: Create something to combine the game data into a simpler place, Diary is in csv and I need that to display the thoughts of the LLM during the betrayal scenes
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
</select>
|
||||
<span id="phase-display">No game loaded</span>
|
||||
</div>
|
||||
<span id="game-id-display">Game: --</span>
|
||||
</div>
|
||||
<div id="map-view" class="map-view"></div>
|
||||
<input type="file" id="file-input" accept=".json">
|
||||
|
|
|
|||
64
ai_animation/show_conversation.sh
Executable file
64
ai_animation/show_conversation.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script to show conversation between two powers in a specific phase
|
||||
# Usage: ./show_conversation.sh <power1> <power2> <phase> [game_file]
|
||||
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Usage: $0 <power1> <power2> <phase> [game_file]"
|
||||
echo "Example: $0 FRANCE ENGLAND S1901M"
|
||||
echo "Example: $0 FRANCE ENGLAND S1901M public/games/0/game.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
POWER1="$1"
|
||||
POWER2="$2"
|
||||
PHASE="$3"
|
||||
GAME_FILE="${4:-public/default_game_formatted.json}"
|
||||
|
||||
# Check if game file exists
|
||||
if [ ! -f "$GAME_FILE" ]; then
|
||||
echo "Error: Game file '$GAME_FILE' not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if jq is installed
|
||||
if ! command -v jq &>/dev/null; then
|
||||
echo "Error: jq is required but not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Conversation between $POWER1 and $POWER2 in phase $PHASE ==="
|
||||
echo "Game file: $GAME_FILE"
|
||||
echo ""
|
||||
|
||||
# Extract messages between the two powers in the specified phase, sorted by time
|
||||
jq -r --arg power1 "$POWER1" --arg power2 "$POWER2" --arg phase "$PHASE" '
|
||||
.phases[] |
|
||||
select(.name == $phase) |
|
||||
.messages[] |
|
||||
select(
|
||||
(.sender == $power1 and .recipient == $power2) or
|
||||
(.sender == $power2 and .recipient == $power1)
|
||||
) |
|
||||
. as $msg |
|
||||
"\($msg.time_sent) \($msg.sender) -> \($msg.recipient): \($msg.message)\n"
|
||||
' "$GAME_FILE" | sort -n #| sed 's/^[0-9]* //'
|
||||
|
||||
# Check if any messages were found
|
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; then
|
||||
message_count=$(jq --arg power1 "$POWER1" --arg power2 "$POWER2" --arg phase "$PHASE" '
|
||||
.phases[] |
|
||||
select(.name == $phase) |
|
||||
.messages[] |
|
||||
select(
|
||||
(.sender == $power1 and .recipient == $power2) or
|
||||
(.sender == $power2 and .recipient == $power1)
|
||||
)
|
||||
' "$GAME_FILE" | jq -s 'length')
|
||||
|
||||
echo ""
|
||||
echo "Found $message_count messages in phase $PHASE between $POWER1 and $POWER2"
|
||||
else
|
||||
echo "No messages found between $POWER1 and $POWER2 in phase $PHASE"
|
||||
fi
|
||||
|
||||
|
|
@ -15,6 +15,21 @@ export function updatePhaseDisplay() {
|
|||
phaseDisplay.style.opacity = '1';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
export function updateGameIdDisplay() {
|
||||
if (!gameIdDisplay) return;
|
||||
|
||||
// Add fade-out effect
|
||||
gameIdDisplay.style.transition = 'opacity 0.3s ease-out';
|
||||
gameIdDisplay.style.opacity = '0';
|
||||
|
||||
// Update text after fade-out
|
||||
setTimeout(() => {
|
||||
gameIdDisplay.textContent = `Game: ${gameState.gameId}`;
|
||||
// Fade back in
|
||||
gameIdDisplay.style.opacity = '1';
|
||||
}, 300);
|
||||
}
|
||||
// --- LOADING & DISPLAYING GAME PHASES ---
|
||||
export function loadGameBtnFunction(file) {
|
||||
const reader = new FileReader();
|
||||
|
|
@ -27,16 +42,41 @@ export function loadGameBtnFunction(file) {
|
|||
reader.readAsText(file);
|
||||
}
|
||||
export const loadBtn = document.getElementById('load-btn');
|
||||
if (null === loadBtn) throw new Error("Element with ID 'load-btn' not found");
|
||||
|
||||
export const fileInput = document.getElementById('file-input');
|
||||
if (null === fileInput) throw new Error("Element with ID 'file-input' not found");
|
||||
|
||||
export const prevBtn = document.getElementById('prev-btn');
|
||||
if (null === prevBtn) throw new Error("Element with ID 'prev-btn' not found");
|
||||
|
||||
export const nextBtn = document.getElementById('next-btn');
|
||||
if (null === nextBtn) throw new Error("Element with ID 'next-btn' not found");
|
||||
|
||||
export const playBtn = document.getElementById('play-btn');
|
||||
if (null === playBtn) throw new Error("Element with ID 'play-btn' not found");
|
||||
|
||||
export const speedSelector = document.getElementById('speed-selector');
|
||||
if (null === speedSelector) throw new Error("Element with ID 'speed-selector' not found");
|
||||
|
||||
export const phaseDisplay = document.getElementById('phase-display');
|
||||
if (null === phaseDisplay) throw new Error("Element with ID 'phase-display' not found");
|
||||
|
||||
export const gameIdDisplay = document.getElementById('game-id-display');
|
||||
if (null === gameIdDisplay) throw new Error("Element with ID 'game-id-display' not found");
|
||||
|
||||
export const mapView = document.getElementById('map-view');
|
||||
if (null === mapView) throw new Error("Element with ID 'map-view' not found");
|
||||
|
||||
export const leaderboard = document.getElementById('leaderboard');
|
||||
if (null === leaderboard) throw new Error("Element with ID 'leaderboard' not found");
|
||||
|
||||
export const standingsBtn = document.getElementById('standings-btn');
|
||||
if (null === standingsBtn) throw new Error("Element with ID 'standings-btn' not found");
|
||||
|
||||
export const relationshipsBtn = document.getElementById('relationships-btn');
|
||||
if (null === relationshipsBtn) throw new Error("Element with ID 'relationships-btn' not found");
|
||||
|
||||
|
||||
// Add roundRect polyfill for browsers that don't support it
|
||||
if (!CanvasRenderingContext2D.prototype.roundRect) {
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ import { updateRelationshipPopup } from "./domElements/relationshipPopup";
|
|||
import { type CoordinateData, CoordinateDataSchema, PowerENUM } from "./types/map"
|
||||
import type { GameSchemaType } from "./types/gameState";
|
||||
import { GameSchema } from "./types/gameState";
|
||||
import { prevBtn, nextBtn, playBtn, speedSelector, mapView } from "./domElements";
|
||||
import { prevBtn, nextBtn, playBtn, speedSelector, mapView, updateGameIdDisplay } from "./domElements";
|
||||
import { createChatWindows } from "./domElements/chatWindows";
|
||||
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";
|
||||
import { MomentsDataSchema, MomentsMetadataSchema } from "./types/moments";
|
||||
import { MomentsDataSchema, MomentsDataSchemaType } from "./types/moments";
|
||||
|
||||
//FIXME: This whole file is a mess. Need to organize and format
|
||||
//FIXME: This whole file is a mess. Need to organkze and format
|
||||
|
||||
enum AvailableMaps {
|
||||
STANDARD = "standard"
|
||||
|
|
@ -34,7 +34,7 @@ class GameState {
|
|||
boardState: CoordinateData
|
||||
gameId: number
|
||||
gameData: GameSchemaType
|
||||
momentsData: MomentSchemaType
|
||||
momentsData: MomentsDataSchemaType
|
||||
phaseIndex: number
|
||||
boardName: string
|
||||
currentPower: PowerENUM
|
||||
|
|
@ -133,6 +133,9 @@ class GameState {
|
|||
// Display the initial phase
|
||||
displayInitialPhase()
|
||||
|
||||
// Update game ID display
|
||||
updateGameIdDisplay();
|
||||
|
||||
this.loadMomentsFile()
|
||||
resolve()
|
||||
} else {
|
||||
|
|
@ -204,6 +207,18 @@ class GameState {
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads the next game in the order, reseting the board and gameState
|
||||
*/
|
||||
loadNextGame = () => {
|
||||
//
|
||||
|
||||
this.gameId += 1
|
||||
|
||||
// Try to load the next game, if it fails, show end screen forever
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a gameId, load that game's state into the GameState Object
|
||||
*/
|
||||
|
|
@ -251,6 +266,7 @@ class GameState {
|
|||
if (this.gameData) {
|
||||
updateRotatingDisplay(this.gameData, this.phaseIndex, this.currentPower);
|
||||
updateRelationshipPopup();
|
||||
updateGameIdDisplay();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
@ -304,7 +320,7 @@ class GameState {
|
|||
})
|
||||
}
|
||||
|
||||
initScene = () => {
|
||||
createThreeScene = () => {
|
||||
if (mapView === null) {
|
||||
throw Error("Cannot find mapView element, unable to continue.")
|
||||
}
|
||||
|
|
@ -332,6 +348,14 @@ class GameState {
|
|||
this.camControls.maxDistance = 2000;
|
||||
this.camControls.maxPolarAngle = Math.PI / 2; // Limit so you don't flip under the map
|
||||
this.camControls.target.set(0, 0, 100); // ADDED: Set control target to new map center
|
||||
|
||||
|
||||
// Lighting (keep it simple)
|
||||
this.scene.add(new THREE.AmbientLight(0xffffff, 0.6));
|
||||
|
||||
const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
|
||||
dirLight.position.set(300, 400, 300);
|
||||
this.scene.add(dirLight);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
background: linear-gradient(90deg, #5a3e2b 0%, #382519 100%);
|
||||
color: #f0e6d2;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
border-bottom: 3px solid #2e1c10;
|
||||
}
|
||||
|
|
@ -46,6 +46,18 @@
|
|||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#game-id-display {
|
||||
font-weight: bold;
|
||||
margin-left: auto;
|
||||
padding: 4px 8px;
|
||||
border: 2px solid #2e1c10;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: #ffd;
|
||||
font-family: "Book Antiqua", Palatino, serif;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Buttons (Load Game, Next, Prev, etc.) */
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue