feat: Doing more polishing (M8b)

This commit is contained in:
gauvainboiche
2026-10-25 18:50:17 +01:00
parent c2103c32e7
commit c55e5a2484
34 changed files with 1605 additions and 106 deletions
+25 -16
View File
@@ -51,12 +51,15 @@ export function drawCard(state, bal, cards) {
const fiscal = computeFiscal(state, bal);
const lastAnswered = new Map();
for (const h of state.cardHistory) lastAnswered.set(h.id, h.day);
const recent = state.cardHistory.slice(-bal.cards.recentWindow).map((h) => h.category);
const waiting = new Set(state.inbox.map((e) => e.id));
// the last few categories seen, answered or still in the tray
const recent = [...state.cardHistory, ...state.inbox].slice(-bal.cards.recentWindow).map((h) => h.category);
const pool = [];
const weights = [];
for (const card of cards) {
if (card.followUpOnly) continue;
if (card.followUpOnly || waiting.has(card.id)) continue;
if (card.scenarios && !card.scenarios.includes(state.scenarioId)) continue;
const last = lastAnswered.get(card.id);
if (last !== undefined) {
if (card.once !== false) continue;
@@ -70,28 +73,34 @@ export function drawCard(state, bal, cards) {
return pickWeighted(state, pool, weights);
}
// Called once a weekly pause is pending: puts the card to answer in state.pendingCard.
// A due follow-up always comes first. With no eligible card the week is simply empty.
// Called when a new week starts (state.pending.weeklyCard): draws the week's proposal and puts it
// at the bottom of the in-tray (state.inbox). A due follow-up always comes first. With no eligible
// card the week is simply empty. Proposals pile up without stopping the clock; the game only waits
// once the tray holds bal.cards.inboxMax of them (pending.inboxFull), until one is answered.
export function ensureCard(state, bal, cards) {
if (!state.pending.weeklyCard || state.pendingCard) return null;
if (!state.pending.weeklyCard) return null;
state.pending.weeklyCard = false;
const byId = indexCards(cards);
const waiting = new Set(state.inbox.map((e) => e.id));
let card = null;
const due = state.followUps.findIndex((f) => f.due <= state.day);
const due = state.followUps.findIndex((f) => f.due <= state.day && !waiting.has(f.id));
if (due >= 0) card = byId.get(state.followUps.splice(due, 1)[0].id) ?? null;
card ??= drawCard(state, bal, cards);
if (!card) return null;
if (!card) {
state.pending.weeklyCard = false;
return null;
}
state.pendingCard = { id: card.id, day: state.day };
state.inbox.push({ id: card.id, day: state.day, category: card.category });
if (state.inbox.length >= bal.cards.inboxMax) state.pending.inboxFull = true;
return card;
}
export function answerCard(state, bal, cards, choice) {
if (!state.pendingCard || (choice !== "yes" && choice !== "no")) return { ok: false };
const card = indexCards(cards).get(state.pendingCard.id);
// Answers the proposal `id` (default: the one on top of the tray, the oldest).
export function answerCard(state, bal, cards, choice, id) {
if (choice !== "yes" && choice !== "no") return { ok: false };
const at = id === undefined ? 0 : state.inbox.findIndex((e) => e.id === id);
const entry = state.inbox[at];
if (!entry) return { ok: false };
const card = indexCards(cards).get(entry.id);
if (!card) return { ok: false };
const option = card[choice];
@@ -101,7 +110,7 @@ export function answerCard(state, bal, cards, choice) {
if (option.followUp) state.followUps.push({ id: option.followUp.id, due: state.day + option.followUp.inDays });
state.cardHistory.push({ id: card.id, category: card.category, day: state.day, choice });
state.pendingCard = null;
state.pending.weeklyCard = false;
state.inbox.splice(at, 1);
state.pending.inboxFull = state.inbox.length >= bal.cards.inboxMax;
return { ok: true, card };
}