Compare commits
2 Commits
8b50482621
...
e11264168c
| Author | SHA1 | Date | |
|---|---|---|---|
| e11264168c | |||
| 1cc85397bd |
104
src/main.ts
104
src/main.ts
@@ -1,7 +1,5 @@
|
|||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
console.log("[DEBUG] Starting main.ts import...");
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ROOT_SHIFT_COST,
|
ROOT_SHIFT_COST,
|
||||||
ROUND_ANIMATION_BONUS_MS,
|
ROUND_ANIMATION_BONUS_MS,
|
||||||
@@ -28,6 +26,7 @@ import {
|
|||||||
createInitiativeDraft,
|
createInitiativeDraft,
|
||||||
} from "./rules-initiative";
|
} from "./rules-initiative";
|
||||||
import {
|
import {
|
||||||
|
WEATHER_CARDS,
|
||||||
createWeatherDraft,
|
createWeatherDraft,
|
||||||
getCurrentWeatherPlayerId,
|
getCurrentWeatherPlayerId,
|
||||||
getWeatherCard,
|
getWeatherCard,
|
||||||
@@ -54,22 +53,17 @@ import type {
|
|||||||
import { keyFor, parseKey, tint, wait } from "./utils";
|
import { keyFor, parseKey, tint, wait } from "./utils";
|
||||||
|
|
||||||
const app = document.querySelector("#app");
|
const app = document.querySelector("#app");
|
||||||
console.log("[DEBUG] App element found:", app);
|
|
||||||
|
|
||||||
if (!(app instanceof HTMLElement)) {
|
if (!(app instanceof HTMLElement)) {
|
||||||
console.error("[DEBUG] #app container not found or not HTMLElement");
|
|
||||||
throw new Error("#app container not found");
|
throw new Error("#app container not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[DEBUG] Initializing state...");
|
|
||||||
let roundAnimationToken = 0;
|
let roundAnimationToken = 0;
|
||||||
let setup: SetupState = createSetupState();
|
let setup: SetupState = createSetupState();
|
||||||
console.log("[DEBUG] Setup created:", setup);
|
|
||||||
let state: GameState = createInitialState(setup);
|
let state: GameState = createInitialState(setup);
|
||||||
console.log("[DEBUG] Initial state created:", state);
|
|
||||||
let isNewGameModalOpen = false;
|
let isNewGameModalOpen = false;
|
||||||
let previousScoreSnapshot: ScoreSnapshot[] | null = null;
|
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> = {}) {
|
function rebuildSetup(overrides: Partial<SetupState> = {}) {
|
||||||
setup = createSetupState(
|
setup = createSetupState(
|
||||||
@@ -84,6 +78,7 @@ function rebuildSetup(overrides: Partial<SetupState> = {}) {
|
|||||||
overrides.initiativeMode ?? setup.initiativeMode,
|
overrides.initiativeMode ?? setup.initiativeMode,
|
||||||
overrides.biddingOrderRule ?? setup.biddingOrderRule,
|
overrides.biddingOrderRule ?? setup.biddingOrderRule,
|
||||||
overrides.weatherDraftEnabled ?? setup.weatherDraftEnabled,
|
overrides.weatherDraftEnabled ?? setup.weatherDraftEnabled,
|
||||||
|
overrides.weatherDraftCount ?? setup.weatherDraftCount,
|
||||||
overrides.winCondition ?? setup.winCondition,
|
overrides.winCondition ?? setup.winCondition,
|
||||||
overrides.maxRounds ?? setup.maxRounds,
|
overrides.maxRounds ?? setup.maxRounds,
|
||||||
overrides.topLeafTarget ?? setup.topLeafTarget,
|
overrides.topLeafTarget ?? setup.topLeafTarget,
|
||||||
@@ -154,6 +149,10 @@ function getTurnLabel() {
|
|||||||
return state.gameOver ? "Game Over" : `${getCurrentPlayer().name}'s turn`;
|
return state.gameOver ? "Game Over" : `${getCurrentPlayer().name}'s turn`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBankingEnabled() {
|
||||||
|
return state.activeRoundEffects.includes("storehouse");
|
||||||
|
}
|
||||||
|
|
||||||
function awardGrowth(player: Player, amount: number) {
|
function awardGrowth(player: Player, amount: number) {
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
return;
|
return;
|
||||||
@@ -207,7 +206,15 @@ function getSelectedRootShiftMoves() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getLegalMovesForSource(sourceKey: NodeKey, player: Player) {
|
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) {
|
function playerHasLegalMove(player: Player) {
|
||||||
@@ -621,6 +628,10 @@ function endTurn() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bankGrowthAndEndTurn() {
|
function bankGrowthAndEndTurn() {
|
||||||
|
if (!isBankingEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const player = getCurrentPlayer();
|
const player = getCurrentPlayer();
|
||||||
if (player.growthPoints <= 0) {
|
if (player.growthPoints <= 0) {
|
||||||
endTurn();
|
endTurn();
|
||||||
@@ -787,6 +798,11 @@ function randomizeStartingLocations() {
|
|||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSetupTab(tab: typeof setupTab) {
|
||||||
|
setupTab = tab;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
function renderNewGameModal() {
|
function renderNewGameModal() {
|
||||||
if (!isNewGameModalOpen) {
|
if (!isNewGameModalOpen) {
|
||||||
return "";
|
return "";
|
||||||
@@ -794,6 +810,7 @@ function renderNewGameModal() {
|
|||||||
|
|
||||||
const maxSeeds = getMaxStartingNodesPerPlayer(setup.playerCount, setup.columns);
|
const maxSeeds = getMaxStartingNodesPerPlayer(setup.playerCount, setup.columns);
|
||||||
const previewPlayers = createPlayers(setup.playerCount, setup.paletteOrder);
|
const previewPlayers = createPlayers(setup.playerCount, setup.paletteOrder);
|
||||||
|
const draftCountMax = WEATHER_CARDS.length;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="modal-backdrop" id="new-game-modal-backdrop">
|
<div class="modal-backdrop" id="new-game-modal-backdrop">
|
||||||
@@ -805,9 +822,15 @@ function renderNewGameModal() {
|
|||||||
</div>
|
</div>
|
||||||
<button class="ghost-button" id="close-new-game">Close</button>
|
<button class="ghost-button" id="close-new-game">Close</button>
|
||||||
</header>
|
</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">
|
<div class="modal-body">
|
||||||
<!-- Board Settings Section -->
|
${setupTab === "board" ? `
|
||||||
<section class="setup-section">
|
<section class="setup-section">
|
||||||
<h2 class="setup-section__title">Board Settings</h2>
|
<h2 class="setup-section__title">Board Settings</h2>
|
||||||
<div class="setup-grid">
|
<div class="setup-grid">
|
||||||
@@ -832,8 +855,9 @@ function renderNewGameModal() {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
` : ""}
|
||||||
|
|
||||||
<!-- Game Rules Section -->
|
${setupTab === "rules" ? `
|
||||||
<section class="setup-section">
|
<section class="setup-section">
|
||||||
<h2 class="setup-section__title">Game Rules</h2>
|
<h2 class="setup-section__title">Game Rules</h2>
|
||||||
<div class="setup-grid setup-grid--2col">
|
<div class="setup-grid setup-grid--2col">
|
||||||
@@ -871,10 +895,21 @@ function renderNewGameModal() {
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
` : ""}
|
||||||
|
|
||||||
<!-- Random Events Section -->
|
${setupTab === "events" ? `
|
||||||
<section class="setup-section">
|
<section class="setup-section">
|
||||||
<h2 class="setup-section__title">Random Events</h2>
|
<h2 class="setup-section__title">Random Events</h2>
|
||||||
<div class="setup-grid setup-grid--3col">
|
<div class="setup-grid setup-grid--3col">
|
||||||
@@ -886,14 +921,11 @@ function renderNewGameModal() {
|
|||||||
<span class="setup-field__label">Disease %</span>
|
<span class="setup-field__label">Disease %</span>
|
||||||
<input id="disease-chance" type="number" min="0" max="100" step="5" value="${setup.diseaseChance}" />
|
<input id="disease-chance" type="number" min="0" max="100" step="5" value="${setup.diseaseChance}" />
|
||||||
</label>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
` : ""}
|
||||||
|
|
||||||
<!-- Players Section -->
|
${setupTab === "players" ? `
|
||||||
<section class="setup-section">
|
<section class="setup-section">
|
||||||
<h2 class="setup-section__title">Players & Colors</h2>
|
<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>
|
<p class="setup-section__help">Reorder players to set turn order. Starting positions are auto-assigned.</p>
|
||||||
@@ -912,6 +944,20 @@ function renderNewGameModal() {
|
|||||||
`).join("")}
|
`).join("")}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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>
|
</div>
|
||||||
|
|
||||||
<footer class="modal-footer">
|
<footer class="modal-footer">
|
||||||
@@ -1259,7 +1305,7 @@ function renderSidebar() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
<button id="end-turn" ${boardLocked ? "disabled" : ""}>End Turn</button>
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -1386,6 +1432,11 @@ function attachEvents() {
|
|||||||
});
|
});
|
||||||
document.querySelector<HTMLInputElement>("#weather-draft-toggle")?.addEventListener("change", (event) => {
|
document.querySelector<HTMLInputElement>("#weather-draft-toggle")?.addEventListener("change", (event) => {
|
||||||
setup.weatherDraftEnabled = (event.currentTarget as HTMLInputElement).checked;
|
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) => {
|
document.querySelector<HTMLSelectElement>("#win-condition")?.addEventListener("change", (event) => {
|
||||||
setup.winCondition = (event.currentTarget as HTMLSelectElement).value as SetupState["winCondition"];
|
setup.winCondition = (event.currentTarget as HTMLSelectElement).value as SetupState["winCondition"];
|
||||||
@@ -1427,13 +1478,15 @@ function attachEvents() {
|
|||||||
chooseWeatherAction(button.dataset.weatherCard as WeatherCardId, button.dataset.weatherAction as "draft" | "ban");
|
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() {
|
function render() {
|
||||||
console.log("[DEBUG] render() called, playerCount:", state?.players?.length);
|
|
||||||
try {
|
|
||||||
const playerCount = state.players.length;
|
const playerCount = state.players.length;
|
||||||
console.log("[DEBUG] Building HTML...");
|
|
||||||
app.innerHTML = `
|
app.innerHTML = `
|
||||||
<main class="layout" style="--player-count: ${playerCount}">
|
<main class="layout" style="--player-count: ${playerCount}">
|
||||||
<section class="game-area">
|
<section class="game-area">
|
||||||
@@ -1448,17 +1501,8 @@ function render() {
|
|||||||
${renderInitiativeModal()}
|
${renderInitiativeModal()}
|
||||||
${renderWeatherDraftModal()}
|
${renderWeatherDraftModal()}
|
||||||
`;
|
`;
|
||||||
console.log("[DEBUG] HTML rendered, attaching events...");
|
|
||||||
attachEvents();
|
attachEvents();
|
||||||
console.log("[DEBUG] Events attached, getting score snapshot...");
|
|
||||||
previousScoreSnapshot = getScoreSnapshot();
|
previousScoreSnapshot = getScoreSnapshot();
|
||||||
console.log("[DEBUG] Render complete!");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[DEBUG] Error in render():", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[DEBUG] About to call render()...");
|
|
||||||
render();
|
render();
|
||||||
console.log("[DEBUG] render() executed");
|
|
||||||
|
|||||||
@@ -28,6 +28,48 @@ function getLeafCounts(state: GameState) {
|
|||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getColumnPresence(state: GameState, column: number) {
|
||||||
|
const owners = new Set<PlayerId>();
|
||||||
|
|
||||||
|
for (let row = 0; row < state.config.rows; row += 1) {
|
||||||
|
const ownerId = state.nodes.get(keyFor(row, column))?.ownerId;
|
||||||
|
if (ownerId !== undefined) {
|
||||||
|
owners.add(ownerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...owners];
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyColumnBaseEnergy(state: GameState, scores: number[], ownerId: PlayerId, playersPresent: PlayerId[]) {
|
||||||
|
const contested = playersPresent.length > 1;
|
||||||
|
|
||||||
|
if (!contested) {
|
||||||
|
scores[ownerId] += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.activeRoundEffects.includes("stalemate")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.activeRoundEffects.includes("split_light")) {
|
||||||
|
playersPresent.forEach((playerId) => {
|
||||||
|
scores[playerId] += 0.5;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.activeRoundEffects.includes("shared_light")) {
|
||||||
|
playersPresent.forEach((playerId) => {
|
||||||
|
scores[playerId] += 1;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scores[ownerId] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
function applyWeatherEffects(state: GameState, scores: number[], energySimulation: EnergySimulation) {
|
function applyWeatherEffects(state: GameState, scores: number[], energySimulation: EnergySimulation) {
|
||||||
if (state.activeRoundEffects.length === 0) {
|
if (state.activeRoundEffects.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -132,6 +174,7 @@ export function buildEnergySimulation(state: GameState): EnergySimulation {
|
|||||||
terminalRow: state.config.rows - 1,
|
terminalRow: state.config.rows - 1,
|
||||||
intercepted: false,
|
intercepted: false,
|
||||||
ownerId: null,
|
ownerId: null,
|
||||||
|
playersPresent: [],
|
||||||
hitNode: null,
|
hitNode: null,
|
||||||
rootKey: null,
|
rootKey: null,
|
||||||
branchNodes: [],
|
branchNodes: [],
|
||||||
@@ -142,6 +185,7 @@ export function buildEnergySimulation(state: GameState): EnergySimulation {
|
|||||||
|
|
||||||
const hitNode = parseKey(hitNodeKey);
|
const hitNode = parseKey(hitNodeKey);
|
||||||
const ownerId = state.nodes.get(hitNodeKey)?.ownerId as PlayerId;
|
const ownerId = state.nodes.get(hitNodeKey)?.ownerId as PlayerId;
|
||||||
|
const playersPresent = getColumnPresence(state, column);
|
||||||
const branchNodes = [hitNode];
|
const branchNodes = [hitNode];
|
||||||
const branchEdges = [];
|
const branchEdges = [];
|
||||||
let cursor = hitNodeKey;
|
let cursor = hitNodeKey;
|
||||||
@@ -153,12 +197,13 @@ export function buildEnergySimulation(state: GameState): EnergySimulation {
|
|||||||
cursor = parentKey;
|
cursor = parentKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
scores[ownerId] += 1;
|
applyColumnBaseEnergy(state, scores, ownerId, playersPresent);
|
||||||
columns.push({
|
columns.push({
|
||||||
column,
|
column,
|
||||||
terminalRow: hitNode.row,
|
terminalRow: hitNode.row,
|
||||||
intercepted: true,
|
intercepted: true,
|
||||||
ownerId,
|
ownerId,
|
||||||
|
playersPresent,
|
||||||
hitNode,
|
hitNode,
|
||||||
rootKey: cursor,
|
rootKey: cursor,
|
||||||
branchNodes,
|
branchNodes,
|
||||||
|
|||||||
@@ -4,12 +4,17 @@ import { shuffleArray } from "./utils";
|
|||||||
export const WEATHER_CARDS: WeatherCardDefinition[] = [
|
export const WEATHER_CARDS: WeatherCardDefinition[] = [
|
||||||
{ id: "leaf_surge", title: "Leaf Surge", description: "Each leaf gives +1." },
|
{ id: "leaf_surge", title: "Leaf Surge", description: "Each leaf gives +1." },
|
||||||
{ id: "branching_season", title: "Branching Season", description: "Each leaf after your first gives +1." },
|
{ id: "branching_season", title: "Branching Season", description: "Each leaf after your first gives +1." },
|
||||||
|
{ id: "storehouse", title: "Storehouse", description: "Banking is enabled this round." },
|
||||||
|
{ id: "sun_ladder", title: "Sun Ladder", description: "Straight-up growth costs 0." },
|
||||||
{ id: "west_light", title: "West Light", description: "Left third columns give +1." },
|
{ id: "west_light", title: "West Light", description: "Left third columns give +1." },
|
||||||
{ id: "east_light", title: "East Light", description: "Right third columns give +1." },
|
{ id: "east_light", title: "East Light", description: "Right third columns give +1." },
|
||||||
{ id: "high_noon", title: "High Noon", description: "Center third columns give +1." },
|
{ id: "high_noon", title: "High Noon", description: "Center third columns give +1." },
|
||||||
{ id: "edge_bloom", title: "Edge Bloom", description: "Edge columns give +1." },
|
{ id: "edge_bloom", title: "Edge Bloom", description: "Edge columns give +1." },
|
||||||
{ id: "wide_reach", title: "Wide Reach", description: "Most columns gets +2." },
|
{ id: "wide_reach", title: "Wide Reach", description: "Most columns gets +2." },
|
||||||
{ id: "tall_reward", title: "Tall Reward", description: "Your tallest leaf gives +2." },
|
{ id: "tall_reward", title: "Tall Reward", description: "Your tallest leaf gives +2." },
|
||||||
|
{ id: "stalemate", title: "Stalemate", description: "Contested columns give no energy." },
|
||||||
|
{ id: "split_light", title: "Split Light", description: "Contested columns give half to each player there." },
|
||||||
|
{ id: "shared_light", title: "Shared Light", description: "Contested columns give full energy to each player there." },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const WEATHER_CARD_LOOKUP = new Map<WeatherCardId, WeatherCardDefinition>(
|
export const WEATHER_CARD_LOOKUP = new Map<WeatherCardId, WeatherCardDefinition>(
|
||||||
@@ -17,7 +22,7 @@ export const WEATHER_CARD_LOOKUP = new Map<WeatherCardId, WeatherCardDefinition>
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function createWeatherDraft(state: GameState): WeatherDraftState {
|
export function createWeatherDraft(state: GameState): WeatherDraftState {
|
||||||
const rowSize = Math.min(WEATHER_CARDS.length, state.players.length + 2);
|
const rowSize = Math.min(WEATHER_CARDS.length, state.config.weatherDraftCount);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
playerOrder: [...state.turnOrder],
|
playerOrder: [...state.turnOrder],
|
||||||
|
|||||||
10
src/state.ts
10
src/state.ts
@@ -79,15 +79,17 @@ export function createSetupState(
|
|||||||
initiativeMode: SetupState["initiativeMode"] = "fixed",
|
initiativeMode: SetupState["initiativeMode"] = "fixed",
|
||||||
biddingOrderRule: SetupState["biddingOrderRule"] = "rotating",
|
biddingOrderRule: SetupState["biddingOrderRule"] = "rotating",
|
||||||
weatherDraftEnabled = true,
|
weatherDraftEnabled = true,
|
||||||
|
weatherDraftCount = playerCount + 2,
|
||||||
winCondition: SetupState["winCondition"] = "rounds",
|
winCondition: SetupState["winCondition"] = "rounds",
|
||||||
maxRounds = 12,
|
maxRounds = 12,
|
||||||
topLeafTarget = 4,
|
topLeafTarget = 4,
|
||||||
): SetupState {
|
): SetupState {
|
||||||
|
console.log("[DEBUG] createSetupState started");
|
||||||
const clampedSeeds = Math.min(startingNodesPerPlayer, getMaxStartingNodesPerPlayer(playerCount, columns));
|
const clampedSeeds = Math.min(startingNodesPerPlayer, getMaxStartingNodesPerPlayer(playerCount, columns));
|
||||||
const defaults = createDefaultSeedInputs(playerCount, columns, clampedSeeds);
|
const defaults = createDefaultSeedInputs(playerCount, columns, clampedSeeds);
|
||||||
const paletteDefaults = createDefaultPaletteOrder(playerCount);
|
const paletteDefaults = createDefaultPaletteOrder(playerCount);
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
playerCount,
|
playerCount,
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
@@ -99,10 +101,13 @@ export function createSetupState(
|
|||||||
initiativeMode,
|
initiativeMode,
|
||||||
biddingOrderRule,
|
biddingOrderRule,
|
||||||
weatherDraftEnabled,
|
weatherDraftEnabled,
|
||||||
|
weatherDraftCount: Math.max(1, weatherDraftCount),
|
||||||
winCondition,
|
winCondition,
|
||||||
maxRounds,
|
maxRounds,
|
||||||
topLeafTarget: Math.max(1, Math.min(columns, topLeafTarget)),
|
topLeafTarget: Math.max(1, Math.min(columns, topLeafTarget)),
|
||||||
};
|
};
|
||||||
|
console.log("[DEBUG] createSetupState completed");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPlayers(playerCount: number, paletteOrder = createDefaultPaletteOrder(playerCount)): Player[] {
|
export function createPlayers(playerCount: number, paletteOrder = createDefaultPaletteOrder(playerCount)): Player[] {
|
||||||
@@ -162,6 +167,7 @@ export function normalizeSeedInputs(setup: SetupState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createInitialState(setup: SetupState): GameState {
|
export function createInitialState(setup: SetupState): GameState {
|
||||||
|
console.log("[DEBUG] createInitialState started");
|
||||||
const playerPaletteOrder = [...setup.paletteOrder];
|
const playerPaletteOrder = [...setup.paletteOrder];
|
||||||
const players = createPlayers(setup.playerCount, playerPaletteOrder);
|
const players = createPlayers(setup.playerCount, playerPaletteOrder);
|
||||||
const turnOrder = players.map((player) => player.id);
|
const turnOrder = players.map((player) => player.id);
|
||||||
@@ -175,6 +181,7 @@ export function createInitialState(setup: SetupState): GameState {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("[DEBUG] createInitialState completed");
|
||||||
return {
|
return {
|
||||||
config: {
|
config: {
|
||||||
columns: setup.columns,
|
columns: setup.columns,
|
||||||
@@ -185,6 +192,7 @@ export function createInitialState(setup: SetupState): GameState {
|
|||||||
initiativeMode: setup.initiativeMode,
|
initiativeMode: setup.initiativeMode,
|
||||||
biddingOrderRule: setup.biddingOrderRule,
|
biddingOrderRule: setup.biddingOrderRule,
|
||||||
weatherDraftEnabled: setup.weatherDraftEnabled,
|
weatherDraftEnabled: setup.weatherDraftEnabled,
|
||||||
|
weatherDraftCount: setup.weatherDraftCount,
|
||||||
winCondition: setup.winCondition,
|
winCondition: setup.winCondition,
|
||||||
maxRounds: setup.maxRounds,
|
maxRounds: setup.maxRounds,
|
||||||
topLeafTarget: setup.topLeafTarget,
|
topLeafTarget: setup.topLeafTarget,
|
||||||
|
|||||||
@@ -91,14 +91,14 @@ html, body {
|
|||||||
/* Board - fits within shell */
|
/* Board - fits within shell */
|
||||||
.board {
|
.board {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: auto;
|
||||||
height: 100%;
|
height: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
aspect-ratio: var(--board-columns) / var(--board-rows);
|
aspect-ratio: var(--board-columns) / var(--board-rows);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(var(--board-columns), 1fr);
|
grid-template-columns: repeat(var(--board-columns), minmax(0, 1fr));
|
||||||
grid-template-rows: repeat(var(--board-rows), 1fr);
|
grid-template-rows: repeat(var(--board-rows), minmax(0, 1fr));
|
||||||
gap: clamp(2px, 0.3cqmin, 4px);
|
gap: clamp(2px, 0.3cqmin, 4px);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
@@ -174,6 +174,7 @@ html, body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell__shade {
|
.cell__shade {
|
||||||
|
|||||||
10
src/types.ts
10
src/types.ts
@@ -24,6 +24,7 @@ export type SetupState = {
|
|||||||
initiativeMode: "fixed" | "bid";
|
initiativeMode: "fixed" | "bid";
|
||||||
biddingOrderRule: "rotating" | "lowest_growth_income";
|
biddingOrderRule: "rotating" | "lowest_growth_income";
|
||||||
weatherDraftEnabled: boolean;
|
weatherDraftEnabled: boolean;
|
||||||
|
weatherDraftCount: number;
|
||||||
winCondition: "rounds" | "top_leaves";
|
winCondition: "rounds" | "top_leaves";
|
||||||
maxRounds: number;
|
maxRounds: number;
|
||||||
topLeafTarget: number;
|
topLeafTarget: number;
|
||||||
@@ -52,6 +53,7 @@ export type GameConfig = {
|
|||||||
initiativeMode: SetupState["initiativeMode"];
|
initiativeMode: SetupState["initiativeMode"];
|
||||||
biddingOrderRule: SetupState["biddingOrderRule"];
|
biddingOrderRule: SetupState["biddingOrderRule"];
|
||||||
weatherDraftEnabled: boolean;
|
weatherDraftEnabled: boolean;
|
||||||
|
weatherDraftCount: number;
|
||||||
winCondition: SetupState["winCondition"];
|
winCondition: SetupState["winCondition"];
|
||||||
maxRounds: number;
|
maxRounds: number;
|
||||||
topLeafTarget: number;
|
topLeafTarget: number;
|
||||||
@@ -110,6 +112,7 @@ export type ColumnEnergy = {
|
|||||||
terminalRow: number;
|
terminalRow: number;
|
||||||
intercepted: boolean;
|
intercepted: boolean;
|
||||||
ownerId: PlayerId | null;
|
ownerId: PlayerId | null;
|
||||||
|
playersPresent: PlayerId[];
|
||||||
hitNode: Position | null;
|
hitNode: Position | null;
|
||||||
rootKey: NodeKey | null;
|
rootKey: NodeKey | null;
|
||||||
branchNodes: Position[];
|
branchNodes: Position[];
|
||||||
@@ -183,12 +186,17 @@ export type GamePhase = "initiative" | "turn" | "round_end" | "game_over";
|
|||||||
export type WeatherCardId =
|
export type WeatherCardId =
|
||||||
| "leaf_surge"
|
| "leaf_surge"
|
||||||
| "branching_season"
|
| "branching_season"
|
||||||
|
| "storehouse"
|
||||||
|
| "sun_ladder"
|
||||||
| "west_light"
|
| "west_light"
|
||||||
| "east_light"
|
| "east_light"
|
||||||
| "high_noon"
|
| "high_noon"
|
||||||
| "edge_bloom"
|
| "edge_bloom"
|
||||||
| "wide_reach"
|
| "wide_reach"
|
||||||
| "tall_reward";
|
| "tall_reward"
|
||||||
|
| "stalemate"
|
||||||
|
| "split_light"
|
||||||
|
| "shared_light";
|
||||||
|
|
||||||
export type WeatherCardDefinition = {
|
export type WeatherCardDefinition = {
|
||||||
id: WeatherCardId;
|
id: WeatherCardId;
|
||||||
|
|||||||
Reference in New Issue
Block a user