From 3e767b396b9d80a6fd2b8ae706fc30464c19e21b Mon Sep 17 00:00:00 2001 From: Rudis Muiznieks Date: Sun, 5 Sep 2021 14:45:37 -0500 Subject: [PATCH] fixes for linter --- .eslintrc.json | 23 +++-- src/main.ts | 12 +-- src/model/GameConfig.ts | 11 ++- src/model/GameState.ts | 126 +++++++++++++++------------ src/model/logging/DebugLogger.ts | 8 +- src/model/logging/ILogger.ts | 2 +- src/model/resource/Church.ts | 6 +- src/model/resource/Credibility.ts | 11 +-- src/model/resource/CryptoCurrency.ts | 4 +- src/model/resource/House.ts | 7 +- src/model/resource/IResource.ts | 34 ++++---- src/model/resource/Infrastructure.ts | 2 +- src/model/resource/Job.ts | 30 +++---- src/model/resource/MegaChurch.ts | 4 +- src/model/resource/Money.ts | 13 ++- src/model/resource/Passive.ts | 21 ++--- src/model/resource/Pastor.ts | 6 +- src/model/resource/PlayerOrg.ts | 50 ++++++----- src/model/resource/Purchasable.ts | 17 ++-- src/model/resource/Religion.ts | 6 +- src/model/resource/Research.ts | 7 +- src/model/resource/Tent.ts | 7 +- src/render/DebugRenderer.ts | 72 ++++++++------- src/render/IRenderer.ts | 4 +- tsconfig.json | 3 +- 25 files changed, 245 insertions(+), 241 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 3c548b9..201db55 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,6 +9,10 @@ "parserOptions": {"project": "tsconfig.json"}, "plugins": ["@typescript-eslint"], "rules": { + "max-len": ["warn", { + "ignoreStrings": true, + "ignoreTemplateLiterals": true, + "ignoreRegExpLiterals": true}], "@typescript-eslint/triple-slash-reference": "off", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/array-type": ["warn", {"default": "array-simple"}], @@ -29,7 +33,6 @@ "@typescript-eslint/no-base-to-string": "error", "@typescript-eslint/no-confusing-non-null-assertion": "error", "@typescript-eslint/no-confusing-void-expression": "error", - "@typescript-eslint/no-dynamic-delete": "warn", "@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-implicit-any-catch": "error", "@typescript-eslint/no-invalid-void-type": "error", @@ -61,12 +64,6 @@ "allowNullableObject": false}], "@typescript-eslint/switch-exhaustiveness-check": "warn", "@typescript-eslint/type-annotation-spacing": "warn", - "@typescript-eslint/typedef": ["error", { - "arrowParameter": true, - "memberVariableDeclaration": true, - "parameter": true, - "propertyDeclaration": true, - "variableDeclaration": true}], "@typescript-eslint/unified-signatures": "warn", "@typescript-eslint/brace-style": "warn", "@typescript-eslint/comma-dangle": ["warn", "always-multiline"], @@ -74,7 +71,17 @@ "@typescript-eslint/default-param-last": "error", "@typescript-eslint/dot-notation": "error", "@typescript-eslint/func-call-spacing": ["warn", "never"], - "@typescript-eslint/indent": ["warn", 2], + "@typescript-eslint/indent": ["warn", 2, { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + "FunctionDeclaration": {"parameters": 1, "body": 1}, + "CallExpression": {"arguments": 1}, + "ArrayExpression": 1, + "ObjectExpression": 1, + "MemberExpression": 2, + "flatTernaryExpressions": false, + "ignoreComments": false}], "@typescript-eslint/keyword-spacing": "error", "@typescript-eslint/lines-between-class-members": ["warn", "always", { "exceptAfterSingleLine": true}], diff --git a/src/main.ts b/src/main.ts index fe8055c..757b685 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,7 @@ /// let globalStartTime = 0; -let globalTimeout: number = null; +let globalTimeout: number | null = null; const cycleLength = 250; function gameLoop (state: GameState, renderer: IRenderer): void { @@ -15,8 +15,9 @@ function gameLoop (state: GameState, renderer: IRenderer): void { // run again in 1sec globalStartTime = new Date().getTime(); - globalTimeout = setTimeout((): void => - gameLoop(state, renderer), cycleLength); + globalTimeout = setTimeout((): void => { + gameLoop(state, renderer); + }, cycleLength); } function startGame (state: GameState, renderer: IRenderer): void { @@ -47,6 +48,7 @@ function startGame (state: GameState, renderer: IRenderer): void { }); if (document.readyState !== 'loading') startGame(state, renderer); - else document.addEventListener('DOMContentLoaded', (): void => - startGame(state, renderer)); + else document.addEventListener('DOMContentLoaded', (): void => { + startGame(state, renderer); + }); })(); diff --git a/src/model/GameConfig.ts b/src/model/GameConfig.ts index e5ee99d..f404e82 100644 --- a/src/model/GameConfig.ts +++ b/src/model/GameConfig.ts @@ -25,15 +25,20 @@ class GameConfig { public relOtherShare = 0.02; public relNoneShare = 0.16; + public cfgStartingPlayerMax = 5; + public cfgStartingMoneyMax = 500000; + public cfgStartingTentMax = 5; + public cfgStartingCryptoMax = 1000; + public cfgStartingMegaChurchMax = 2; + public cfgTitheAmount = 10; public cfgTimeBetweenTithes = 30000; public cfgCryptoReturnAmount = 1; public cfgCredibilityRestoreRate = 0.25; - public cfgPastorRecruitRate= 0.01; + public cfgPastorRecruitRate = 0.01; public generateState (): GameState { - const state: GameState = new GameState(); - state.config = this; + const state: GameState = new GameState(this); // create player organization state.addResource('plorg', new PlayerOrg()); diff --git a/src/model/GameState.ts b/src/model/GameState.ts index ea76436..40f4e65 100644 --- a/src/model/GameState.ts +++ b/src/model/GameState.ts @@ -1,20 +1,25 @@ class GameState { + public readonly config: GameConfig; + + public onResourceClick: Array<() => void> = []; + public logger: ILogger | null = null; + public numberFormatDigits = 1; + + public now = 0; + private readonly _versionMaj: number = 0; private readonly _versionMin: number = 1; - public config: GameConfig; - private _timeSinceSave = 0; private readonly _timeBetweenSaves: number = 10000; - private _resources: {[key: string]: IResource} = { }; - private _resourceKeys: string[] = []; + private _resources: { [key: string]: IResource } = { }; + private readonly _resourceKeys: string[] = []; - public onResourceClick: Array<() => void> = []; - public logger: ILogger = null; - public numberFormatDigits = 1; - public now = 0; + constructor (config: GameConfig) { + this.config = config; + } public addResource (key: string, resource: IResource): void { this._resourceKeys.push(key); @@ -32,32 +37,29 @@ class GameState { // advance each resource for (const rkey of this._resourceKeys) { - if (this._resources[rkey].isUnlocked(this) - && this._resources[rkey].advanceAction !== null) { - this._resources[rkey].advanceAction(time, this); + const resource = this._resources[rkey]; + if (this._resources[rkey].isUnlocked(this)) { + if (resource.advanceAction !== null) + resource.advanceAction(time, this); } } // perform auto increments for (const rkey of this._resourceKeys) { - if (!this._resources[rkey].isUnlocked(this)) continue; + const resource = this._resources[rkey]; + if (!resource.isUnlocked(this)) continue; - const max: number = this._resources[rkey].max - ? this._resources[rkey].max(this) - : null; - const inc: number = this._resources[rkey].inc - ? this._resources[rkey].inc(this) - : 0; - if (inc > 0 && (max === null - || this._resources[rkey].value < max)) { - this._resources[rkey].addValue(inc * time / 1000, this); + if (resource.inc !== null && (resource.max === null + || this._resources[rkey].value < resource.max(this))) { + this._resources[rkey].addValue(resource.inc(this) * time / 1000, this); } - const val: number = this._resources[rkey].value; - if (max !== null && val > max) { - this._resources[rkey].addValue((val - max) * -1, this); + + if (resource.max !== null && resource.value > resource.max(this)) { + this._resources[rkey].addValue( + (resource.value - resource.max(this)) * -1, this); } - if (val < 0) { - this._resources[rkey].addValue(val * -1, this); + if (resource.value < 0) { + this._resources[rkey].addValue(resource.value * -1, this); } } } @@ -71,18 +73,19 @@ class GameState { } public performClick (resourceKey: string): void { - if (!this._resources[resourceKey].isUnlocked(this)) return; + const resource = this._resources[resourceKey]; + if (!resource.isUnlocked(this)) return; - if (this._resources[resourceKey].clickAction !== null) { - this._resources[resourceKey].clickAction(this); + if (resource.clickAction !== null) { + resource.clickAction(this); for (const callback of this.onResourceClick) { callback(); } } } - public deductCost (cost: { [rkey: string]: number }): boolean { - if (cost === null || Object.keys(cost) === null) return true; + public deductCost (cost: { [rkey: string]: number } | null): boolean { + if (cost === null) return true; if (!this.isPurchasable(cost)) return false; for (const rkey of Object.keys(cost)) { this._resources[rkey].addValue(cost[rkey] * -1, this); @@ -90,8 +93,8 @@ class GameState { return true; } - public isPurchasable (cost: { [rkey: string]: number }): boolean { - if (cost === null || Object.keys(cost) === null) return true; + public isPurchasable (cost: { [rkey: string]: number } | null): boolean { + if (cost === null) return true; for (const rkey of Object.keys(cost)) { if (this._resources[rkey].value < cost[rkey]) { return false; @@ -101,24 +104,24 @@ class GameState { } public formatNumber (num: number): string { - type vlookup = { value: number, symbol: string }; - const lookup: vlookup[] = [ + type UnitLookup = { value: number, symbol: string }; + const lookup: UnitLookup[] = [ { 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' } + { value: 1e18, symbol: 'E' }, ]; const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; - let item: vlookup; + let item: UnitLookup | undefined; for (item of lookup.slice().reverse()) { if (num >= item.value) break; } - return item - ? (num / item.value).toFixed(this.numberFormatDigits) - .replace(rx, '$1') + item.symbol + return item !== undefined + ? (num / item.value).toFixed( + this.numberFormatDigits).replace(rx, '$1') + item.symbol : num.toFixed(this.numberFormatDigits).replace(rx, '$1'); } @@ -129,15 +132,15 @@ class GameState { } public save (): void { - const saveObj: any = { }; + const saveObj: SaveData = {}; saveObj.version = { maj: this._versionMaj, - min: this._versionMin + min: this._versionMin, }; for (const rkey of this._resourceKeys) { saveObj[rkey] = { value: this._resources[rkey].value, - cost: this._resources[rkey].cost + cost: this._resources[rkey].cost, }; } const saveStr: string = btoa(JSON.stringify(saveObj)); @@ -145,33 +148,32 @@ class GameState { } public load (): void { - const saveStr: string = localStorage.getItem('savegame'); + const saveStr: string | null = localStorage.getItem('savegame'); if (saveStr !== null) { try { - const saveObj: { [key: string]: any } = - JSON.parse(atob(saveStr)); - if (this._versionMaj === saveObj.version.maj) { + const saveObj: SaveData = JSON.parse(atob(saveStr)); + if (this._versionMaj === saveObj.version?.maj) { for (const rkey of this._resourceKeys) { - if (saveObj[rkey] !== undefined - && saveObj[rkey].value !== undefined - && saveObj[rkey].cost !== undefined) { - // @ts-ignore - this._resources[rkey].value = saveObj[rkey].value; - // @ts-ignore - this._resources[rkey].cost = saveObj[rkey].cost; + const saveRes = <{ + value: number; + cost: { [key: string]: number } | null; + } | undefined> saveObj[rkey]; + if (saveRes !== undefined) { + // @ts-expect-error writing read-only value from save data + this._resources[rkey].value = saveRes.value; + // @ts-expect-error writing read-only cost from save data + this._resources[rkey].cost = saveRes.cost ?? null; } } } else { // tslint:disable-next-line console.log('The saved game is too old to load.'); } - } catch (e) { - // tslint:disable-next-line + } catch (e: unknown) { console.log('There was an error loading the saved game.'); - console.log(e); // tslint:disable-line + console.log(e); } } else { - // tslint:disable-next-line console.log('No save game was found.'); } } @@ -183,3 +185,11 @@ class GameState { this.log('Reset all game resources.'); } } + +type SaveData = { + [key: string]: { + value: number; + cost: { [key: string]: number } | null; + } | { maj: number, min: number } | undefined; + version?: { maj: number, min: number }; +}; diff --git a/src/model/logging/DebugLogger.ts b/src/model/logging/DebugLogger.ts index 9eab0e5..d71f52b 100644 --- a/src/model/logging/DebugLogger.ts +++ b/src/model/logging/DebugLogger.ts @@ -1,5 +1,5 @@ class DebugLogger implements ILogger { - private _container: HTMLElement; + private readonly _container: HTMLElement; constructor (container: HTMLElement) { this._container = container; @@ -9,7 +9,9 @@ class DebugLogger implements ILogger { const p: HTMLElement = document.createElement('p'); p.innerText = text; this._container.appendChild(p); - this._container.parentElement.scrollTop = - this._container.parentElement.scrollHeight; + if (this._container.parentElement !== null) { + this._container.parentElement.scrollTop = + this._container.parentElement.scrollHeight; + } } } diff --git a/src/model/logging/ILogger.ts b/src/model/logging/ILogger.ts index 2ea8fd6..2dc7c31 100644 --- a/src/model/logging/ILogger.ts +++ b/src/model/logging/ILogger.ts @@ -1,3 +1,3 @@ interface ILogger { - msg (text: string): void; + msg: (text: string) => void; } diff --git a/src/model/resource/Church.ts b/src/model/resource/Church.ts index 9ebcfd9..48dcddc 100644 --- a/src/model/resource/Church.ts +++ b/src/model/resource/Church.ts @@ -8,10 +8,8 @@ class Church extends Infrastructure { this._costMultiplier.money = 1.01; } - public max (state: GameState): number { - // one church per compound - return state.getResource('cmpnd').value; - } + public max: (state: GameState) => number = (state) => + state.getResource('cmpnd').value; public isUnlocked (state: GameState): boolean { if (this._isUnlocked) return true; diff --git a/src/model/resource/Credibility.ts b/src/model/resource/Credibility.ts index 49f2b84..d928740 100644 --- a/src/model/resource/Credibility.ts +++ b/src/model/resource/Credibility.ts @@ -5,15 +5,10 @@ class Credibility extends Passive { super( 'Credibility', 'Affects your ability to recruit and retain followers.'); - this._baseMax = 100; this.value = 100; } - public max (): number { - return 100; - } - - public inc (state: GameState): number { - return state.config.cfgCredibilityRestoreRate; - } + public max: (state: GameState) => number = (_state) => 100; + public inc: (state: GameState) => number = (state) => + state.config.cfgCredibilityRestoreRate; } diff --git a/src/model/resource/CryptoCurrency.ts b/src/model/resource/CryptoCurrency.ts index ab475b0..10efdba 100644 --- a/src/model/resource/CryptoCurrency.ts +++ b/src/model/resource/CryptoCurrency.ts @@ -6,7 +6,9 @@ class CryptoCurrency extends Purchasable { "A crypto coin that 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; this.valueInWholeNumbers = false; } + + public max: (state: GameState) => number = (state) => + state.config.cfgStartingCryptoMax; } diff --git a/src/model/resource/House.ts b/src/model/resource/House.ts index eb65f09..8e92233 100644 --- a/src/model/resource/House.ts +++ b/src/model/resource/House.ts @@ -8,10 +8,9 @@ class House extends Infrastructure { this._costMultiplier.money = 1.01; } - public max (state: GameState): number { - // two houses per compound - return state.getResource('cmpnd').value * 2; - } + // two houses per compound + public max: (state: GameState) => number = (state) => + state.getResource('cmpnd').value * 2; public isUnlocked (state: GameState): boolean { if (this._isUnlocked) return true; diff --git a/src/model/resource/IResource.ts b/src/model/resource/IResource.ts index 75c4af6..4ee0815 100644 --- a/src/model/resource/IResource.ts +++ b/src/model/resource/IResource.ts @@ -1,29 +1,29 @@ enum ResourceType { - Religion = 'religion', - Job = 'job', - Consumable = 'consumable', - Infrastructure = 'infrastructure', - Research = 'research', - Passive = 'passive' + religion = 'religion', + job = 'job', + consumable = 'consumable', + infrastructure = 'infrastructure', + research = 'research', + passive = 'passive', } interface IResource { readonly resourceType: ResourceType; - readonly name: string | null; - readonly description: string | null; + readonly name: string; + readonly description: string; readonly valueInWholeNumbers: boolean; - readonly clickText: string; - readonly clickDescription: string; + readonly clickText: string | null; + readonly clickDescription: string | null; // readonly altClickText?: string; // readonly altClickDescription?: string; readonly value: number; - readonly cost: { [key: string]: number }; + readonly cost: { [key: string]: number } | null; - max (state: GameState): number | null; - inc (state: GameState): number | null; - clickAction(state: GameState): void; + max: ((state: GameState) => number) | null; + inc: ((state: GameState) => number) | null; + clickAction: ((state: GameState) => void) | null; // altClickAction (state: GameState): void; - addValue (amount: number, state: GameState): void; - isUnlocked (state: GameState): boolean; - advanceAction (time: number, state: GameState): void; + addValue: (amount: number, state: GameState) => void; + isUnlocked: (state: GameState) => boolean; + advanceAction: ((time: number, state: GameState) => void) | null; } diff --git a/src/model/resource/Infrastructure.ts b/src/model/resource/Infrastructure.ts index 9daec6a..7fb19ff 100644 --- a/src/model/resource/Infrastructure.ts +++ b/src/model/resource/Infrastructure.ts @@ -1,5 +1,5 @@ /// abstract class Infrastructure extends Purchasable { - public readonly resourceType: ResourceType = ResourceType.Infrastructure; + public readonly resourceType: ResourceType = ResourceType.infrastructure; } diff --git a/src/model/resource/Job.ts b/src/model/resource/Job.ts index c71d948..2144060 100644 --- a/src/model/resource/Job.ts +++ b/src/model/resource/Job.ts @@ -1,14 +1,16 @@ /// abstract class Job implements IResource { - public readonly resourceType: ResourceType = ResourceType.Job; + public readonly resourceType: ResourceType = ResourceType.job; public readonly valueInWholeNumbers: boolean = true; public readonly clickText: string = 'Hire'; - public readonly clickDescription: string = - 'Promote one of your followers.'; + public readonly clickDescription: string = 'Promote one of your followers.'; public value = 0; public readonly cost: { [key: string]: number } = { }; + public max: ((state: GameState) => number) | null = null; + public inc: ((state: GameState) => number) | null = null; + protected _costMultiplier: { [key: string]: number } = { }; protected _isUnlocked = false; @@ -17,20 +19,14 @@ abstract class Job implements IResource { public readonly description: string ) { } - public max (state: GameState): number | null { - return null; - } - - public inc (): number | null { - return null; - } public clickAction (state: GameState): void { if (this._availableJobs(state) <= 0) { state.log('You have no unemployed followers to promote.'); return; } - if (this.value < this.max(state) && state.deductCost(this.cost)) { + if (this.max !== null && this.value < this.max(state) + && state.deductCost(this.cost)) { this.addValue(1); state.log(this._hireLog(1, state)); for (const rkey of Object.keys(this._costMultiplier)) { @@ -43,21 +39,21 @@ abstract class Job implements IResource { this.value += amount; } - public isUnlocked (state: GameState): boolean { + public isUnlocked (_state: GameState): boolean { return this._isUnlocked; } - public advanceAction (time: number, state: GameState): void { + public advanceAction (_time: number, _state: GameState): void { return; } protected _availableJobs (state: GameState): number { // number of followers minus the number of filled jobs const followers: number = state.getResource('plorg').value; - const hired: number = state.getResources() - .reduce((tot: number, rkey: string): number => { + const hired: number = state.getResources().reduce( + (tot: number, rkey: string): number => { const res: IResource = state.getResource(rkey); - return res.resourceType === ResourceType.Job + return res.resourceType === ResourceType.job ? tot + res.value : tot; }, 0); @@ -66,7 +62,7 @@ abstract class Job implements IResource { return max; } - protected _hireLog (amount: number, state: GameState): string { + protected _hireLog (amount: number, _state: GameState): string { return `You hired ${amount} x ${this.name}.`; } } diff --git a/src/model/resource/MegaChurch.ts b/src/model/resource/MegaChurch.ts index 0a18437..498e819 100644 --- a/src/model/resource/MegaChurch.ts +++ b/src/model/resource/MegaChurch.ts @@ -6,9 +6,11 @@ class MegaChurch extends Infrastructure { 'Room for 5 pastors'); this.cost.money = 7500000; this._costMultiplier.money = 1.01; - this._baseMax = 2; } + public max: (state: GameState) => number = (state) => + state.config.cfgStartingMegaChurchMax; + public isUnlocked (state: GameState): boolean { if (this._isUnlocked) return true; const permit: IResource = state.getResource('blpmt'); diff --git a/src/model/resource/Money.ts b/src/model/resource/Money.ts index 0a29a95..71b4840 100644 --- a/src/model/resource/Money.ts +++ b/src/model/resource/Money.ts @@ -1,7 +1,7 @@ /// class Money extends Purchasable { - public readonly resourceType: ResourceType = ResourceType.Consumable; + public readonly resourceType: ResourceType = ResourceType.consumable; private _lastCollectionTime = 0; @@ -11,18 +11,17 @@ class Money extends Purchasable { super('Money', 'Used to purchase goods and services.'); this.clickText = 'Collect Tithes'; this.clickDescription = 'Voluntary contributions from followers.'; - this._baseMax = 500000; this.valueInWholeNumbers = false; this._isUnlocked = true; } - public max (state: GameState): number | null { - let max: number = this._baseMax; + public max: (state: GameState) => number = (state: GameState) => { + let max: number = state.config.cfgStartingMoneyMax; max += state.getResource('cmpnd').value * 500000; return max; - } + }; - public inc (state: GameState): number { + public inc: (state: GameState) => number = (state) => { let inc = 0; // crypto currency @@ -30,7 +29,7 @@ class Money extends Purchasable { * state.config.cfgCryptoReturnAmount; return inc; - } + }; protected _purchaseAmount (state: GameState): number { const plorg: IResource = state.getResource('plorg'); diff --git a/src/model/resource/Passive.ts b/src/model/resource/Passive.ts index fbecc79..0f6c376 100644 --- a/src/model/resource/Passive.ts +++ b/src/model/resource/Passive.ts @@ -1,7 +1,7 @@ /// abstract class Passive implements IResource { - public readonly resourceType: ResourceType = ResourceType.Passive; + public readonly resourceType: ResourceType = ResourceType.passive; public readonly valueInWholeNumbers: boolean = false; public readonly clickText: null = null; public readonly clickDescription: null = null; @@ -10,31 +10,22 @@ abstract class Passive implements IResource { public readonly clickAction: null = null; - protected _baseMax: number | null; - protected _baseInc: number | null; + public max: ((state: GameState) => number) | null = null; + public inc: ((state: GameState) => number) | null = null; + public advanceAction: ((time: number, state: GameState) => void) | null = null; constructor ( public readonly name: string, public readonly description: string ) { } - public max (state: GameState): number | null { - return this._baseMax; - } - public inc (state: GameState): number | null { - return this._baseInc; - } - - public addValue (amount: number, state: GameState): void { + public addValue (amount: number, _state: GameState): void { this.value += amount; } - public isUnlocked (state: GameState): boolean { + public isUnlocked (_state: GameState): boolean { return true; } - public advanceAction (time: number, state: GameState): void { - return; - } } diff --git a/src/model/resource/Pastor.ts b/src/model/resource/Pastor.ts index 88bd2ec..8a56e80 100644 --- a/src/model/resource/Pastor.ts +++ b/src/model/resource/Pastor.ts @@ -8,11 +8,11 @@ class Pastor extends Job { 'Collect tithings for you and recruit new members from other faiths automatically.'); } - public max (state: GameState): number { + public max: (state: GameState) => number = (state) => { let max: number = state.getResource('chrch').value * 2; max += state.getResource('mchch').value * 5; return max; - } + }; public isUnlocked (state: GameState): boolean { if (this._isUnlocked) return true; @@ -30,7 +30,7 @@ class Pastor extends Job { if (Math.floor(plorg.value) < tithed) tithed = Math.floor(plorg.value); let collected: number = tithed * state.config.cfgTitheAmount; - if (collected > money.max(state) - money.value) + if (money.max !== null && collected > money.max(state) - money.value) collected = money.max(state) - money.value; if (collected > 0) { money.addValue(collected, state); diff --git a/src/model/resource/PlayerOrg.ts b/src/model/resource/PlayerOrg.ts index 5d7e214..ed4f3a3 100644 --- a/src/model/resource/PlayerOrg.ts +++ b/src/model/resource/PlayerOrg.ts @@ -1,7 +1,7 @@ /// class PlayerOrg implements IResource { - public readonly resourceType: ResourceType = ResourceType.Religion; + public readonly resourceType: ResourceType = ResourceType.religion; public readonly name: string = 'Player'; public readonly description: string = 'In you they trust.'; public readonly valueInWholeNumbers: boolean = true; @@ -11,13 +11,12 @@ class PlayerOrg implements IResource { public readonly cost: null = null; private _timeSinceLastLost = 0; - private _baseMax = 5; private _lastRecruitmentLog = 0; private _followerSources: { [key: string]: number } = { }; private _followerDests: { [key: string]: number } = { }; public max (state: GameState): number { - let max: number = this._baseMax; + let max: number = state.config.cfgStartingPlayerMax; max += state.getResource('tents').value * 2; max += state.getResource('house').value * 10; return max; @@ -27,12 +26,12 @@ class PlayerOrg implements IResource { let inc = 0; // pastor recruiting - const pastors: number = state.getResource('pstor').value; + const pastors = state.getResource('pstor').value; inc += pastors * state.config.cfgPastorRecruitRate; // credibility adjustment - const creds: IResource = state.getResource('creds'); - inc *= creds.value / creds.max(state); + const creds = state.getResource('creds'); + if (creds.max !== null) inc *= creds.value / creds.max(state); return inc; } @@ -46,10 +45,12 @@ class PlayerOrg implements IResource { // 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 recruitment efforts failed.'); - return; + if (creds.max !== null) { + const ratio: number = Math.ceil(creds.value) / creds.max(state); + if (Math.random() > ratio) { + state.log('Your recruitment efforts failed.'); + return; + } } this._lastRecruitmentLog = 0; // always log on click @@ -57,17 +58,17 @@ class PlayerOrg implements IResource { } public addValue (amount: number, state: GameState): void { - const oldValue: number = this.value; + const oldValue = this.value; this.value += amount; - const diff: number = Math.floor(this.value) - Math.floor(oldValue); + const diff = Math.floor(this.value) - Math.floor(oldValue); if (diff > 0) { // gained followers must come from other faiths for (let i = 0; i < diff; i++) { - const source: [string, IResource] = this._getRandomReligion(state); + const source = this._getRandomReligion(state); source[1].addValue(-1, state); - const curFollowers: number = this._followerSources[source[0]]; - this._followerSources[source[0]] = curFollowers + const curFollowers = this._followerSources[source[0]]; + this._followerSources[source[0]] = !isNaN(curFollowers) ? curFollowers + 1 : 1; } @@ -77,14 +78,14 @@ class PlayerOrg implements IResource { const dest: [string, IResource] = this._getRandomReligion(state); dest[1].addValue(1, state); const curFollowers: number = this._followerDests[dest[0]]; - this._followerDests[dest[0]] = curFollowers + this._followerDests[dest[0]] = !isNaN(curFollowers) ? curFollowers + 1 : 1; } } } - public isUnlocked (state: GameState): boolean { + public isUnlocked (_state: GameState): boolean { return true; } @@ -93,11 +94,13 @@ class PlayerOrg implements IResource { this._timeSinceLastLost += time; if (this._timeSinceLastLost > 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.addValue(lost * -1, state); + const creds = state.getResource('creds'); + if (creds.max !== null) { + 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.addValue(lost * -1, state); + } } } this._timeSinceLastLost = 0; @@ -113,8 +116,7 @@ class PlayerOrg implements IResource { for (const rkey of Object.keys(this._followerDests)) { if (msg !== '') msg += ', '; const religion: IResource = state.getResource(rkey); - msg += - `${state.formatNumber(this._followerDests[rkey])} to ${religion.name}`; + msg += `${state.formatNumber(this._followerDests[rkey])} to ${religion.name}`; total += this._followerDests[rkey]; delete this._followerDests[rkey]; } diff --git a/src/model/resource/Purchasable.ts b/src/model/resource/Purchasable.ts index 7ea0f33..e943638 100644 --- a/src/model/resource/Purchasable.ts +++ b/src/model/resource/Purchasable.ts @@ -1,15 +1,17 @@ /// abstract class Purchasable implements IResource { - public readonly resourceType: ResourceType = ResourceType.Consumable; + public readonly resourceType: ResourceType = ResourceType.consumable; public valueInWholeNumbers = true; public clickText = 'Purchase'; public clickDescription = 'Purchase'; public value = 0; public readonly cost: { [key: string]: number } = { }; + public inc: ((state: GameState) => number) | null = null; + public max: ((_state: GameState) => number) | null = null; + protected _costMultiplier: { [key: string]: number } = { }; - protected _baseMax: number | null = null; protected _isUnlocked = false; constructor ( @@ -17,16 +19,9 @@ abstract class Purchasable implements IResource { public readonly description: string ) { } - public max (state: GameState): number | null { - return this._baseMax; - } - - public inc (state: GameState): number | null { - return null; - } public clickAction (state: GameState): void { - if (this.max(state) !== null && this.value >= this.max(state)) return; + if (this.max !== null && this.value >= this.max(state)) return; if (state.deductCost(this.cost)) { const amount: number = this._purchaseAmount(state); if (amount > 0) { @@ -39,7 +34,7 @@ abstract class Purchasable implements IResource { } } - public addValue (amount: number, state: GameState): void { + public addValue (amount: number, _state: GameState): void { this.value += amount; } diff --git a/src/model/resource/Religion.ts b/src/model/resource/Religion.ts index b958177..c513955 100644 --- a/src/model/resource/Religion.ts +++ b/src/model/resource/Religion.ts @@ -1,7 +1,7 @@ /// class Religion implements IResource { - public readonly resourceType: ResourceType = ResourceType.Religion; + public readonly resourceType: ResourceType = ResourceType.religion; public readonly valueInWholeNumbers: boolean = true; public readonly clickText: null = null; public readonly clickDescription: null = null; @@ -18,11 +18,11 @@ class Religion implements IResource { public value: number, ) { } - public addValue (amount: number, state: GameState): void { + public addValue (amount: number, _state: GameState): void { this.value += amount; } - public isUnlocked (state: GameState): boolean { + public isUnlocked (_state: GameState): boolean { return true; } } diff --git a/src/model/resource/Research.ts b/src/model/resource/Research.ts index a822a59..84c86a4 100644 --- a/src/model/resource/Research.ts +++ b/src/model/resource/Research.ts @@ -1,7 +1,7 @@ /// abstract class Research extends Purchasable { - public readonly resourceType: ResourceType = ResourceType.Research; + public readonly resourceType: ResourceType = ResourceType.research; constructor ( public readonly name: string, @@ -9,8 +9,9 @@ abstract class Research extends Purchasable { ) { super(name, description); this.value = 0; - this._baseMax = 1; this.clickText = 'Learn'; - this.clickDescription = 'Complete this research.' + this.clickDescription = 'Complete this research.'; } + + public max: (_state: GameState) => number = (_state) => 1; } diff --git a/src/model/resource/Tent.ts b/src/model/resource/Tent.ts index 1c970fd..5602c69 100644 --- a/src/model/resource/Tent.ts +++ b/src/model/resource/Tent.ts @@ -6,13 +6,12 @@ class Tent extends Infrastructure { 'Provides room to house 2 followers.'); this.cost.money = 250; this._costMultiplier.money = 1.05; - this._baseMax = 5; } - public max (state: GameState): number { + public max: (state: GameState) => number = (state) => { // ten extra tents per compound - let max: number = this._baseMax; + let max: number = state.config.cfgStartingTentMax; max += state.getResource('cmpnd').value * 10; return max; - } + }; } diff --git a/src/render/DebugRenderer.ts b/src/render/DebugRenderer.ts index aa71b26..51ed772 100644 --- a/src/render/DebugRenderer.ts +++ b/src/render/DebugRenderer.ts @@ -1,38 +1,41 @@ /// -class DebugRenderer implements IRenderer { // eslint-disable-line @typescript-eslint/no-unused-vars +class DebugRenderer implements IRenderer { private _initialized = false; private _handleClick = true; public render (state: GameState): void { const rkeys: string[] = state.getResources(); + const container = document.getElementById('irreligious-game'); if (!this._initialized) { - const container: HTMLElement = - document.getElementById('irreligious-game'); + if (container === null) { + console.error('could not find game container'); + return; + } this._initialized = true; state.onResourceClick.push((): void => { this._handleClick = true; }); - const style: HTMLElement = document.createElement('link'); + const style = document.createElement('link'); style.setAttribute('rel', 'stylesheet'); style.setAttribute('href', 'css/debugger.css'); - const head: HTMLElement = document.getElementsByTagName('head')[0]; + const head = document.getElementsByTagName('head')[0]; head.appendChild(style); // create resource area and logging area - const resDiv: HTMLElement = document.createElement('div'); + const resDiv = document.createElement('div'); resDiv.id = 'resource-section'; container.appendChild(resDiv); - const logDiv: HTMLElement = document.createElement('div'); + const logDiv = document.createElement('div'); logDiv.id = 'logging-section'; container.appendChild(logDiv); - const logContent: HTMLElement = document.createElement('div'); + const logContent = document.createElement('div'); logDiv.appendChild(logContent); state.logger = new DebugLogger(logContent); // create containers for each resource type for (const item in ResourceType) { if (isNaN(Number(item))) { - const el: HTMLElement = document.createElement('div'); - el.id = `resource-container-${ResourceType[item]}`; + const el = document.createElement('div'); + el.id = `resource-container-${item.toString()}`; el.className = 'resource-type-container'; resDiv.appendChild(el); } @@ -40,23 +43,21 @@ class DebugRenderer implements IRenderer { // eslint-disable-line @typescript-es // create containers for each resource for (const rkey of rkeys) { const resource: IResource = state.getResource(rkey); - const resContainer: HTMLElement = - document.getElementById( - `resource-container-${resource.resourceType}`); - const el: HTMLElement = document.createElement('div'); + const resContainer = document.getElementById( + `resource-container-${resource.resourceType}`); + if (resContainer === null) continue; + const el = document.createElement('div'); el.className = 'resource locked'; el.id = `resource-details-${rkey}`; let content = ` - ${this._escape(resource.name - ? resource.name - : rkey)}
+ ${this._escape(resource.name)}
`; - if (resource.clickText !== null) { + if (resource.clickText !== null && resource.clickDescription !== null) { content += `
`; resDiv.appendChild(footer); - document.getElementById('dbg-btn-reset') - .addEventListener('click', (): void => { + document.getElementById('dbg-btn-reset')?.addEventListener('click', + (): void => { state.reset(); - document.getElementById('irreligious-game').innerHTML = ''; + container.innerHTML = ''; this._initialized = false; }); } for (const rkey of rkeys) { const resource: IResource = state.getResource(rkey); - const container: HTMLElement = document - .getElementById(`resource-container-${resource.resourceType}`); - const el: HTMLElement = document - .getElementById(`resource-details-${rkey}`); - if (resource.isUnlocked(state)) { + const el = document.getElementById(`resource-details-${rkey}`); + if (el !== null && resource.isUnlocked(state)) { if (el.className !== 'resource') el.className = 'resource'; const elV: Element = el.getElementsByClassName('resource-value')[0]; @@ -106,15 +105,13 @@ class DebugRenderer implements IRenderer { // eslint-disable-line @typescript-es : resource.value; elV.innerHTML = state.formatNumber(value); elT.innerHTML = resource.max !== null - && resource.max(state) !== null ? ` / ${state.formatNumber(resource.max(state))}` : ''; const elB: HTMLCollectionOf = el.getElementsByClassName('resource-btn'); if (elB.length > 0) { const enabled: boolean = state.isPurchasable(resource.cost) - && (resource.max(state) === null - || resource.value < resource.max(state)); + && (resource.max === null || resource.value < resource.max(state)); if (enabled) elB[0].removeAttribute('disabled'); else elB[0].setAttribute('disabled', 'disabled'); } @@ -132,7 +129,7 @@ class DebugRenderer implements IRenderer { // eslint-disable-line @typescript-es } } } else { - if (el.className !== 'resource locked') + if (el !== null && el.className !== 'resource locked') el.className = 'resource locked'; } } @@ -146,8 +143,8 @@ class DebugRenderer implements IRenderer { // eslint-disable-line @typescript-es '>': '>', '"': '"', "'": ''', - '/': '/' - } + '/': '/', + }; const escaper = /[&<>"'/]/g; return text.replace(escaper, (match: string): string => escapes[match]); @@ -155,8 +152,9 @@ class DebugRenderer implements IRenderer { // eslint-disable-line @typescript-es private _getCostStr (resource: IResource, state: GameState): string { let cost = ''; - for (const rkey of state.getResources()) { - if (resource.cost[rkey] !== undefined) { + if (resource.cost !== null) { + for (const rkey of state.getResources()) { + if (isNaN(resource.cost[rkey])) continue; if (cost !== '') cost += ', '; if (rkey === 'money') { cost += `$${state.formatNumber(resource.cost[rkey])}`; diff --git a/src/render/IRenderer.ts b/src/render/IRenderer.ts index 37f5ce2..6dddd04 100644 --- a/src/render/IRenderer.ts +++ b/src/render/IRenderer.ts @@ -1,3 +1,3 @@ -interface IRenderer { // eslint-disable-line @typescript-eslint/no-unused-vars - render (state: GameState): void; +interface IRenderer { + render: (state: GameState) => void; } diff --git a/tsconfig.json b/tsconfig.json index f37b2c3..45b5ee1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,13 @@ { "compilerOptions": { - "noImplicitAny": false, "rootDir": "./src/", "outFile": "./public/js/irreligious.js", "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "strictNullChecks": true, + "noImplicitAny": true, + "strictPropertyInitialization": true, "target": "ES5", "module": "none", "plugins": [{"name": "typescript-eslint-language-service"}]