Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e232d4a
Another fix for the turns getting stuck
Tylermarques Jun 5, 2025
5177d76
FIX: Map jump bug, leaderboard misname
Tylermarques Jun 8, 2025
d293ec3
Updating style to focus on 1080p display
Tylermarques Jun 8, 2025
1583503
FIX: Rotating Display rotates, Diplo relations no longer doubles
Tylermarques Jun 8, 2025
c3cd837
FIX: SC History now works and correctly updates on phase move
Tylermarques Jun 8, 2025
0316728
WIP: Working on fixing moving from game to game
Tylermarques Jun 9, 2025
5411f0b
Endgame correctly moves on
Tylermarques Jun 9, 2025
2733e4a
Typefixes
Tylermarques Jun 9, 2025
5bf6914
Cleanup of some lingering code
Tylermarques Jun 10, 2025
d527535
Another fix for the turns getting stuck
Tylermarques Jun 5, 2025
b8a8f5a
FIX: Map jump bug, leaderboard misname
Tylermarques Jun 8, 2025
9836e9e
Updating style to focus on 1080p display
Tylermarques Jun 8, 2025
8453666
FIX: Rotating Display rotates, Diplo relations no longer doubles
Tylermarques Jun 8, 2025
fd00131
FIX: SC History now works and correctly updates on phase move
Tylermarques Jun 8, 2025
6f9c050
WIP: Working on fixing moving from game to game
Tylermarques Jun 9, 2025
b9a88e1
Endgame correctly moves on
Tylermarques Jun 9, 2025
e7a862a
Typefixes
Tylermarques Jun 9, 2025
d454c68
Cleanup of some lingering code
Tylermarques Jun 10, 2025
2b52a9c
WIP: An attempt at a completly event queue driven game.
Tylermarques Jun 10, 2025
a929bf5
WIP: Majority converted to event queue, continuing to work on others.
Tylermarques Jun 10, 2025
ef3e3a7
WIP: Mostly working version that uses the eventqueue
Tylermarques Jun 13, 2025
9333f53
WIP: Fixing no events in queue
Tylermarques Jun 13, 2025
bad4ad3
FIX: Messages were triggering at the same time
Tylermarques Jun 13, 2025
af3e9c1
Merge conflict resolved by accepting their changes
Tylermarques Jun 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"version": "0.2.0",
"configurations": [
{
"type": "firefox",
"type": "pwa-chrome",
"request": "launch",
"name": "Firefox Debug 9223",
"url": "http://localhost:5173",
"name": "Chrome Debug 5179",
"url": "http://localhost:5179",
"webRoot": "${workspaceFolder}/ai_animation/",
"sourceMapPathOverrides": {
"http://localhost:5173/*": "${webRoot}/*"
"http://localhost:5179/*": "${webRoot}/*"
},
"runtimeArgs": [
"--remote-debugging-port=9223"
Expand Down
4 changes: 1 addition & 3 deletions ai_animation/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<div class="top-controls">
<div>
<button id="load-btn">Load Game</button>
<button id="standings-btn">📊 Leaderboard</button>
<button id="prev-btn" disabled>← Prev</button>
<button id="next-btn" disabled>Next →</button>
<button id="play-btn" disabled>▶ Play</button>
Expand All @@ -30,9 +29,8 @@
</div>
<div id="map-view" class="map-view"></div>
<input type="file" id="file-input" accept=".json">
<div id="info-panel"></div>
<!-- New leaderboard element -->
<div id="leaderboard"></div>
<div id="rotating-display"></div>
<!-- Chat windows container -->
<div id="chat-container"></div>
<!-- Add this after the info-panel div -->
Expand Down
11 changes: 7 additions & 4 deletions ai_animation/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

182 changes: 182 additions & 0 deletions ai_animation/src/backgroundAudio.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/**
* Tests for background audio looping functionality
*/

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';

// Mock HTMLAudioElement
class MockAudio {
loop = false;
volume = 1;
paused = true;
src = '';
currentTime = 0;
duration = 10;

constructor() {
// Constructor can be empty or set default values
}

play = vi.fn().mockResolvedValue(undefined);
pause = vi.fn();
addEventListener = vi.fn();
}

describe('Background Audio Looping', () => {
let originalAudio: any;

beforeEach(() => {
// Store original and replace with mock
originalAudio = global.Audio;
global.Audio = MockAudio as any;

// Clear any module state by re-importing
vi.resetModules();
});

afterEach(() => {
// Restore original Audio constructor
global.Audio = originalAudio;
});

it('should set loop property to true during initialization', async () => {
// Import fresh module to avoid state pollution
const { initializeBackgroundAudio } = await import('./backgroundAudio');

// Initialize background audio
initializeBackgroundAudio();

// The actual test is that initialization doesn't throw and sets up the audio correctly
// We can't directly access the private audio instance, but we can test the behavior
expect(global.Audio).toBeDefined();
});

it('should handle audio loop property correctly', () => {
// Create a mock audio instance to test loop behavior
const audioElement = new MockAudio() as any;

// Set loop to true (like our code does)
audioElement.loop = true;
audioElement.duration = 5; // 5 second track

// Simulate audio playing and ending
audioElement.paused = false;
audioElement.currentTime = 0;

// Simulate what happens when audio reaches the end
audioElement.currentTime = audioElement.duration;

// With loop=true, browser automatically restarts
const simulateLoopBehavior = () => {
if (audioElement.loop && !audioElement.paused && audioElement.currentTime >= audioElement.duration) {
audioElement.currentTime = 0; // Browser resets to start
return true; // Indicates loop occurred
}
return false;
};

// Test loop behavior
const looped = simulateLoopBehavior();

expect(audioElement.loop).toBe(true);
expect(looped).toBe(true);
expect(audioElement.currentTime).toBe(0); // Should be reset to start
});

it('should verify loop property is essential for continuous playback', () => {
const audioWithLoop = new MockAudio() as any;
const audioWithoutLoop = new MockAudio() as any;

// Setup both audio elements
audioWithLoop.loop = true;
audioWithoutLoop.loop = false;

audioWithLoop.duration = 10;
audioWithoutLoop.duration = 10;

// Both start playing
audioWithLoop.paused = false;
audioWithoutLoop.paused = false;

// Both reach the end
audioWithLoop.currentTime = audioWithLoop.duration;
audioWithoutLoop.currentTime = audioWithoutLoop.duration;

// Simulate end behavior
const testLooping = (audio: any) => {
if (audio.currentTime >= audio.duration) {
if (audio.loop) {
audio.currentTime = 0; // Loop back to start
return 'looped';
} else {
audio.paused = true; // Stop playing
return 'stopped';
}
}
return 'playing';
};

const resultWithLoop = testLooping(audioWithLoop);
const resultWithoutLoop = testLooping(audioWithoutLoop);

expect(resultWithLoop).toBe('looped');
expect(resultWithoutLoop).toBe('stopped');
expect(audioWithLoop.currentTime).toBe(0); // Reset to start
expect(audioWithoutLoop.paused).toBe(true); // Stopped
});

it('should test the actual module behavior', async () => {
// Import fresh module
const { initializeBackgroundAudio, startBackgroundAudio, stopBackgroundAudio } = await import('./backgroundAudio');

// Test initialization doesn't throw
expect(() => initializeBackgroundAudio()).not.toThrow();

// Test double initialization protection
expect(() => initializeBackgroundAudio()).toThrow('Attempted to init audio twice.');
});

it('should demonstrate loop property importance with realistic scenario', () => {
// This test demonstrates why loop=true is critical for background music
const backgroundTrack = new MockAudio() as any;

// Our code sets this to true
backgroundTrack.loop = true;
backgroundTrack.volume = 0.4;
backgroundTrack.src = './sounds/background_ambience.mp3';
backgroundTrack.duration = 30; // 30 second ambient track

// Start playing
backgroundTrack.paused = false;
backgroundTrack.currentTime = 0;

// Simulate game running for longer than track duration
const gameRuntime = 90; // 90 seconds
const timeStep = 1; // 1 second steps

let currentGameTime = 0;
let trackRestarts = 0;

while (currentGameTime < gameRuntime) {
currentGameTime += timeStep;
backgroundTrack.currentTime += timeStep;

// Check if track ended and needs to loop
if (backgroundTrack.currentTime >= backgroundTrack.duration) {
if (backgroundTrack.loop) {
backgroundTrack.currentTime = 0; // Restart
trackRestarts++;
} else {
backgroundTrack.paused = true; // Would stop without loop
break;
}
}
}

// With a 30-second track and 90-second game, we expect 3 restarts (0-30, 30-60, 60-90)
expect(backgroundTrack.loop).toBe(true);
expect(trackRestarts).toBe(3);
expect(backgroundTrack.paused).toBe(false); // Still playing
expect(currentGameTime).toBe(gameRuntime); // Game completed full duration
});
});
43 changes: 30 additions & 13 deletions ai_animation/src/backgroundAudio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,27 @@ let isAudioInitialized = false;
* Only loads in streaming mode to avoid unnecessary downloads
*/
export function initializeBackgroundAudio(): void {
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';

if (!isStreamingMode || isAudioInitialized) {
return;

if (isAudioInitialized) {
throw new Error("Attempted to init audio twice.")
}

isAudioInitialized = true;

// Create audio element
backgroundAudio = new Audio();
backgroundAudio.loop = true;
backgroundAudio.volume = 0.4; // 40% volume as requested

// For now, we'll use a placeholder - you should download and convert the wave file
// to a smaller MP3 format (aim for < 10MB) and place it in public/sounds/
backgroundAudio.src = './sounds/background_ambience.mp3';

// Handle audio loading
backgroundAudio.addEventListener('canplaythrough', () => {
console.log('Background audio loaded and ready to play');
});

backgroundAudio.addEventListener('error', (e) => {
console.error('Failed to load background audio:', e);
});
Expand All @@ -42,19 +41,37 @@ export function initializeBackgroundAudio(): void {
* Will only work after user interaction due to browser policies
*/
export function startBackgroundAudio(): void {
if (backgroundAudio && backgroundAudio.paused) {
backgroundAudio.play().catch(err => {
console.log('Background audio autoplay blocked, will retry on user interaction:', err);
if (!backgroundAudio) {
console.warn('Background audio not initialized');
return;
}

if (backgroundAudio.paused) {
console.log('Starting background audio...');
backgroundAudio.play().then(() => {
console.log('Background audio started successfully');
}).catch(err => {
console.warn('Background audio autoplay blocked, will retry on user interaction:', err);
});
} else {
console.log('Background audio already playing');
}
}

/**
* Stop background audio
*/
export function stopBackgroundAudio(): void {
if (backgroundAudio && !backgroundAudio.paused) {
if (!backgroundAudio) {
console.warn('Background audio not initialized');
return;
}

if (!backgroundAudio.paused) {
console.log('Stopping background audio...');
backgroundAudio.pause();
} else {
console.log('Background audio already paused');
}
}

Expand All @@ -66,4 +83,4 @@ export function setBackgroundAudioVolume(volume: number): void {
if (backgroundAudio) {
backgroundAudio.volume = Math.max(0, Math.min(1, volume));
}
}
}
Loading
Loading