// Gamble tab: chips are hard to buy and hard to keep. Stake them on a cycle. import { chipCost, odds, reliabilityCost } from '../engine/gamble.js'; import { chipDemandBonus } from '../engine/formulas.js'; import { t } from '../services/i18n.js'; import { h, icon } from './dom.js'; import { moneyFormatter, setText } from './text.js'; const STAKES = [ { id: 'one', count: () => 1 }, { id: 'tenth', fraction: 0.1 }, { id: 'half', fraction: 0.5 }, { id: 'all', fraction: 1 }, ]; const LOG_SIZE = 6; export function createGambleView(root, actions) { let ctx; let fmt; let money; let stake = STAKES[0]; let els; const log = []; const stakeCount = (chips) => (stake.count ? stake.count() : Math.max(1, Math.ceil(chips * stake.fraction))); function stakeLabel(option) { if (option.id === 'one') return t('gamble.stakeOne'); if (option.id === 'all') return t('gamble.stakeAll'); return t('gamble.stakePercent', { percent: option.fraction * 100 }); } function renderLog() { els.log.replaceChildren( ...log.map((entry) => h('li', { class: `log__entry log__entry--${entry.outcome}`, text: t(`gamble.result.${entry.outcome}`, { amount: fmt.number(Math.abs(entry.delta)) }) }), ), ); els.logEmpty.hidden = log.length > 0; } function run(state) { const result = actions.run(stakeCount(state.chips)); if (!result) return; log.unshift(result); log.length = Math.min(log.length, LOG_SIZE); renderLog(); } let lastState; function rebuild(newCtx, formatter) { ctx = newCtx; fmt = formatter; money = moneyFormatter(fmt); els = { chips: h('strong'), bonus: h('span', { class: 'hint' }), buyChip: h('button', { class: 'btn btn--wide', type: 'button', onclick: () => actions.buyChip() }), reliability: h('button', { class: 'btn btn--wide', type: 'button', onclick: () => actions.buyReliability() }), odds: h('p', { class: 'hint' }), run: h('button', { class: 'btn btn--primary btn--big', type: 'button', onclick: () => run(lastState) }), log: h('ul', { class: 'log' }), logEmpty: h('p', { class: 'hint', text: t('gamble.logEmpty') }), }; root.replaceChildren( h( 'section', { class: 'panel' }, h('h2', { class: 'panel__title', text: t('theme.tab.gamble') }), h('p', { class: 'hint', text: t('gamble.intro') }), h( 'div', { class: 'tile' }, icon('res.chips'), h('div', {}, h('span', { class: 'tile__label', text: t('theme.res.chips') }), els.chips, els.bonus), ), els.buyChip, els.reliability, ), h( 'section', { class: 'panel' }, h('h2', { class: 'panel__title', text: t('gamble.play') }), h('p', { class: 'field__label', text: t('gamble.stake') }), h( 'fieldset', { class: 'segmented' }, h('legend', { class: 'sr-only', text: t('gamble.stake') }), ...STAKES.map((option) => h( 'label', {}, h('input', { type: 'radio', name: 'gamble-stake', value: option.id, checked: option === stake, onchange: () => { stake = option; }, }), h('span', { text: stakeLabel(option) }), ), ), ), els.odds, els.run, h('h3', { class: 'dialog__subtitle', text: t('gamble.log') }), els.logEmpty, els.log, ), ); renderLog(); } function update(state, d) { lastState = state; const g = ctx.rules.gamble; setText(els.chips, fmt.number(state.chips)); setText(els.bonus, t('gamble.bonus', { bonus: fmt.number(chipDemandBonus(state.chips, g)) })); const cost = chipCost(state, ctx); setText(els.buyChip, t('gamble.buyChip', { cost: money(cost) })); els.buyChip.disabled = state.funds < cost || state.chips >= g.maxChips; const maxed = state.reliabilityLevel >= g.reliability.maxLevel; setText( els.reliability, maxed ? t('gamble.reliabilityMax', { level: state.reliabilityLevel }) : t('gamble.reliability', { level: state.reliabilityLevel, max: g.reliability.maxLevel, cost: money(reliabilityCost(state, ctx)), }), ); els.reliability.disabled = maxed || state.funds < reliabilityCost(state, ctx); const p = odds(state, ctx, d.mods); const percent = (x) => fmt.number(Math.round(x * 1000) / 10); setText( els.odds, t('gamble.odds', { win: percent(p.win), jackpot: percent(p.jackpot), multiplier: g.jackpotMultiplier, crash: percent(p.crash), lose: percent(p.lose), }), ); const cooling = state.gambleCooldown > 0; setText( els.run, cooling ? t('gamble.cooldown', { seconds: fmt.number(Math.ceil(state.gambleCooldown * 10) / 10) }) : `${t('theme.action.gamble')} (${fmt.number(Math.min(stakeCount(state.chips), state.chips))})`, ); els.run.disabled = cooling || state.chips < 1; } return { rebuild, update }; }