Add weather rules and streamline setup
This commit is contained in:
138
src/main.ts
138
src/main.ts
@@ -1,7 +1,5 @@
|
||||
import "./styles.css";
|
||||
|
||||
console.log("[DEBUG] Starting main.ts import...");
|
||||
|
||||
import {
|
||||
ROOT_SHIFT_COST,
|
||||
ROUND_ANIMATION_BONUS_MS,
|
||||
@@ -28,6 +26,7 @@ import {
|
||||
createInitiativeDraft,
|
||||
} from "./rules-initiative";
|
||||
import {
|
||||
WEATHER_CARDS,
|
||||
createWeatherDraft,
|
||||
getCurrentWeatherPlayerId,
|
||||
getWeatherCard,
|
||||
@@ -54,22 +53,17 @@ import type {
|
||||
import { keyFor, parseKey, tint, wait } from "./utils";
|
||||
|
||||
const app = document.querySelector("#app");
|
||||
console.log("[DEBUG] App element found:", app);
|
||||
|
||||
if (!(app instanceof HTMLElement)) {
|
||||
console.error("[DEBUG] #app container not found or not HTMLElement");
|
||||
throw new Error("#app container not found");
|
||||
}
|
||||
|
||||
console.log("[DEBUG] Initializing state...");
|
||||
let roundAnimationToken = 0;
|
||||
let setup: SetupState = createSetupState();
|
||||
console.log("[DEBUG] Setup created:", setup);
|
||||
let state: GameState = createInitialState(setup);
|
||||
console.log("[DEBUG] Initial state created:", state);
|
||||
let isNewGameModalOpen = false;
|
||||
let previousScoreSnapshot: ScoreSnapshot[] | null = null;
|
||||
console.log("[DEBUG] State initialized, proceeding to render...");
|
||||
let setupTab: "board" | "rules" | "events" | "players" = "board";
|
||||
|
||||
function rebuildSetup(overrides: Partial<SetupState> = {}) {
|
||||
setup = createSetupState(
|
||||
@@ -84,6 +78,7 @@ function rebuildSetup(overrides: Partial<SetupState> = {}) {
|
||||
overrides.initiativeMode ?? setup.initiativeMode,
|
||||
overrides.biddingOrderRule ?? setup.biddingOrderRule,
|
||||
overrides.weatherDraftEnabled ?? setup.weatherDraftEnabled,
|
||||
overrides.weatherDraftCount ?? setup.weatherDraftCount,
|
||||
overrides.winCondition ?? setup.winCondition,
|
||||
overrides.maxRounds ?? setup.maxRounds,
|
||||
overrides.topLeafTarget ?? setup.topLeafTarget,
|
||||
@@ -154,6 +149,10 @@ function getTurnLabel() {
|
||||
return state.gameOver ? "Game Over" : `${getCurrentPlayer().name}'s turn`;
|
||||
}
|
||||
|
||||
function isBankingEnabled() {
|
||||
return state.activeRoundEffects.includes("storehouse");
|
||||
}
|
||||
|
||||
function awardGrowth(player: Player, amount: number) {
|
||||
if (amount <= 0) {
|
||||
return;
|
||||
@@ -207,7 +206,15 @@ function getSelectedRootShiftMoves() {
|
||||
}
|
||||
|
||||
function getLegalMovesForSource(sourceKey: NodeKey, player: Player) {
|
||||
return getLegalMovesForSourceForState(state, sourceKey, player);
|
||||
const moves = getLegalMovesForSourceForState(state, sourceKey, player);
|
||||
|
||||
if (!state.activeRoundEffects.includes("sun_ladder")) {
|
||||
return moves;
|
||||
}
|
||||
|
||||
return moves.map((move) => move.direction === "vertical"
|
||||
? { ...move, cost: Math.max(0, move.cost - 1) }
|
||||
: move);
|
||||
}
|
||||
|
||||
function playerHasLegalMove(player: Player) {
|
||||
@@ -621,6 +628,10 @@ function endTurn() {
|
||||
}
|
||||
|
||||
function bankGrowthAndEndTurn() {
|
||||
if (!isBankingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const player = getCurrentPlayer();
|
||||
if (player.growthPoints <= 0) {
|
||||
endTurn();
|
||||
@@ -787,6 +798,11 @@ function randomizeStartingLocations() {
|
||||
render();
|
||||
}
|
||||
|
||||
function setSetupTab(tab: typeof setupTab) {
|
||||
setupTab = tab;
|
||||
render();
|
||||
}
|
||||
|
||||
function renderNewGameModal() {
|
||||
if (!isNewGameModalOpen) {
|
||||
return "";
|
||||
@@ -794,6 +810,7 @@ function renderNewGameModal() {
|
||||
|
||||
const maxSeeds = getMaxStartingNodesPerPlayer(setup.playerCount, setup.columns);
|
||||
const previewPlayers = createPlayers(setup.playerCount, setup.paletteOrder);
|
||||
const draftCountMax = WEATHER_CARDS.length;
|
||||
|
||||
return `
|
||||
<div class="modal-backdrop" id="new-game-modal-backdrop">
|
||||
@@ -805,9 +822,15 @@ function renderNewGameModal() {
|
||||
</div>
|
||||
<button class="ghost-button" id="close-new-game">Close</button>
|
||||
</header>
|
||||
<nav class="setup-tabs" aria-label="Setup categories">
|
||||
<button class="setup-tab${setupTab === "board" ? " setup-tab--active" : ""}" data-setup-tab="board"><span aria-hidden="true">▦</span><span>Board</span></button>
|
||||
<button class="setup-tab${setupTab === "rules" ? " setup-tab--active" : ""}" data-setup-tab="rules"><span aria-hidden="true">⚖</span><span>Rules</span></button>
|
||||
<button class="setup-tab${setupTab === "events" ? " setup-tab--active" : ""}" data-setup-tab="events"><span aria-hidden="true">☀</span><span>Events</span></button>
|
||||
<button class="setup-tab${setupTab === "players" ? " setup-tab--active" : ""}" data-setup-tab="players"><span aria-hidden="true">◉</span><span>Players</span></button>
|
||||
</nav>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Board Settings Section -->
|
||||
${setupTab === "board" ? `
|
||||
<section class="setup-section">
|
||||
<h2 class="setup-section__title">Board Settings</h2>
|
||||
<div class="setup-grid">
|
||||
@@ -832,8 +855,9 @@ function renderNewGameModal() {
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
` : ""}
|
||||
|
||||
<!-- Game Rules Section -->
|
||||
${setupTab === "rules" ? `
|
||||
<section class="setup-section">
|
||||
<h2 class="setup-section__title">Game Rules</h2>
|
||||
<div class="setup-grid setup-grid--2col">
|
||||
@@ -871,10 +895,21 @@ function renderNewGameModal() {
|
||||
</select>
|
||||
</label>
|
||||
` : ""}
|
||||
<label class="setup-field setup-field--checkbox">
|
||||
<span class="setup-field__label">Weather Draft</span>
|
||||
<input id="weather-draft-toggle" type="checkbox" ${setup.weatherDraftEnabled ? "checked" : ""} />
|
||||
</label>
|
||||
${setup.weatherDraftEnabled ? `
|
||||
<label class="setup-field">
|
||||
<span class="setup-field__label">Weather cards in draft</span>
|
||||
<input id="weather-draft-count" type="number" min="1" max="${draftCountMax}" step="1" value="${setup.weatherDraftCount}" />
|
||||
</label>
|
||||
` : ""}
|
||||
</div>
|
||||
</section>
|
||||
` : ""}
|
||||
|
||||
<!-- Random Events Section -->
|
||||
${setupTab === "events" ? `
|
||||
<section class="setup-section">
|
||||
<h2 class="setup-section__title">Random Events</h2>
|
||||
<div class="setup-grid setup-grid--3col">
|
||||
@@ -886,14 +921,11 @@ function renderNewGameModal() {
|
||||
<span class="setup-field__label">Disease %</span>
|
||||
<input id="disease-chance" type="number" min="0" max="100" step="5" value="${setup.diseaseChance}" />
|
||||
</label>
|
||||
<label class="setup-field setup-field--checkbox">
|
||||
<span class="setup-field__label">Weather Draft</span>
|
||||
<input id="weather-draft-toggle" type="checkbox" ${setup.weatherDraftEnabled ? "checked" : ""} />
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
` : ""}
|
||||
|
||||
<!-- Players Section -->
|
||||
${setupTab === "players" ? `
|
||||
<section class="setup-section">
|
||||
<h2 class="setup-section__title">Players & Colors</h2>
|
||||
<p class="setup-section__help">Reorder players to set turn order. Starting positions are auto-assigned.</p>
|
||||
@@ -912,6 +944,20 @@ function renderNewGameModal() {
|
||||
`).join("")}
|
||||
</div>
|
||||
</section>
|
||||
<section class="setup-section">
|
||||
<h2 class="setup-section__title">Starting columns</h2>
|
||||
<p class="setup-section__help">Use 1-based column numbers. Duplicate or invalid picks are auto-corrected.</p>
|
||||
<button class="ghost-button randomize-button" id="randomize-starting-locations">Randomize Starting Locations</button>
|
||||
<div class="player-list">
|
||||
${previewPlayers.map((currentPlayer, index) => `
|
||||
<label class="setup-field">
|
||||
<span class="setup-field__label" style="color: ${currentPlayer.color};">${currentPlayer.name}</span>
|
||||
<input class="seed-input" data-player-id="${index}" type="text" value="${setup.seedInputs[index] ?? ""}" placeholder="e.g. 2, 5" />
|
||||
</label>
|
||||
`).join("")}
|
||||
</div>
|
||||
</section>
|
||||
` : ""}
|
||||
</div>
|
||||
|
||||
<footer class="modal-footer">
|
||||
@@ -1259,7 +1305,7 @@ function renderSidebar() {
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button id="end-turn" ${boardLocked ? "disabled" : ""}>End Turn</button>
|
||||
<button id="bank-turn" class="ghost-button" ${boardLocked ? "disabled" : ""}>Bank Remaining</button>
|
||||
<button id="bank-turn" class="ghost-button" ${boardLocked || !isBankingEnabled() ? "disabled" : ""}>Bank Remaining</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1386,6 +1432,11 @@ function attachEvents() {
|
||||
});
|
||||
document.querySelector<HTMLInputElement>("#weather-draft-toggle")?.addEventListener("change", (event) => {
|
||||
setup.weatherDraftEnabled = (event.currentTarget as HTMLInputElement).checked;
|
||||
render();
|
||||
});
|
||||
document.querySelector<HTMLInputElement>("#weather-draft-count")?.addEventListener("change", (event) => {
|
||||
rebuildSetup({ weatherDraftCount: Math.max(1, Number((event.currentTarget as HTMLInputElement).value) || 1) });
|
||||
render();
|
||||
});
|
||||
document.querySelector<HTMLSelectElement>("#win-condition")?.addEventListener("change", (event) => {
|
||||
setup.winCondition = (event.currentTarget as HTMLSelectElement).value as SetupState["winCondition"];
|
||||
@@ -1427,38 +1478,31 @@ function attachEvents() {
|
||||
chooseWeatherAction(button.dataset.weatherCard as WeatherCardId, button.dataset.weatherAction as "draft" | "ban");
|
||||
});
|
||||
});
|
||||
document.querySelectorAll<HTMLElement>("[data-setup-tab]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
setSetupTab(button.dataset.setupTab as typeof setupTab);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function render() {
|
||||
console.log("[DEBUG] render() called, playerCount:", state?.players?.length);
|
||||
try {
|
||||
const playerCount = state.players.length;
|
||||
console.log("[DEBUG] Building HTML...");
|
||||
app.innerHTML = `
|
||||
<main class="layout" style="--player-count: ${playerCount}">
|
||||
<section class="game-area">
|
||||
${renderBoard()}
|
||||
</section>
|
||||
${renderSidebar()}
|
||||
<footer class="scoreboard">
|
||||
${renderScoreboard()}
|
||||
</footer>
|
||||
</main>
|
||||
${renderNewGameModal()}
|
||||
${renderInitiativeModal()}
|
||||
${renderWeatherDraftModal()}
|
||||
`;
|
||||
console.log("[DEBUG] HTML rendered, attaching events...");
|
||||
attachEvents();
|
||||
console.log("[DEBUG] Events attached, getting score snapshot...");
|
||||
previousScoreSnapshot = getScoreSnapshot();
|
||||
console.log("[DEBUG] Render complete!");
|
||||
} catch (error) {
|
||||
console.error("[DEBUG] Error in render():", error);
|
||||
throw error;
|
||||
}
|
||||
const playerCount = state.players.length;
|
||||
app.innerHTML = `
|
||||
<main class="layout" style="--player-count: ${playerCount}">
|
||||
<section class="game-area">
|
||||
${renderBoard()}
|
||||
</section>
|
||||
${renderSidebar()}
|
||||
<footer class="scoreboard">
|
||||
${renderScoreboard()}
|
||||
</footer>
|
||||
</main>
|
||||
${renderNewGameModal()}
|
||||
${renderInitiativeModal()}
|
||||
${renderWeatherDraftModal()}
|
||||
`;
|
||||
attachEvents();
|
||||
previousScoreSnapshot = getScoreSnapshot();
|
||||
}
|
||||
|
||||
console.log("[DEBUG] About to call render()...");
|
||||
render();
|
||||
console.log("[DEBUG] render() executed");
|
||||
|
||||
Reference in New Issue
Block a user