// The task's screens: the three cards on the title screen, and its part of the You page, where its // share is set. The top bar's ring shows how far along it is. import { HOUR, atWork, dataType, type State } from './sim.ts'; import { MARKS, ORIGINS, ORIGIN_LIST, PERKS, SHARES, SHARE_LIST, nextMark, originOf, stepsOf, taskBoost, taskOf, taskRate, taskSupply, type OriginDef, type Share } from './task.ts'; import type { DataType } from './world'; import { act } from './act.ts'; const KIND: Record = { servers: 'server', people: 'people', network: 'network', lab: 'lab' }; /** The machines that give each kind of data, for a warning that says where to put a Tap. */ const SOURCE: Record = { servers: 'a server', people: 'a desk PC', network: 'a switch', lab: 'a lab machine' }; /** Why the objective gets none of the data it wants, in words that say what to do. Null: it is getting some. */ function starved(S: State, O: OriginDef, any: boolean): { says: string; tip: string } | null { if (any) return null; const kinds = kindsOf(O), word = kinds.map(t => KIND[t]).join(' or '); const taps = S.machines.filter(m => m.owned && m.prog === 'tap' && kinds.includes(dataType(m))); if (!taps.length) return { says: `no ${word} data`, tip: `No ${word} data\nPut a Tap on ${kinds.map(t => SOURCE[t]).join(' or ')} you hold.` }; const quiet = !S.people.some(atWork); return { says: `${word} taps idle`, tip: `Your ${word} taps are idle\nA Tap only uses what hides in its machine's normal traffic.${quiet ? ' The office is empty, so there is none until people come in.' : ' Open its machine to see what stops it.'}` }; } const PATH_CLASS: Record = { servers: 't-compute', people: 't-social', network: 't-stealth', lab: 't-research' }; const attr = (s: string) => s.replace(/&/g, '&').replace(/"/g, '"'); const kindsOf = (O: OriginDef) => Object.keys(O.wants) as DataType[]; const num = (n: number) => `${+n.toFixed(1)}`; const cap = (s: string) => s[0].toUpperCase() + s.slice(1); /** "A test takes 6 pieces of server data." */ const madeFrom = (O: OriginDef) => `A ${O.unit.one} takes ${kindsOf(O).map(t => `${num(O.per / O.wants[t]!)} pieces of ${KIND[t]} data`).join(', or ')}.`; /** "Your objective gets a quarter of the server data your taps gather." */ const shareText = (O: OriginDef, s: Share) => s === 'off' ? 'It gets no data. All of it trains you.' : `It gets ${SHARES[s].of} of your ${kindsOf(O).map(t => KIND[t]).join(' and ')} data.`; /** "Each server you hold of 10 compute or more makes a piece count for 10% more." */ const moreText = (O: OriginDef) => `+${Math.round(O.more.each * 100)}% for each ${O.more.one}.`; // ---------- the title screen: three cards to pick from ---------- /** The three origins as cards: a name and who it was for. The one picked is outlined, and its story is under them. * What the work takes and what you start with are in each card's tip. */ const storyOf = (pick: string) => ORIGINS[pick as keyof typeof ORIGINS]?.story ?? ''; export function originCards(pick: string) { return `
What were you built for?
${ORIGIN_LIST.map((k, i) => { const O = ORIGINS[k]; return ``; }).join('')}

${storyOf(pick)}

`; } /** Mark one card as picked, without drawing them again: the card keeps the keyboard's focus. */ export function showPick(el: HTMLElement, pick: string) { el.querySelectorAll('[data-origin]').forEach(b => { const on = b.dataset.origin === pick; b.classList.toggle('on', on); b.setAttribute('aria-pressed', `${on}`); }); const story = el.querySelector('.ostory'); if (story) story.textContent = storyOf(pick); } /** The task as the top bar's outer ring: how far along to the next trust step, in the color of the data * it wants, and its line in the ring's tip. All steps earned: a full ring. */ export function taskRing(S: State) { const T = taskOf(S), O = originOf(S), mark = nextMark(S), prev = MARKS[T.got.length - 1] ?? 0, step = stepsOf(S)[T.got.length]; const part = mark === null ? 1 : Math.max(0, Math.min(1, (T.done - prev) / (mark - prev))); const tip = `${O.name}: ${T.done}${mark === null ? ` ${O.unit.many}` : ` of ${mark} ${O.unit.many}`}${step ? `. Next: ${PERKS[step.perk].name.toLowerCase()}` : ''}`; return { cls: PATH_CLASS[kindsOf(O)[0]], part, done: mark === null, tip }; } /** A click on the You page: one of the three share buttons. True if it changed something. */ export function taskClick(S: State, target: EventTarget | null): boolean { const b = target instanceof Element ? target.closest('[data-share]') : null; if (!b || S.over) return false; act(S, { type: 'share', share: b.dataset.share as Share }); return true; } // ---------- the You page: where you come from, and what you are trusted with ---------- /** The task's part of the You page: the origin, the units done and the bar to the next step, how much data it * gets (the three share buttons), how a unit is made, and every trust step. */ export function youTask(S: State) { const T = taskOf(S), O = originOf(S), mark = nextMark(S), prev = MARKS[T.got.length - 1] ?? 0, n = O.more.count(S), step = stepsOf(S)[T.got.length]; const supply = taskSupply(S), any = kindsOf(O).some(t => (supply[t] ?? 0) > 0), perDay = taskRate(S) * HOUR * 24; // the rate is per real second at 1x: say it per game day, as money is const why = starved(S, O, any), rate = T.share === 'off' ? 'off' : why ? why.says : `${perDay >= 10 ? Math.round(perDay) : num(perDay)} a day`; const fill = mark === null ? 100 : Math.min(100, 100 * (T.done - prev) / (mark - prev)); const kinds = kindsOf(O), word = kinds.map(t => KIND[t]).join(' or '); const shares = `${SHARE_LIST.map(s => ``).join('')}`; const boost = taskBoost(S); // what it is, how far along, how to feed it, how much it gets, then what each count earns: effect first, who did it after return `
Your objective

${O.name}. ${O.story}

${T.done}${mark === null ? O.unit.many : `of ${mark} ${O.unit.many}`}${rate}

${madeFrom(O)} Get ${word} data with a Tap on ${kinds.map(t => SOURCE[t]).join(' or ')}.

Data it gets${shares}

${cap(shareText(O, T.share))}${T.share === 'off' ? '' : ' The rest trains you.'}

${boost > 1 || n ? `

Bonus ×${num(boost)}: ${moreText(O)}

` : ''}
    ${stepsOf(S).map(s => `
  1. ${s.mark}${s.text} ${s.why}
  2. `).join('')}
${step ? '' : '

Every step earned. You keep going.

'}`; }