From f2023c7af31fff8030c4379adf7c14292c5c418b Mon Sep 17 00:00:00 2001 From: Valerie Date: Tue, 15 Jun 2021 15:02:47 -0400 Subject: [PATCH] submenus, progressbar stuff inventory --- package.json | 1 - src/ChopTreeTask.ts | 5 ++ src/Game.ts | 3 +- src/Inventory.ts | 42 ++++++++----- src/Item.ts | 30 ++++++++- src/Menu.ts | 147 +++++++++++++++++++++++++++++++++++++------- src/Progressbar.ts | 18 +++++- src/Task.ts | 4 +- src/TaskList.ts | 7 ++- src/index.ts | 2 - yarn.lock | 4 -- 11 files changed, 208 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index 6d7f1cf..0da8db6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "@types/blessed": "^0.1.17", "chalk": "^4.1.1", "faker": "^5.5.3", - "frigid": "^1.3.3", "logger": "^0.0.1", "neo-blessed": "^0.2.0", "printable-characters": "^1.0.42", diff --git a/src/ChopTreeTask.ts b/src/ChopTreeTask.ts index 662eb31..5292965 100644 --- a/src/ChopTreeTask.ts +++ b/src/ChopTreeTask.ts @@ -8,6 +8,11 @@ import { Task } from './Task.js'; export class ChopTreeTask extends Task { work = 100; + constructor(count) { + super(); + + } + reward() { Game.current.inv.add(Item.LOG, 1); } diff --git a/src/Game.ts b/src/Game.ts index e8350ae..8aa735b 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -53,16 +53,15 @@ export class Game extends Frigid implements Tickable, Renderable { } ctor () { + game = this; this.pawns ??= []; this.selected ??= this.pawns[0] || null; this.menu = new Menu(); this.board ??= new TaskList(); - this.board.game = this; this.inventory ??= new Inventory(); this.clock ??= new Time(); this.clock.thing = this; this.clock.start(); - game = this; render(this); } diff --git a/src/Inventory.ts b/src/Inventory.ts index 02aaedd..c5d94f4 100644 --- a/src/Inventory.ts +++ b/src/Inventory.ts @@ -1,29 +1,37 @@ import { Serializable } from 'frigid'; -import { Item } from './Item.js'; -import { SMap } from './SMap.js'; -import { ItemID } from './index.js'; +import { Game } from './Game.js'; +import { Item, ItemState } from './Item.js'; +import { Renderable } from './UI.js'; -export class Inventory extends Serializable { - items = new SMap(); +export class Inventory extends Serializable implements Renderable { + items: ItemState[]; + + ctor() { + this.items ??= []; + } static serializationDependencies() { - return [SMap]; + return [ItemState]; } add(item: Item, qty: number = 1) { const id = item.id; - this.ditem(id, qty); + const existingArr = this.items.filter(itemState => { + return itemState.itemId === id; + }); + let existing: ItemState = null; + if(existingArr.length === 1) { + existing = existingArr[0]; + } + if(existing) { + existing.qty += qty; + } else { + this.items.push(new ItemState(item, qty, {})); + } + Game.current.sync(); } - remove(item: Item, qty: number = 1) { - const id = item.id; - this.ditem(id, -qty); - } - - ditem(id, n) { - if (this.items.has(id)) - this.items.set(id, this.items.get(id) + n); - else - this.items.set(id, n); + render() { + return this.items.map(item => item.render()).join('\n'); } } diff --git a/src/Item.ts b/src/Item.ts index c3301d5..6d73d87 100644 --- a/src/Item.ts +++ b/src/Item.ts @@ -1,5 +1,9 @@ import { Serializable } from 'frigid'; -import { ItemID } from './index.js'; +import { Renderable } from './UI'; + +export type ItemID = string; + +const items = new Map(); // ITEMS SHALL BE SINGULAR export class Item extends Serializable { @@ -13,8 +17,30 @@ export class Item extends Serializable { return this; } - setId(id) { + setId(id: ItemID) { this.id = id; + items.set(this.id, this); return this; } } + +export class ItemState extends Serializable implements Renderable { + qty: number; + itemId: ItemID; + data: any; + + get item() { + return items.get(this.itemId); + } + + constructor(item: Item, amount: number, data: any) { + super(); + this.qty = amount; + this.itemId = item.id; + this.data = data; + } + + render() { + return ` ${this.item.name}{|}${this.qty} `; + } +} \ No newline at end of file diff --git a/src/Menu.ts b/src/Menu.ts index 27b6c4e..e240b55 100644 --- a/src/Menu.ts +++ b/src/Menu.ts @@ -6,9 +6,25 @@ import { Game } from './Game.js'; import { Task } from './Task.js'; import { ChopTreeTask } from './ChopTreeTask.js'; import { progressbar } from './Progressbar.js'; +import { inspect } from 'util'; + +enum SubMenu { + NONE = 'NONE', + TREES = 'TREES' +}; + +enum View { + PAWNS = 'Pawns', + INVENTORY = 'Inventory', + BUILDINGS = 'Buildings', +}; export class Menu implements Renderable { + trees: number = 10; + subMenu: SubMenu = SubMenu.NONE; + view: View = View.PAWNS; + constructor() { screen.on('keypress', (evt, key) => { log.info('keypress', key); @@ -18,22 +34,41 @@ export class Menu implements Renderable { Game.current.advanceSelection(-1); } else if (key.full === 'down') { Game.current.advanceSelection(1); - } else if (key.full === 'c') { - Game.current.pawns.push(new Pawn()); - Game.current.sync(); - } else if (key.full === 'x') { - let i = 0; - for(const task of Game.current.board.tasks) { - setTimeout(_ => { - Game.current.board.removeTask(task); - }, i * 100); - i ++; - } - Game.current.sync(); - } else if (key.full === 't') { - const job: Task = new ChopTreeTask(); - Game.current.board.addTask(job); + } else 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)]] + } 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)]] + } else if (key.full === 'q') { + this.subMenu = SubMenu.TREES; + } else if (key.full === 'escape') { + this.subMenu = SubMenu.NONE; } + + if(this.subMenu === SubMenu.TREES) { + if (key.full === '=') { + this.trees ++; + } else if (key.full === '-') { + this.trees --; + this.trees = Math.max(this.trees, 1); + } else if (key.full === '+') { + this.trees += 10; + } else if (key.full === '_') { + this.trees -= 10; + this.trees = Math.max(this.trees, 1); + } else if (key.full === 'enter') { + for(let i = 0; i < this.trees; i ++) { + Game.current.board.addTask(new ChopTreeTask(1)); + } + this.subMenu = SubMenu.NONE; + } + } else if(this.subMenu === SubMenu.NONE) { + if (key.full === 'z') { + Game.current.pawns.push(new Pawn()); + } else if (key.full === 'x') { + Game.current.board.clear(); + } + } + // const pawn = new Pawn(); // Game.current.pawns.push(pawn); Game.current.sync(); @@ -41,15 +76,30 @@ export class Menu implements Renderable { } renderJobs() { - return (`\ - ${chalk.greenBright('t')}: Chop Trees - ${chalk.greenBright('c')}: Create Pawn - ${chalk.greenBright('x')}: Clear Tasks + + const colSpace = ((menuPanel.width - 2) / 2); + + return (` Menus:${' '.repeat(colSpace - 8)}Actions:\n ${ + chalk.greenBright('q') + }: ${ + (this.subMenu !== SubMenu.TREES ? chalk.bold.black : _ => _)('Chop Trees') + }${ + ' '.repeat(colSpace - 15) + }${chalk.greenBright('z')}: ${ + (this.subMenu !== SubMenu.NONE ? chalk.bold.black : _ => _)('Create Pawn') + + }\n${ + ' '.repeat(colSpace) + }${ + chalk.greenBright('x') + }: ${ + (this.subMenu !== SubMenu.NONE ? chalk.bold.black : _ => _)('Clear Tasks') + }\ `); } - topBar() { + renderTopBar() { const idlers = Game.current.pawns.filter(pawn => pawn.idle); return ` ${Game.current.clock.toString()}{|}Idle: ${idlers.length} `; } @@ -70,10 +120,63 @@ export class Menu implements Renderable { }`; } + renderView() { + const colSpace = ((menuPanel.width - 2) / 2); + return `${ + // ' '.repeat(colSpace - 20) + '{center}' + }${(() => { + return Object.values(View).map(view => { + if(view === this.view) { + return chalk.cyan.inverse(` ${view} `); + } else { + return chalk.cyan(` ${view} `); + } + }).join(''); + })()}{/center}\n\n${(() => { + switch(this.view) { + case View.PAWNS: return this.renderPawns(); + case View.INVENTORY: return this.renderInv(); + } + })()}` + } + + renderInv() { + return Game.current.inv.render(); + } + + renderSubMenu() { + return `${(() => { + switch(this.subMenu) { + case SubMenu.NONE: + return `{center}${tags.bright}${tags.black.fg}* Select a menu above for options *`; + case SubMenu.TREES: + return this.renderTreesSubMenu(); + } + })()}`; + } + + renderTreesSubMenu() { + return [ + `{center}Chop Trees`, + `{left} ${chalk.greenBright('-=_+')}: ${this.trees}`, + `{left} ${chalk.greenBright('enter')}: Create Task`, + `{left} ${chalk.greenBright('escape')}: Cancel` + ].join('\n'); + } + render() { const width = menuPanel.width - 2; const hr = chalk.bold.black('━'.repeat(width)); - const content = [this.topBar(), hr, this.renderPawns(), hr, this.renderJobs()].join('\n'); + const content = [ + this.renderTopBar(), + hr, + this.renderView(), + hr, + this.renderJobs(), + hr, + this.renderSubMenu() + ].join('\n'); menuPanel.setContent(content); } -} +} \ No newline at end of file diff --git a/src/Progressbar.ts b/src/Progressbar.ts index d9c2795..dcac833 100644 --- a/src/Progressbar.ts +++ b/src/Progressbar.ts @@ -1,12 +1,26 @@ import chalk from "chalk"; -export function progressbar(completion, width, style = chalk.bold.bgRed.green) { +export enum ProgressbarStyle { + indicator = 'indicator', + progress = 'progress' +} + +export function progressbar(completion, width, style: ProgressbarStyle = ProgressbarStyle.indicator) { + let chalkFn + if(style === ProgressbarStyle.indicator) { + if(completion > 0.8) chalkFn = chalk.bgBlue.cyan; + else if(completion > 0.5) chalkFn = chalk.bgBlue.green; + else if(completion > 0.2) chalkFn = chalk.bgBlue.yellow; + else chalkFn = chalk.bgBlue.red; + } else if(style === ProgressbarStyle.progress) { + chalkFn = chalk.bgBlue.cyan; + } const chars = [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█']; let str = ''; for(let i = 0; i < width; i ++) { const remainder = Math.floor(Math.min(Math.max(0, (completion * width) - i), 1) * 8); const char = chars[remainder]; - str += style(char); + str += chalkFn(char); } return str; } \ No newline at end of file diff --git a/src/Task.ts b/src/Task.ts index 480e0cc..d128a83 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -4,7 +4,7 @@ import chalk from 'chalk'; import { Pawn } from './Pawn.js'; import { render, tasksPanel } from './UI.js'; import { Game } from './Game.js'; -import { progressbar } from './Progressbar.js'; +import { progressbar, ProgressbarStyle } from './Progressbar.js'; export class Task extends Serializable { work = 0; @@ -53,7 +53,7 @@ export class Task extends Serializable { const width = tasksPanel.width - 2; const left = ' ' + this.title + ' ' + (this.worker?.toString() || chalk.bold.black('Queued')); const bar = width - 2; - return `${left}\n ${progressbar(this.completion, bar)}\n`; + return `${left}\n ${progressbar(this.completion, bar, ProgressbarStyle.progress)}\n`; } get title() { diff --git a/src/TaskList.ts b/src/TaskList.ts index b6118f7..6eca62d 100644 --- a/src/TaskList.ts +++ b/src/TaskList.ts @@ -6,7 +6,12 @@ import { render, Renderable, tasksPanel } from './UI.js'; export class TaskList extends Serializable implements Renderable { tasks: Task[] = []; - game: Game; + + clear() { + for(const task of this.tasks) { + this.removeTask(task); + } + } static serializationDependencies() { return [ChopTreeTask, Task]; diff --git a/src/index.ts b/src/index.ts index 13e96e0..b455d2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,5 @@ import { Game } from './Game.js'; import { render } from './UI.js'; -export type ItemID = string; - const game = Game.create('data/world01.json'); render(game); diff --git a/yarn.lock b/yarn.lock index 0fbc73e..299986d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,10 +39,6 @@ faker@^5.5.3: version "5.5.3" resolved "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz" -frigid@^1.3.3: - version "1.3.5" - resolved "https://registry.yarnpkg.com/frigid/-/frigid-1.3.5.tgz#8712a349061b3f816758b45bc317d5f7c0b8aef0" - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"