mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-27 17:23:21 +00:00
Updating phase state control to allow for reinit of units at any phase.
I wanted to be able to cause the turns to either progress, or to set the units in their specific spots. This way if I skip ahead a bunch of turns for one reason or another, I don't have to worry about the units on the board being all messed up.
This commit is contained in:
parent
9a43be9b9c
commit
3642e391bc
7 changed files with 101 additions and 56 deletions
2
ai_animation/.gitignore
vendored
2
ai_animation/.gitignore
vendored
|
|
@ -26,4 +26,4 @@ dist-ssr
|
|||
# AI things
|
||||
.claude/
|
||||
|
||||
./public/games/
|
||||
public/games/
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { Tween, Group as TweenGroup } from "@tweenjs/tween.js";
|
|||
import { hideStandingsBoard, } from "./domElements/standingsBoard";
|
||||
import { MomentsDataSchema, MomentsDataSchemaType } from "./types/moments";
|
||||
|
||||
//FIXME: This whole file is a mess. Need to organkze and format
|
||||
//FIXME: This whole file is a mess. Need to organize and format
|
||||
|
||||
enum AvailableMaps {
|
||||
STANDARD = "standard"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { loadBtn, prevBtn, nextBtn, speedSelector, fileInput, playBtn, mapView,
|
|||
import { updateChatWindows } from "./domElements/chatWindows";
|
||||
import { initStandingsBoard, hideStandingsBoard, showStandingsBoard } from "./domElements/standingsBoard";
|
||||
import { initRelationshipPopup, hideRelationshipPopup, updateRelationshipPopup } from "./domElements/relationshipPopup";
|
||||
import { displayPhaseWithAnimation, advanceToNextPhase, resetToPhase } from "./phase";
|
||||
import { displayPhaseWithAnimation, advanceToNextPhase, resetToPhase, nextPhase, previousPhase } from "./phase";
|
||||
import { config } from "./config";
|
||||
import { Tween, Group, Easing } from "@tweenjs/tween.js";
|
||||
import { initRotatingDisplay, updateRotatingDisplay } from "./components/rotatingDisplay";
|
||||
|
|
@ -23,15 +23,8 @@ let prevPos
|
|||
|
||||
// --- INITIALIZE SCENE ---
|
||||
function initScene() {
|
||||
gameState.initScene()
|
||||
gameState.createThreeScene()
|
||||
|
||||
// Lighting (keep it simple)
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||||
gameState.scene.add(ambientLight);
|
||||
|
||||
const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
|
||||
dirLight.position.set(300, 400, 300);
|
||||
gameState.scene.add(dirLight);
|
||||
|
||||
// Initialize standings board
|
||||
initStandingsBoard();
|
||||
|
|
@ -267,12 +260,10 @@ fileInput.addEventListener('change', e => {
|
|||
});
|
||||
|
||||
prevBtn.addEventListener('click', () => {
|
||||
if (gameState.phaseIndex > 0) {
|
||||
resetToPhase(gameState.phaseIndex - 1)
|
||||
}
|
||||
previousPhase()
|
||||
});
|
||||
nextBtn.addEventListener('click', () => {
|
||||
advanceToNextPhase()
|
||||
nextPhase()
|
||||
});
|
||||
|
||||
playBtn.addEventListener('click', togglePlayback);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,42 @@ import { config } from "./config";
|
|||
|
||||
|
||||
|
||||
function _setPhase(phaseIndex: number) {
|
||||
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 (Math.abs(phaseIndex - gameState.phaseIndex) > 1) {
|
||||
// We're moving more than one Phase, to do so clear the board and reInit the units on the correct phase
|
||||
gameState.unitAnimations = [];
|
||||
initUnits(phaseIndex)
|
||||
updateMapOwnership()
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Finally, update the gameState with the current phaseIndex
|
||||
gameState.phaseIndex = phaseIndex
|
||||
// If we're at the end of the game, don't attempt to animate.
|
||||
if (phaseIndex === gameLength - 1) {
|
||||
|
||||
} else {
|
||||
|
||||
displayPhase()
|
||||
}
|
||||
}
|
||||
|
||||
export function nextPhase() {
|
||||
_setPhase(gameState.phaseIndex + 1)
|
||||
}
|
||||
|
||||
export function previousPhase() {
|
||||
_setPhase(gameState.phaseIndex - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified function to display a phase with proper transitions
|
||||
* Handles both initial display and animated transitions between phases
|
||||
|
|
@ -86,8 +122,8 @@ export function displayPhase(skipMessages = false) {
|
|||
* Used when first loading a game
|
||||
*/
|
||||
export function displayInitialPhase() {
|
||||
initUnits();
|
||||
gameState.phaseIndex = 0;
|
||||
initUnits(0);
|
||||
displayPhase(true);
|
||||
}
|
||||
|
||||
|
|
@ -99,17 +135,6 @@ export function displayPhaseWithAnimation() {
|
|||
displayPhase(false);
|
||||
}
|
||||
|
||||
// Explicityly sets the phase to a given index,
|
||||
// Removes and recreates all units.
|
||||
export function resetToPhase(index: number) {
|
||||
gameState.phaseIndex = index
|
||||
gameState.unitAnimations = [];
|
||||
gameState.unitMeshes.map(unitMesh => gameState.scene.remove(unitMesh))
|
||||
|
||||
updateMapOwnership()
|
||||
initUnits()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances to the next phase in the game sequence
|
||||
|
|
@ -178,7 +203,7 @@ function displayFinalPhase() {
|
|||
|
||||
// Get the final phase to determine the winner
|
||||
const finalPhase = gameState.gameData.phases[gameState.gameData.phases.length - 1];
|
||||
|
||||
|
||||
if (!finalPhase.state?.centers) {
|
||||
logger.log("No supply center data available to determine winner");
|
||||
return;
|
||||
|
|
@ -187,7 +212,7 @@ function displayFinalPhase() {
|
|||
// Find the power with the most supply centers
|
||||
let winner = '';
|
||||
let maxCenters = 0;
|
||||
|
||||
|
||||
for (const [power, centers] of Object.entries(finalPhase.state.centers)) {
|
||||
const centerCount = Array.isArray(centers) ? centers.length : 0;
|
||||
if (centerCount > maxCenters) {
|
||||
|
|
@ -199,13 +224,13 @@ function displayFinalPhase() {
|
|||
// Display victory message
|
||||
if (winner && maxCenters > 0) {
|
||||
const victoryMessage = `🏆 GAME OVER - ${winner} WINS with ${maxCenters} supply centers! 🏆`;
|
||||
|
||||
|
||||
// Add victory message to news banner with dramatic styling
|
||||
addToNewsBanner(victoryMessage);
|
||||
|
||||
|
||||
// Log the victory
|
||||
logger.log(`Victory! ${winner} wins the game with ${maxCenters} supply centers.`);
|
||||
|
||||
|
||||
// Display final standings in console
|
||||
const standings = Object.entries(finalPhase.state.centers)
|
||||
.map(([power, centers]) => ({
|
||||
|
|
@ -213,7 +238,7 @@ function displayFinalPhase() {
|
|||
centers: Array.isArray(centers) ? centers.length : 0
|
||||
}))
|
||||
.sort((a, b) => b.centers - a.centers);
|
||||
|
||||
|
||||
console.log("Final Standings:");
|
||||
standings.forEach((entry, index) => {
|
||||
const medal = index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : " ";
|
||||
|
|
|
|||
|
|
@ -21,7 +21,27 @@ const PhaseSchema = z.object({
|
|||
messages: z.array(z.any()),
|
||||
name: z.string(),
|
||||
orders: z.record(PowerENUMSchema, z.array(OrderFromString).nullable()),
|
||||
results: z.record(z.string(), z.array(z.any())),
|
||||
results: z.record(z.string(), z.array(z.any())).transform((originalResults) => {
|
||||
// Transform results from {"A BUD": [results]} to {A: {"BUD": [results]}, F: {"BUD": [results]}}
|
||||
const transformed: { A: Record<string, any[]>, F: Record<string, any[]> } = { A: {}, F: {} };
|
||||
|
||||
for (const [key, value] of Object.entries(originalResults)) {
|
||||
const tokens = key.split(' ');
|
||||
if (tokens.length >= 2) {
|
||||
const unitType = tokens[0]; // "A" or "F"
|
||||
const province = tokens[1].split('/')[0]; // Remove coast specification if present
|
||||
|
||||
if (unitType === 'A' || unitType === 'F') {
|
||||
if (!transformed[unitType as 'A' | 'F']) {
|
||||
transformed[unitType as 'A' | 'F'] = {};
|
||||
}
|
||||
transformed[unitType as 'A' | 'F'][province] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transformed;
|
||||
}),
|
||||
state: z.object({
|
||||
units: z.record(PowerENUMSchema, z.array(z.string())),
|
||||
centers: z.record(PowerENUMSchema, z.array(ProvinceENUMSchema)),
|
||||
|
|
|
|||
|
|
@ -94,25 +94,26 @@ export function createAnimationsForNextPhase() {
|
|||
}
|
||||
for (const order of orders) {
|
||||
// Check if unit bounced
|
||||
let lastPhaseResultMatches = Object.entries(previousPhase.results).filter(([key, _]) => {
|
||||
return key.split(" ")[1] == order.unit.origin
|
||||
}).map(val => {
|
||||
// in the form "A BER" (unitType origin)
|
||||
let orderSplit = val[0].split(" ")
|
||||
return { origin: orderSplit[1], unitType: orderSplit[0], result: val[1][0] }
|
||||
})
|
||||
// This should always exist. If we don't have a match here, that means something went wrong with our order parsing
|
||||
if (!lastPhaseResultMatches) {
|
||||
throw new Error("No result present in current phase for previous phase order. Cannot continue")
|
||||
// With new format: {A: {"BUD": [results]}, F: {"BUD": [results]}}
|
||||
const unitType = order.unit.type;
|
||||
const unitOrigin = order.unit.origin;
|
||||
|
||||
let result = undefined;
|
||||
if (previousPhase.results && previousPhase.results[unitType] && previousPhase.results[unitType][unitOrigin]) {
|
||||
const resultArray = previousPhase.results[unitType][unitOrigin];
|
||||
result = resultArray.length > 0 ? resultArray[0] : null;
|
||||
|
||||
}
|
||||
if (lastPhaseResultMatches.length > 1) {
|
||||
throw new Error("Multiple matching results from last phase. Should only ever be 1.")
|
||||
|
||||
if (result === undefined) {
|
||||
throw new Error(`No result present in current phase for previous phase order: ${unitType} ${unitOrigin}. Cannot continue`);
|
||||
}
|
||||
if (lastPhaseResultMatches[0].result === "bounce") {
|
||||
|
||||
if (result === "bounce") {
|
||||
order.type = "bounce"
|
||||
}
|
||||
// If the result is void, that means the move was not valid?
|
||||
if (lastPhaseResultMatches[0].result === "void") continue;
|
||||
if (result === "void") continue;
|
||||
let unitIndex = -1
|
||||
|
||||
switch (order.type) {
|
||||
|
|
@ -163,10 +164,6 @@ export function createAnimationsForNextPhase() {
|
|||
|
||||
case "support":
|
||||
break
|
||||
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,11 +144,17 @@ export function createUnitMesh(unitData: UnitData): THREE.Group {
|
|||
return group;
|
||||
}
|
||||
|
||||
function _removeUnitsFromBoard() {
|
||||
|
||||
// Creates the units for the current gameState.phaseIndex.
|
||||
export function initUnits() {
|
||||
createSupplyCenters()
|
||||
for (const [power, unitArr] of Object.entries(gameState.gameData.phases[gameState.phaseIndex].state.units)) {
|
||||
gameState.unitMeshes.map((mesh) => gameState.scene.remove(mesh))
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a phaseIndex, Add the units for that phase to the board, in the province specified in the game.json file.
|
||||
*/
|
||||
function _addUnitsToBoard(phaseIndex: number) {
|
||||
_removeUnitsFromBoard()
|
||||
for (const [power, unitArr] of Object.entries(gameState.gameData.phases[phaseIndex].state.units)) {
|
||||
unitArr.forEach(unitStr => {
|
||||
const match = unitStr.match(/^([AF])\s+(.+)$/);
|
||||
if (match) {
|
||||
|
|
@ -163,3 +169,9 @@ export function initUnits() {
|
|||
});
|
||||
}
|
||||
}
|
||||
// Creates the units for the current gameState.phaseIndex.
|
||||
export function initUnits(phaseIndex: number) {
|
||||
if (phaseIndex === undefined) throw new Error("Cannot pass undefined phaseIndex");
|
||||
createSupplyCenters()
|
||||
_addUnitsToBoard(phaseIndex)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue