Private
Public Access
1
0

refacto: Changing gameplay so teams don't know what the other team got + both teams can now chart the same cell without knowing + planet capture gameplay implemented

This commit is contained in:
gauvainboiche
2026-04-01 17:17:52 +02:00
parent 5aa347eb13
commit 99d34c58c6
12 changed files with 432 additions and 186 deletions

View File

@@ -22,14 +22,14 @@
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.122"
inkscape:cx="75.311944"
inkscape:cy="524.95544"
inkscape:window-width="1718"
inkscape:window-height="1341"
inkscape:window-x="1712"
inkscape:window-y="231"
inkscape:window-maximized="0"
inkscape:zoom="0.79337381"
inkscape:cx="255.23908"
inkscape:cy="554.59355"
inkscape:window-width="3440"
inkscape:window-height="1351"
inkscape:window-x="-9"
inkscape:window-y="222"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" /><defs
id="defs1"><clipPath
clipPathUnits="userSpaceOnUse"
@@ -354,8 +354,9 @@
.st32{display:none;fill:#1D7A18;}
.st33{fill:#631077;}
</style><path
style="opacity:1;fill:#000000;fill-opacity:1;stroke-width:250;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers"
d="m 288.32442,328.43138 c 0,0 -23.84135,-35.205 -12.47771,-55.7041 11.36363,-20.49911 28.07486,-26.06952 31.19429,-36.09626 3.11943,-10.02674 13.36899,-39.66132 22.05883,-43.00357 8.68984,-3.34224 16.93404,-6.90731 26.29233,-6.90731 9.35829,0 25.8467,4.45633 33.86809,2.22817 8.02139,-2.22817 34.53655,-13.81462 45.45455,-12.70054 10.918,1.11409 38.77005,5.79323 43.67201,11.36364 4.90197,5.57041 18.27095,28.96613 17.37968,34.75936 -0.89126,5.79323 -2.45098,18.93939 -2.00534,28.07487 0.44563,9.13547 4.01069,19.83066 3.11943,24.95543 -0.89127,5.12478 -5.57041,17.15687 -5.57041,17.15687 0,0 11.80927,2.67379 13.14616,-4.23352 1.3369,-6.9073 7.13013,-16.26559 11.14082,-20.94474 4.0107,-4.67914 26.29234,-25.62388 27.85205,-30.74866 1.55972,-5.12478 8.46703,-22.72727 16.04279,-27.40642 7.57575,-4.67914 40.10695,-10.47237 55.92691,-9.35829 15.81997,1.11408 40.7754,2.45098 52.13904,6.68449 11.36364,4.23352 28.96613,16.04279 32.75401,22.28164 3.78788,6.23886 12.03209,20.2763 12.47772,24.28699 0.44563,4.0107 -7.13013,21.61319 -7.13013,21.61319 0,0 10.47238,14.70589 16.48842,16.93405 6.01604,2.22816 10.69519,1.78253 14.48306,6.01604 3.78788,4.23351 -0.22281,9.58111 3.78788,10.91801 4.0107,1.33689 13.81462,2.89661 14.9287,8.02139 1.11408,5.12477 -4.90196,12.92335 -3.56506,18.49376 1.3369,5.57041 5.12478,18.49376 4.90196,22.95009 -0.22282,4.45633 -7.57576,19.16221 -7.57576,19.16221 0,0 13.36899,2.45098 10.69519,11.14082 -2.6738,8.68984 -13.5918,14.03743 -13.5918,14.03743 0,0 -4.67914,18.49376 -0.22282,33.86809 4.45633,15.37434 14.9287,42.55794 14.9287,53.69876 0,11.14082 -3.78788,80.88235 -7.13012,102.49554 -3.34225,21.61319 -18.04813,78.43138 -28.96613,97.14795 -10.91801,18.71658 -16.71123,28.74332 -22.28164,32.97683 -5.57042,4.23351 -40.77541,20.27629 -40.77541,20.27629 l -47.68271,9.58111 c 0,0 12.47772,31.1943 10.47237,38.99287 -2.00534,7.79857 -1.33689,13.14617 -8.68984,14.70588 -7.35294,1.55972 -27.85205,-0.66845 -32.08556,-2.22816 -4.23351,-1.55972 -9.80392,-6.23886 -15.81996,-6.46168 -6.01604,-0.22281 -18.9394,-6.90731 -25.62389,-5.57041 -6.68449,1.3369 -11.80927,1.3369 -14.48306,7.35294 -2.6738,6.01605 -17.6025,36.09626 -17.6025,36.09626 0,0 -21.83601,-5.34759 -26.51515,1.11408 -4.67915,6.46168 2.00535,16.04278 -2.22817,19.16221 -4.23351,3.11943 -18.04812,1.55972 -21.16755,-4.90196 -3.11943,-6.46167 0.44563,-13.36898 -7.13013,-15.15151 -7.57576,-1.78253 -26.29233,-2.89662 -29.18895,-0.22282 -2.89661,2.6738 -22.50445,12.47772 -28.29768,13.81462 -5.79323,1.3369 -18.93939,9.13547 -23.61854,7.57576 -4.67914,-1.55972 -7.57576,-16.93405 -16.93405,-19.16222 -9.35828,-2.22816 -13.5918,-7.79857 -13.81461,-11.58645 -0.22282,-3.78788 -5.57041,-21.39037 -10.02674,-25.17825 -4.45633,-3.78788 -6.46168,-5.79323 -11.80927,-0.66845 -5.34759,5.12478 -13.5918,15.81996 -19.60784,14.48307 -6.01605,-1.3369 -16.48842,-1.3369 -16.93405,-6.23886 -0.44563,-4.90196 3.56506,-26.96079 8.68984,-38.10161 5.12478,-11.14082 10.47237,-26.06952 10.02674,-31.86274 -0.44563,-5.79323 0.66845,-19.16221 -10.69519,-27.18361 -11.36364,-8.02139 -19.83066,-16.48841 -27.85205,-17.15686 -8.02139,-0.66845 -23.39572,0 -22.50446,-7.79857 0.89127,-7.79858 20.05348,-44.56328 17.82532,-55.7041 -2.22817,-11.14082 -61.0517,-72.19252 -107.39751,-69.51872 -46.34581,2.6738 -96.256685,21.39037 -103.38681,49.46524 -7.130125,28.07487 -21.836007,165.7754 8.02139,182.70945 29.857398,16.93405 134.13547,48.12834 147.95009,63.72549 13.81462,15.59715 105.16934,95.36542 126.11408,108.73441 20.94475,13.36898 219.25134,24.95543 238.85919,18.71657 19.60784,-6.23886 134.13547,-74.42068 170.67736,-91.80035 36.54189,-17.37968 90.01783,-15.59715 135.91801,-23.61854 45.90017,-8.02139 76.64884,-60.16043 73.08378,-83.77897 -3.56507,-23.61854 -94.02853,-135.02674 -68.18182,-204.99109 25.8467,-69.96435 75.31194,-163.54724 75.31194,-194.2959 0,-30.74867 0.44563,-141.2656 -33.42246,-147.50446 -33.86809,-6.23886 -101.15865,16.48841 -119.42959,0.89127 -18.27095,-15.59715 -52.13904,-75.31195 -65.95366,-91.80036 -13.81461,-16.48842 -78.877,-95.365422 -146.16756,-102.495547 -67.29055,-7.130125 -189.39394,41.889484 -228.60962,41.889484 -39.21569,0 -107.39751,-54.367202 -144.38503,-47.68271 -36.98752,6.684492 -148.395725,37.878788 -152.406421,81.996433 -4.010695,44.11765 -16.934046,159.53655 23.172906,183.15508 40.106955,23.61854 110.962565,81.10517 149.732625,64.61676 38.77005,-16.48841 50.3565,-40.55258 50.3565,-40.55258 z"
id="outer_regions"
inkscape:label="outer_regions"><title
style="opacity:1;fill:#000000;fill-opacity:1;stroke-width:250;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers"
inkscape:label="outer_regions"
d="m 224.75391,38.650391 c -2.54074,-0.03101 -4.97344,0.146672 -7.28516,0.564453 -36.98752,6.684492 -148.395554,37.880402 -152.40625,81.998046 -4.010695,44.11765 -8.53489,149.07962 23.171875,183.1543 31.902735,34.28528 65.067715,81.91168 59.714845,168.0039 -6.71811,108.04971 -90.949717,101.4651 -100.265626,138.14649 -7.130125,28.07487 -21.837866,165.77493 8.019531,182.70898 29.857398,16.93405 134.136555,48.12746 147.951175,63.72461 13.81462,15.59715 105.16854,95.36539 126.11328,108.73438 20.94475,13.36898 219.25152,24.95565 238.85937,18.71679 19.60784,-6.23886 134.13585,-74.42111 170.67774,-91.80078 36.54189,-17.37968 90.01779,-15.5958 135.91797,-23.61718 45.90017,-8.02139 76.64904,-60.16076 73.08398,-83.7793 -3.56507,-23.61854 -94.02835,-135.02589 -68.18164,-204.99024 25.8467,-69.96435 75.3125,-163.54821 75.3125,-194.29687 0,-30.74867 0.44426,-141.26505 -33.42383,-147.50391 -33.86809,-6.23886 -101.15875,16.48777 -119.42969,0.89063 -18.27095,-15.59715 -52.1385,-75.31237 -65.95312,-91.80078 C 722.81625,131.01549 657.75345,52.139891 590.46289,45.009766 523.17234,37.879641 401.0692,86.898437 361.85352,86.898438 c -36.76471,0 -98.98855,-47.78294 -137.09961,-48.248047 z M 432.49414,176.18164 c 0.79591,-0.0242 1.53833,-0.003 2.2207,0.0664 10.918,1.11409 38.76992,5.79287 43.67188,11.36328 4.90197,5.57041 18.27018,28.96653 17.3789,34.75976 -0.89125,5.79323 -2.44954,18.93874 -2.0039,28.07422 0.44563,9.13547 4.0104,19.83031 3.11914,24.95508 -0.89127,5.12478 -5.57031,17.1582 -5.57031,17.1582 0,0 11.80959,2.67294 13.14648,-4.23437 1.3369,-6.9073 7.12994,-16.26616 11.14063,-20.94531 4.0107,-4.67914 26.29185,-25.62327 27.85156,-30.74805 1.55972,-5.12478 8.46721,-22.7271 16.04297,-27.40625 7.57575,-4.67914 40.10582,-10.4715 55.92578,-9.35742 15.81997,1.11408 40.77698,2.45008 52.14062,6.68359 11.36364,4.23352 28.96603,16.0424 32.75391,22.28125 3.78788,6.23886 12.03093,20.27642 12.47656,24.28711 0.44563,4.0107 -7.1289,21.61328 -7.1289,21.61328 0,0 10.47224,14.70544 16.48828,16.9336 6.01604,2.22816 10.69455,1.78406 14.48242,6.01757 3.78788,4.23351 -0.22358,9.58107 3.78711,10.91797 4.0107,1.33689 13.81561,2.89671 14.92969,8.02149 1.11408,5.12477 -4.90136,12.92177 -3.56446,18.49218 1.3369,5.57041 5.12321,18.49485 4.90039,22.95118 -0.22282,4.45633 -7.57422,19.16211 -7.57421,19.16211 0,0 13.36911,2.45078 10.69531,11.14062 -2.6738,8.68984 -13.5918,14.03711 -13.5918,14.03711 0,0 -4.67898,18.49481 -0.22266,33.86914 4.45633,15.37434 14.92774,42.55645 14.92774,53.69727 0,11.14082 -3.78862,80.8829 -7.13086,102.49609 -3.34225,21.61319 -18.04684,78.43187 -28.96484,97.14844 -10.91801,18.71658 -16.71084,28.74305 -22.28125,32.97656 -5.57042,4.23351 -40.7754,20.27539 -40.7754,20.27539 l -47.68359,9.58203 c 0,0 12.47801,31.19362 10.47266,38.99219 -2.00534,7.79857 -1.33651,13.14732 -8.68946,14.70703 -7.35294,1.55972 -27.85242,-0.6688 -32.08593,-2.22851 -4.23351,-1.55972 -9.80428,-6.24008 -15.82032,-6.4629 -6.01604,-0.22281 -18.93855,-6.90721 -25.62304,-5.57031 -6.68449,1.3369 -11.81059,1.33748 -14.48438,7.35352 -2.6738,6.01605 -17.60156,36.0957 -17.60156,36.0957 0,0 -21.83648,-5.34644 -26.51562,1.11523 -4.67915,6.46169 2.005,16.04268 -2.22852,19.16211 -4.23351,3.11943 -18.04854,1.55934 -21.16797,-4.90234 -3.11943,-6.46167 0.44685,-13.36981 -7.12891,-15.15234 -7.57576,-1.78253 -26.29283,-2.89646 -29.18945,-0.22266 -2.89661,2.6738 -22.50364,12.47755 -28.29687,13.81445 -5.79323,1.3369 -18.93999,9.13589 -23.61914,7.57618 -4.67914,-1.55972 -7.57531,-16.93394 -16.9336,-19.16211 -9.35828,-2.22816 -13.59164,-7.79806 -13.81445,-11.58594 -0.22282,-3.78788 -5.57101,-21.38986 -10.02735,-25.17774 -4.45632,-3.78788 -6.461,-5.7947 -11.80859,-0.66992 -5.34759,5.12478 -13.59333,15.82127 -19.60937,14.48438 -6.01605,-1.3369 -16.48797,-1.33828 -16.9336,-6.24024 -0.44563,-4.90196 3.56468,-26.96074 8.68946,-38.10156 5.12478,-11.14082 10.47297,-26.06811 10.02734,-31.86133 -0.44563,-5.79323 0.66833,-19.16219 -10.69531,-27.18359 -11.36364,-8.02139 -19.83018,-16.48975 -27.85157,-17.1582 -8.02139,-0.66845 -23.39516,0.002 -22.5039,-7.79688 0.89127,-7.79858 20.05238,-44.56426 17.82422,-55.70508 -5.91385,-29.56912 -90.50493,-52.46721 -73.97461,-107.84375 8.91265,-29.85739 47.68222,-46.34576 41.44336,-78.87695 -3.29922,-17.20304 4.73668,-71.76212 12.0332,-74.86523 38.77005,-16.48841 50.35547,-40.55274 50.35547,-40.55274 0,0 -23.8402,-35.20598 -12.47656,-55.70508 11.36363,-20.49911 28.07393,-26.06896 31.19336,-36.0957 3.11943,-10.02674 13.36875,-39.66166 22.05859,-43.00391 8.68984,-3.34224 16.93468,-6.90625 26.29297,-6.90625 9.35829,0 25.8458,4.45668 33.86719,2.22852 7.52005,-2.08891 31.2958,-12.40447 43.23437,-12.76758 z"
sodipodi:nodetypes="ssssssssssssssssssssssssssscsssssssscsssssscscsssssccsssssscsssssssssssssssssssssscsssssss"><title
id="title36">outer_regions</title></path></svg>

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -93,10 +93,6 @@
<div class="scoreTeam scoreTeam--blue">
<span class="scoreTeamName">Résistance</span>
<div class="scoreStats">
<div class="scoreStat">
<span class="scoreStatVal scoreValue" id="scoreBlue">0</span>
<span class="scoreStatLabel">Tuiles</span>
</div>
<div class="scoreStat">
<span class="scoreStatVal scoreVP" id="vpBlue">0</span>
<span class="scoreStatLabel">Points</span>
@@ -111,10 +107,6 @@
<span class="scoreStatVal scoreVP" id="vpRed">0</span>
<span class="scoreStatLabel">Points</span>
</div>
<div class="scoreStat">
<span class="scoreStatVal scoreValue" id="scoreRed">0</span>
<span class="scoreStatLabel">Tuiles</span>
</div>
</div>
</div>
</div>
@@ -267,10 +259,10 @@
En explorant la galaxie, vous révélez des tuiles qui peuvent contenir des planètes. Chaque planète possède des ressources naturelles (minerais, bois, pétrole, etc.) dont la valeur contribue au revenu de votre équipe.</p>
<p><strong>⚡ Bonus d'exploration</strong><br/>
Les planètes produisent des éléments (matières premières, carburant, nourriture, science…) qui offrent un bonus cumulatif. Ce bonus réduit le temps de recharge entre deux clics, permettant d'explorer plus vite.</p>
Les planètes produisent des éléments (matières premières, carburant, nourriture, science…) qui offrent un bonus cumulatif. Ce bonus augmente le pourcentage d'actions disponibles pour vous.</p>
<p><strong>⚔️ Puissance militaire</strong><br/>
La population des planètes conquises fournit des unités militaires. Lorsque vous accumulez suffisamment de troupes, vous pouvez lancer une attaque sur une tuile ennemie pour la capturer. Chaque attaque consomme une partie de vos forces.</p>
La population des planètes conquises fournit de la puissance militaire. Cette puissance augmente le quota d'actions d'équipe pour capturer des planètes.</p>
</div>
</details>
@@ -313,6 +305,19 @@
</div>
</div>
</div>
<!-- Planet capture confirmation modal -->
<div class="attackOverlay hidden" id="captureOverlay">
<div class="attackModal">
<div class="attackModal__icon">🏴</div>
<div class="attackModal__title">Capturer la Planète</div>
<div class="attackModal__body" id="captureModalBody"></div>
<div class="attackModal__actions">
<button type="button" class="attackModal__btn attackModal__btn--cancel" id="captureModalNo">Annuler</button>
<button type="button" class="attackModal__btn attackModal__btn--confirm" id="captureModalYes">Capturer !</button>
</div>
</div>
</div>
</main>
</div>

View File

@@ -10,15 +10,11 @@ export async function apiFetchConfig(team) {
return res.json();
}
export async function apiFetchScores() {
const res = await fetch("/api/scores");
if (!res.ok) throw new Error("scores_fetch_failed");
return res.json();
}
/** Returns the raw Response so the caller can inspect status codes (410, etc.). */
export async function apiFetchGrid(seed) {
return fetch(`/api/grid/${encodeURIComponent(seed)}`);
const token = localStorage.getItem("authToken");
const headers = token ? { Authorization: `Bearer ${token}` } : {};
return fetch(`/api/grid/${encodeURIComponent(seed)}`, { headers });
}
/** Returns the raw Response so the caller can inspect status codes (409, 410, etc.). */
@@ -136,3 +132,16 @@ export async function apiFetchCellAttackCount(x, y) {
if (!res.ok) throw new Error("cell_attacks_fetch_failed");
return res.json();
}
/** Returns the raw Response so the caller can inspect status codes. */
export async function apiCaptureCell(seed, x, y) {
const token = localStorage.getItem("authToken");
return fetch("/api/cell/capture", {
method: "POST",
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify({ seed, x, y }),
});
}

View File

@@ -60,7 +60,7 @@ export function computeTeamIncome(team, cells, resourceWorth) {
let total = 0;
for (const [, meta] of cells) {
if (meta.discoveredBy !== team) continue;
if (meta.controlledBy !== team) continue;
if (!meta.hasPlanet || !meta.planet) continue;
const { naturalResources } = meta.planet;
if (!naturalResources) continue;
@@ -102,7 +102,7 @@ const ELEMENT_LABEL_TO_KEY = Object.fromEntries(
export function computeTeamElementBonus(team, cells, elementWorth) {
let bonus = 0;
for (const [, meta] of cells) {
if (meta.discoveredBy !== team) continue;
if (meta.controlledBy !== team) continue;
if (!meta.hasPlanet || !meta.planet) continue;
const { production } = meta.planet;
if (!production) continue;
@@ -130,7 +130,7 @@ export function computeTeamElementBonusDetailed(team, cells, elementWorth) {
const byElement = new Map();
let total = 0;
for (const [, meta] of cells) {
if (meta.discoveredBy !== team) continue;
if (meta.controlledBy !== team) continue;
if (!meta.hasPlanet || !meta.planet) continue;
const { production } = meta.planet;
if (!production) continue;
@@ -298,7 +298,7 @@ export function computeTeamMilitaryDetailed(team, cells, militaryPower) {
const byType = new Map();
let total = 0;
for (const [, meta] of cells) {
if (meta.discoveredBy !== team) continue;
if (meta.controlledBy !== team) continue;
if (!meta.hasPlanet || !meta.planet) continue;
const pop = meta.planet.population;
if (!pop) continue;

View File

@@ -1,6 +1,6 @@
import { fnv1a32, hash2u32, mulberry32 } from "./rng.js";
import { formatPlanet, generatePlanet } from "./planetGeneration.js";
import { apiFetchConfig, apiFetchScores, apiFetchGrid, apiRevealCell, apiFetchEconScores, apiTickEconScores, apiFetchElementBonus, apiTickElementBonus, apiFetchDbInfo, apiFetchVictoryPoints, apiFetchActivePlayers, apiFetchMilitaryDeductions, apiMilitaryAttack } from "./api.js";
import { apiFetchConfig, apiFetchGrid, apiRevealCell, apiFetchEconScores, apiTickEconScores, apiFetchElementBonus, apiTickElementBonus, apiFetchDbInfo, apiFetchVictoryPoints, apiFetchActivePlayers, apiFetchMilitaryDeductions, apiMilitaryAttack, apiCaptureCell } from "./api.js";
import { computeTeamIncome, computeTeamElementBonus, computeTeamElementBonusDetailed, renderResourceTable, renderElementBonusTable, setEconSort, getEconSort, setElemSort, getElemSort, computeTeamMilitaryDetailed, renderMilitaryTable, setMilSort, getMilSort } from "./economy.js";
// ── Constants ─────────────────────────────────────────────────────────────────
@@ -34,7 +34,7 @@ export const GAME_CONFIG = {
};
window.GAME_CONFIG = GAME_CONFIG;
/** @type {Map<string, { discoveredBy: string, hasPlanet: boolean, planet: object|null }>} */
/** @type {Map<string, { controlledBy: string|null, hasPlanet: boolean, planet: object|null }>} */
export const cells = new Map();
export let currentTeam = "blue";
@@ -206,8 +206,6 @@ const cooldownCfgEl = document.getElementById("cooldownConfig");
const seedDisplayEl = document.getElementById("worldSeedDisplay");
const nextPeriodEl = document.getElementById("nextPeriodUtc");
const resetCountEl = document.getElementById("refreshCountdown");
const scoreBlueEl = document.getElementById("scoreBlue");
const scoreRedEl = document.getElementById("scoreRed");
const vpBlueEl = document.getElementById("vpBlue");
const vpRedEl = document.getElementById("vpRed");
const dbCreatedAtEl = document.getElementById("dbCreatedAt");
@@ -231,6 +229,10 @@ const attackOverlayEl = document.getElementById("attackOverlay");
const attackModalBodyEl = document.getElementById("attackModalBody");
const attackModalYesEl = document.getElementById("attackModalYes");
const attackModalNoEl = document.getElementById("attackModalNo");
const captureOverlayEl = document.getElementById("captureOverlay");
const captureModalBodyEl = document.getElementById("captureModalBody");
const captureModalYesEl = document.getElementById("captureModalYes");
const captureModalNoEl = document.getElementById("captureModalNo");
const teamQuotaEl = document.getElementById("teamActionsRemaining");
// ── Cell helpers ──────────────────────────────────────────────────────────────
@@ -247,8 +249,8 @@ function hasPlanetAt(x, y) {
}
export function cellMeta(key) { return cells.get(key) ?? null; }
export function isOpponentTile(key) { const m = cellMeta(key); return m !== null && m.discoveredBy !== currentTeam; }
export function isOwnTile(key) { const m = cellMeta(key); return m !== null && m.discoveredBy === currentTeam; }
export function isOpponentTile(key) { const m = cellMeta(key); return m !== null && m.controlledBy !== null && m.controlledBy !== currentTeam; }
export function isOwnTile(key) { const m = cellMeta(key); return m !== null && m.controlledBy === currentTeam; }
// ── Config display ────────────────────────────────────────────────────────────
@@ -306,14 +308,6 @@ export function updateResetCountdown() {
// ── Scores ────────────────────────────────────────────────────────────────────
export async function fetchAndApplyScores() {
try {
const { blue, red } = await apiFetchScores();
if (scoreBlueEl) scoreBlueEl.textContent = String(blue ?? 0);
if (scoreRedEl) scoreRedEl.textContent = String(red ?? 0);
} catch { /* ignore */ }
}
export async function loadVictoryPoints() {
try {
const { blue, red } = await apiFetchVictoryPoints();
@@ -537,7 +531,7 @@ export async function fetchGridForSeed(seed, depth = 0) {
cells.clear();
for (const row of data.cells || []) {
cells.set(cellKey(row.x, row.y), {
discoveredBy: row.discovered_by ?? row.discoveredBy,
controlledBy: row.discovered_by ?? row.discoveredBy ?? null,
hasPlanet: Boolean(row.has_planet),
planet: row.planet_json ?? null,
});
@@ -622,20 +616,22 @@ export function draw() {
const meta = cellMeta(k);
const alpha = tileAlpha(k);
ctx.globalAlpha = alpha;
if (!meta) ctx.fillStyle = COLOR_RING_IDLE;
else if (meta.discoveredBy !== currentTeam) ctx.fillStyle = COLOR_OPPONENT_GREY;
else ctx.fillStyle = currentTeam === "blue" ? COLOR_BLUE_DISCOVERED : COLOR_RED_DISCOVERED;
if (!meta) ctx.fillStyle = COLOR_RING_IDLE;
else if (!meta.hasPlanet) ctx.fillStyle = COLOR_OPPONENT_GREY; // empty or unexploitable
else if (meta.controlledBy === null) ctx.fillStyle = COLOR_OPPONENT_GREY; // neutral planet
else if (meta.controlledBy === currentTeam) ctx.fillStyle = currentTeam === "blue" ? COLOR_BLUE_DISCOVERED : COLOR_RED_DISCOVERED;
else ctx.fillStyle = currentTeam === "blue" ? COLOR_RED_DISCOVERED : COLOR_BLUE_DISCOVERED; // opponent-controlled
ctx.fillRect(x * cw, y * ch, cw, ch);
ctx.globalAlpha = 1;
}
}
// 5. Draw planet dots on own discovered tiles
// 5. Draw planet dots on all revealed tiles with planets
for (const [key, meta] of cells) {
if (meta.discoveredBy !== currentTeam) continue;
if (!meta.hasPlanet) continue;
const [xs, ys] = key.split(",").map(Number);
if (xs < xMin || xs > xMax || ys < yMin || ys > yMax) continue;
if (!isExploitable(xs, ys) || !meta.hasPlanet) continue;
if (!isExploitable(xs, ys)) continue;
const h32 = hash2u32(xs, ys, seedU32 ^ 0x1337);
const rng = mulberry32(h32);
ctx.fillStyle = `hsl(${Math.floor(rng() * 360)} 85% 65%)`;
@@ -682,13 +678,15 @@ function refreshCursor(ev) {
return;
}
const key = cellKey(cell.x, cell.y);
if (isOpponentTile(key)) {
const milPower = GAME_CONFIG.militaryPower;
const myMilRaw = computeTeamMilitaryDetailed(currentTeam, cells, milPower).total;
const myDeduct = currentTeam === "blue" ? milDeductBlue : milDeductRed;
canvas.style.cursor = (myMilRaw - myDeduct >= 1.0) ? "crosshair" : "default";
} else {
const meta = cells.get(key);
if (!meta) {
// Not yet revealed — can reveal if quota available
canvas.style.cursor = cooldownActive() ? "default" : "pointer";
} else if (meta.hasPlanet && meta.controlledBy !== currentTeam) {
// Capturable (neutral or opponent-controlled) planet
canvas.style.cursor = "pointer";
} else {
canvas.style.cursor = "default";
}
}
@@ -702,7 +700,7 @@ function applyRevealPayload(cell) {
const _revealKey = cellKey(cell.x, cell.y);
markTileReveal(_revealKey);
cells.set(_revealKey, {
discoveredBy: cell.discoveredBy ?? currentTeam,
controlledBy: cell.discoveredBy ?? null,
hasPlanet: Boolean(cell.hasPlanet),
planet: cell.planet ?? null,
});
@@ -717,21 +715,26 @@ function applyRevealPayload(cell) {
details.textContent = `Tuile (${cell.x},${cell.y})\n\nStatus : Vide`;
return;
}
const controlStatus = cell.discoveredBy === null
? "Neutre (non capturée)"
: cell.discoveredBy === currentTeam
? `Contrôlée (${currentTeam === "blue" ? "Résistance" : "Premier Ordre"})`
: `Contrôlée par l'adversaire`;
hint.textContent = `(${cell.x},${cell.y}) Planète présente`;
details.textContent = `Tuile (${cell.x},${cell.y})\n\nStatus : Planète\n\n${formatPlanet(cell.planet)}`;
details.textContent = `Tuile (${cell.x},${cell.y})\n\nStatus : ${controlStatus}\n\n${formatPlanet(cell.planet)}`;
}
function showLocalSelection(x, y) {
const k = cellKey(x, y);
const meta = cellMeta(k);
details.classList.remove("details--hidden");
if (!isOwnTile(k)) return;
if (!meta) return;
if (!isExploitable(x, y)) {
hint.textContent = `(${x},${y}) Inexploitable`;
details.textContent = `Tuile (${x},${y})\n\nStatus : Inexploitable`;
return;
}
const meta = cellMeta(k);
if (!meta?.hasPlanet) {
if (!meta.hasPlanet) {
hint.textContent = `(${x},${y}) Vide`;
details.textContent = `Tuile (${x},${y})\n\nStatus : Vide`;
return;
@@ -741,8 +744,13 @@ function showLocalSelection(x, y) {
const h = hash2u32(x, y, seedU32 ^ 0xa5a5a5a5);
planet = generatePlanet(mulberry32(h));
}
const controlStatus = meta.controlledBy === null
? "Neutre (non capturée)"
: meta.controlledBy === currentTeam
? `Contrôlée (${currentTeam === "blue" ? "Résistance" : "Premier Ordre"})`
: `Contrôlée par l'adversaire`;
hint.textContent = `(${x},${y}) Planète présente`;
details.textContent = `Tuile (${x},${y})\n\nStatus : Planète\n\n${formatPlanet(planet)}`;
details.textContent = `Tuile (${x},${y})\n\nStatus : ${controlStatus}\n\n${formatPlanet(planet)}`;
}
// ── Military attack modal ────────────────────────────────────────────────────
@@ -772,89 +780,133 @@ function showAttackModal(milNetM, x, y) {
});
}
// ── Planet capture modal ─────────────────────────────────────────────────────
/**
* Shows the capture confirmation modal.
* Returns a Promise<boolean> — true if confirmed, false if cancelled.
*/
function showCaptureModal(planet, controlledBy, cost, x, y) {
const opponentName = currentTeam === "blue" ? "Premier Ordre" : "Résistance";
const controlLabel = controlledBy === null
? "Planète neutre"
: `Planète contrôlée par ${opponentName} (coût doublé)`;
captureModalBodyEl.textContent =
`Planète (${x}, ${y}) — ${planet?.name ?? "Inconnue"}\n\n` +
`${controlLabel}\n\n` +
`Coût : ${cost} action${cost > 1 ? "s" : ""} d'équipe\n` +
`Actions d'équipe disponibles : ${teamActionsRemaining ?? "?"}\n\n` +
`La planète passera sous votre contrôle. Ses ressources, bonus et puissance militaire\n` +
`seront calculés pour votre équipe.`;
captureOverlayEl.classList.remove("hidden");
return new Promise((resolve) => {
function cleanup() {
captureOverlayEl.classList.add("hidden");
captureModalYesEl.removeEventListener("click", onYes);
captureModalNoEl.removeEventListener("click", onNo);
}
function onYes() { cleanup(); resolve(true); }
function onNo() { cleanup(); resolve(false); }
captureModalYesEl.addEventListener("click", onYes);
captureModalNoEl.addEventListener("click", onNo);
});
}
// ── Canvas click handler ──────────────────────────────────────────────────────
async function onCanvasClick(ev) {
const cell = pickCell(ev);
if (!cell || !isExploitable(cell.x, cell.y)) return;
const key = cellKey(cell.x, cell.y);
const meta = cells.get(key);
// ── Opponent tile: offer military attack if team has ≥ 1 billion soldiers ──
if (isOpponentTile(key)) {
const milPower = GAME_CONFIG.militaryPower;
const myMilRaw = computeTeamMilitaryDetailed(currentTeam, cells, milPower).total;
const myDeduct = currentTeam === "blue" ? milDeductBlue : milDeductRed;
const myMilNet = myMilRaw - myDeduct; // in billions
if (myMilNet >= 1.0) {
const confirmed = await showAttackModal(myMilNet * 1000, cell.x, cell.y);
if (!confirmed) return;
try {
const res = await apiMilitaryAttack(seedStr, cell.x, cell.y);
if (res.status === 410) {
hint.textContent = "Le serveur change sa base de planètes — synchronisation...";
await refreshFromServer(); return;
}
if (res.status === 409) {
hint.textContent = "Impossible d'attaquer votre propre tuile."; return;
}
if (!res.ok) { hint.textContent = "Erreur lors de l'attaque militaire."; return; }
const data = await res.json();
if (currentTeam === "blue") milDeductBlue = data.deductions.blue ?? milDeductBlue;
else milDeductRed = data.deductions.red ?? milDeductRed;
// Transfer tile in local cells Map
const existing = cells.get(key);
markTileReveal(key);
cells.set(key, { ...existing, discoveredBy: currentTeam });
hint.textContent = `⚔️ Tuile (${cell.x},${cell.y}) conquise !`;
updateEconomyDisplay();
fetchAndApplyScores();
draw();
} catch {
hint.textContent = "Erreur réseau lors de l'attaque militaire.";
}
} else {
hint.textContent = `Puissance militaire insuffisante — il faut ≥ 1 000 (actuellement ${(myMilNet * 1000).toFixed(1)}).`;
}
return;
}
if (isOwnTile(key)) { showLocalSelection(cell.x, cell.y); return; }
if (cooldownActive()) {
hint.textContent = "Quota d'actions épuisé pour aujourd'hui — revenez après 12h00 UTC.";
return;
}
try {
const res = await apiRevealCell(seedStr, cell.x, cell.y, currentTeam);
if (res.status === 409) {
hint.textContent = "Cette tuile a déjà été découverte par votre adversaire.";
await fetchGridForSeed(seedStr);
draw();
return;
}
if (res.status === 429) {
actionsRemaining = 0;
updateActionsDisplay();
// ── Not yet revealed: first click = spend user action to reveal ──
if (!meta) {
if (cooldownActive()) {
hint.textContent = "Quota d'actions épuisé pour aujourd'hui — revenez après 12h00 UTC.";
return;
}
try {
const res = await apiRevealCell(seedStr, cell.x, cell.y, currentTeam);
if (res.status === 429) {
actionsRemaining = 0;
updateActionsDisplay();
hint.textContent = "Quota d'actions épuisé pour aujourd'hui — revenez après 12h00 UTC.";
return;
}
if (res.status === 410) {
hint.textContent = "Le serveur change sa base de planètes — synchronisation...";
await refreshFromServer(); return;
}
if (!res.ok) throw new Error("reveal");
applyRevealPayload(await res.json());
startCooldown();
updateEconomyDisplay();
draw();
} catch (e) {
if (e?.code === "SEED_EXPIRED") { await refreshFromServer(); return; }
hint.textContent = "Could not save reveal — check server / database.";
}
return;
}
// ── Already revealed: empty cell or own planet — show info only ──
if (!meta.hasPlanet || meta.controlledBy === currentTeam) {
showLocalSelection(cell.x, cell.y);
return;
}
// ── Revealed planet, neutral or opponent-controlled: offer capture ──
showLocalSelection(cell.x, cell.y);
const popBillions = meta.planet?.population?.billions ?? 0;
const baseCost = Math.max(1, Math.ceil(popBillions / 10));
const cost = (meta.controlledBy !== null) ? baseCost * 2 : baseCost;
if (teamActionsRemaining !== null && teamActionsRemaining < cost) {
hint.textContent = `Planète repérée. Actions d'équipe insuffisantes pour capturer (coût : ${cost}, disponibles : ${teamActionsRemaining}).`;
return;
}
const confirmed = await showCaptureModal(meta.planet, meta.controlledBy, cost, cell.x, cell.y);
if (!confirmed) return;
try {
const res = await apiCaptureCell(seedStr, cell.x, cell.y);
if (res.status === 410) {
hint.textContent = "Le serveur change sa base de planètes — synchronisation...";
await refreshFromServer();
await refreshFromServer(); return;
}
if (res.status === 429) {
const data = await res.json();
teamActionsRemaining = data.teamActionsRemaining ?? teamActionsRemaining;
updateTeamQuotaDisplay();
hint.textContent = `Actions d'équipe insuffisantes (coût : ${data.cost ?? cost}, disponibles : ${data.teamActionsRemaining ?? "?"}).`;
return;
}
if (!res.ok) throw new Error("reveal");
applyRevealPayload(await res.json());
startCooldown();
if (res.status === 409) {
const data = await res.json();
if (data.error === "already_owned") {
hint.textContent = "Cette planète vous appartient déjà.";
} else {
hint.textContent = "Impossible de capturer cette tuile.";
}
return;
}
if (!res.ok) { hint.textContent = "Erreur lors de la capture."; return; }
const data = await res.json();
markTileReveal(key);
cells.set(key, { ...meta, controlledBy: currentTeam });
if (data.teamActionsRemaining !== undefined && data.teamActionsRemaining !== null) {
teamActionsRemaining = data.teamActionsRemaining;
updateTeamQuotaDisplay();
}
hint.textContent = `🏴 Planète (${cell.x},${cell.y}) capturée !`;
showLocalSelection(cell.x, cell.y);
updateEconomyDisplay();
draw();
fetchAndApplyScores();
} catch (e) {
if (e?.code === "SEED_EXPIRED") { await refreshFromServer(); return; }
hint.textContent = "Could not save reveal — check server / database.";
} catch {
hint.textContent = "Erreur réseau lors de la capture.";
}
}
@@ -887,7 +939,6 @@ export async function refreshFromServer() {
hint.textContent = "Le monde a été réinitilisaté. Vous pouvez cliquer sur une tuile pour recommencer le jeu.";
}
await fetchGridForSeed(seedStr);
await fetchAndApplyScores();
await fetchAndApplyActivePlayers();
updateEconomyDisplay();
draw();

View File

@@ -4,7 +4,6 @@ import {
updateResetCountdown,
fetchConfig,
fetchGridForSeed,
fetchAndApplyScores,
fetchAndApplyActivePlayers,
updateEconomyDisplay,
loadEconScores,
@@ -55,7 +54,6 @@ const ECON_TICK_SECONDS = 5;
function scheduleScorePoll() {
clearTimeout(scorePollTimer);
scorePollTimer = window.setTimeout(async () => {
await fetchAndApplyScores();
await fetchAndApplyActivePlayers();
await loadEconScores();
await loadElementBonus();
@@ -104,7 +102,6 @@ async function boot() {
try {
await fetchConfig();
await fetchGridForSeed(seedStr);
await fetchAndApplyScores();
await fetchAndApplyActivePlayers();
await loadEconScores();
await loadVictoryPoints();

View File

@@ -352,8 +352,8 @@ body {
}
.scoreTeam--blue .scoreVP {
color: rgba(130, 230, 130, 1);
text-shadow: 0 0 16px rgba(90, 200, 130, 0.35);
color: rgba(90, 200, 255, 1);
text-shadow: 0 0 16px rgba(90, 200, 255, 0.4);
}
.scoreTeam--red .scoreValue {
@@ -362,8 +362,8 @@ body {
}
.scoreTeam--red .scoreVP {
color: rgba(230, 150, 80, 1);
text-shadow: 0 0 16px rgba(220, 130, 60, 0.35);
color: rgba(220, 75, 85, 1);
text-shadow: 0 0 16px rgba(220, 75, 85, 0.4);
}
.scoreSep {