feat: New features (research, invest, gambling, new game+, trophies)
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
// 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user