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:
Tyler Marques 2025-03-13 11:33:30 -04:00
parent dff01db83f
commit ae2db39729
No known key found for this signature in database
GPG key ID: 7672EFD79378341C
13 changed files with 1363 additions and 1322 deletions

View file

@ -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) })
})
}

View 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)
}
}
}