replaced eslint formatting rules with prettier

This commit is contained in:
Rudis Muiznieks 2021-09-06 12:02:38 -05:00
parent afe0592498
commit e939dec388
29 changed files with 485 additions and 234 deletions

View File

@ -2,13 +2,18 @@
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking" "plugin:@typescript-eslint/recommended-requiring-type-checking",
"prettier"
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": {"project": "tsconfig.json"}, "parserOptions": {"project": "tsconfig.json"},
"plugins": ["@typescript-eslint"], "plugins": [
"@typescript-eslint",
"prettier"
],
"rules": { "rules": {
"@typescript-eslint/triple-slash-reference": "off", "@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/no-unused-vars": "off" "@typescript-eslint/no-unused-vars": "off",
"prettier/prettier": "warn"
} }
} }

3
.prettierrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

103
package-lock.json generated
View File

@ -9,6 +9,9 @@
"@typescript-eslint/parser": "^4.30.0", "@typescript-eslint/parser": "^4.30.0",
"@typescript-eslint/typescript-estree": "^4.30.0", "@typescript-eslint/typescript-estree": "^4.30.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.3.2",
"typescript": "^4.4.2", "typescript": "^4.4.2",
"typescript-eslint-language-service": "^4.1.5", "typescript-eslint-language-service": "^4.1.5",
"typescript-language-server": "^0.6.2" "typescript-language-server": "^0.6.2"
@ -762,6 +765,39 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint-config-prettier": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-prettier": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
"integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==",
"dev": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0"
},
"engines": {
"node": ">=6.0.0"
},
"peerDependencies": {
"eslint": ">=7.28.0",
"prettier": ">=2.0.0"
},
"peerDependenciesMeta": {
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -910,6 +946,12 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true "dev": true
}, },
"node_modules/fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.2.7", "version": "3.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
@ -1483,6 +1525,30 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/progress": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@ -2561,6 +2627,22 @@
"v8-compile-cache": "^2.0.3" "v8-compile-cache": "^2.0.3"
} }
}, },
"eslint-config-prettier": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
"dev": true,
"requires": {}
},
"eslint-plugin-prettier": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
"integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-scope": { "eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -2671,6 +2753,12 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true "dev": true
}, },
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"fast-glob": { "fast-glob": {
"version": "3.2.7", "version": "3.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
@ -3112,6 +3200,21 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true "dev": true
}, },
"prettier": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
"dev": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"progress": { "progress": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",

View File

@ -9,6 +9,9 @@
"@typescript-eslint/parser": "^4.30.0", "@typescript-eslint/parser": "^4.30.0",
"@typescript-eslint/typescript-estree": "^4.30.0", "@typescript-eslint/typescript-estree": "^4.30.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.3.2",
"typescript": "^4.4.2", "typescript": "^4.4.2",
"typescript-eslint-language-service": "^4.1.5", "typescript-eslint-language-service": "^4.1.5",
"typescript-language-server": "^0.6.2" "typescript-language-server": "^0.6.2"

View File

@ -1,9 +1,9 @@
class ConsoleLogger implements ILogger { class ConsoleLogger implements ILogger {
public msg (text: string): void { public msg(text: string): void {
console.log(text); console.log(text);
} }
public unsafeMsg (text: string): void { public unsafeMsg(text: string): void {
console.error(text); console.error(text);
} }
} }

View File

@ -1,19 +1,19 @@
class DebugLogger implements ILogger { class DebugLogger implements ILogger {
private readonly _container: HTMLElement; private readonly _container: HTMLElement;
constructor (container: HTMLElement) { constructor(container: HTMLElement) {
this._container = container; this._container = container;
} }
public msg (text: string): void { public msg(text: string): void {
this._doMsg(text, true); this._doMsg(text, true);
} }
public unsafeMsg (text: string): void { public unsafeMsg(text: string): void {
this._doMsg(text, false); this._doMsg(text, false);
} }
private _doMsg (text: string, safe: boolean): void { private _doMsg(text: string, safe: boolean): void {
const el = document.createElement('p'); const el = document.createElement('p');
if (safe) { if (safe) {
el.innerText = text; el.innerText = text;

View File

@ -8,10 +8,10 @@ let globalStartTime = 0;
let globalTimeout: number | null = null; let globalTimeout: number | null = null;
const cycleLength = 250; const cycleLength = 250;
function gameLoop (state: GameState, renderer: IRenderer): void { function gameLoop(state: GameState, renderer: IRenderer): void {
// figure out how much actual time has passed // figure out how much actual time has passed
const elapsedTime: number = globalStartTime > 0 const elapsedTime: number =
? new Date().getTime() - globalStartTime : 0; globalStartTime > 0 ? new Date().getTime() - globalStartTime : 0;
state.advance(elapsedTime); state.advance(elapsedTime);
renderer.render(state); renderer.render(state);
@ -23,14 +23,15 @@ function gameLoop (state: GameState, renderer: IRenderer): void {
}, cycleLength); }, cycleLength);
} }
function startGame (state: GameState, renderer: IRenderer): void { function startGame(state: GameState, renderer: IRenderer): void {
state.load(); // load saved game if one exists state.load(); // load saved game if one exists
gameLoop(state, renderer); // start the main loop gameLoop(state, renderer); // start the main loop
} }
function initialRender (state: GameState): void { function initialRender(state: GameState): void {
if (state.logger === null) return; if (state.logger === null) return;
state.logger.unsafeMsg(`<strong>Welcome to irreligio.us!</strong> <em>alpha v${versionMajor}.${versionMinor}</em> state.logger
.unsafeMsg(`<strong>Welcome to irreligio.us!</strong> <em>alpha v${versionMajor}.${versionMinor}</em>
<br><br> <br><br>
The game is still in an active state of development and nowhere near its final form. This is a debugging interface that can show all resources even before they're unlocked, and many factors may be sped up significantly to aid in development. There is a chance that playing it now may spoil aspects of the game for you later when it's closer to being finished. The game is still in an active state of development and nowhere near its final form. This is a debugging interface that can show all resources even before they're unlocked, and many factors may be sped up significantly to aid in development. There is a chance that playing it now may spoil aspects of the game for you later when it's closer to being finished.
<br><br> <br><br>
@ -63,7 +64,8 @@ The game's source code on <a href='https://github.com/rudism/irreligious'>Github
}); });
if (document.readyState !== 'loading') startGame(state, renderer); if (document.readyState !== 'loading') startGame(state, renderer);
else document.addEventListener('DOMContentLoaded', (): void => { else
startGame(state, renderer); document.addEventListener('DOMContentLoaded', (): void => {
}); startGame(state, renderer);
});
})(); })();

View File

