submenus, progressbar stuff inventory

master
Valerie 2021-06-15 15:02:47 -04:00
parent 83821b4668
commit f2023c7af3
11 changed files with 208 additions and 55 deletions

View File

@ -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",

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<ItemID, number>();
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');
}
}

View File

@ -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<ItemID, Item>();
// 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} `;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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() {

View File

@ -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];

View File

@ -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);

View File

@ -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"