Skip to content

Commit 78524af

Browse files
committed
Merge conflict resolved by accepting their changes
2 parents 5bf6914 + bad4ad3 commit 78524af

27 files changed

+1555
-460
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,4 @@ model_power_statistics.csv
154154
# Playwrite test results
155155
**/test-results/
156156
**/playwright-report/
157+
/diplomacy_cicero

.vscode/launch.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
"version": "0.2.0",
33
"configurations": [
44
{
5-
"type": "firefox",
5+
"type": "pwa-chrome",
66
"request": "launch",
7-
"name": "Firefox Debug 9223",
8-
"url": "http://localhost:5173",
7+
"name": "Chrome Debug 5179",
8+
"url": "http://localhost:5179",
99
"webRoot": "${workspaceFolder}/ai_animation/",
1010
"sourceMapPathOverrides": {
11-
"http://localhost:5173/*": "${webRoot}/*"
11+
"http://localhost:5179/*": "${webRoot}/*"
1212
},
1313
"runtimeArgs": [
1414
"--remote-debugging-port=9223"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# AI Diplomacy: LLM-Powered Strategic Gameplay
2-
Created by Alex Duffy @Alx-Ai
2+
Created by Alex Duffy @Alx-Ai & Tyler Marques @Tylermarques
33

44
## Overview
55

ai_animation/.env-old

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
STREAM_KEY="live_1169505855_xRtRaFujX2xHJ9IoxxzRe9v1YOVwtH"
2+
VITE_ELEVENLABS_API_KEY="sk_3f36800ad8c60d91328a9d406b27e3f46615b4583b1e198e"
3+
VITE_DEBUG_MODE=True
4+
VITE_INSTANT_MODE=True

ai_animation/package-lock.json

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Tests for background audio looping functionality
3+
*/
4+
5+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6+
7+
// Mock HTMLAudioElement
8+
class MockAudio {
9+
loop = false;
10+
volume = 1;
11+
paused = true;
12+
src = '';
13+
currentTime = 0;
14+
duration = 10;
15+
16+
constructor() {
17+
// Constructor can be empty or set default values
18+
}
19+
20+
play = vi.fn().mockResolvedValue(undefined);
21+
pause = vi.fn();
22+
addEventListener = vi.fn();
23+
}
24+
25+
describe('Background Audio Looping', () => {
26+
let originalAudio: any;
27+
28+
beforeEach(() => {
29+
// Store original and replace with mock
30+
originalAudio = global.Audio;
31+
global.Audio = MockAudio as any;
32+
33+
// Clear any module state by re-importing
34+
vi.resetModules();
35+
});
36+
37+
afterEach(() => {
38+
// Restore original Audio constructor
39+
global.Audio = originalAudio;
40+
});
41+
42+
it('should set loop property to true during initialization', async () => {
43+
// Import fresh module to avoid state pollution
44+
const { initializeBackgroundAudio } = await import('./backgroundAudio');
45+
46+
// Initialize background audio
47+
initializeBackgroundAudio();
48+
49+
// The actual test is that initialization doesn't throw and sets up the audio correctly
50+
// We can't directly access the private audio instance, but we can test the behavior
51+
expect(global.Audio).toBeDefined();
52+
});
53+
54+
it('should handle audio loop property correctly', () => {
55+
// Create a mock audio instance to test loop behavior
56+
const audioElement = new MockAudio() as any;
57+
58+
// Set loop to true (like our code does)
59+
audioElement.loop = true;
60+
audioElement.duration = 5; // 5 second track
61+
62+
// Simulate audio playing and ending
63+
audioElement.paused = false;
64+
audioElement.currentTime = 0;
65+
66+
// Simulate what happens when audio reaches the end
67+
audioElement.currentTime = audioElement.duration;
68+
69+
// With loop=true, browser automatically restarts
70+
const simulateLoopBehavior = () => {
71+
if (audioElement.loop && !audioElement.paused && audioElement.currentTime >= audioElement.duration) {
72+
audioElement.currentTime = 0; // Browser resets to start
73+
return true; // Indicates loop occurred
74+
}
75+
return false;
76+
};
77+
78+
// Test loop behavior
79+
const looped = simulateLoopBehavior();
80+
81+
expect(audioElement.loop).toBe(true);
82+
expect(looped).toBe(true);
83+
expect(audioElement.currentTime).toBe(0); // Should be reset to start
84+
});
85+
86+
it('should verify loop property is essential for continuous playback', () => {
87+
const audioWithLoop = new MockAudio() as any;
88+
const audioWithoutLoop = new MockAudio() as any;
89+
90+
// Setup both audio elements
91+
audioWithLoop.loop = true;
92+
audioWithoutLoop.loop = false;
93+
94+
audioWithLoop.duration = 10;
95+
audioWithoutLoop.duration = 10;
96+
97+
// Both start playing
98+
audioWithLoop.paused = false;
99+
audioWithoutLoop.paused = false;
100+
101+
// Both reach the end
102+
audioWithLoop.currentTime = audioWithLoop.duration;
103+
audioWithoutLoop.currentTime = audioWithoutLoop.duration;
104+
105+
// Simulate end behavior
106+
const testLooping = (audio: any) => {
107+
if (audio.currentTime >= audio.duration) {
108+
if (audio.loop) {
109+
audio.currentTime = 0; // Loop back to start
110+
return 'looped';
111+
} else {
112+
audio.paused = true; // Stop playing
113+
return 'stopped';
114+
}
115+
}
116+
return 'playing';
117+
};
118+
119+
const resultWithLoop = testLooping(audioWithLoop);
120+
const resultWithoutLoop = testLooping(audioWithoutLoop);
121+
122+
expect(resultWithLoop).toBe('looped');
123+
expect(resultWithoutLoop).toBe('stopped');
124+
expect(audioWithLoop.currentTime).toBe(0); // Reset to start
125+
expect(audioWithoutLoop.paused).toBe(true); // Stopped
126+
});
127+
128+
it('should test the actual module behavior', async () => {
129+
// Import fresh module
130+
const { initializeBackgroundAudio, startBackgroundAudio, stopBackgroundAudio } = await import('./backgroundAudio');
131+
132+
// Test initialization doesn't throw
133+
expect(() => initializeBackgroundAudio()).not.toThrow();
134+
135+
// Test double initialization protection
136+
expect(() => initializeBackgroundAudio()).toThrow('Attempted to init audio twice.');
137+
});
138+
139+
it('should demonstrate loop property importance with realistic scenario', () => {
140+
// This test demonstrates why loop=true is critical for background music
141+
const backgroundTrack = new MockAudio() as any;
142+
143+
// Our code sets this to true
144+
backgroundTrack.loop = true;
145+
backgroundTrack.volume = 0.4;
146+
backgroundTrack.src = './sounds/background_ambience.mp3';
147+
backgroundTrack.duration = 30; // 30 second ambient track
148+
149+
// Start playing
150+
backgroundTrack.paused = false;
151+
backgroundTrack.currentTime = 0;
152+
153+
// Simulate game running for longer than track duration
154+
const gameRuntime = 90; // 90 seconds
155+
const timeStep = 1; // 1 second steps
156+
157+
let currentGameTime = 0;
158+
let trackRestarts = 0;
159+
160+
while (currentGameTime < gameRuntime) {
161+
currentGameTime += timeStep;
162+
backgroundTrack.currentTime += timeStep;
163+
164+
// Check if track ended and needs to loop
165+
if (backgroundTrack.currentTime >= backgroundTrack.duration) {
166+
if (backgroundTrack.loop) {
167+
backgroundTrack.currentTime = 0; // Restart
168+
trackRestarts++;
169+
} else {
170+
backgroundTrack.paused = true; // Would stop without loop
171+
break;
172+
}
173+
}
174+
}
175+
176+
// With a 30-second track and 90-second game, we expect 3 restarts (0-30, 30-60, 60-90)
177+
expect(backgroundTrack.loop).toBe(true);
178+
expect(trackRestarts).toBe(3);
179+
expect(backgroundTrack.paused).toBe(false); // Still playing
180+
expect(currentGameTime).toBe(gameRuntime); // Game completed full duration
181+
});
182+
});

ai_animation/src/backgroundAudio.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,37 @@ export function initializeBackgroundAudio(): void {
4141
* Will only work after user interaction due to browser policies
4242
*/
4343
export function startBackgroundAudio(): void {
44-
if (backgroundAudio && backgroundAudio.paused) {
45-
backgroundAudio.play().catch(err => {
46-
console.log('Background audio autoplay blocked, will retry on user interaction:', err);
44+
if (!backgroundAudio) {
45+
console.warn('Background audio not initialized');
46+
return;
47+
}
48+
49+
if (backgroundAudio.paused) {
50+
console.log('Starting background audio...');
51+
backgroundAudio.play().then(() => {
52+
console.log('Background audio started successfully');
53+
}).catch(err => {
54+
console.warn('Background audio autoplay blocked, will retry on user interaction:', err);
4755
});
56+
} else {
57+
console.log('Background audio already playing');
4858
}
4959
}
5060

5161
/**
5262
* Stop background audio
5363
*/
5464
export function stopBackgroundAudio(): void {
55-
if (backgroundAudio && !backgroundAudio.paused) {
65+
if (!backgroundAudio) {
66+
console.warn('Background audio not initialized');
67+
return;
68+
}
69+
70+
if (!backgroundAudio.paused) {
71+
console.log('Stopping background audio...');
5672
backgroundAudio.pause();
73+
} else {
74+
console.log('Background audio already paused');
5775
}
5876
}
5977

ai_animation/src/components/rotatingDisplay.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export function updateRotatingDisplay(
125125
//containerElement.style.opacity = '0';
126126

127127
// Update content after fade-out
128-
setTimeout(() => {
128+
gameState.eventQueue.scheduleDelay(300, () => {
129129
// Render the appropriate view based on current display type
130130
switch (currentDisplayType) {
131131
case DisplayType.SC_HISTORY_CHART:
@@ -141,7 +141,7 @@ export function updateRotatingDisplay(
141141

142142
// Update last rendered type
143143
lastRenderedDisplayType = currentDisplayType;
144-
}, 300);
144+
}, `rotating-display-update-${Date.now()}`);
145145
}
146146
}
147147

0 commit comments

Comments
 (0)