@ -79,55 +79,108 @@ class GameConfig {
public cfgTitheAmount = 10; public cfgTitheAmount = 10;
public cfgTitheCredibilityHitFactor = 3; public cfgTitheCredibilityHitFactor = 3;
constructor ( constructor(public versionMajor: number, public versionMinor: number) {}
public versionMajor: number,
public versionMinor: number,
) {}
public generateState (): GameState { public generateState(): GameState {
const state = new GameState(this); const state = new GameState(this);
// create player organization // create player organization
state.addResource(ResourceKey.followers, new Follower()); state.addResource(ResourceKey.followers, new Follower());
// create world religions // create world religions
state.addResource(ResourceKey.christianity, new Religion( state.addResource(
'Christianity', 'christian', 'christians', 'God, Jesus, Bible, churches.', ResourceKey.christianity,
(this.cfgReligion.christianity ?? 0) * this.worldPopulation)); new Religion(
'Christianity',
'christian',
'christians',
'God, Jesus, Bible, churches.',
(this.cfgReligion.christianity ?? 0) * this.worldPopulation
)
);
state.addResource(ResourceKey.islam, new Religion( state.addResource(
'Islam', 'muslim', 'muslims', 'God, Muhammad, Quran, mosques.', ResourceKey.islam,
(this.cfgReligion.islam ?? 0) * this.worldPopulation)); new Religion(
'Islam',
'muslim',
'muslims',
'God, Muhammad, Quran, mosques.',
(this.cfgReligion.islam ?? 0) * this.worldPopulation
)
);
state.addResource(ResourceKey.hinduism, new Religion( state.addResource(
'Hinduism', 'hindu', 'hindus', 'Dogma-free spiritualism.', ResourceKey.hinduism,
(this.cfgReligion.hinduism ?? 0) * this.worldPopulation)); new Religion(
'Hinduism',
'hindu',
'hindus',
'Dogma-free spiritualism.',
(this.cfgReligion.hinduism ?? 0) * this.worldPopulation
)
);
state.addResource(ResourceKey.buddhism, new Religion( state.addResource(
'Buddhism', 'buddhist', 'buddhists', 'The minimization of suffering.', ResourceKey.buddhism,
(this.cfgReligion.buddhism ?? 0) * this.worldPopulation)); new Religion(
'Buddhism',
'buddhist',
'buddhists',
'The minimization of suffering.',
(this.cfgReligion.buddhism ?? 0) * this.worldPopulation
)
);
state.addResource(ResourceKey.sikhism, new Religion( state.addResource(
'Sikhism', 'sikh', 'sikhs', 'Meditation and ten Gurus', ResourceKey.sikhism,
(this.cfgReligion.sikhism ?? 0) * this.worldPopulation)); new Religion(
'Sikhism',
'sikh',
'sikhs',
'Meditation and ten Gurus',
(this.cfgReligion.sikhism ?? 0) * this.worldPopulation
)
);
state.addResource(ResourceKey.judaism, new Religion( state.addResource(
'Judaism', 'jew', 'jews', 'God, Abraham, Torah, synagogues.', ResourceKey.judaism,
(this.cfgReligion.judaism ?? 0) * this.worldPopulation)); new Religion(
'Judaism',
'jew',
'jews',
'God, Abraham, Torah, synagogues.',
(this.cfgReligion.judaism ?? 0) * this.worldPopulation
)
);
state.addResource(ResourceKey.other, new Religion( state.addResource(
'Other', 'person from other faiths', 'people from other faiths', 'A variety of belief systems.', ResourceKey.other,
(this.cfgReligion.other ?? 0) * this.worldPopulation)); new Religion(
'Other',
'person from other faiths',
'people from other faiths',
'A variety of belief systems.',
(this.cfgReligion.other ?? 0) * this.worldPopulation
)
);
state.addResource(ResourceKey.atheism, new Religion( state.addResource(
'Non-Religious', 'atheist', 'atheists', 'Atheists and agnostics.', ResourceKey.atheism,
(this.cfgReligion.atheism ?? 0) * this.worldPopulation)); new Religion(
'Non-Religious',
'atheist',
'atheists',
'Atheists and agnostics.',
(this.cfgReligion.atheism ?? 0) * this.worldPopulation
)
);
// add jobs // add jobs
state.addResource(ResourceKey.pastors, new Pastor()); state.addResource(ResourceKey.pastors, new Pastor());
// add resources // add resources
state.addResource(ResourceKey.money, new Money(3.50)); state.addResource(ResourceKey.money, new Money(3.5));
state.addResource(ResourceKey.cryptoCurrency, new CryptoCurrency(this)); state.addResource(ResourceKey.cryptoCurrency, new CryptoCurrency(this));
state.addResource(ResourceKey.tents, new Tent(this)); state.addResource(ResourceKey.tents, new Tent(this));
state.addResource(ResourceKey.houses, new House(this)); state.addResource(ResourceKey.houses, new House(this));

View File

@ -11,27 +11,27 @@ class GameState {
private _timeSinceSave = 0; private _timeSinceSave = 0;
private readonly _timeBetweenSaves = 10000; private readonly _timeBetweenSaves = 10000;
private _resources: { [key in ResourceKey]?: IResource } = { }; private _resources: { [key in ResourceKey]?: IResource } = {};
private readonly _resourceKeys: ResourceKey[] = []; private readonly _resourceKeys: ResourceKey[] = [];
constructor (config: GameConfig) { constructor(config: GameConfig) {
this.config = config; this.config = config;
} }
public get resource (): { [key in ResourceKey]?: IResource } { public get resource(): { [key in ResourceKey]?: IResource } {
return this._resources; return this._resources;
} }
public get resources (): ResourceKey[] { public get resources(): ResourceKey[] {
return this._resourceKeys; return this._resourceKeys;
} }
public addResource (key: ResourceKey, resource: IResource): void { public addResource(key: ResourceKey, resource: IResource): void {
this._resourceKeys.push(key); this._resourceKeys.push(key);
this._resources[key] = resource; this._resources[key] = resource;
} }
public advance (time: number): void { public advance(time: number): void {
this.now = new Date().getTime(); this.now = new Date().getTime();
this._timeSinceSave += time; this._timeSinceSave += time;
@ -54,9 +54,11 @@ class GameState {
const resource = this._resources[rkey]; const resource = this._resources[rkey];
if (resource === undefined || !resource.isUnlocked(this)) continue; if (resource === undefined || !resource.isUnlocked(this)) continue;
if (resource.inc !== undefined && (resource.max === undefined if (
|| resource.value < resource.max(this))) { resource.inc !== undefined &&
resource.addValue(resource.inc(this) * time / 1000, this); (resource.max === undefined || resource.value < resource.max(this))
) {
resource.addValue((resource.inc(this) * time) / 1000, this);
} }
if (resource.max !== undefined && resource.value > resource.max(this)) { if (resource.max !== undefined && resource.value > resource.max(this)) {
@ -68,11 +70,15 @@ class GameState {
} }
} }
public performAction (resourceKey: ResourceKey, actionIndex: number): void { public performAction(resourceKey: ResourceKey, actionIndex: number): void {
const resource = this._resources[resourceKey]; const resource = this._resources[resourceKey];
if (resource === undefined || resource.userActions === undefined if (
|| actionIndex > resource.userActions.length resource === undefined ||
|| !resource.isUnlocked(this)) return; resource.userActions === undefined ||
actionIndex > resource.userActions.length ||
!resource.isUnlocked(this)
)
return;
const action = resource.userActions[actionIndex]; const action = resource.userActions[actionIndex];
@ -82,7 +88,7 @@ class GameState {
} }
} }
public deductCost (cost: ResourceNumber | null): boolean { public deductCost(cost: ResourceNumber | null): boolean {
if (cost === null) return true; if (cost === null) return true;
if (!this.isPurchasable(cost)) return false; if (!this.isPurchasable(cost)) return false;
for (const key in cost) { for (const key in cost) {
@ -95,7 +101,7 @@ class GameState {
return true; return true;
} }
public isPurchasable (cost?: ResourceNumber): boolean { public isPurchasable(cost?: ResourceNumber): boolean {
if (cost === undefined) return true; if (cost === undefined) return true;
for (const key in cost) { for (const key in cost) {
const rkey = <ResourceKey>key; const rkey = <ResourceKey>key;
@ -106,13 +112,13 @@ class GameState {
return true; return true;
} }
public log (text: string): void { public log(text: string): void {
if (this.logger !== null) { if (this.logger !== null) {
this.logger.msg(text); this.logger.msg(text);
} }
} }
public save (): void { public save(): void {
const saveObj: SaveData = { const saveObj: SaveData = {
version: { version: {
maj: this.config.versionMajor, maj: this.config.versionMajor,
@ -137,7 +143,7 @@ class GameState {
localStorage.setItem('savegame', saveStr); localStorage.setItem('savegame', saveStr);
} }
public load (): void { public load(): void {
const saveStr: string | null = localStorage.getItem('savegame'); const saveStr: string | null = localStorage.getItem('savegame');
if (saveStr !== null) { if (saveStr !== null) {
try { try {
@ -153,8 +159,10 @@ class GameState {
resource.value = saveRes.value; resource.value = saveRes.value;
// @ts-expect-error writing read-only cost from save data // @ts-expect-error writing read-only cost from save data
resource.cost = saveRes.cost; resource.cost = saveRes.cost;
if (saveRes.config !== undefined if (
&& resource.restoreConfig !== undefined) { saveRes.config !== undefined &&
resource.restoreConfig !== undefined
) {
resource.restoreConfig(saveRes.config); resource.restoreConfig(saveRes.config);
} }
} }
@ -172,7 +180,7 @@ class GameState {
} }
} }
public reset (): void { public reset(): void {
const newState: GameState = this.config.generateState(); const newState: GameState = this.config.generateState();
localStorage.clear(); localStorage.clear();
this._resources = newState._resources; this._resources = newState._resources;

View File

@ -1,7 +1,7 @@
const numberFormatDigits = 1; const numberFormatDigits = 1;
function formatNumber (num: number): string { function formatNumber(num: number): string {
type UnitLookup = { value: number, symbol: string }; type UnitLookup = { value: number; symbol: string };
const lookup: UnitLookup[] = [ const lookup: UnitLookup[] = [
{ value: 1, symbol: '' }, { value: 1, symbol: '' },
{ value: 1e3, symbol: 'K' }, { value: 1e3, symbol: 'K' },
@ -17,7 +17,7 @@ function formatNumber (num: number): string {
if (num >= item.value) break; if (num >= item.value) break;
} }
return item !== undefined return item !== undefined
? (num / item.value).toFixed( ? (num / item.value).toFixed(numberFormatDigits).replace(rx, '$1') +
numberFormatDigits).replace(rx, '$1') + item.symbol item.symbol
: num.toFixed(numberFormatDigits).replace(rx, '$1'); : num.toFixed(numberFormatDigits).replace(rx, '$1');
} }

View File

@ -1,16 +1,17 @@
/// <reference path="./Research.ts" /> /// <reference path="./Research.ts" />
class BuildingPermit extends Research { class BuildingPermit extends Research {
constructor (config: GameConfig) { constructor(config: GameConfig) {
super( super(
'Building Permit', 'Building Permit',
'building permit', 'building permit',
'building permits', 'building permits',
'Unlocks several new buildings you can build outside of your compounds.'); 'Unlocks several new buildings you can build outside of your compounds.'
this.cost.money = config.cfgInitialMax.buildingPermit; );
this.cost.money = config.cfgInitialCost.buildingPermit;
} }
public isUnlocked (state: GameState): boolean { public isUnlocked(state: GameState): boolean {
if (this._isUnlocked) return true; if (this._isUnlocked) return true;
const compounds = state.resource.compounds; const compounds = state.resource.compounds;
if (compounds !== undefined && compounds.value > 0) { if (compounds !== undefined && compounds.value > 0) {

View File

@ -1,21 +1,24 @@
/// <reference path="./Infrastructure.ts" /> /// <reference path="./Infrastructure.ts" />
class Church extends Infrastructure { class Church extends Infrastructure {
constructor (config: GameConfig) { constructor(config: GameConfig) {
super( super(
'Churches', 'Churches',
'church', 'church',
'churches', 'churches',
`Preaching grounds for ${formatNumber(config.cfgCapacity.churches?.pastors ?? 0)} pastors.`); `Preaching grounds for ${formatNumber(
config.cfgCapacity.churches?.pastors ?? 0
)} pastors.`
);
this.cost.money = config.cfgInitialCost.churches; this.cost.money = config.cfgInitialCost.churches;
this._costMultiplier.money = config.cfgCostMultiplier.churches; this._costMultiplier.money = config.cfgCostMultiplier.churches;
} }
public max: (state: GameState) => number = (state) => public max: (state: GameState) => number = (state) =>
(state.resource.compounds?.value ?? 0) (state.resource.compounds?.value ?? 0) *
* (state.config.cfgCapacity.compounds?.churches ?? 0); (state.config.cfgCapacity.compounds?.churches ?? 0);
public isUnlocked (state: GameState): boolean { public isUnlocked(state: GameState): boolean {
if (this._isUnlocked) return true; if (this._isUnlocked) return true;
const compounds = state.resource.compounds; const compounds = state.resource.compounds;
if (compounds !== undefined && compounds.value > 0) { if (compounds !== undefined && compounds.value > 0) {

View File

@ -1,21 +1,24 @@
/// <reference path="./Infrastructure.ts" /> /// <reference path="./Infrastructure.ts" />
class Compound extends Infrastructure { class Compound extends Infrastructure {
constructor (config: GameConfig) { constructor(config: GameConfig) {
super( super(
'Compounds', 'Compounds',
'compound', 'compound',
'compounds', 'compounds',
'Provides space for tents, houses, and churches and a place to hide more money.'); 'Provides space for tents, houses, and churches and a place to hide more money.'
);
this.cost.money = config.cfgInitialCost.compounds; this.cost.money = config.cfgInitialCost.compounds;
this._costMultiplier.money = config.cfgCostMultiplier.compounds; this._costMultiplier.money = config.cfgCostMultiplier.compounds;
} }
public isUnlocked (state: GameState): boolean { public isUnlocked(state: GameState): boolean {
if (this._isUnlocked) return true; if (this._isUnlocked) return true;
const tents = state.resource.tents; const tents = state.resource.tents;
if (tents !== undefined if (
&& tents.value >= (state.config.cfgInitialMax.tents ?? 0)) { tents !== undefined &&
tents.value >= (state.config.cfgInitialMax.tents ?? 0)
) {
this._isUnlocked = true; this._isUnlocked = true;
} }
return this._isUnlocked; return this._isUnlocked;

View File

@ -1,12 +1,13 @@
/// <reference path="./Passive.ts" /> /// <reference path="./Passive.ts" />
class Credibility extends Passive { class Credibility extends Passive {
constructor (config: GameConfig) { constructor(config: GameConfig) {
super( super(
'Credibility', 'Credibility',
'credibility', 'credibility',
'credibilities', 'credibilities',
'Affects your ability to recruit and retain followers.'); 'Affects your ability to recruit and retain followers.'
);
this.value = config.cfgPassiveMax; this.value = config.cfgPassiveMax;
} }

View File

@ -1,12 +1,13 @@
/// <reference path="./Purchasable.ts" /> /// <reference path="./Purchasable.ts" />
class CryptoCurrency extends Purchasable { class CryptoCurrency extends Purchasable {
constructor (config: GameConfig) { constructor(config: GameConfig) {
super( super(
'FaithCoin', 'FaithCoin',
'faithcoin', 'faithcoin',
'faithcoins', 'faithcoins',
"A crypto coin that can't be spent directly, but provides a steady stream of passive income."); "A crypto coin that can't be spent directly, but provides a steady stream of passive income."
);
this.cost.money = config.cfgInitialCost.cryptoCurrency; this.cost.money = config.cfgInitialCost.cryptoCurrency;
this._costMultiplier.money = config.cfgCostMultiplier.cryptoCurrency; this._costMultiplier.money = config.cfgCostMultiplier.cryptoCurrency;
this.valueInWholeNumbers = false; this.valueInWholeNumbers = false;

View File

@ -22,19 +22,21 @@ class Follower implements IResource {
private _timeSinceLastLost = 0; private _timeSinceLastLost = 0;
private _lastRecruitmentLog = 0; private _lastRecruitmentLog = 0;
private _followerSources: ResourceNumber = { }; private _followerSources: ResourceNumber = {};
private _followerDests: ResourceNumber = { }; private _followerDests: ResourceNumber = {};
public max (state: GameState): number { public max(state: GameState): number {
let max = state.config.cfgInitialMax.followers ?? 0; let max = state.config.cfgInitialMax.followers ?? 0;
max += (state.resource.tents?.value ?? 0) max +=
* (state.config.cfgCapacity.tents?.followers ?? 0); (state.resource.tents?.value ?? 0) *
max += (state.resource.houses?.value ?? 0) (state.config.cfgCapacity.tents?.followers ?? 0);
* (state.config.cfgCapacity.houses?.followers ?? 0); max +=
(state.resource.houses?.value ?? 0) *
(state.config.cfgCapacity.houses?.followers ?? 0);
return max; return max;
} }
public inc (state: GameState): number { public inc(state: GameState): number {
let inc = 0; let inc = 0;
// pastor recruiting // pastor recruiting
@ -48,7 +50,7 @@ class Follower implements IResource {
return inc; return inc;
} }
public addValue (amount: number, state: GameState): void { public addValue(amount: number, state: GameState): void {
const oldValue = this.value; const oldValue = this.value;
this.value += amount; this.value += amount;
const diff = Math.floor(this.value) - Math.floor(oldValue); const diff = Math.floor(this.value) - Math.floor(oldValue);
@ -76,23 +78,24 @@ class Follower implements IResource {
} }
} }
public isUnlocked (_state: GameState): boolean { public isUnlocked(_state: GameState): boolean {
return true; return true;
} }
public advanceAction (time: number, state: GameState): void { public advanceAction(time: number, state: GameState): void {
// chance to lose some followers every 10s if credibility < 100% // chance to lose some followers every 10s if credibility < 100%
this._timeSinceLastLost += time; this._timeSinceLastLost += time;
if (this._timeSinceLastLost > state.config.cfgCredibilityFollowerLossTime) { if (this._timeSinceLastLost > state.config.cfgCredibilityFollowerLossTime) {
if (this.value > 0) { if (this.value > 0) {
const creds = state.resource.credibility; const creds = state.resource.credibility;
if (creds?.max !== undefined) { if (creds?.max !== undefined) {
const ratio = const ratio = Math.ceil(creds.value) / creds.max(state);
Math.ceil(creds.value) / creds.max(state);
if (Math.random() > ratio) { if (Math.random() > ratio) {
const lost = Math.ceil(this.value const lost = Math.ceil(
* state.config.cfgCredibilityFollowerLossRatio this.value *
* (1 - ratio)); state.config.cfgCredibilityFollowerLossRatio *
(1 - ratio)
);
this.addValue(lost * -1, state); this.addValue(lost * -1, state);
} }
} }
@ -101,10 +104,12 @@ class Follower implements IResource {
} }
// log lost and gained followers every 10s // log lost and gained followers every 10s
if (state.now if (
- this._lastRecruitmentLog > state.config.cfgFollowerGainLossLogTimer state.now - this._lastRecruitmentLog >
&& (Object.keys(this._followerSources).length > 0 state.config.cfgFollowerGainLossLogTimer &&
|| Object.keys(this._followerDests).length > 0)) { (Object.keys(this._followerSources).length > 0 ||
Object.keys(this._followerDests).length > 0)
) {
if (Object.keys(this._followerDests).length > 0) { if (Object.keys(this._followerDests).length > 0) {
let msg = ''; let msg = '';
let total = 0; let total = 0;
@ -114,12 +119,18 @@ class Follower implements IResource {
const followers = this._followerDests[rkey]; const followers = this._followerDests[rkey];
if (religion !== undefined && followers !== undefined) { if (religion !== undefined && followers !== undefined) {
if (msg !== '') msg += ', '; if (msg !== '') msg += ', ';
msg += `${formatNumber(followers)} ${followers > 1 ? religion.pluralName : religion.singularName}`; msg += `${formatNumber(followers)} ${
followers > 1 ? religion.pluralName : religion.singularName
}`;
total += followers; total += followers;
delete this._followerDests[rkey]; delete this._followerDests[rkey];
} }
} }
state.log(`You lost ${formatNumber(total)} ${total > 1 ? this.pluralName : this.singularName}: ${msg}`); state.log(
`You lost ${formatNumber(total)} ${
total > 1 ? this.pluralName : this.singularName
}: ${msg}`
);
} }
if (Object.keys(this._followerSources).length > 0) { if (Object.keys(this._followerSources).length > 0) {
let msg = ''; let msg = '';
@ -130,19 +141,24 @@ class Follower implements IResource {
const followers = this._followerSources[rkey]; const followers = this._followerSources[rkey];
if (religion !== undefined && followers !== undefined) { if (religion !== undefined && followers !== undefined) {
if (msg !== '') msg += ', '; if (msg !== '') msg += ', ';
msg += msg += `${formatNumber(followers)} ${
`${formatNumber(followers)} ${followers > 1 ? religion.pluralName : religion.singularName}`; followers > 1 ? religion.pluralName : religion.singularName
}`;
total += followers; total += followers;
delete this._followerSources[rkey]; delete this._followerSources[rkey];
} }
} }
state.log(`You gained ${formatNumber(total)} ${total > 1 ? this.pluralName : this.singularName}: ${msg}`); state.log(
`You gained ${formatNumber(total)} ${
total > 1 ? this.pluralName : this.singularName
}: ${msg}`
);
} }
this._lastRecruitmentLog = state.now; this._lastRecruitmentLog = state.now;
} }
} }
private _recruitFollower (state: GameState): void { private _recruitFollower(state: GameState): void {
// don't exceed max // don't exceed max
if (this.value >= this.max(state)) { if (this.value >= this.max(state)) {
state.log('You have no room for more followers.'); state.log('You have no room for more followers.');
@ -163,11 +179,19 @@ class Follower implements IResource {
this.addValue(1, state); this.addValue(1, state);
} }
private _getRandomReligion ( private _getRandomReligion(
state: GameState): [ResourceKey, IResource] | null { state: GameState
const religs = [ResourceKey.christianity, ResourceKey.islam, ): [ResourceKey, IResource] | null {
ResourceKey.hinduism, ResourceKey.buddhism, ResourceKey.sikhism, const religs = [
ResourceKey.judaism, ResourceKey.other, ResourceKey.atheism]; ResourceKey.christianity,
ResourceKey.islam,
ResourceKey.hinduism,
ResourceKey.buddhism,
ResourceKey.sikhism,
ResourceKey.judaism,
ResourceKey.other,
ResourceKey.atheism,
];
const source = religs[Math.floor(Math.random() * 8)]; const source = religs[Math.floor(Math.random() * 8)];
const resource = state.resource[source]; const resource = state.resource[source];
return resource !== undefined ? [source, resource] : null; return resource !== undefined ? [source, resource] : null;

View File

@ -1,21 +1,24 @@
/// <reference path="./Infrastructure.ts" /> /// <reference path="./Infrastructure.ts" />
class House extends Infrastructure { class House extends Infrastructure {
constructor (config: GameConfig) { constructor(config: GameConfig) {
super( super(
'Houses', 'Houses',
'house', 'house',
'houses', 'houses',
`Provides room to house ${formatNumber(config.cfgCapacity.houses?.followers ?? 0)} followers.`); `Provides room to house ${formatNumber(
config.cfgCapacity.houses?.followers ?? 0
)} followers.`
);
this.cost.money = config.cfgInitialCost.houses; this.cost.money = config.cfgInitialCost.houses;
this._costMultiplier.money = config.cfgCostMultiplier.houses; this._costMultiplier.money = config.cfgCostMultiplier.houses;
} }
public max: (state: GameState) => number = (state) => public max: (state: GameState) => number = (state) =>
(state.resource.compounds?.value ?? 0) (state.resource.compounds?.value ?? 0) *
* (state.config.cfgCapacity.compounds?.houses ?? 0); (state.config.cfgCapacity.compounds?.houses ?? 0);
public isUnlocked (state: GameState): boolean { public isUnlocked(state: GameState): boolean {
if (this._isUnlocked) return true; if (this._isUnlocked) return true;
const compounds = state.resource.compounds; const compounds = state.resource.compounds;
if (compounds !== undefined && compounds.value > 0) { if (compounds !== undefined && compounds.value > 0) {

View File

@ -20,6 +20,7 @@ interface IResource {
isUnlocked: (state: GameState) => boolean; isUnlocked: (state: GameState) => boolean;
emitConfig?: () => { [key: string]: string | number | boolean }; emitConfig?: () => { [key: string]: string | number | boolean };
restoreConfig?: ( restoreConfig?: (config: {
config: { [key: string]: string | number | boolean }) => void; [key: string]: string | number | boolean;
}) => void;
} }

View File

@ -4,7 +4,7 @@ abstract class Job implements IResource {
public readonly resourceType = ResourceType.job; public readonly resourceType = ResourceType.job;
public readonly valueInWholeNumbers = true; public readonly valueInWholeNumbers = true;
public value = 0; public value = 0;
public readonly cost: ResourceNumber = { }; public readonly cost: ResourceNumber = {};
public max?: (state: GameState) => number = undefined; public max?: (state: GameState) => number = undefined;
public inc?: (state: GameState) => number = undefined; public inc?: (state: GameState) => number = undefined;
@ -14,8 +14,8 @@ abstract class Job implements IResource {
name: 'Hire', name: 'Hire',
description: 'Promote one of your followers.', description: 'Promote one of your followers.',
isEnabled: (state: GameState): boolean => isEnabled: (state: GameState): boolean =>
(this.max === undefined || this.value < this.max(state)) (this.max === undefined || this.value < this.max(state)) &&
&& this._availableJobs(state) > 0, this._availableJobs(state) > 0,
performAction: (state: GameState): void => { performAction: (state: GameState): void => {
this._promoteFollower(state); this._promoteFollower(state);
}, },
@ -30,26 +30,26 @@ abstract class Job implements IResource {
}, },
]; ];
protected _costMultiplier: { [key in ResourceKey]?: number } = { }; protected _costMultiplier: { [key in ResourceKey]?: number } = {};
protected _isUnlocked = false; protected _isUnlocked = false;
constructor ( constructor(
public readonly label: string, public readonly label: string,
public readonly singularName: string, public readonly singularName: string,
public readonly pluralName: string, public readonly pluralName: string,
public readonly description: string public readonly description: string
) { } ) {}
public addValue (amount: number): void { public addValue(amount: number): void {
this.value += amount; this.value += amount;
if (this.value < 0) this.value = 0; if (this.value < 0) this.value = 0;
} }
public isUnlocked (_state: GameState): boolean { public isUnlocked(_state: GameState): boolean {
return this._isUnlocked; return this._isUnlocked;
} }
public advanceAction (_time: number, state: GameState): void { public advanceAction(_time: number, state: GameState): void {
// if we're out of followers then the jobs also vacate // if we're out of followers then the jobs also vacate
const avail = this._availableJobs(state); const avail = this._availableJobs(state);
if (avail < 0 && this.value > 0) { if (avail < 0 && this.value > 0) {
@ -58,42 +58,49 @@ abstract class Job implements IResource {
return; return;
} }
protected _availableJobs (state: GameState): number { protected _availableJobs(state: GameState): number {
// number of followers minus the number of filled jobs // number of followers minus the number of filled jobs
const followers = state.resource.followers?.value ?? 0; const followers = state.resource.followers?.value ?? 0;
const hired = state.resources.reduce( const hired = state.resources.reduce(
(tot: number, rkey: ResourceKey): number => { (tot: number, rkey: ResourceKey): number => {
const res = state.resource[rkey]; const res = state.resource[rkey];
return res?.resourceType === ResourceType.job return res?.resourceType === ResourceType.job ? tot + res.value : tot;
? tot + res.value },
: tot; 0
}, 0); );
return followers - hired; return followers - hired;
} }
protected _totalPayroll (state: GameState): number { protected _totalPayroll(state: GameState): number {
// number of followers minus the number of filled jobs // number of followers minus the number of filled jobs
const followers = state.resource.followers?.value ?? 0; const followers = state.resource.followers?.value ?? 0;
const hired = state.resources.reduce( const hired = state.resources.reduce(
(tot: number, rkey: ResourceKey): number => { (tot: number, rkey: ResourceKey): number => {
const res = state.resource[rkey]; const res = state.resource[rkey];
return res?.resourceType === ResourceType.job return res?.resourceType === ResourceType.job ? tot + res.value : tot;
? tot + res.value },
: tot; 0
}, 0); );
return followers - hired; return followers - hired;
} }
protected _hireLog (amount: number, _state: GameState): string { protected _hireLog(amount: number, _state: GameState): string {
return amount > 0 return amount > 0
? `You hired ${amount} ${amount > 1 ? this.pluralName : this.singularName}.` ? `You hired ${amount} ${
: `You fired ${amount * -1} ${amount * -1 > 1 ? this.pluralName : this.singularName}.`; amount > 1 ? this.pluralName : this.singularName
}.`
: `You fired ${amount * -1} ${
amount * -1 > 1 ? this.pluralName : this.singularName
}.`;
} }
private _promoteFollower (state: GameState): void { private _promoteFollower(state: GameState): void {
if (this._availableJobs(state) <= 0) return; if (this._availableJobs(state) <= 0) return;
if (this.max !== undefined && this.value < this.max(state) if (
&& state.deductCost(this.cost)) { this.max !== undefined &&
this.value < this.max(state) &&
state.deductCost(this.cost)
) {
this.addValue(1); this.addValue(1);
state.log(this._hireLog(1, state)); state.log(this._hireLog(1, state));
for (const key in this._costMultiplier) { for (const key in this._costMultiplier) {
@ -104,7 +111,7 @@ abstract class Job implements IResource {
} }
} }
private _demoteFollower (state: GameState): void { private _demoteFollower(state: GameState): void {
if (this.value <= 0) return; if (this.value <= 0) return;
this.addValue(-1); this.addValue(-1);
state.log(this._hireLog(-1, state)); state.log(this._hireLog(-1, state));

View File

@ -1,12 +1,15 @@
/// <reference path="./Infrastructure.ts" /> /// <reference path="./Infrastructure.ts" />
class Megachurch extends Infrastructure { class Megachurch extends Infrastructure {
constructor (config: GameConfig) { constructor(config: GameConfig) {
super( super(
'Megachurches', 'Megachurches',
'megachurch', 'megachurch',
'megachurches', 'megachurches',
`Room for ${formatNumber(config.cfgCapacity.megaChurches?.pastors ?? 0)} pastors`); `Room for ${formatNumber(
config.cfgCapacity.megaChurches?.pastors ?? 0
)} pastors`
);
this.cost.money = config.cfgInitialCost.megaChurches; this.cost.money = config.cfgInitialCost.megaChurches;
this._costMultiplier.money = config.cfgCostMultiplier.megaChurches; this._costMultiplier.money = config.cfgCostMultiplier.megaChurches;
} }
@ -14,7 +17,7 @@ class Megachurch extends Infrastructure {
public max: (state: GameState) => number = (state) => public max: (state: GameState) => number = (state) =>
state.config.cfgInitialMax.megaChurches ?? 0; state.config.cfgInitialMax.megaChurches ?? 0;
public isUnlocked (state: GameState): boolean { public isUnlocked(state: GameState): boolean {
if (this._isUnlocked) return true; if (this._isUnlocked) return true;
const permit = state.resource.buildingPermit; const permit = state.resource.buildingPermit;
if (permit !== undefined && permit.value > 0) { if (permit !== undefined && permit.value > 0) {

View File

@ -13,8 +13,8 @@ class Money implements IResource {
name: 'Collect Tithes', name: 'Collect Tithes',
description: 'Voluntary contributions from followers.', description: 'Voluntary contributions from followers.',
isEnabled: (state: GameState): boolean => isEnabled: (state: GameState): boolean =>
this.value < this.max(state) this.value < this.max(state) &&
&& (state.resource.followers?.value ?? 0) >= 1, (state.resource.followers?.value ?? 0) >= 1,
performAction: (state: GameState): void => { performAction: (state: GameState): void => {
this._collectTithes(state); this._collectTithes(state);
}, },
@ -23,20 +23,19 @@ class Money implements IResource {
private _lastCollectionTime = 0; private _lastCollectionTime = 0;
constructor ( constructor(public value: number) {}
public value: number
) {}
public isUnlocked = (_state: GameState): boolean => true; public isUnlocked = (_state: GameState): boolean => true;
public addValue (amount: number, _state: GameState): void { public addValue(amount: number, _state: GameState): void {
this.value += amount; this.value += amount;
} }
public max: (state: GameState) => number = (state: GameState) => { public max: (state: GameState) => number = (state: GameState) => {
let max = state.config.cfgInitialMax.money ?? 0; let max = state.config.cfgInitialMax.money ?? 0;
max += (state.resource.compounds?.value ?? 0) max +=
* (state.config.cfgCapacity.compounds?.money ?? 0); (state.resource.compounds?.value ?? 0) *
(state.config.cfgCapacity.compounds?.money ?? 0);
return max; return max;
}; };
@ -44,17 +43,19 @@ class Money implements IResource {
let inc = 0; let inc = 0;
// crypto currency // crypto currency
inc += (state.resource.cryptoCurrency?.value ?? 0) inc +=
* state.config.cfgCryptoReturnAmount; (state.resource.cryptoCurrency?.value ?? 0) *
state.config.cfgCryptoReturnAmount;
// salaries // salaries
inc -= (state.resource.pastors?.value ?? 0) inc -=
* (state.config.cfgSalary.pastors ?? 0); (state.resource.pastors?.value ?? 0) *
(state.config.cfgSalary.pastors ?? 0);
return inc; return inc;
}; };
protected _collectTithes (state: GameState): void { protected _collectTithes(state: GameState): void {
if (this.value >= this.max(state)) return; if (this.value >= this.max(state)) return;
const followers = state.resource.followers?.value ?? 0; const followers = state.resource.followers?.value ?? 0;
@ -63,8 +64,10 @@ class Money implements IResource {
// collecting too frequently hurts credibility // collecting too frequently hurts credibility
const diff = state.now - this._lastCollectionTime; const diff = state.now - this._lastCollectionTime;
if (diff < state.config.cfgTimeBetweenTithes) { if (diff < state.config.cfgTimeBetweenTithes) {
const lost = state.config.cfgTimeBetweenTithes const lost =
/ diff / state.config.cfgTitheCredibilityHitFactor; state.config.cfgTimeBetweenTithes /
diff /
state.config.cfgTitheCredibilityHitFactor;
state.resource.credibility?.addValue(lost * -1, state); state.resource.credibility?.addValue(lost * -1, state);
} }
@ -77,10 +80,14 @@ class Money implements IResource {
} }
} }
protected _purchaseLog (amount: number, state: GameState): string { protected _purchaseLog(amount: number, state: GameState): string {
const followers = state.resource.followers; const followers = state.resource.followers;
if (followers !== undefined) { if (followers !== undefined) {
return `You collected $${formatNumber(amount)} from ${formatNumber(followers.value)} ${followers.value > 1 ? followers.pluralName : followers.singularName}.`; return `You collected $${formatNumber(amount)} from ${formatNumber(
followers.value
)} ${
followers.value > 1 ? followers.pluralName : followers.singularName
}.`;
} }
return `You collected $${formatNumber(amount)} in tithings.`; return `You collected $${formatNumber(amount)} in tithings.`;
} }

View File

@ -7,20 +7,18 @@ abstract class Passive implements IResource {
public advanceAction?: (time: number, state: GameState) => void = undefined; public advanceAction?: (time: number, state: GameState) => void = undefined;
constructor ( constructor(
public readonly label: string, public readonly label: string,
public readonly singularName: string, public readonly singularName: string,
public readonly pluralName: string, public readonly pluralName: string,
public readonly description: string public readonly description: string
) { } ) {}
public addValue(amount: number, _state: GameState): void {
public addValue (amount: number, _state: GameState): void {
this.value += amount; this.value += amount;
} }
public isUnlocked (_state: GameState): boolean { public isUnlocked(_state: GameState): boolean {
return true; return true;
} }
} }

View File

@ -3,46 +3,58 @@
class Pastor extends Job { class Pastor extends Job {
private _timeSinceLastTithe = 0; private _timeSinceLastTithe = 0;
constructor () { constructor() {
super( super(
'Pastors', 'Pastors',
'pastor', 'pastor',
'pastors', 'pastors',
'Collect tithings for you and recruit new members from other faiths automatically.'); 'Collect tithings for you and recruit new members from other faiths automatically.'
);
} }
public max: (state: GameState) => number = (state) => { public max: (state: GameState) => number = (state) => {
let max = (state.resource.churches?.value ?? 0) let max =
* (state.config.cfgCapacity.churches?.pastors ?? 0); (state.resource.churches?.value ?? 0) *
max += (state.resource.megaChurches?.value ?? 0) (state.config.cfgCapacity.churches?.pastors ?? 0);
* (state.config.cfgCapacity.megaChurches?.pastors ?? 0); max +=
(state.resource.megaChurches?.value ?? 0) *
(state.config.cfgCapacity.megaChurches?.pastors ?? 0);
return max; return max;
}; };
public isUnlocked (state: GameState): boolean { public isUnlocked(state: GameState): boolean {
if (this._isUnlocked) return true; if (this._isUnlocked) return true;
this._isUnlocked = state.resource.churches?.isUnlocked(state) === true; this._isUnlocked = state.resource.churches?.isUnlocked(state) === true;
return this._isUnlocked; return this._isUnlocked;
} }
public advanceAction (time: number, state: GameState): void { public advanceAction(time: number, state: GameState): void {
super.advanceAction(time, state); super.advanceAction(time, state);
this._timeSinceLastTithe += time; this._timeSinceLastTithe += time;
if (this._timeSinceLastTithe >= state.config.cfgTimeBetweenTithes) { if (this._timeSinceLastTithe >= state.config.cfgTimeBetweenTithes) {
const money = state.resource.money; const money = state.resource.money;
const followers = state.resource.followers; const followers = state.resource.followers;
let tithed = Math.floor(this.value let tithed = Math.floor(
* state.config.cfgPastorTitheCollectionFollowerMax); this.value * state.config.cfgPastorTitheCollectionFollowerMax
);
if (Math.floor(followers?.value ?? 0) < tithed) if (Math.floor(followers?.value ?? 0) < tithed)
tithed = Math.floor(followers?.value ?? 0); tithed = Math.floor(followers?.value ?? 0);
let collected = tithed * state.config.cfgTitheAmount; let collected = tithed * state.config.cfgTitheAmount;
if (money?.max !== undefined if (
&& collected > money.max(state) - money.value) money?.max !== undefined &&
collected > money.max(state) - money.value
)
collected = money.max(state) - money.value; collected = money.max(state) - money.value;
if (collected > 0) { if (collected > 0) {
money?.addValue(collected, state); money?.addValue(collected, state);
if (followers !== undefined) { if (followers !== undefined) {
state.log(`Your pastors collected $${formatNumber(collected)} in tithings from ${formatNumber(tithed)} ${tithed > 1 ? followers.pluralName : followers.singularName}.`); state.log(
`Your pastors collected $${formatNumber(
collected
)} in tithings from ${formatNumber(tithed)} ${
tithed > 1 ? followers.pluralName : followers.singularName
}.`
);
} }
} }
this._timeSinceLastTithe = 0; this._timeSinceLastTithe = 0;

View File

@ -4,7 +4,7 @@ abstract class Purchasable implements IResource {
public readonly resourceType: ResourceType = ResourceType.consumable; public readonly resourceType: ResourceType = ResourceType.consumable;
public valueInWholeNumbers = true; public valueInWholeNumbers = true;
public value = 0; public value = 0;
public readonly cost: ResourceNumber = { }; public readonly cost: ResourceNumber = {};
public inc?: (state: GameState) => number = undefined; public inc?: (state: GameState) => number = undefined;
public max?: (_state: GameState) => number = undefined; public max?: (_state: GameState) => number = undefined;
@ -14,46 +14,48 @@ abstract class Purchasable implements IResource {
name: this._purchaseButtonText, name: this._purchaseButtonText,
description: this._purchaseDescription, description: this._purchaseDescription,
isEnabled: (state: GameState): boolean => isEnabled: (state: GameState): boolean =>
(this.max === undefined || this.value < this.max(state)) (this.max === undefined || this.value < this.max(state)) &&
&& state.isPurchasable(this.cost), state.isPurchasable(this.cost),
performAction: (state: GameState): void => { performAction: (state: GameState): void => {
this._purchase(state); this._purchase(state);
}, },
}, },
]; ];
protected _costMultiplier: ResourceNumber = { }; protected _costMultiplier: ResourceNumber = {};
protected _isUnlocked = false; protected _isUnlocked = false;
constructor ( constructor(
public readonly label: string, public readonly label: string,
public readonly singularName: string, public readonly singularName: string,
public readonly pluralName: string, public readonly pluralName: string,
public readonly description: string, public readonly description: string,
private readonly _purchaseButtonText: string = 'Purchase', private readonly _purchaseButtonText: string = 'Purchase',
private readonly _purchaseDescription: string = `Buy a ${singularName}.`, private readonly _purchaseDescription: string = `Buy a ${singularName}.`
) { } ) {}
public addValue (amount: number, _state: GameState): void { public addValue(amount: number, _state: GameState): void {
this.value += amount; this.value += amount;
} }
public isUnlocked (state: GameState): boolean { public isUnlocked(state: GameState): boolean {
if (!this._isUnlocked && state.isPurchasable(this.cost)) { if (!this._isUnlocked && state.isPurchasable(this.cost)) {
this._isUnlocked = true; this._isUnlocked = true;
} }
return this._isUnlocked; return this._isUnlocked;
} }
public advanceAction (_time: number, _state: GameState): void { public advanceAction(_time: number, _state: GameState): void {
return; return;
} }
protected _purchaseLog (amount: number, _state: GameState): string { protected _purchaseLog(amount: number, _state: GameState): string {
return `You purchased ${amount} ${amount > 1 ? this.pluralName : this.singularName}.`; return `You purchased ${amount} ${
amount > 1 ? this.pluralName : this.singularName
}.`;
} }
private _purchase (state: GameState): void { private _purchase(state: GameState): void {
if (this.max !== undefined && this.value >= this.max(state)) return; if (this.max !== undefined && this.value >= this.max(state)) return;
if (state.deductCost(this.cost)) { if (state.deductCost(this.cost)) {
this.value += 1; this.value += 1;

View File

@ -4,19 +4,19 @@ class Religion implements IResource {
public readonly resourceType = ResourceType.religion; public readonly resourceType = ResourceType.religion;
public readonly valueInWholeNumbers = true; public readonly valueInWholeNumbers = true;
constructor ( constructor(
public readonly label: string, public readonly label: string,
public readonly singularName: string, public readonly singularName: string,
public readonly pluralName: string, public readonly pluralName: string,
public readonly description: string, public readonly description: string,
public value: number, public value: number
) { } ) {}
public addValue (amount: number, _state: GameState): void { public addValue(amount: number, _state: GameState): void {
this.value += amount; this.value += amount;
} }
public isUnlocked (_state: GameState): boolean { public isUnlocked(_state: GameState): boolean {
return true; return true;
} }
} }

View File

@ -4,7 +4,7 @@ abstract class Research extends Purchasable {
public readonly resourceType = ResourceType.research; public readonly resourceType = ResourceType.research;
public inc = undefined; public inc = undefined;
constructor ( constructor(
public readonly label: string, public readonly label: string,
public readonly singularName: string, public readonly singularName: string,
public readonly pluralName: string, public readonly pluralName: string,
@ -16,7 +16,8 @@ abstract class Research extends Purchasable {
pluralName, pluralName,
description, description,
'Learn', 'Learn',
'Complete this research.'); 'Complete this research.'
);
this.value = 0; this.value = 0;
} }

View File

@ -47,6 +47,6 @@ type ResourceConfig = {
}; };
type SaveData = { type SaveData = {
version: { maj: number, min: number }; version: { maj: number; min: number };
resources: { [key in ResourceKey]?: ResourceConfig }; resources: { [key in ResourceKey]?: ResourceConfig };
}; };

View File

@ -1,12 +1,15 @@
/// <reference path="./Infrastructure.ts" /> /// <reference path="./Infrastructure.ts" />
class Tent extends Infrastructure { class Tent extends Infrastructure {
constructor (config: GameConfig) { constructor(config: GameConfig) {
super( super(
'Tents', 'Tents',
'tent', 'tent',
'tents', 'tents',
`Provides room to house ${formatNumber(config.cfgCapacity.tents?.followers ?? 0)} followers.`); `Provides room to house ${formatNumber(
config.cfgCapacity.tents?.followers ?? 0
)} followers.`
);
this.cost.money = config.cfgInitialCost.tents; this.cost.money = config.cfgInitialCost.tents;
this._costMultiplier.money = config.cfgCostMultiplier.tents; this._costMultiplier.money = config.cfgCostMultiplier.tents;
} }
@ -14,8 +17,9 @@ class Tent extends Infrastructure {
public max: (state: GameState) => number = (state) => { public max: (state: GameState) => number = (state) => {
// ten extra tents per compound // ten extra tents per compound
let max = state.config.cfgInitialMax.tents ?? 0; let max = state.config.cfgInitialMax.tents ?? 0;
max += (state.resource.compounds?.value ?? 0) max +=
* (state.config.cfgCapacity.compounds?.tents ?? 0); (state.resource.compounds?.value ?? 0) *
(state.config.cfgCapacity.compounds?.tents ?? 0);
return max; return max;
}; };
} }

View File

@ -6,7 +6,7 @@ class DebugRenderer implements IRenderer {
private _initialized = false; private _initialized = false;
private _handleClick = true; private _handleClick = true;
public render (state: GameState): void { public render(state: GameState): void {
const rkeys = state.resources; const rkeys = state.resources;
const container = document.getElementById('irreligious-game'); const container = document.getElementById('irreligious-game');
if (!this._initialized) { if (!this._initialized) {
@ -46,7 +46,8 @@ class DebugRenderer implements IRenderer {
const resource = state.resource[rkey]; const resource = state.resource[rkey];
if (resource === undefined || resource.label === undefined) continue; if (resource === undefined || resource.label === undefined) continue;
const resContainer = document.getElementById( const resContainer = document.getElementById(
`resource-container-${resource.resourceType}`); `resource-container-${resource.resourceType}`
);
if (resContainer === null) continue; if (resContainer === null) continue;
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'resource locked'; el.className = 'resource locked';
@ -70,8 +71,10 @@ class DebugRenderer implements IRenderer {
${this._escape(action.name)}</button>`; ${this._escape(action.name)}</button>`;
} }
} }
if (resource.cost !== undefined if (
&& Object.keys(resource.cost).length !== 0) { resource.cost !== undefined &&
Object.keys(resource.cost).length !== 0
) {
content += "<br>Cost: <span class='resource-cost'></span>"; content += "<br>Cost: <span class='resource-cost'></span>";
} }
el.innerHTML = content; el.innerHTML = content;
@ -93,8 +96,9 @@ class DebugRenderer implements IRenderer {
<button id='dbg-btn-reset'>Reset Game</button> <button id='dbg-btn-reset'>Reset Game</button>
`; `;
resDiv.appendChild(footer); resDiv.appendChild(footer);
document.getElementById('dbg-btn-reset')?.addEventListener('click', document
(): void => { .getElementById('dbg-btn-reset')
?.addEventListener('click', (): void => {
state.reset(); state.reset();
container.innerHTML = ''; container.innerHTML = '';
this._initialized = false; this._initialized = false;
@ -112,9 +116,10 @@ class DebugRenderer implements IRenderer {
? Math.floor(resource.value) ? Math.floor(resource.value)
: resource.value; : resource.value;
elV.innerHTML = formatNumber(value); elV.innerHTML = formatNumber(value);
elT.innerHTML = resource.max !== undefined elT.innerHTML =
? ` / ${formatNumber(resource.max(state))}` resource.max !== undefined
: ''; ? ` / ${formatNumber(resource.max(state))}`
: '';
if (resource.userActions !== undefined) { if (resource.userActions !== undefined) {
for (let i = 0; i < resource.userActions.length; i++) { for (let i = 0; i < resource.userActions.length; i++) {
const elB = document.getElementById(`resource-btn-${rkey}-${i}`); const elB = document.getElementById(`resource-btn-${rkey}-${i}`);
@ -128,8 +133,7 @@ class DebugRenderer implements IRenderer {
} }
if (resource.inc !== undefined && resource.inc(state) > 0) { if (resource.inc !== undefined && resource.inc(state) > 0) {
const elI = el.getElementsByClassName('resource-inc')[0]; const elI = el.getElementsByClassName('resource-inc')[0];
elI.innerHTML = elI.innerHTML = ` +${formatNumber(resource.inc(state))}/s`;
` +${formatNumber(resource.inc(state))}/s`;
} }
if (this._handleClick) { if (this._handleClick) {
const elC = el.getElementsByClassName('resource-cost'); const elC = el.getElementsByClassName('resource-cost');
@ -150,7 +154,7 @@ class DebugRenderer implements IRenderer {
this._initialized = true; this._initialized = true;
} }
private _escape (text: string): string { private _escape(text: string): string {
const escapes: { [key: string]: string } = { const escapes: { [key: string]: string } = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
@ -160,11 +164,10 @@ class DebugRenderer implements IRenderer {
'/': '&#x2F;', '/': '&#x2F;',
}; };
const escaper = /[&<>"'/]/g; const escaper = /[&<>"'/]/g;
return text.replace(escaper, (match: string): string => return text.replace(escaper, (match: string): string => escapes[match]);
escapes[match]);
} }
private _getCostStr (resource: IResource, state: GameState): string { private _getCostStr(resource: IResource, state: GameState): string {
let cost = ''; let cost = '';
for (const rkey of state.resources) { for (const rkey of state.resources) {
if (resource.cost?.[rkey] !== undefined) { if (resource.cost?.[rkey] !== undefined) {