purchasing resources with cost multipliers

This commit is contained in:
Rudis Muiznieks 2021-08-20 23:00:24 -05:00
parent 65fe779d9a
commit 80c74ed86b
10 changed files with 107 additions and 47 deletions

View File

@ -26,12 +26,12 @@ function gameLoop (state: GameState, renderer: IRenderer): void {
const state = config.generateState(); const state = config.generateState();
// re-run main loop immediately on user clicks // re-run main loop immediately on user clicks
state.onResourceClick = () => { state.onResourceClick.push(() => {
if (globalTimeout !== null) { if (globalTimeout !== null) {
clearTimeout(globalTimeout); clearTimeout(globalTimeout);
gameLoop(state, renderer); gameLoop(state, renderer);
} }
} });
if (document.readyState !== 'loading') gameLoop(state, renderer); if (document.readyState !== 'loading') gameLoop(state, renderer);
else document.addEventListener('DOMContentLoaded', () => gameLoop(state, renderer)); else document.addEventListener('DOMContentLoaded', () => gameLoop(state, renderer));

View File

@ -1,7 +1,7 @@
/// <reference path="./GameState.ts" /> /// <reference path="./GameState.ts" />
/// <reference path="./resource/Money.ts" /> /// <reference path="./resource/Money.ts" />
/// <reference path="./resource/PlayerOrganization.ts" />
/// <reference path="./resource/Religion.ts" /> /// <reference path="./resource/Religion.ts" />
/// <reference path="./resource/SavingsBonds.ts" />
class GameConfig { class GameConfig {
public worldPopulation: number = 790000000; public worldPopulation: number = 790000000;
@ -20,7 +20,8 @@ class GameConfig {
const state = new GameState(); const state = new GameState();
// create player organization // create player organization
state.addResource('plorg', new PlayerOrganization()); state.addResource('plorg', new Religion(
'Player', 'In you they trust.', 0));
// create world religions // create world religions
state.addResource('xtian', new Religion( state.addResource('xtian', new Religion(
@ -55,8 +56,9 @@ class GameConfig {
'Non-Religious', 'Atheists and agnostics.', 'Non-Religious', 'Atheists and agnostics.',
this.relNoneShare * this.worldPopulation)); this.relNoneShare * this.worldPopulation));
// add crafting resources // add purchasable resources
state.addResource('money', new Money(0, 1000)); state.addResource('money', new Money(0, 1000));
state.addResource('bonds', new SavingsBonds(0));
return state; return state;
} }

View File

@ -4,7 +4,7 @@ class GameState {
private _resources: Record<string, IResource> = { }; private _resources: Record<string, IResource> = { };
private _resourceKeys: string[] = []; private _resourceKeys: string[] = [];
public onResourceClick: () => void = null; public onResourceClick: (() => void)[] = [];
public addResource (key: string, resource: IResource): void { public addResource (key: string, resource: IResource): void {
this._resourceKeys.push(key); this._resourceKeys.push(key);
@ -30,22 +30,28 @@ class GameState {
public performClick (resourceKey: string): void { public performClick (resourceKey: string): void {
if (this._resources[resourceKey].clickAction !== null) { if (this._resources[resourceKey].clickAction !== null) {
this._resources[resourceKey].clickAction(this); this._resources[resourceKey].clickAction(this);
if (this.onResourceClick !== null) { for (const callback of this.onResourceClick) {
this.onResourceClick(); callback();
} }
} }
} }
public deductCost (cost: { [rkey: string]: number }): boolean { public deductCost (cost: { [rkey: string]: number }): boolean {
if (cost === null || Object.keys(cost) === null) return true; if (cost === null || Object.keys(cost) === null) return true;
for (const rkey of Object.keys(cost)) { if (!this.isPurchasable(cost)) return false;
if (this._resources[rkey].value < cost[rkey]) {
return false;
}
}
for (const rkey of Object.keys(cost)) { for (const rkey of Object.keys(cost)) {
this._resources[rkey].value -= cost[rkey]; this._resources[rkey].value -= cost[rkey];
} }
return true; return true;
} }
public isPurchasable (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;
}
}
return true;
}
} }

View File

@ -11,7 +11,9 @@ interface IResource {
resourceType: ResourceType; resourceType: ResourceType;
value: number; value: number;
max?: number; max?: number;
unlocked: boolean; cost: { [key: string]: number };
isUnlocked: (state: GameState) => boolean;
clickText: string; clickText: string;
clickDescription: string; clickDescription: string;

View File

@ -5,9 +5,12 @@ class Money extends Purchasable {
public value: number, public value: number,
public max: number public max: number
) { ) {
super('Money', 'Used to purchase goods and services.', null); super('Money', 'Used to purchase goods and services.');
this.clickText = 'Beg'; this.clickText = 'Beg';
this.clickDescription = 'Alms for the poor.'; this.clickDescription = 'Alms for the poor.';
this.unlocked = true; }
public isUnlocked (state: GameState): boolean {
return true;
} }
} }

View File

@ -1,18 +0,0 @@
/// <reference path="./IResource.ts" />
class PlayerOrganization implements IResource {
public readonly name = 'Player';
public readonly description = 'In you they trust.';
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;
public value = 0;
}

View File

@ -4,25 +4,36 @@ abstract class Purchasable implements IResource {
public readonly resourceType = ResourceType.Infrastructure; public readonly resourceType = ResourceType.Infrastructure;
public readonly max?: number = null; public readonly max?: number = null;
public value: number = 0; public value: number = 0;
public unlocked: boolean = false;
public clickText: string = "Purchase"; public clickText: string = 'Purchase';
public clickDescription: string = null; public clickDescription: string = 'Purchase';
public cost: { [key: string]: number } = null;
protected _costMultiplier: { [key: string]: number } = null;
constructor ( constructor (
public readonly name: string, public readonly name: string,
public readonly description: string, public readonly description: string
private _cost: { [key: string]: number }
) { } ) { }
public clickAction (state: GameState) { public clickAction (state: GameState) {
if (this.max !== null && this.value >= this.max) return; if (this.max !== null && this.value >= this.max) return;
if (state.deductCost(this._cost)) { if (state.deductCost(this.cost)) {
this.value += 1; this.value += 1;
if (this._costMultiplier !== null
&& Object.keys(this._costMultiplier !== null)) {
for (const rkey of Object.keys(this._costMultiplier)) {
this.cost[rkey] *= this._costMultiplier[rkey];
}
}
} }
} }
public advanceAction (time: number, state: GameState) { public advanceAction (time: number, state: GameState): void {
// do nothing return;
}
public isUnlocked (state: GameState): boolean {
return false;
} }
} }

