Files

188 lines
5.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 { bump, floater, pop, purchased } from './fx.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);
pop(els.run);
const sign = result.delta > 0 ? '+' : '−';
floater(els.run, `${sign}${fmt.number(Math.abs(result.delta))}`, undefined, result.delta > 0 ? 'good' : 'bad');
bump(els.chips);
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: () => {
if (actions.buyChip()) {
purchased(els.buyChip, '+1');
bump(els.chips);
}
},
}),
reliability: h('button', {
class: 'btn btn--wide',
type: 'button',
onclick: () => {
if (actions.buyReliability()) purchased(els.reliability, '+1');
},
}),
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 };
}