diff --git a/src/Pawn.ts b/src/Pawn.ts index 85f6b35..16ebf57 100644 --- a/src/Pawn.ts +++ b/src/Pawn.ts @@ -1,6 +1,5 @@ import { Serializable } from 'frigid'; import faker from 'faker'; -import chalk from 'chalk'; import log from './log.js'; import { Task } from './tasks/Task.js'; import Time, { Tickable } from './Time.js'; @@ -10,13 +9,13 @@ import { render } from './ui/UI.js'; import { Memory } from './Memory.js'; const LABORS = { - CUT_TREE: Symbol('CUT_TREE'), - MINING: Symbol('CUT_TREE'), + CUT_TREE: Symbol('CUT_TREE'), + MINING: Symbol('CUT_TREE'), } const SKILLS = { - PICKAXE: Symbol('PICKAXE'), - HATCHET: Symbol('HATCHET') + PICKAXE: Symbol('PICKAXE'), + HATCHET: Symbol('HATCHET') } // const STATUS = { @@ -26,121 +25,121 @@ const SKILLS = { const energyScale = 0.1; export class Pawn extends Serializable implements Tickable { - name: { - first: string, - last: string - }; - job: Task; - awake: boolean; - sex: number; + name: { + first: string, + last: string + }; + job: Task; + awake: boolean; + sex: number; - energy: number; - fun: number; + energy: number; + fun: number; - age: number; + age: number; - memories: Memory[]; + memories: Memory[]; - async tick() { - this.age ++; + async tick() { + this.age ++; - this.energy -= energyScale; + this.energy -= energyScale; - if(this.awake === false) { - this.energy += energyScale * 4; - if(this.energy >= 100) { - this.awake = true; - } - } else { - if(this.job) { - this.job.doWork(1, this); - this.energy -= energyScale; - if(this.job?.completed) { - this.stopWorking(); - } - } else { - const inactive = Game.current.board.tasks.filter(task => { - return task.worker === null; - }); - if(inactive.length > 0) { - const task = inactive[0]; - // const task = inactive[Math.floor(Math.random() * inactive.length)]; - this.assignJob(task); - } - } - if(this.energy <= 0) { - this.stopWorking(); - this.awake = false; - } - } + if(this.awake === false) { + this.energy += energyScale * 4; + if(this.energy >= 100) { + this.awake = true; + } + } else { + if(this.job) { + this.job.doWork(1, this); + this.energy -= energyScale; + if(this.job?.completed) { + this.stopWorking(); + } + } else { + const inactive = Game.current.board.tasks.filter(task => { + return task.worker === null; + }); + if(inactive.length > 0) { + const task = inactive[0]; + // const task = inactive[Math.floor(Math.random() * inactive.length)]; + this.assignJob(task); + } + } + if(this.energy <= 0) { + this.stopWorking(); + this.awake = false; + } + } - } + } - get idle() { - return !this.job && this.awake; - } + get idle() { + return !this.job && this.awake; + } - ctor() { - log.info('Pawn::ctor') - this.name ??= { - first: faker.name.firstName(), - last: faker.name.lastName() - }; - if(!this.sex) { - this.sex = Math.round(Math.random()); - this.name.first = faker.name.firstName(this.sex); - } - this.awake ??= true; - this.energy ??= 100; - this.memories ??= []; - if(!this.age) { - this.age = Math.floor(525600 * (16 + Math.random() * 9)); - this.memories.push({ - type: "birth", - location: Game.current.name, - time: { - age: 0, - locale: new Time(Game.current.clock.stamp - this.age).toString() - } - }) - } + ctor() { + log.info('Pawn::ctor') + this.name ??= { + first: faker.name.firstName(), + last: faker.name.lastName() + }; + if(!this.sex) { + this.sex = Math.round(Math.random()); + this.name.first = faker.name.firstName(this.sex); + } + this.awake ??= true; + this.energy ??= 100; + this.memories ??= []; + if(!this.age) { + this.age = Math.floor(525600 * (16 + Math.random() * 9)); + this.memories.push({ + type: "birth", + location: Game.current.name, + time: { + age: 0, + locale: new Time(Game.current.clock.stamp - this.age).toString() + } + }) + } - if(this.job?.completed) { - this.stopWorking(); - } - } + if(this.job?.completed) { + this.stopWorking(); + } + } - stopWorking() { - if(this.job) { - this.job.stopJob(); - this.job = null; - } - } + stopWorking() { + if(this.job) { + this.job.stopJob(); + this.job = null; + } + } - assignJob(task: Task) { - this.job?.stopJob() - this.job = task; - this.job.claim(this); - } + assignJob(task: Task) { + this.job?.stopJob() + this.job = task; + this.job.claim(this); + } - get status() { - if(this.job) { - return this.job.status; - } else { - return this.awake ? chalk.bold.black('IDLE') : chalk.blue('RESTING') - } - } + get status() { + if(this.job) { + return this.job.status; + } else { + return this.awake ? chalk.bold.black('IDLE') : chalk.blue('RESTING') + } + } - static serializationDependencies() { - return [Task, ChopTreeTask] - } + static serializationDependencies() { + return [Task, ChopTreeTask] + } - toString() { - if(this.name) { - return this.name.first + ' ' + this.name.last; - } else { - return '[Object Pawn]'; - } - } + toString() { + if(this.name) { + return this.name.first + ' ' + this.name.last; + } else { + return '[Object Pawn]'; + } + } } diff --git a/src/Time.ts b/src/Time.ts index 38be5bc..05fafd8 100644 --- a/src/Time.ts +++ b/src/Time.ts @@ -1,197 +1,198 @@ import chalk from "chalk"; import { Serializable } from "frigid"; -import { isThisTypeNode } from "typescript"; -import log from "./log.js"; import { getTheme } from "./ui/Theme.js"; import { Renderable } from "./ui/UI.js"; +type AbbreviatedMonthName = string; + + const daysInMonth = [ - 31, 28, 31, - 30, 31, 30, - 31, 31, 30, - 31, 30, 31 + 31, 28, 31, + 30, 31, 30, + 31, 31, 30, + 31, 30, 31 ]; -const months = [ - 'Jan', 'Feb', 'Mar', - 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', - 'Oct', 'Nov', 'Dec' +const months: AbbreviatedMonthName[] = [ + 'Jan', 'Feb', 'Mar', + 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec' ] export default class Time extends Serializable implements Renderable { - rate: number; - paused = true; + rate: number; + paused = true; - thing: Tickable; + thing: Tickable; - year: number; - month: number; - day: number; - hour: number; - minute: number; + year: number; + month: number; + day: number; + hour: number; + minute: number; - constructor(timestamp: number = 0) { - super(); - this.minute = timestamp; - this.normalize(); - } + constructor(timestamp: number = 0) { + super(); + this.minute = timestamp; + this.normalize(); + } - asAge() { - if(this.year > 1) { - return this.year + ' years old'; - } else { - if(this.month > 2) { - return this.month + ' months old'; - } else { - if(this.day > 1) { - return this.day + ' days old'; - } else if(this.day === 1) { - return '1 day old'; - } else { - return 'newborn'; - } - } - } - } + asAge() { + if(this.year > 1) { + return this.year + ' years old'; + } else { + if(this.month > 2) { + return this.month + ' months old'; + } else { + if(this.day > 1) { + return this.day + ' days old'; + } else if(this.day === 1) { + return '1 day old'; + } else { + return 'newborn'; + } + } + } + } - render() { - const sym = (this.hour >= 6 && this.hour < 20) ? - chalk.ansi256(226).bgAnsi256(27)(' ⬤ ') : - chalk.ansi256(254).bgAnsi256(17)(' ☾ ') + render() { + const sym = (this.hour >= 6 && this.hour < 20) ? + chalk.ansi256(226).bgAnsi256(27)(' ☀ ') : + chalk.ansi256(254).bgAnsi256(17)(' ☾ ') - return `${sym} ${ - getTheme().normal(`${ - this.hour.toString().padStart(2, ' ') - }:${ - this.minute.toString().padStart(2, '0') - } ${ - months[this.month] - } ${ - this.day + 1 - }, ${ - this.normalizedYear - }`) - }`; + return `${sym} ${ + getTheme().normal(`${ + this.hour.toString().padStart(2, ' ') + }:${ + this.minute.toString().padStart(2, '0') + } ${ + months[this.month] + } ${ + this.day + 1 + }, ${ + this.normalizedYear + }`) + }`; - // return '☾' || '☼'; - } + // return '☾' || '☼'; + } - toString() { - return `${this.hour}:${this.minute.toString().padStart(2, '0')} ${months[this.month]} ${this.day + 1}, ${this.normalizedYear}` - } + toString() { + return `${this.hour}:${this.minute.toString().padStart(2, '0')} ${months[this.month]} ${this.day + 1}, ${this.normalizedYear}` + } - ctor() { - this.rate = 60; - this.minute ??= 0; - this.hour ??= 0; - this.day ??= 0; - this.month ??= 0; - this.year ??= 0; - } + ctor() { + this.rate = 60; + this.minute ??= 0; + this.hour ??= 0; + this.day ??= 0; + this.month ??= 0; + this.year ??= 0; + } - get stamp() { - let minute = this.minute; - let hour = this.hour; - let day = this.day; - let month = this.month; - let year = this.year; + get stamp() { + let minute = this.minute; + let hour = this.hour; + let day = this.day; + let month = this.month; + let year = this.year; - const daysInYear = daysInMonth.reduce((a, b) => a+b, 0); + const daysInYear = daysInMonth.reduce((a, b) => a+b, 0); - day += daysInYear * year; - year = 0; - while(month > 0) { - day += daysInMonth[month]; - month --; - } + day += daysInYear * year; + year = 0; + while(month > 0) { + day += daysInMonth[month]; + month --; + } - hour += day * 24; - day = 0; + hour += day * 24; + day = 0; - minute += hour * 60; - hour = 0; + minute += hour * 60; + hour = 0; - return minute; - } + return minute; + } - pause() { - this.paused = true; - } + pause() { + this.paused = true; + } - start() { - this.paused = false; - setTimeout(this.doTick.bind(this), 0); - } - - advanceTime(minutes) { - this.minute ++; - this.normalize() - } + start() { + this.paused = false; + setTimeout(this.doTick.bind(this), 0); + } + + advanceTime(minutes) { + this.minute ++; + this.normalize() + } - get normalizedYear() { - if(this.year >= 0) { - return (this.year + 1).toString().padStart(4, '0') + ' CE'; - } else { - return Math.abs(this.year).toString().padStart(4, '0') + ' BCE'; - } - } + get normalizedYear() { + if(this.year >= 0) { + return (this.year + 1).toString().padStart(4, '0') + ' CE'; + } else { + return Math.abs(this.year).toString().padStart(4, '0') + ' BCE'; + } + } - normalize() { - while(this.minute >= 60) { - this.minute -= 60; - this.hour ++; - } - while(this.minute < 0) { - this.minute += 60; - this.hour --; - } + normalize() { + while(this.minute >= 60) { + this.minute -= 60; + this.hour ++; + } + while(this.minute < 0) { + this.minute += 60; + this.hour --; + } - while(this.hour >= 24) { - this.hour -= 24; - this.day ++; - } - while(this.hour < 0) { - this.hour += 24; - this.day --; - } + while(this.hour >= 24) { + this.hour -= 24; + this.day ++; + } + while(this.hour < 0) { + this.hour += 24; + this.day --; + } - while(this.day < 0) { - this.day += daysInMonth[ - ((this.month % months.length) + months.length) % months.length - ]; - this.month --; - } - while(this.day >= daysInMonth[this.month % months.length]) { - this.day -= daysInMonth[this.month % months.length]; - this.month ++; - } + while(this.day < 0) { + this.day += daysInMonth[ + ((this.month % months.length) + months.length) % months.length + ]; + this.month --; + } + while(this.day >= daysInMonth[this.month % months.length]) { + this.day -= daysInMonth[this.month % months.length]; + this.month ++; + } - while(this.month >= 12) { - this.month -= 12; - this.year ++; - } - while(this.month < 0) { - this.month += 12; - this.year --; - } - } + while(this.month >= 12) { + this.month -= 12; + this.year ++; + } + while(this.month < 0) { + this.month += 12; + this.year --; + } + } - async doTick() { - this.advanceTime(1); - const timeout = 1000 / this.rate; - const start = new Date().getTime(); - if(this.thing) { - await this.thing.tick(); - } - const elapsed = new Date().getTime() - start; - const wait = Math.max(timeout - elapsed, 0); - if(this.paused) return; - setTimeout(this.doTick.bind(this), wait) - } + async doTick() { + this.advanceTime(1); + const timeout = 1000 / this.rate; + const start = new Date().getTime(); + if(this.thing) { + await this.thing.tick(); + } + const elapsed = new Date().getTime() - start; + const wait = Math.max(timeout - elapsed, 0); + if(this.paused) return; + setTimeout(this.doTick.bind(this), wait) + } } export interface Tickable { - tick: () => Promise + tick: () => Promise } \ No newline at end of file diff --git a/src/ui/Menu.ts b/src/ui/Menu.ts index b20ab99..4a5cb4a 100644 --- a/src/ui/Menu.ts +++ b/src/ui/Menu.ts @@ -2,29 +2,17 @@ import { Pawn } from '../Pawn.js'; import log from '../log.js'; import { menuPanel, Renderable } from './UI.js'; import { Game } from '../Game.js'; -import { ChopTreeTask } from '../tasks/ChopTreeTask.js'; -import { progressbar, stats, barCache } from '../Progressbar.js'; +import { progressbar, stats } from '../Progressbar.js'; import { Popup } from './Popup.js'; import mdns from '../multiplayer/mDNS.js'; -import { GiftPopup } from './GiftPopup.js'; -import { PawnDetails } from './PawnDetails.js'; -import { defaultTheme, getTheme } from './Theme.js'; +import { getTheme } from './Theme.js'; import { inspect } from 'util'; import PawnsView from './view/PawnsView.js'; import InventoryView from './view/InventoryView.js'; import MultiplayerView from './view/MultiplayerView.js'; +import { View } from './View.js'; -// TODO extract View -export abstract class View implements Renderable, KeypressAcceptor { - abstract render(): void; - abstract keypress(key: {full: string}): void; - - static PAWNS: View = new PawnsView(); - static INVENTORY: View = new InventoryView(); - static MULTIPLAYER: View = new MultiplayerView(); - - name: string -} +const clamp = (min, max, value) => Math.min(Math.max(value, min), max); // TODO move KeypressAcceptor to ui something idk export interface KeypressAcceptor { @@ -34,16 +22,35 @@ export interface KeypressAcceptor { export class Menu implements Renderable { trees: number = 10; - view: View = View.PAWNS; + viewIndex: number = 0; + views: View[] = [ + new PawnsView(), + new InventoryView(), + new MultiplayerView() + ] + + get view() { + return this.views[this.viewIndex]; + } + + advanceView() { + this.viewIndex ++; + this.viewIndex = clamp(0, this.views.length - 1, this.viewIndex); + } + + regressView() { + this.viewIndex --; + this.viewIndex = clamp(0, this.views.length - 1, this.viewIndex); + } constructor() { menuPanel.on('keypress', (evt, key) => { log.info('keypress', key); if (key.full === 'left') { - this.view = View[Object.keys(View)[Math.min(Math.max(Object.values(View).indexOf(this.view) - 1, 0), Object.keys(View).length - 1)]] + this.regressView(); } else if (key.full === 'right') { - this.view = View[Object.keys(View)[Math.min(Math.max(Object.values(View).indexOf(this.view) + 1, 0), Object.keys(View).length - 1)]] + this.advanceView(); // debugging hotkeys } else if (key.full === '1') { @@ -116,7 +123,7 @@ export class Menu implements Renderable { } }).join(''); })()}{/center}\n\n${(() => { - this.view.view.render(); + this.view.render(); })()}` } diff --git a/src/ui/Popup.ts b/src/ui/Popup.ts index 2e2c4d0..ba2867b 100644 --- a/src/ui/Popup.ts +++ b/src/ui/Popup.ts @@ -15,12 +15,15 @@ export class Popup { this.box = blessed.box({ top: 'center', left: 'center', - width: 'shrink', + width: '100%', height: 'shrink', - content: getTheme().normal(content) + `\n\n{|}` + getTheme().hotkey('enter') + getTheme().normal(`: Okay `), + // content: getTheme().normal(content) + `\n\n{|}` + getTheme().hotkey('enter') + getTheme().normal(`: Okay `), tags: true, ...boxStyle(), }); + let stuff = ''; + for(let i = 16; i < 232; i ++) stuff += chalk.bgAnsi256(i).black(` ${i.toString().padStart(3, ' ')} ${(i-15)%18===0?'\n':''}`) + this.box.setContent(stuff) this.box.on('keypress', (evt, key) => { if(key.full === 'escape' || key.full === 'enter') { Game.current.clock.start(); diff --git a/src/ui/Theme.ts b/src/ui/Theme.ts index 792e709..6eb2f38 100644 --- a/src/ui/Theme.ts +++ b/src/ui/Theme.ts @@ -1,77 +1,89 @@ -import chalk from "chalk"; +// blessed doesnt know QUITE how to deal with 16m color modes +// it will always downsample them to 256. which is fine, but +// blessed's algorithm sucks, and comes out with incorrect +// mappings for certain colors. Instead of dealing with that, +// here, we simply tell chalk to always output ansi256 codes +// instead of upsampling them to 16m codes. +import chalk from 'chalk'; +chalk.level = 2; type StyleFunction = (text: string) => string; export type Theme = { - header: StyleFunction, - subheader: StyleFunction, - normal: StyleFunction, - selected: StyleFunction, - hotkey: StyleFunction, - tab: { - normal: StyleFunction, - selected: StyleFunction - }, - border: { - focused: string, - normal: string - }, - progressBar: { - indicator: { - critical: StyleFunction, - warning: StyleFunction, - normal: StyleFunction, - excellent: StyleFunction, - buckets: [number, number, number] - }, - normal: StyleFunction - } + header: StyleFunction, + subheader: StyleFunction, + normal: StyleFunction, + selected: StyleFunction, + hotkey: StyleFunction, + tab: { + normal: StyleFunction, + selected: StyleFunction + }, + border: { + focused: string, + normal: string + }, + progressBar: { + indicator: { + critical: StyleFunction, + warning: StyleFunction, + normal: StyleFunction, + excellent: StyleFunction, + buckets: [number, number, number] + }, + normal: StyleFunction + }, + status: { + idle: StyleFunction, + self: StyleFunction, + + } } export const defaultTheme: Theme = { - header: chalk.ansi256(255).bold, - subheader: chalk.ansi256(243).bold, - normal: chalk.ansi256(243), - selected: chalk.ansi256(250), - hotkey: chalk.ansi256(40), - tab: { - normal: chalk.ansi256(117), - selected: chalk.ansi256(117).inverse - }, - border: { - focused: '#ffffff', - normal: '#222222' - }, - progressBar: { - indicator: { - critical: chalk.bgAnsi256(235).ansi256(88), - warning: chalk.bgAnsi256(235).ansi256(202), - normal: chalk.bgAnsi256(235).ansi256(70), - excellent: chalk.bgAnsi256(235).ansi256(87), - buckets: [.1, .25, .95] - }, - normal: chalk.bgAnsi256(235).ansi256(243) - } + header: chalk.ansi256(255).bold, + subheader: chalk.ansi256(243).bold, + normal: chalk.ansi256(243), + selected: chalk.ansi256(250), + hotkey: chalk.ansi256(40), + tab: { + normal: chalk.ansi256(117), + selected: chalk.ansi256(117).inverse + }, + border: { + focused: '#ffffff', + normal: '#222222' + }, + progressBar: { + indicator: { + critical: chalk.bgAnsi256(235).ansi256(88), + warning: chalk.bgAnsi256(235).ansi256(202), + normal: chalk.bgAnsi256(235).ansi256(70), + excellent: chalk.bgAnsi256(235).ansi256(87), + buckets: [.1, .25, .95] + }, + normal: chalk.bgAnsi256(235).ansi256(243) + } } const debugStyle = chalk.ansi256(213); export const debugTheme: Theme = { - header: debugStyle.inverse, - subheader: debugStyle, - normal: debugStyle, - selected: debugStyle.inverse, - hotkey: debugStyle, - tab: { - normal: debugStyle, - selected: debugStyle.inverse, - }, - border: { - focused: '#ff88ff', - normal: '#ff00ff' - }, - progressBar: defaultTheme.progressBar + header: debugStyle.inverse, + subheader: debugStyle, + normal: debugStyle, + selected: debugStyle.inverse, + hotkey: debugStyle, + tab: { + normal: debugStyle, + selected: debugStyle.inverse, + }, + border: { + focused: '#ff88ff', + normal: '#ff00ff' + }, + progressBar: defaultTheme.progressBar } export function getTheme(): Theme { - return defaultTheme; + return defaultTheme; } \ No newline at end of file diff --git a/src/ui/View.ts b/src/ui/View.ts new file mode 100644 index 0000000..d7b278e --- /dev/null +++ b/src/ui/View.ts @@ -0,0 +1,9 @@ +import { Renderable } from './UI.js'; +import { KeypressAcceptor } from './Menu.js'; + +export abstract class View implements Renderable, KeypressAcceptor { + abstract render(): void; + abstract keypress(key: { full: string; }): void; + + name: string; +} diff --git a/src/ui/view/InventoryView.ts b/src/ui/view/InventoryView.ts index 4b6fffc..77d2fe4 100644 --- a/src/ui/view/InventoryView.ts +++ b/src/ui/view/InventoryView.ts @@ -1,6 +1,10 @@ -import { View } from "../Menu"; +import { View } from "../View.js"; export default class InventoryView extends View { + constructor() { + super(); + this.name = 'Inventory'; + } keypress: (key: { full: string; }) => void; render() { void 0 }; } \ No newline at end of file diff --git a/src/ui/view/MultiplayerView.ts b/src/ui/view/MultiplayerView.ts index ae7ee0b..30b1929 100644 --- a/src/ui/view/MultiplayerView.ts +++ b/src/ui/view/MultiplayerView.ts @@ -1,7 +1,10 @@ -import { KeypressAcceptor, View } from "../Menu"; -import { Renderable } from "../UI"; +import { View } from "../View.js"; export default class MultiplayerView extends View { + constructor() { + super(); + this.name = 'Multiplayer'; + } keypress: (key: { full: string; }) => void; render() { void 0 }; } \ No newline at end of file diff --git a/src/ui/view/PawnsView.ts b/src/ui/view/PawnsView.ts index e6d19d4..b46f8c8 100644 --- a/src/ui/view/PawnsView.ts +++ b/src/ui/view/PawnsView.ts @@ -1,4 +1,4 @@ -import { View } from "../Menu.js"; +import { View } from "../View.js"; export default class PawnsView extends View { constructor() { diff --git a/yarn.lock b/yarn.lock index da32f0f..8618512 100644 --- a/yarn.lock +++ b/yarn.lock @@ -210,6 +210,7 @@ multicast-dns@^6.0.1: neo-blessed@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/neo-blessed/-/neo-blessed-0.2.0.tgz#30f9495fdd104494402b62c6273a9c9b82de4f2b" + integrity sha512-C2kC4K+G2QnNQFXUIxTQvqmrdSIzGTX1ZRKeDW6ChmvPRw8rTkTEJzbEQHiHy06d36PCl/yMOCjquCRV8SpSQw== object-is@^1.0.1: version "1.1.5"