View File

@ -3,16 +3,19 @@
class Religion implements IResource { class Religion implements IResource {
public readonly resourceType = ResourceType.Religion; public readonly resourceType = ResourceType.Religion;
public readonly max?: number = null; public readonly max?: number = null;
public readonly unlocked = true;
public readonly clickText: string = null; public readonly clickText: string = null;
public readonly clickDescription: string = null; public readonly clickDescription: string = null;
public readonly clickAction: () => void = null; public readonly clickAction: () => void = null;
public readonly advanceAction: (time: number) => void = null; public readonly advanceAction: (time: number) => void = null;
public readonly cost: { [key: string]: number } = null;
constructor ( constructor (
public readonly name: string, public readonly name: string,
public readonly description: string, public readonly description: string,
public value: number, public value: number,
) { ) { }
public isUnlocked (state: GameState): boolean {
return true;
} }
} }

View File

@ -0,0 +1,23 @@
/// <reference path="./Purchasable.ts" />
class SavingsBonds extends Purchasable {
public max?: number = null;
private _isUnlocked = false;
constructor (
public value: number,
) {
super('Savings Bonds', 'Grows money by a small amount over time.');
this.cost = { 'money': 25 };
this._costMultiplier = { 'money': 1.1 };
}
public isUnlocked (state: GameState): boolean {
if (this._isUnlocked) return true;
if (state.getResource('money').value >= 25) {
this._isUnlocked = true;
return true;
}
return false;
}
}

