diff --git a/public/css/debugger.css b/public/css/debugger.css
index af26d36..dccd315 100644
--- a/public/css/debugger.css
+++ b/public/css/debugger.css
@@ -3,8 +3,20 @@
flex-wrap: wrap;
}
.resource {
- min-width: 8em;
+ min-width: 12em;
border: 2px solid black;
padding: 0.25em 0.5em;
margin: 0 0.5em 0.5em 0;
}
+#resource-container-religion .resource {
+ background-color: #ccf;
+}
+#resource-container-consumable .resource {
+ background-color: #cfc;
+}
+#resource-container-infrastructure .resource {
+ background-color: #fcc;
+}
+#resource-container-passive .resource {
+ background-color: #ffc;
+}
diff --git a/src/main.ts b/src/main.ts
index d1d2a2b..1f2aa6c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -12,8 +12,8 @@ function gameLoop (state: GameState, renderer: IRenderer): void {
const elapsedTime: number = globalStartTime > 0
? (new Date()).getTime() - globalStartTime : 0;
- renderer.render(state);
state.advance(elapsedTime);
+ renderer.render(state);
// run again in 1sec
globalStartTime = (new Date()).getTime();
diff --git a/src/model/GameConfig.ts b/src/model/GameConfig.ts
index f5d74a6..a60cab2 100644
--- a/src/model/GameConfig.ts
+++ b/src/model/GameConfig.ts
@@ -1,5 +1,6 @@
///
///
+///
///
///
///
@@ -57,10 +58,11 @@ class GameConfig {
this.relNoneShare * this.worldPopulation));
// add hidden resources
- state.addResource('creds', new Credibility(2));
+ state.addResource('creds', new Credibility());
// add resources
- state.addResource('money', new Money(100));
+ state.addResource('money', new Money(3.50));
+ state.addResource('crpto', new CryptoCurrency());
return state;
}
diff --git a/src/model/GameState.ts b/src/model/GameState.ts
index bdeea2c..4d882bf 100644
--- a/src/model/GameState.ts
+++ b/src/model/GameState.ts
@@ -21,13 +21,16 @@ class GameState {
// advance each resource
for (const rkey of this._resourceKeys) {
- if (this._resources[rkey].advanceAction !== null) {
+ if (this._resources[rkey].isUnlocked(this)
+ && this._resources[rkey].advanceAction !== null) {
this._resources[rkey].advanceAction(time, this);
}
}
// perform auto increments
for (const rkey of this._resourceKeys) {
+ if (!this._resources[rkey].isUnlocked(this)) continue;
+
const max: number = this._resources[rkey].max
? this._resources[rkey].max(this)
: null;
@@ -41,6 +44,9 @@ class GameState {
if (max !== null && this._resources[rkey].value > max) {
this._resources[rkey].value = max;
}
+ if (this._resources[rkey].value < 0) {
+ this._resources[rkey].value = 0;
+ }
}
}
diff --git a/src/model/resource/Credibility.ts b/src/model/resource/Credibility.ts
index 1ebb3b4..f424437 100644
--- a/src/model/resource/Credibility.ts
+++ b/src/model/resource/Credibility.ts
@@ -1,27 +1,20 @@
-///
+///
-class Credibility extends Hidden {
- private _lastValue: number;
+class Credibility extends Passive {
+ private _lastValue: number = 100;
- constructor (public value: number) {
- super(value);
- this._lastValue = value;
+ constructor () {
+ super(
+ 'Credibility',
+ 'How trustworthy you are perceived to be. Affects your ability to recruit and retain followers.',
+ 100, 100, 0.25);
}
public max (state: GameState): number {
- return 2;
+ return 100;
}
public inc (state: GameState): number {
- return 0.01;
- }
-
- public advanceAction (time: number, state: GameState): void {
- if (Math.ceil(this._lastValue) < Math.ceil(this.value)) {
- state.log('Your credibility has gone up.');
- } else if (Math.ceil(this._lastValue) > Math.ceil(this.value)) {
- state.log('Your credibility has gone down.');
- }
- this._lastValue = this.value;
+ return 0.25;
}
}
diff --git a/src/model/resource/CryptoCurrency.ts b/src/model/resource/CryptoCurrency.ts
new file mode 100644
index 0000000..31f7a0d
--- /dev/null
+++ b/src/model/resource/CryptoCurrency.ts
@@ -0,0 +1,11 @@
+///
+
+class CryptoCurrency extends Purchasable {
+ constructor () {
+ super('CryptoCurrency',
+ "Can't be spent directly, but provides a steady stream of passive income.");
+ this.cost.money = 100;
+ this._costMultiplier.money = 1.1;
+ this._baseMax = 1000;
+ }
+}
diff --git a/src/model/resource/IResource.ts b/src/model/resource/IResource.ts
index 46ef5db..8c58c44 100644
--- a/src/model/resource/IResource.ts
+++ b/src/model/resource/IResource.ts
@@ -2,7 +2,7 @@ enum ResourceType {
Religion = 'religion',
Consumable = 'consumable',
Infrastructure = 'infrastructure',
- Hidden = 'hidden'
+ Passive = 'passive'
}
interface IResource {
diff --git a/src/model/resource/Money.ts b/src/model/resource/Money.ts
index 11160f5..ae6ff6d 100644
--- a/src/model/resource/Money.ts
+++ b/src/model/resource/Money.ts
@@ -3,6 +3,7 @@
class Money extends Purchasable {
private _lastCollectionTime: number = 0;
+ public resourceType: ResourceType = ResourceType.Consumable;
public cost: { [key: string]: number } = { };
constructor (
@@ -11,22 +12,28 @@ class Money extends Purchasable {
super('Money', 'Used to purchase goods and services.');
this.clickText = 'Collect Tithes';
this.clickDescription = 'Voluntary contributions from followers.';
+ this._baseMax = 10000;
}
public isUnlocked (state: GameState): boolean {
return true;
}
- protected _incrementAmount (state: GameState): number {
+ public inc (state: GameState): number {
+ // crypto currency
+ return state.getResource('crpto').value * 0.5;
+ }
+
+ protected _purchaseAmount (state: GameState): number {
const plorg: IResource = state.getResource('plorg');
if (plorg.value === 0) {
state.log('You have no followers to collect from!');
return 0;
}
- if (state.now - this._lastCollectionTime < 30000) {
- this.cost.creds = 0.05;
- state.deductCost(this.cost);
- delete this.cost.creds;
+ const diff: number = state.now - this._lastCollectionTime;
+ if (diff < 30000) {
+ const lost: number = 30000 / diff / 3;
+ state.getResource('creds').value -= lost;
}
// each follower gives you $10
const tithings: number = plorg.value * 10;
diff --git a/src/model/resource/Hidden.ts b/src/model/resource/Passive.ts
similarity index 56%
rename from src/model/resource/Hidden.ts
rename to src/model/resource/Passive.ts
index 21da49d..fe5a4e9 100644
--- a/src/model/resource/Hidden.ts
+++ b/src/model/resource/Passive.ts
@@ -1,22 +1,28 @@
///
-abstract class Hidden implements IResource {
- public readonly resourceType: ResourceType = ResourceType.Hidden;
+abstract class Passive implements IResource {
+ public readonly resourceType: ResourceType = ResourceType.Passive;
public readonly clickText: null = null;
public readonly clickDescription: null = null;
public readonly cost: null = null;
public readonly clickAction: null = null;
- public readonly name: null = null;
- public readonly description: null = null;
- protected _baseMax: number | null = null;
+ protected _baseMax: number | null;
+ protected _baseInc: number | null;
constructor (
- public value: number
- ) { }
+ public name: string,
+ public description: string,
+ public value: number,
+ max: number | null,
+ inc: number | null
+ ) {
+ this._baseMax = max;
+ this._baseInc = inc;
+ }
public inc (state: GameState): number | null {
- return null;
+ return this._baseInc;
}
public max (state: GameState): number | null {
diff --git a/src/model/resource/PlayerOrg.ts b/src/model/resource/PlayerOrg.ts
index 9f41471..03a53c7 100644
--- a/src/model/resource/PlayerOrg.ts
+++ b/src/model/resource/PlayerOrg.ts
@@ -6,7 +6,6 @@ class PlayerOrg implements IResource {
public readonly description: string = 'In you they trust.';
public cost: { [key: string]: number } = { };
- public readonly max: null = null;
public readonly inc: null = null;
public value: number = 0;
@@ -15,42 +14,57 @@ class PlayerOrg implements IResource {
public clickDescription: string = 'Gather new followers.';
private _lastLostTime: number = 0;
+ private _baseMax: number = 5;
public isUnlocked (state: GameState): boolean {
return true;
}
+ public max (state: GameState): number {
+ return this._baseMax;
+ }
+
public clickAction (state: GameState): void {
- const creds: number =
- Math.pow(Math.ceil(state.getResource('creds').value), 2);
- if (this.value >= creds) {
+ // don't exceed max
+ if (this.value >= this.max(state)) {
+ state.log('You have no room for more followers.');
+ return;
+ }
+
+ // chance to fail increases as credibility decreases
+ const creds: IResource = state.getResource('creds');
+ const ratio: number = Math.ceil(creds.value) / creds.max(state);
+ if (Math.random() > ratio) {
state.log('Your recruiting efforts failed.');
+ return;
+ }
+
+ const source: [string, IResource] = this._getRandomReligion(state);
+ this.cost[source[0]] = 1;
+ if (state.deductCost(this.cost)) {
+ this.value++;
+ delete this.cost[source[0]];
+ state.log(`You converted one new follower from ${source[1].name}!`);
} else {
- const source: [string, IResource] = this._getRandomReligion(state);
- this.cost[source[0]] = 1;
- if (state.deductCost(this.cost)) {
- this.value++;
- delete this.cost[source[0]];
- state.log(`You converted one new follower from ${source[1].name}!`);
- } else {
- state.log('Your recruiting efforts failed.');
- }
+ state.log('Your recruiting efforts failed.');
}
}
public advanceAction (time: number, state: GameState): void {
- const creds: number =
- Math.pow(Math.ceil(state.getResource('creds').value), 2);
- if (this.value > creds) {
- if (state.now - this._lastLostTime > 10000) {
- const lost: number =
- Math.ceil((this.value - creds) / 10 * Math.random());
- this.value -= lost;
- const dest: [string, IResource] = this._getRandomReligion(state);
- dest[1].value += lost;
- state.log(`You lost ${lost} followers to ${dest[1].name}.`);
- this._lastLostTime = state.now;
+ // chance to lose some followers every 10s if credibility < 100%
+ if (state.now - this._lastLostTime > 10000) {
+ if (this.value > 0) {
+ const creds: IResource = state.getResource('creds');
+ const ratio: number = Math.ceil(creds.value) / creds.max(state);
+ if (Math.random() > ratio) {
+ const lost: number = Math.ceil(this.value / 25 * (1 - ratio));
+ this.value -= lost;
+ const dest: [string, IResource] = this._getRandomReligion(state);
+ dest[1].value += lost;
+ state.log(`You lost ${lost} followers to ${dest[1].name}.`);
+ }
}
+ this._lastLostTime = state.now;
}
}
diff --git a/src/model/resource/Purchasable.ts b/src/model/resource/Purchasable.ts
index 2cc9b06..2e0b0d3 100644
--- a/src/model/resource/Purchasable.ts
+++ b/src/model/resource/Purchasable.ts
@@ -7,10 +7,11 @@ abstract class Purchasable implements IResource {
public clickText: string = 'Purchase';
public clickDescription: string = 'Purchase';
- public cost: { [key: string]: number } | null = null;
+ public cost: { [key: string]: number } = { };
- protected _costMultiplier: { [key: string]: number } | null = null;
+ protected _costMultiplier: { [key: string]: number } = { };
protected _baseMax: number | null = null;
+ protected _isUnlocked: boolean = false;
constructor (
public readonly name: string,
@@ -20,12 +21,9 @@ abstract class Purchasable implements IResource {
public clickAction (state: GameState): void {
if (this.max(state) !== null && this.value >= this.max(state)) return;
if (state.deductCost(this.cost)) {
- this.value += this._incrementAmount(state);
- if (this._costMultiplier !== null
- && Object.keys(this._costMultiplier !== null)) {
- for (const rkey of Object.keys(this._costMultiplier)) {
- this.cost[rkey] *= this._costMultiplier[rkey];
- }
+ this.value += this._purchaseAmount(state);
+ for (const rkey of Object.keys(this._costMultiplier)) {
+ this.cost[rkey] *= this._costMultiplier[rkey];
}
}
}
@@ -43,10 +41,13 @@ abstract class Purchasable implements IResource {
}
public isUnlocked (state: GameState): boolean {
- return false;
+ if (!this._isUnlocked && state.isPurchasable(this.cost)) {
+ this._isUnlocked = true;
+ }
+ return this._isUnlocked;
}
- protected _incrementAmount (state: GameState): number {
+ protected _purchaseAmount (state: GameState): number {
return 1;
}
}
diff --git a/src/render/DebugRenderer.ts b/src/render/DebugRenderer.ts
index 6f94eef..075b0be 100644
--- a/src/render/DebugRenderer.ts
+++ b/src/render/DebugRenderer.ts
@@ -22,8 +22,7 @@ class DebugRenderer implements IRenderer {
head.appendChild(style);
// create containers for each resource type
for (const item in ResourceType) {
- if (isNaN(Number(item))
- && ResourceType[item] !== ResourceType.Hidden) {
+ if (isNaN(Number(item))) {
const el: HTMLElement = document.createElement('div');
el.id = `resource-container-${ResourceType[item]}`;
el.className = 'resource-type-container';
@@ -34,7 +33,6 @@ class DebugRenderer implements IRenderer {
const rkeys: string[] = state.getResources();
for (const rkey of rkeys) {
const resource: IResource = state.getResource(rkey);
- if (resource.resourceType === ResourceType.Hidden) continue;
const container: HTMLElement = document
.getElementById(`resource-container-${resource.resourceType}`);
if (resource.isUnlocked(state)) {
@@ -45,8 +43,11 @@ class DebugRenderer implements IRenderer {
el.className = 'resource';
el.id = `resource-details-${rkey}`;
let content: string = `
-
- ${resource.name}
+
+ ${this._escape(resource.name
+ ? resource.name
+ : rkey)}
@@ -54,8 +55,8 @@ class DebugRenderer implements IRenderer {
if (resource.clickText !== null) {
content += `
`;
+ title='${this._escape(resource.clickDescription)}'>
+ ${this._escape(resource.clickText)}`;
}
if (resource.cost !== null
&& Object.keys(resource.cost).length !== 0) {
@@ -89,7 +90,7 @@ class DebugRenderer implements IRenderer {
const elC: HTMLCollectionOf =
el.getElementsByClassName('resource-cost');
if (elC.length > 0) {
- elC[0].innerHTML = this.getCostStr(resource, state);
+ elC[0].innerHTML = this._getCostStr(resource, state);
}
}
}
@@ -97,7 +98,21 @@ class DebugRenderer implements IRenderer {
this._handleClick = false;
}
- private getCostStr (resource: IResource, state: GameState): string {
+ private _escape (text: string): string {
+ const escapes: { [key: string]: string } = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '/': '/'
+ }
+ const escaper: RegExp = /[&<>"'\/]/g;
+ return text.replace(escaper, (match: string): string =>
+ escapes[match]);
+ }
+
+ private _getCostStr (resource: IResource, state: GameState): string {
let cost: string = '';
for (const rkey of state.getResources()) {
if (resource.cost[rkey] !== undefined) {
diff --git a/tslint.json b/tslint.json
index 0699b91..53bd174 100644
--- a/tslint.json
+++ b/tslint.json
@@ -14,7 +14,7 @@
true, {
"limit": 75,
"check-strings": true,
- "ignore-pattern": "\\s+state\\.log\\("
+ "ignore-pattern": "(^\\s+state\\.log\\(|^\\s+['\"`])"
}
],
"whitespace": [