more card rule tweaks
This commit is contained in:
@@ -41,6 +41,14 @@ function getColumnPresence(state: GameState, column: number) {
|
|||||||
return [...owners];
|
return [...owners];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addRoundedHalfBonus(scores: number[], counts: number[]) {
|
||||||
|
counts.forEach((count, playerId) => {
|
||||||
|
if (count > 0) {
|
||||||
|
scores[playerId] += Math.ceil(count * 0.5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function applyColumnBaseEnergy(state: GameState, scores: number[], ownerId: PlayerId, playersPresent: PlayerId[]) {
|
function applyColumnBaseEnergy(state: GameState, scores: number[], ownerId: PlayerId, playersPresent: PlayerId[]) {
|
||||||
const contested = playersPresent.length > 1;
|
const contested = playersPresent.length > 1;
|
||||||
|
|
||||||
@@ -105,11 +113,25 @@ function applyWeatherEffects(state: GameState, scores: number[], energySimulatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (effectId === "tall_reward") {
|
if (effectId === "tall_reward") {
|
||||||
|
const bestRow = tallestLeaves.reduce<number | null>((currentBest, row) => {
|
||||||
|
if (row === null) {
|
||||||
|
return currentBest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBest === null || row < currentBest) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentBest;
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
if (bestRow !== null) {
|
||||||
tallestLeaves.forEach((row, playerId) => {
|
tallestLeaves.forEach((row, playerId) => {
|
||||||
if (row !== null) {
|
if (row === bestRow) {
|
||||||
scores[playerId] += 2;
|
scores[playerId] += 2;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,25 +145,39 @@ function applyWeatherEffects(state: GameState, scores: number[], energySimulatio
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const westLightCounts = state.players.map(() => 0);
|
||||||
|
const eastLightCounts = state.players.map(() => 0);
|
||||||
|
const highNoonCounts = state.players.map(() => 0);
|
||||||
|
|
||||||
energySimulation.columns.forEach((column) => {
|
energySimulation.columns.forEach((column) => {
|
||||||
if (!column.intercepted || column.ownerId === null) {
|
if (!column.intercepted || column.ownerId === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const region = getColumnRegion(state, column.column);
|
const region = getColumnRegion(state, column.column);
|
||||||
if (effectId === "west_light" && region === "left") {
|
if (region === "left") {
|
||||||
scores[column.ownerId] += 1;
|
westLightCounts[column.ownerId] += 1;
|
||||||
}
|
}
|
||||||
if (effectId === "east_light" && region === "right") {
|
if (region === "right") {
|
||||||
scores[column.ownerId] += 1;
|
eastLightCounts[column.ownerId] += 1;
|
||||||
}
|
}
|
||||||
if (effectId === "high_noon" && region === "center") {
|
if (region === "center") {
|
||||||
scores[column.ownerId] += 1;
|
highNoonCounts[column.ownerId] += 1;
|
||||||
}
|
}
|
||||||
if (effectId === "edge_bloom" && (column.column === 0 || column.column === state.config.columns - 1)) {
|
if (effectId === "edge_bloom" && (column.column === 0 || column.column === state.config.columns - 1)) {
|
||||||
scores[column.ownerId] += 1;
|
scores[column.ownerId] += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (effectId === "west_light") {
|
||||||
|
addRoundedHalfBonus(scores, westLightCounts);
|
||||||
|
}
|
||||||
|
if (effectId === "east_light") {
|
||||||
|
addRoundedHalfBonus(scores, eastLightCounts);
|
||||||
|
}
|
||||||
|
if (effectId === "high_noon") {
|
||||||
|
addRoundedHalfBonus(scores, highNoonCounts);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,12 +6,12 @@ export const WEATHER_CARDS: WeatherCardDefinition[] = [
|
|||||||
{ 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: "storehouse", title: "Storehouse", description: "Banking is enabled this round." },
|
||||||
{ id: "sun_ladder", title: "Sun Ladder", description: "Your first 3 vertical growths cost 0." },
|
{ id: "sun_ladder", title: "Sun Ladder", description: "Your first 3 vertical growths cost 0." },
|
||||||
{ id: "west_light", title: "West Light", description: "Left third columns give +1." },
|
{ id: "west_light", title: "West Light", description: "Left third energy gets +50%, rounded up." },
|
||||||
{ id: "east_light", title: "East Light", description: "Right third columns give +1." },
|
{ id: "east_light", title: "East Light", description: "Right third energy gets +50%, rounded up." },
|
||||||
{ id: "high_noon", title: "High Noon", description: "Center third columns give +1." },
|
{ id: "high_noon", title: "High Noon", description: "Center third energy gets +50%, rounded up." },
|
||||||
{ 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: "Tallest leaf on the board gives +2." },
|
||||||
{ id: "stalemate", title: "Stalemate", description: "Contested columns give no energy." },
|
{ 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: "split_light", title: "Split Light", description: "Contested columns give half to each player there." },
|
||||||
];
|
];
|
||||||
@@ -38,6 +38,7 @@ export function createWeatherDraft(state: GameState): WeatherDraftState {
|
|||||||
offers: shuffleArray(WEATHER_OFFER_PAIRS).slice(0, rowSize),
|
offers: shuffleArray(WEATHER_OFFER_PAIRS).slice(0, rowSize),
|
||||||
drafted: [],
|
drafted: [],
|
||||||
banned: [],
|
banned: [],
|
||||||
|
locked: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +52,10 @@ export function isWeatherCardAvailable(draft: WeatherDraftState, offerId: string
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return offer.options.includes(cardId) && !draft.banned.includes(cardId) && !draft.drafted.includes(cardId);
|
return offer.options.includes(cardId)
|
||||||
|
&& !draft.banned.includes(cardId)
|
||||||
|
&& !draft.drafted.includes(cardId)
|
||||||
|
&& !draft.locked.includes(cardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isWeatherOfferResolved(draft: WeatherDraftState, offerId: string) {
|
export function isWeatherOfferResolved(draft: WeatherDraftState, offerId: string) {
|
||||||
@@ -60,7 +64,7 @@ export function isWeatherOfferResolved(draft: WeatherDraftState, offerId: string
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return offer.options.every((cardId) => draft.banned.includes(cardId) || draft.drafted.includes(cardId));
|
return offer.options.some((cardId) => draft.banned.includes(cardId) || draft.drafted.includes(cardId));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWeatherCard(cardId: WeatherCardId) {
|
export function getWeatherCard(cardId: WeatherCardId) {
|
||||||
@@ -222,6 +222,7 @@ export type WeatherDraftState = {
|
|||||||
offers: WeatherOfferPair[];
|
offers: WeatherOfferPair[];
|
||||||
drafted: WeatherCardId[];
|
drafted: WeatherCardId[];
|
||||||
banned: WeatherCardId[];
|
banned: WeatherCardId[];
|
||||||
|
locked: WeatherCardId[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GameState = {
|
export type GameState = {
|
||||||
13
src/main.ts
13
src/main.ts
@@ -1404,13 +1404,20 @@ function renderSidebar() {
|
|||||||
|
|
||||||
return `
|
return `
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<section class="panel status-panel status-panel--weather">
|
<section class="panel accordion-panel accordion-panel--top">
|
||||||
<h2>In Effect</h2>
|
<details class="accordion">
|
||||||
|
<summary>In Effect</summary>
|
||||||
|
<div class="accordion__content">
|
||||||
${activeEffectsMarkup}
|
${activeEffectsMarkup}
|
||||||
${state.roundSummary?.event ? `<p class="event-note">${state.roundSummary.event}</p>` : ""}
|
${state.roundSummary?.event ? `<p class="event-note">${state.roundSummary.event}</p>` : ""}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel controls-panel">
|
<section class="panel controls-panel">
|
||||||
|
<details class="accordion" open>
|
||||||
|
<summary>End Turn</summary>
|
||||||
|
<div class="accordion__content">
|
||||||
<div class="active-turn" style="--player-color: ${player.color}; --player-glow: ${player.glow};">
|
<div class="active-turn" style="--player-color: ${player.color}; --player-glow: ${player.glow};">
|
||||||
<p class="eyebrow">Round ${state.round}</p>
|
<p class="eyebrow">Round ${state.round}</p>
|
||||||
<h2>${getTurnLabel()}</h2>
|
<h2>${getTurnLabel()}</h2>
|
||||||
@@ -1422,6 +1429,8 @@ function renderSidebar() {
|
|||||||
<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 || !isBankingEnabled() ? "disabled" : ""}>Bank Remaining</button>
|
<button id="bank-turn" class="ghost-button" ${boardLocked || !isBankingEnabled() ? "disabled" : ""}>Bank Remaining</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel accordion-panel">
|
<section class="panel accordion-panel">
|
||||||
|
|||||||
@@ -110,11 +110,8 @@ html, body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
min-height: 0;
|
||||||
}
|
height: 100%;
|
||||||
|
|
||||||
.new-game-launch {
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bottom bar - Fixed height player scores */
|
/* Bottom bar - Fixed height player scores */
|
||||||
@@ -164,6 +161,30 @@ html, body {
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-panel--weather {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-panel {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-panel--top {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finish-panel {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finish-panel__actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
.log-panel {
|
.log-panel {
|
||||||
max-height: 88px;
|
max-height: 88px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -588,12 +609,31 @@ button:disabled {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accordion[open] {
|
||||||
|
background: rgba(255, 255, 255, 0.045);
|
||||||
|
}
|
||||||
|
|
||||||
.accordion summary {
|
.accordion summary {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.7rem 0.85rem;
|
padding: 0.7rem 0.85rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #f4f7fb;
|
color: #f4f7fb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion summary::after {
|
||||||
|
content: "+";
|
||||||
|
color: rgba(231, 238, 247, 0.62);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion[open] summary::after {
|
||||||
|
content: "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion summary::-webkit-details-marker {
|
.accordion summary::-webkit-details-marker {
|
||||||
@@ -1105,11 +1145,20 @@ input[type="range"] {
|
|||||||
gap: 0.7rem;
|
gap: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.weather-pair__divider {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(231, 238, 247, 0.42);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.weather-pair__option {
|
.weather-pair__option {
|
||||||
padding: 0.75rem 0.8rem;
|
padding: 0.75rem 0.8rem;
|
||||||
border-radius: 0.85rem;
|
border-radius: 0.85rem;
|
||||||
background: rgba(255, 255, 255, 0.04);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
transition: transform 120ms ease, opacity 120ms ease, padding 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.weather-pair__option--drafted {
|
.weather-pair__option--drafted {
|
||||||
@@ -1121,6 +1170,20 @@ input[type="range"] {
|
|||||||
opacity: 0.72;
|
opacity: 0.72;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.weather-pair__option--locked {
|
||||||
|
padding: 0.5rem 0.65rem;
|
||||||
|
opacity: 0.56;
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-pair__option--locked h2 {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-pair__option--locked p {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
.weather-card--drafted {
|
.weather-card--drafted {
|
||||||
border-color: rgba(130, 224, 182, 0.55);
|
border-color: rgba(130, 224, 182, 0.55);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user