View File

@ -3,6 +3,7 @@
class DebugRenderer implements IRenderer { class DebugRenderer implements IRenderer {
private _initialized = false; private _initialized = false;
private _handleClick = true;
public render (state: GameState) { public render (state: GameState) {
const container = document.getElementById('irreligious-game'); const container = document.getElementById('irreligious-game');
@ -11,15 +12,17 @@ class DebugRenderer implements IRenderer {
} else { } else {
if (!this._initialized) { if (!this._initialized) {
this._initialized = true; this._initialized = true;
state.onResourceClick.push(() => this._handleClick = true);
const style = document.createElement('link'); const style = document.createElement('link');
style.setAttribute('rel', 'stylesheet'); style.setAttribute('rel', 'stylesheet');
style.setAttribute('href', 'css/debugger.css'); style.setAttribute('href', 'css/debugger.css');
const head = document.getElementsByTagName('head')[0]; const head = document.getElementsByTagName('head')[0];
head.appendChild(style); head.appendChild(style);
} }
for (const rkey of state.getResources()) { const rkeys = state.getResources();
for (const rkey of rkeys) {
const resource = state.getResource(rkey); const resource = state.getResource(rkey);
if (resource.unlocked) { if (resource.isUnlocked(state)) {
let el = document.getElementById(`r_${rkey}`); let el = document.getElementById(`r_${rkey}`);
if (el === null) { if (el === null) {
el = document.createElement('div'); el = document.createElement('div');
@ -27,11 +30,14 @@ class DebugRenderer implements IRenderer {
el.id = `r_${rkey}`; el.id = `r_${rkey}`;
let content = ` let content = `
<span class='resourceTitle' title='${resource.description}'>${resource.name}</span><br> <span class='resourceTitle' title='${resource.description}'>${resource.name}</span><br>
<span class='value'></span><span class='max'></span> <span class='value'></span><span class='max'></span><span class='inc'></span>
`; `;
if (resource.clickText !== null) { if (resource.clickText !== null) {
content += `<br><button class='btn' title='${resource.clickDescription}'>${resource.clickText}</button>`; content += `<br><button class='btn' title='${resource.clickDescription}'>${resource.clickText}</button>`;
} }
if (resource.cost !== null && Object.keys(resource.cost) !== null) {
content += `<br>Cost: <span class='cost'></span>`;
}
el.innerHTML = content; el.innerHTML = content;
container.appendChild(el); container.appendChild(el);
if (resource.clickAction !== null) { if (resource.clickAction !== null) {
@ -43,9 +49,31 @@ class DebugRenderer implements IRenderer {
const elT = el.getElementsByClassName('max')[0]; const elT = el.getElementsByClassName('max')[0];
elV.innerHTML = this.formatNumber(resource.value, 1); elV.innerHTML = this.formatNumber(resource.value, 1);
elT.innerHTML = resource.max !== null ? ` / ${this.formatNumber(resource.max, 2)}` : ''; elT.innerHTML = resource.max !== null ? ` / ${this.formatNumber(resource.max, 2)}` : '';
if (this._handleClick) {
const elC = el.getElementsByClassName('cost');
if (elC.length > 0) {
elC[0].innerHTML = this.getCostStr(resource, state);
}
}
}
}
this._handleClick = false;
}
}
private getCostStr (resource: IResource, state: GameState) {
let cost = '';
for (const rkey of state.getResources()) {
if (resource.cost[rkey] !== undefined) {
if (cost !== '') cost += ', ';
if (rkey === 'money') {
cost += `$${this.formatNumber(resource.cost[rkey], 1)}`;
} else {
cost += `${this.formatNumber(resource.cost[rkey], 1)} ${state.getResource(rkey).name}`;
} }
} }
} }
return cost;
} }
private formatNumber (num: number, digits: number): string { private formatNumber (num: number, digits: number): string {