mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-27 17:23:21 +00:00
WIP: Moving most globals into gamestate
The gamestate object is now where most of the "state" of both the map and the board live. The units load, just working on loading the colors of the map ownership again.
This commit is contained in:
parent
dff01db83f
commit
ae2db39729
13 changed files with 1363 additions and 1322 deletions
|
|
@ -2,104 +2,106 @@ import * as THREE from "three";
|
|||
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
|
||||
import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
|
||||
import { createLabel } from "./labels"
|
||||
import { coordinateData } from "../gameState";
|
||||
import { gameState } from "../gameState";
|
||||
import { getPowerHexColor } from "../units/create";
|
||||
|
||||
export function initMap(scene,) {
|
||||
const loader = new SVGLoader();
|
||||
loader.load('assets/maps/standard/map.svg',
|
||||
function (data) {
|
||||
fetch('assets/maps/standard/styles.json')
|
||||
.then(resp => resp.json())
|
||||
.then(map_styles => {
|
||||
const paths = data.paths;
|
||||
const group = new THREE.Group();
|
||||
const textGroup = new THREE.Group();
|
||||
let fillColor;
|
||||
export function initMap(scene): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const loader = new SVGLoader();
|
||||
loader.load('assets/maps/standard/map.svg',
|
||||
function (data) {
|
||||
fetch('assets/maps/standard/styles.json')
|
||||
.then(resp => resp.json())
|
||||
.then(map_styles => {
|
||||
const paths = data.paths;
|
||||
const group = new THREE.Group();
|
||||
const textGroup = new THREE.Group();
|
||||
let fillColor;
|
||||
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
fillColor = "";
|
||||
const path = paths[i];
|
||||
// The "standard" map has keys like _mos, so remove that then send them to caps
|
||||
let provinceKey = path.userData.node.id.substring(1).toUpperCase();
|
||||
let nodeClass = path.userData.node.classList[0]
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
fillColor = "";
|
||||
const path = paths[i];
|
||||
// The "standard" map has keys like _mos, so remove that then send them to caps
|
||||
let provinceKey = path.userData.node.id.substring(1).toUpperCase();
|
||||
let nodeClass = path.userData.node.classList[0]
|
||||
|
||||
switch (nodeClass) {
|
||||
case undefined:
|
||||
continue
|
||||
case "water":
|
||||
fillColor = "#c5dfea"
|
||||
break
|
||||
case "nopower":
|
||||
fillColor = getPowerHexColor(undefined)
|
||||
}
|
||||
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: fillColor,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false
|
||||
});
|
||||
|
||||
const shapes = SVGLoader.createShapes(path);
|
||||
|
||||
for (let j = 0; j < shapes.length; j++) {
|
||||
|
||||
const shape = shapes[j];
|
||||
const geometry = new THREE.ShapeGeometry(shape);
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
mesh.rotation.x = Math.PI / 2;
|
||||
if (provinceKey && coordinateData.provinces[provinceKey]) {
|
||||
coordinateData.provinces[provinceKey].mesh = mesh
|
||||
switch (nodeClass) {
|
||||
case undefined:
|
||||
continue
|
||||
case "water":
|
||||
fillColor = "#c5dfea"
|
||||
break
|
||||
case "nopower":
|
||||
fillColor = getPowerHexColor(undefined)
|
||||
}
|
||||
|
||||
|
||||
// Create an edges geometry from the shape geometry.
|
||||
const edges = new THREE.EdgesGeometry(geometry);
|
||||
// Create a line material with black color for the border.
|
||||
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 2 });
|
||||
// Create the line segments object to display the border.
|
||||
const line = new THREE.LineSegments(edges, lineMaterial);
|
||||
// Add the border as a child of the mesh.
|
||||
mesh.add(line);
|
||||
group.add(mesh);
|
||||
}
|
||||
}
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: fillColor,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false
|
||||
});
|
||||
|
||||
// Load all the labels for each map position
|
||||
const fontLoader = new FontLoader();
|
||||
fontLoader.load('assets/fonts/helvetiker_regular.typeface.json', function (font) {
|
||||
for (const [key, value] of Object.entries(coordinateData.provinces)) {
|
||||
const shapes = SVGLoader.createShapes(path);
|
||||
|
||||
textGroup.add(createLabel(font, key, value))
|
||||
for (let j = 0; j < shapes.length; j++) {
|
||||
|
||||
const shape = shapes[j];
|
||||
const geometry = new THREE.ShapeGeometry(shape);
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
mesh.rotation.x = Math.PI / 2;
|
||||
if (provinceKey && gameState.boardState.provinces[provinceKey]) {
|
||||
gameState.boardState.provinces[provinceKey].mesh = mesh
|
||||
}
|
||||
|
||||
|
||||
// Create an edges geometry from the shape geometry.
|
||||
const edges = new THREE.EdgesGeometry(geometry);
|
||||
// Create a line material with black color for the border.
|
||||
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 2 });
|
||||
// Create the line segments object to display the border.
|
||||
const line = new THREE.LineSegments(edges, lineMaterial);
|
||||
// Add the border as a child of the mesh.
|
||||
mesh.add(line);
|
||||
group.add(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Load all the labels for each map position
|
||||
const fontLoader = new FontLoader();
|
||||
fontLoader.load('assets/fonts/helvetiker_regular.typeface.json', function (font) {
|
||||
for (const [key, value] of Object.entries(gameState.boardState.provinces)) {
|
||||
|
||||
textGroup.add(createLabel(font, key, value))
|
||||
}
|
||||
})
|
||||
// This rotates the SVG the "correct" way round, and scales it down
|
||||
group.scale.set(1, -1, 1)
|
||||
textGroup.rotation.x = Math.PI / 2;
|
||||
textGroup.scale.set(1, -1, 1)
|
||||
|
||||
// After adding all meshes to the group, update its matrix:
|
||||
group.updateMatrixWorld(true);
|
||||
textGroup.updateMatrixWorld(true);
|
||||
|
||||
// Compute the bounding box of the group:
|
||||
const box = new THREE.Box3().setFromObject(group);
|
||||
const center = new THREE.Vector3();
|
||||
box.getCenter(center);
|
||||
|
||||
|
||||
scene.add(group);
|
||||
scene.add(textGroup);
|
||||
resolve()
|
||||
|
||||
})
|
||||
// This rotates the SVG the "correct" way round, and scales it down
|
||||
group.scale.set(1, -1, 1)
|
||||
textGroup.rotation.x = Math.PI / 2;
|
||||
textGroup.scale.set(1, -1, 1)
|
||||
|
||||
// After adding all meshes to the group, update its matrix:
|
||||
group.updateMatrixWorld(true);
|
||||
textGroup.updateMatrixWorld(true);
|
||||
|
||||
// Compute the bounding box of the group:
|
||||
const box = new THREE.Box3().setFromObject(group);
|
||||
const center = new THREE.Vector3();
|
||||
box.getCenter(center);
|
||||
|
||||
|
||||
scene.add(group);
|
||||
scene.add(textGroup);
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading map styles:', error);
|
||||
});
|
||||
},
|
||||
// Progress function
|
||||
undefined,
|
||||
function (error) { console.log(error) })
|
||||
|
||||
.catch(error => {
|
||||
console.error('Error loading map styles:', error);
|
||||
});
|
||||
},
|
||||
// Progress function
|
||||
undefined,
|
||||
function (error) { console.log(error) })
|
||||
})
|
||||
}
|
||||
|
|
|
|||
122
ai_animation/src/map/state.ts
Normal file
122
ai_animation/src/map/state.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { getPowerHexColor } from "../units/create";
|
||||
import { gameState } from "../gameState";
|
||||
import { leaderboard } from "../domElements";
|
||||
import type { GamePhase } from "../types/gameState";
|
||||
|
||||
|
||||
export function updateSupplyCenterOwnership(centers) {
|
||||
if (!centers) return;
|
||||
const ownershipMap = {};
|
||||
// centers is typically { "AUSTRIA":["VIE","BUD"], "FRANCE":["PAR","MAR"], ... }
|
||||
for (const [power, provinces] of Object.entries(centers)) {
|
||||
provinces.forEach(p => {
|
||||
// No messages, animate units immediately
|
||||
ownershipMap[p.toUpperCase()] = power.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
gameState.unitMeshes.forEach(obj => {
|
||||
if (obj.userData && obj.userData.isSupplyCenter) {
|
||||
const prov = obj.userData.province;
|
||||
const owner = ownershipMap[prov];
|
||||
if (owner) {
|
||||
const c = getPowerHexColor(owner);
|
||||
obj.userData.starMesh.material.color.setHex(c);
|
||||
|
||||
// Add a pulsing animation
|
||||
if (!obj.userData.pulseAnimation) {
|
||||
obj.userData.pulseAnimation = {
|
||||
speed: 0.003 + Math.random() * 0.002,
|
||||
intensity: 0.3,
|
||||
time: Math.random() * Math.PI * 2
|
||||
};
|
||||
if (!gameState.scene.userData.animatedObjects) gameState.scene.userData.animatedObjects = [];
|
||||
gameState.scene.userData.animatedObjects.push(obj);
|
||||
}
|
||||
} else {
|
||||
// Neutral
|
||||
obj.userData.starMesh.material.color.setHex(0xFFD700);
|
||||
// remove pulse
|
||||
obj.userData.pulseAnimation = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function updateLeaderboard(phase) {
|
||||
// Get supply center counts
|
||||
const centerCounts = {};
|
||||
const unitCounts = {};
|
||||
|
||||
// Count supply centers by power
|
||||
if (phase.state?.centers) {
|
||||
for (const [power, provinces] of Object.entries(phase.state.centers)) {
|
||||
centerCounts[power] = provinces.length;
|
||||
}
|
||||
}
|
||||
|
||||
// Count units by power
|
||||
if (phase.state?.units) {
|
||||
for (const [power, units] of Object.entries(phase.state.units)) {
|
||||
unitCounts[power] = units.length;
|
||||
}
|
||||
}
|
||||
|
||||
// Combine all powers from both centers and units
|
||||
const allPowers = new Set([
|
||||
...Object.keys(centerCounts),
|
||||
...Object.keys(unitCounts)
|
||||
]);
|
||||
|
||||
// Sort powers by supply center count (descending)
|
||||
const sortedPowers = Array.from(allPowers).sort((a, b) => {
|
||||
return (centerCounts[b] || 0) - (centerCounts[a] || 0);
|
||||
});
|
||||
|
||||
// Build HTML for leaderboard
|
||||
let html = `<strong>Council Standings</strong><br/>`;
|
||||
|
||||
sortedPowers.forEach(power => {
|
||||
const centers = centerCounts[power] || 0;
|
||||
const units = unitCounts[power] || 0;
|
||||
|
||||
// Use CSS classes instead of inline styles for better contrast
|
||||
html += `<div style="margin: 5px 0; display: flex; justify-content: space-between;">
|
||||
<span class="power-${power.toLowerCase()}">${power}</span>
|
||||
<span>${centers} SCs, ${units} units</span>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
// Add victory condition reminder
|
||||
html += `<hr style="border-color: #555; margin: 8px 0;"/>
|
||||
<small>Victory: 18 supply centers</small>`;
|
||||
|
||||
leaderboard.innerHTML = html;
|
||||
}
|
||||
|
||||
export function updateMapOwnership(currentPhase: GamePhase) {
|
||||
//FIXME: This only works in the forward direction, we currently don't update ownership correctly when going to previous phase
|
||||
|
||||
for (const [power, unitArr] of Object.entries(currentPhase.state.units)) {
|
||||
unitArr.forEach(unitStr => {
|
||||
const match = unitStr.match(/^([AF])\s+(.+)$/);
|
||||
if (!match) return;
|
||||
const unitType = match[1];
|
||||
const location = match[2];
|
||||
const normalized = location.toUpperCase().replace('/', '_');
|
||||
const base = normalized.split('_')[0];
|
||||
if (gameState.boardState.provinces[base] === undefined) {
|
||||
console.log(base)
|
||||
}
|
||||
gameState.boardState.provinces[base].owner = power
|
||||
})
|
||||
}
|
||||
for (const [key, value] of Object.entries(gameState.boardState.provinces)) {
|
||||
// Update the color of the provinces if needed
|
||||
if (gameState.boardState.provinces[key].owner) {
|
||||
let powerColor = getPowerHexColor(gameState.boardState.provinces[key].owner)
|
||||
let powerColorHex = parseInt(powerColor.substring(1), 16);
|
||||
gameState.boardState.provinces[key].mesh?.material.color.setHex(powerColorHex)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue