submenus, progressbar stuff inventory
parent
83821b4668
commit
f2023c7af3
|
|
@ -8,7 +8,6 @@
|
||||||
"@types/blessed": "^0.1.17",
|
"@types/blessed": "^0.1.17",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"frigid": "^1.3.3",
|
|
||||||
"logger": "^0.0.1",
|
"logger": "^0.0.1",
|
||||||
"neo-blessed": "^0.2.0",
|
"neo-blessed": "^0.2.0",
|
||||||
"printable-characters": "^1.0.42",
|
"printable-characters": "^1.0.42",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@ import { Task } from './Task.js';
|
||||||
export class ChopTreeTask extends Task {
|
export class ChopTreeTask extends Task {
|
||||||
work = 100;
|
work = 100;
|
||||||
|
|
||||||
|
constructor(count) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
reward() {
|
reward() {
|
||||||
Game.current.inv.add(Item.LOG, 1);
|
Game.current.inv.add(Item.LOG, 1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,15 @@ export class Game extends Frigid implements Tickable, Renderable {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctor () {
|
ctor () {
|
||||||
|
game = this;
|
||||||
this.pawns ??= [];
|
this.pawns ??= [];
|
||||||
this.selected ??= this.pawns[0] || null;
|
this.selected ??= this.pawns[0] || null;
|
||||||
this.menu = new Menu();
|
this.menu = new Menu();
|
||||||
this.board ??= new TaskList();
|
this.board ??= new TaskList();
|
||||||
this.board.game = this;
|
|
||||||
this.inventory ??= new Inventory();
|
this.inventory ??= new Inventory();
|
||||||
this.clock ??= new Time();
|
this.clock ??= new Time();
|
||||||
this.clock.thing = this;
|
this.clock.thing = this;
|
||||||
this.clock.start();
|
this.clock.start();
|
||||||
game = this;
|
|
||||||
render(this);
|
render(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,37 @@
|
||||||
import { Serializable } from 'frigid';
|
import { Serializable } from 'frigid';
|
||||||
import { Item } from './Item.js';
|
import { Game } from './Game.js';
|
||||||
import { SMap } from './SMap.js';
|
import { Item, ItemState } from './Item.js';
|
||||||
import { ItemID } from './index.js';
|
import { Renderable } from './UI.js';
|
||||||
|
|
||||||
export class Inventory extends Serializable {
|
export class Inventory extends Serializable implements Renderable {
|
||||||
items = new SMap<ItemID, number>();
|
items: ItemState[];
|
||||||
|
|
||||||
|
ctor() {
|
||||||
|
this.items ??= [];
|
||||||
|
}
|
||||||
|
|
||||||
static serializationDependencies() {
|
static serializationDependencies() {
|
||||||
return [SMap];
|
return [ItemState];
|
||||||
}
|
}
|
||||||
|
|
||||||
add(item: Item, qty: number = 1) {
|
add(item: Item, qty: number = 1) {
|
||||||
const id = item.id;
|
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) {
|
render() {
|
||||||
const id = item.id;
|
return this.items.map(item => item.render()).join('\n');
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
src/Item.ts
30
src/Item.ts
|
|
@ -1,5 +1,9 @@
|
||||||
import { Serializable } from 'frigid';
|
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
|
// ITEMS SHALL BE SINGULAR
|
||||||
export class Item extends Serializable {
|
export class Item extends Serializable {
|
||||||
|
|
@ -13,8 +17,30 @@ export class Item extends Serializable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setId(id) {
|
setId(id: ItemID) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
items.set(this.id, this);
|
||||||
return 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} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/Menu.ts
145
src/Menu.ts
|
|
@ -6,9 +6,25 @@ import { Game } from './Game.js';
|
||||||
import { Task } from './Task.js';
|
import { Task } from './Task.js';
|
||||||
import { ChopTreeTask } from './ChopTreeTask.js';
|
import { ChopTreeTask } from './ChopTreeTask.js';
|
||||||
import { progressbar } from './Progressbar.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 {
|
export class Menu implements Renderable {
|
||||||
|
|
||||||
|
trees: number = 10;
|
||||||
|
subMenu: SubMenu = SubMenu.NONE;
|
||||||
|
view: View = View.PAWNS;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
screen.on('keypress', (evt, key) => {
|
screen.on('keypress', (evt, key) => {
|
||||||
log.info('keypress', key);
|
log.info('keypress', key);
|
||||||
|
|
@ -18,22 +34,41 @@ export class Menu implements Renderable {
|
||||||
Game.current.advanceSelection(-1);
|
Game.current.advanceSelection(-1);
|
||||||
} else if (key.full === 'down') {
|
} else if (key.full === 'down') {
|
||||||
Game.current.advanceSelection(1);
|
Game.current.advanceSelection(1);
|
||||||
} else if (key.full === 'c') {
|
} else if (key.full === 'left') {
|
||||||
Game.current.pawns.push(new Pawn());
|
this.view = View[Object.keys(View)[Math.min(Math.max(Object.values(View).indexOf(this.view) - 1, 0), Object.keys(View).length - 1)]]
|
||||||
Game.current.sync();
|
} else if (key.full === 'right') {
|
||||||
} else if (key.full === 'x') {
|
this.view = View[Object.keys(View)[Math.min(Math.max(Object.values(View).indexOf(this.view) + 1, 0), Object.keys(View).length - 1)]]
|
||||||
let i = 0;
|
} else if (key.full === 'q') {
|
||||||
for(const task of Game.current.board.tasks) {
|
this.subMenu = SubMenu.TREES;
|
||||||
setTimeout(_ => {
|
} else if (key.full === 'escape') {
|
||||||
Game.current.board.removeTask(task);
|
this.subMenu = SubMenu.NONE;
|
||||||
}, i * 100);
|
|
||||||
i ++;
|
|
||||||
}
|
|
||||||
Game.current.sync();
|
|
||||||
} else if (key.full === 't') {
|
|
||||||
const job: Task = new ChopTreeTask();
|
|
||||||
Game.current.board.addTask(job);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
// const pawn = new Pawn();
|
||||||
// Game.current.pawns.push(pawn);
|
// Game.current.pawns.push(pawn);
|
||||||
Game.current.sync();
|
Game.current.sync();
|
||||||
|
|
@ -41,15 +76,30 @@ export class Menu implements Renderable {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJobs() {
|
renderJobs() {
|
||||||
return (`\
|
|
||||||
${chalk.greenBright('t')}: Chop Trees
|
const colSpace = ((menuPanel.width - 2) / 2);
|
||||||
${chalk.greenBright('c')}: Create Pawn
|
|
||||||
${chalk.greenBright('x')}: Clear Tasks
|
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);
|
const idlers = Game.current.pawns.filter(pawn => pawn.idle);
|
||||||
return ` ${Game.current.clock.toString()}{|}Idle: ${idlers.length} `;
|
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() {
|
render() {
|
||||||
const width = menuPanel.width - 2;
|
const width = menuPanel.width - 2;
|
||||||
const hr = chalk.bold.black('━'.repeat(width));
|
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);
|
menuPanel.setContent(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,26 @@
|
||||||
import chalk from "chalk";
|
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 = [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'];
|
const chars = [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'];
|
||||||
let str = '';
|
let str = '';
|
||||||
for(let i = 0; i < width; i ++) {
|
for(let i = 0; i < width; i ++) {
|
||||||
const remainder = Math.floor(Math.min(Math.max(0, (completion * width) - i), 1) * 8);
|
const remainder = Math.floor(Math.min(Math.max(0, (completion * width) - i), 1) * 8);
|
||||||
const char = chars[remainder];
|
const char = chars[remainder];
|
||||||
str += style(char);
|
str += chalkFn(char);
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import chalk from 'chalk';
|
||||||
import { Pawn } from './Pawn.js';
|
import { Pawn } from './Pawn.js';
|
||||||
import { render, tasksPanel } from './UI.js';
|
import { render, tasksPanel } from './UI.js';
|
||||||
import { Game } from './Game.js';
|
import { Game } from './Game.js';
|
||||||
import { progressbar } from './Progressbar.js';
|
import { progressbar, ProgressbarStyle } from './Progressbar.js';
|
||||||
|
|
||||||
export class Task extends Serializable {
|
export class Task extends Serializable {
|
||||||
work = 0;
|
work = 0;
|
||||||
|
|
@ -53,7 +53,7 @@ export class Task extends Serializable {
|
||||||
const width = tasksPanel.width - 2;
|
const width = tasksPanel.width - 2;
|
||||||
const left = ' ' + this.title + ' ' + (this.worker?.toString() || chalk.bold.black('Queued'));
|
const left = ' ' + this.title + ' ' + (this.worker?.toString() || chalk.bold.black('Queued'));
|
||||||
const bar = width - 2;
|
const bar = width - 2;
|
||||||
return `${left}\n ${progressbar(this.completion, bar)}\n`;
|
return `${left}\n ${progressbar(this.completion, bar, ProgressbarStyle.progress)}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,12 @@ import { render, Renderable, tasksPanel } from './UI.js';
|
||||||
|
|
||||||
export class TaskList extends Serializable implements Renderable {
|
export class TaskList extends Serializable implements Renderable {
|
||||||
tasks: Task[] = [];
|
tasks: Task[] = [];
|
||||||
game: Game;
|
|
||||||
|
clear() {
|
||||||
|
for(const task of this.tasks) {
|
||||||
|
this.removeTask(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static serializationDependencies() {
|
static serializationDependencies() {
|
||||||
return [ChopTreeTask, Task];
|
return [ChopTreeTask, Task];
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { Game } from './Game.js';
|
import { Game } from './Game.js';
|
||||||
import { render } from './UI.js';
|
import { render } from './UI.js';
|
||||||
|
|
||||||
export type ItemID = string;
|
|
||||||
|
|
||||||
const game = Game.create('data/world01.json');
|
const game = Game.create('data/world01.json');
|
||||||
render(game);
|
render(game);
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,6 @@ faker@^5.5.3:
|
||||||
version "5.5.3"
|
version "5.5.3"
|
||||||
resolved "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz"
|
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:
|
has-flag@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue