renderer plus working purchasable resource
This commit is contained in:
parent
88bc020f1a
commit
65fe779d9a
|
@ -0,0 +1,13 @@
|
|||
all: lint build
|
||||
|
||||
build:
|
||||
tsc
|
||||
|
||||
lint:
|
||||
tslint --project .
|
||||
|
||||
clean:
|
||||
rm -f public/js/irreligious.js
|
||||
|
||||
run:
|
||||
firefox public/index.html
|
|
@ -0,0 +1,10 @@
|
|||
#irreligious-game {
|
||||
overflow: auto;
|
||||
}
|
||||
div.resource {
|
||||
float: left;
|
||||
border: 2px solid black;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
padding: 5px 10px;
|
||||
}
|
|
@ -6,5 +6,6 @@
|
|||
<script src='js/irreligious.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='irreligious-game'></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
21
src/main.ts
21
src/main.ts
|
@ -1,25 +1,38 @@
|
|||
/// <reference path="./model/GameConfig.ts" />
|
||||
/// <reference path="./model/GameState.ts" />
|
||||
/// <reference path="./render/DebugRenderer.ts" />
|
||||
/// <reference path="./render/IRenderer.ts" />
|
||||
|
||||
let globalStartTime = 0;
|
||||
let globalTimeout: number = null;
|
||||
|
||||
function gameLoop (state: GameState): void {
|
||||
function gameLoop (state: GameState, renderer: IRenderer): void {
|
||||
// figure out how much actual time has passed
|
||||
const elapsedTime = globalStartTime > 0
|
||||
? (new Date()).getTime() - globalStartTime : 0;
|
||||
|
||||
renderer.render(state);
|
||||
state.advance(elapsedTime);
|
||||
|
||||
// run again in 1sec
|
||||
globalStartTime = (new Date()).getTime();
|
||||
setTimeout(() => gameLoop(state), 1000);
|
||||
globalTimeout = setTimeout(() => gameLoop(state, renderer), 1000);
|
||||
}
|
||||
|
||||
// run with default config at startup
|
||||
(() => {
|
||||
const config = new GameConfig();
|
||||
const renderer = new DebugRenderer();
|
||||
const state = config.generateState();
|
||||
|
||||
if (document.readyState !== 'loading') gameLoop(state);
|
||||
else document.addEventListener('DOMContentLoaded', () => gameLoop(state));
|
||||
// re-run main loop immediately on user clicks
|
||||
state.onResourceClick = () => {
|
||||
if (globalTimeout !== null) {
|
||||
clearTimeout(globalTimeout);
|
||||
gameLoop(state, renderer);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') gameLoop(state, renderer);
|
||||
else document.addEventListener('DOMContentLoaded', () => gameLoop(state, renderer));
|
||||
})();
|
||||
|
|
|
@ -1,13 +1,51 @@
|
|||
/// <reference path="./IResource.ts" />
|
||||
/// <reference path="./resource/IResource.ts" />
|
||||
|
||||
class GameState {
|
||||
private _resources: {[key: string]: IResource} = {};
|
||||
private _resources: Record<string, IResource> = { };
|
||||
private _resourceKeys: string[] = [];
|
||||
|
||||
public onResourceClick: () => void = null;
|
||||
|
||||
public addResource (key: string, resource: IResource): void {
|
||||
this._resourceKeys.push(key);
|
||||
this._resources[key] = resource;
|
||||
}
|
||||
|
||||
public advance (time: number): void {
|
||||
console.log(`Advancing state by ${time}ms...`);
|
||||
for (const rkey of this._resourceKeys) {
|
||||
if (this._resources[rkey].advanceAction !== null) {
|
||||
this._resources[rkey].advanceAction(time, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getResources (): string[] {
|
||||
return this._resourceKeys;
|
||||
}
|
||||
|
||||
public getResource (key: string): IResource {
|
||||
return this._resources[key];
|
||||
}
|
||||
|
||||
public performClick (resourceKey: string): void {
|
||||
if (this._resources[resourceKey].clickAction !== null) {
|
||||
this._resources[resourceKey].clickAction(this);
|
||||
if (this.onResourceClick !== null) {
|
||||
this.onResourceClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public deductCost (cost: { [rkey: string]: number }): boolean {
|
||||
if (cost === null || Object.keys(cost) === null) return true;
|
||||
for (const rkey of Object.keys(cost)) {
|
||||
if (this._resources[rkey].value < cost[rkey]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const rkey of Object.keys(cost)) {
|
||||
this._resources[rkey].value -= cost[rkey];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
enum ResourceType {
|
||||
Religion,
|
||||
Consumable
|
||||
}
|
||||
|
||||
interface IResource {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
resourceType: ResourceType;
|
||||
value: number;
|
||||
max?: number;
|
||||
unlocked: boolean;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/// <reference path="../IResource.ts" />
|
||||
|
||||
class Consumable implements IResource {
|
||||
public readonly resourceType = ResourceType.Consumable;
|
||||
|
||||
constructor (
|
||||
public readonly name: string,
|
||||
public readonly description: string,
|
||||
public value: number,
|
||||
public unlocked: boolean,
|
||||
public max?: number,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
enum ResourceType {
|
||||
Religion,
|
||||
Consumable,
|
||||
Infrastructure
|
||||
}
|
||||
|
||||
interface IResource {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
resourceType: ResourceType;
|
||||
value: number;
|
||||
max?: number;
|
||||
unlocked: boolean;
|
||||
|
||||
clickText: string;
|
||||
clickDescription: string;
|
||||
clickAction: (state: GameState) => void;
|
||||
|
||||
advanceAction: (time: number, state: GameState) => void;
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
/// <reference path="./Consumable.ts" />
|
||||
/// <reference path="./Purchasable.ts" />
|
||||
|
||||
class Money extends Consumable {
|
||||
class Money extends Purchasable {
|
||||
constructor (
|
||||
public value: number,
|
||||
public max: number
|
||||
) {
|
||||
super('Money', 'Used to purchase goods and services.',
|
||||
value, true, max);
|
||||
super('Money', 'Used to purchase goods and services.', null);
|
||||
this.clickText = 'Beg';
|
||||
this.clickDescription = 'Alms for the poor.';
|
||||
this.unlocked = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="../IResource.ts" />
|
||||
/// <reference path="./IResource.ts" />
|
||||
|
||||
class PlayerOrganization implements IResource {
|
||||
public readonly name = 'Player';
|
||||
|
@ -7,5 +7,12 @@ class PlayerOrganization implements IResource {
|
|||
public readonly max?: number = null;
|
||||
public readonly unlocked = true;
|
||||
|
||||
public readonly clickText: string = null;
|
||||
public readonly clickDescription: string = null;
|
||||
public readonly clickAction: () => void = null;
|
||||
|
||||
public readonly advanceAction: (time: number) => void = null;
|
||||
|
||||
public value = 0;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/// <reference path="./IResource.ts" />
|
||||
|
||||
abstract class Purchasable implements IResource {
|
||||
public readonly resourceType = ResourceType.Infrastructure;
|
||||
public readonly max?: number = null;
|
||||
public value: number = 0;
|
||||
public unlocked: boolean = false;
|
||||
|
||||
public clickText: string = "Purchase";
|
||||
public clickDescription: string = null;
|
||||
|
||||
constructor (
|
||||
public readonly name: string,
|
||||
public readonly description: string,
|
||||
private _cost: { [key: string]: number }
|
||||
) { }
|
||||
|
||||
public clickAction (state: GameState) {
|
||||
if (this.max !== null && this.value >= this.max) return;
|
||||
if (state.deductCost(this._cost)) {
|
||||
this.value += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public advanceAction (time: number, state: GameState) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
/// <reference path="../IResource.ts" />
|
||||
/// <reference path="./IResource.ts" />
|
||||
|
||||
class Religion implements IResource {
|
||||
public readonly resourceType = ResourceType.Religion;
|
||||
public readonly max?: number = null;
|
||||
public readonly unlocked = true;
|
||||
public readonly clickText: string = null;
|
||||
public readonly clickDescription: string = null;
|
||||
public readonly clickAction: () => void = null;
|
||||
public readonly advanceAction: (time: number) => void = null;
|
||||
|
||||
constructor (
|
||||
public readonly name: string,
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/// <reference path="../model/GameState.ts" />
|
||||
/// <reference path="./IRenderer.ts" />
|
||||
|
||||
class DebugRenderer implements IRenderer {
|
||||
private _initialized = false;
|
||||
|
||||
public render (state: GameState) {
|
||||
const container = document.getElementById('irreligious-game');
|
||||
if (container === null) {
|
||||
console.log('Cannot find #irreligious-game container.'); // tslint:disable-line
|
||||
} else {
|
||||
if (!this._initialized) {
|
||||
this._initialized = true;
|
||||
const style = document.createElement('link');
|
||||
style.setAttribute('rel', 'stylesheet');
|
||||
style.setAttribute('href', 'css/debugger.css');
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
head.appendChild(style);
|
||||
}
|
||||
for (const rkey of state.getResources()) {
|
||||
const resource = state.getResource(rkey);
|
||||
if (resource.unlocked) {
|
||||
let el = document.getElementById(`r_${rkey}`);
|
||||
if (el === null) {
|
||||
el = document.createElement('div');
|
||||
el.className = 'resource';
|
||||
el.id = `r_${rkey}`;
|
||||
let content = `
|
||||
<span class='resourceTitle' title='${resource.description}'>${resource.name}</span><br>
|
||||
<span class='value'></span><span class='max'></span>
|
||||
`;
|
||||
if (resource.clickText !== null) {
|
||||
content += `<br><button class='btn' title='${resource.clickDescription}'>${resource.clickText}</button>`;
|
||||
}
|
||||
el.innerHTML = content;
|
||||
container.appendChild(el);
|
||||
if (resource.clickAction !== null) {
|
||||
const btn = el.getElementsByClassName('btn')[0];
|
||||
btn.addEventListener('click', () => state.performClick(rkey));
|
||||
}
|
||||
}
|
||||
const elV = el.getElementsByClassName('value')[0];
|
||||
const elT = el.getElementsByClassName('max')[0];
|
||||
elV.innerHTML = this.formatNumber(resource.value, 1);
|
||||
elT.innerHTML = resource.max !== null ? ` / ${this.formatNumber(resource.max, 2)}` : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private formatNumber (num: number, digits: number): string {
|
||||
const lookup = [
|
||||
{ value: 1, symbol: "" },
|
||||
{ value: 1e3, symbol: "k" },
|
||||
{ value: 1e6, symbol: "M" },
|
||||
{ value: 1e9, symbol: "G" },
|
||||
{ value: 1e12, symbol: "T" },
|
||||
{ value: 1e15, symbol: "P" },
|
||||
{ value: 1e18, symbol: "E" }
|
||||
];
|
||||
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
|
||||
const item = lookup.slice().reverse().find((i) => num >= i.value);
|
||||
return item
|
||||
? (num / item.value).toFixed(digits).replace(rx, "$1") + item.symbol
|
||||
: "0";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/// <reference path="../model/GameState.ts" />
|
||||
|
||||
interface IRenderer {
|
||||
render (state: GameState);
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "ES5",
|
||||
"target": "ES6",
|
||||
"module": "none"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"no-reference": false,
|
||||
"space-before-function-paren": true,
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-rest-spread",
|
||||
"check-type",
|
||||
"check-typecast",
|
||||
"check-type-operator",
|
||||
"check-preblock",
|
||||
"check-postbrace"
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue