From 1bc4206a3236a344c3e21cdcb6513204ece56d44 Mon Sep 17 00:00:00 2001 From: Rudis Muiznieks Date: Mon, 6 Sep 2021 00:12:05 -0500 Subject: [PATCH] ability to define mulitple user actions per resource --- .eslintrc.json | 2 - src/model/GameConfig.ts | 81 +++++++------------ src/model/GameState.ts | 18 +++-- src/model/Utils.ts | 23 ++++++ src/model/resource/BuildingPermit.ts | 4 +- src/model/resource/Church.ts | 6 +- src/model/resource/Compound.ts | 4 +- src/model/resource/Credibility.ts | 3 +- src/model/resource/CryptoCurrency.ts | 4 +- .../resource/{PlayerOrg.ts => Follower.ts} | 68 +++++++++------- src/model/resource/House.ts | 6 +- src/model/resource/IResource.ts | 44 +--------- src/model/resource/Job.ts | 54 ++++++++----- .../resource/{MegaChurch.ts => Megachurch.ts} | 8 +- src/model/resource/Money.ts | 11 ++- src/model/resource/Passive.ts | 3 +- src/model/resource/Pastor.ts | 6 +- src/model/resource/Purchasable.ts | 57 ++++++++----- src/model/resource/Religion.ts | 3 +- src/model/resource/Research.ts | 12 ++- src/model/resource/SharedTypes.ts | 41 ++++++++++ src/model/resource/Tent.ts | 6 +- src/render/DebugRenderer.ts | 60 ++++++++------ 23 files changed, 302 insertions(+), 222 deletions(-) create mode 100644 src/model/Utils.ts rename src/model/resource/{PlayerOrg.ts => Follower.ts} (87%) rename src/model/resource/{MegaChurch.ts => Megachurch.ts} (76%) create mode 100644 src/model/resource/SharedTypes.ts diff --git a/.eslintrc.json b/.eslintrc.json index a9ce638..e4a913e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -37,8 +37,6 @@ "@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-implicit-any-catch": "error", "@typescript-eslint/no-invalid-void-type": "error", - "@typescript-eslint/no-parameter-properties": ["error", { - "allows": ["public", "readonly", "public readonly"]}], "@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn", "@typescript-eslint/no-unnecessary-condition": "warn", "@typescript-eslint/no-unnecessary-qualifier": "warn", diff --git a/src/model/GameConfig.ts b/src/model/GameConfig.ts index 84211b0..166520d 100644 --- a/src/model/GameConfig.ts +++ b/src/model/GameConfig.ts @@ -4,27 +4,28 @@ /// /// /// +/// /// -/// +/// /// /// -/// /// /// class GameConfig { public worldPopulation = 790000000; - public numberFormatDigits = 1; // religion configs - public relChristianitySharer = 0.325; - public relIslamShare = 0.215; - public relHinduismShare = 0.16; - public relBuddhismShare = 0.06; - public relSikhismShare = 0.04; - public relJudaismShare = 0.02; - public relOtherShare = 0.02; - public relNoneShare = 0.16; + public cfgReligion: ResourceNumber = { + christianity: 0.325, + islam: 0.215, + hinduism: 0.16, + buddhism: 0.06, + sikhism: 0.04, + judaism: 0.02, + other: 0.02, + atheism: 0.16, + }; // general configs public cfgInitialMax: ResourceNumber = { @@ -81,40 +82,40 @@ class GameConfig { const state = new GameState(this); // create player organization - state.addResource(ResourceKey.playerOrg, new PlayerOrg()); + state.addResource(ResourceKey.playerOrg, new Follower()); // create world religions state.addResource(ResourceKey.christianity, new Religion( - 'Christianity', 'God, Jesus, Bible, churches.', - this.relChristianitySharer * this.worldPopulation)); + 'christian', 'christians', 'God, Jesus, Bible, churches.', + (this.cfgReligion.christianity ?? 0) * this.worldPopulation)); state.addResource(ResourceKey.islam, new Religion( - 'Islam', 'God, Muhammad, Quran, mosques.', - this.relIslamShare * this.worldPopulation)); + 'muslim', 'muslims', 'God, Muhammad, Quran, mosques.', + (this.cfgReligion.islam ?? 0) * this.worldPopulation)); state.addResource(ResourceKey.hinduism, new Religion( - 'Hinduism', 'Dogma-free spiritualism.', - this.relHinduismShare * this.worldPopulation)); + 'hindu', 'hindus', 'Dogma-free spiritualism.', + (this.cfgReligion.hinduism ?? 0) * this.worldPopulation)); state.addResource(ResourceKey.buddhism, new Religion( - 'Buddhism', 'The minimization of suffering.', - this.relBuddhismShare * this.worldPopulation)); + 'buddhist', 'buddhists', 'The minimization of suffering.', + (this.cfgReligion.buddhism ?? 0) * this.worldPopulation)); state.addResource(ResourceKey.sikhism, new Religion( - 'Sikhism', 'Meditation and ten Gurus', - this.relSikhismShare * this.worldPopulation)); + 'sikh', 'sikhs', 'Meditation and ten Gurus', + (this.cfgReligion.sikhism ?? 0) * this.worldPopulation)); state.addResource(ResourceKey.judaism, new Religion( - 'Judaism', 'God, Abraham, Torah, synagogues.', - this.relJudaismShare * this.worldPopulation)); + 'jew', 'jews', 'God, Abraham, Torah, synagogues.', + (this.cfgReligion.judaism ?? 0) * this.worldPopulation)); state.addResource(ResourceKey.other, new Religion( - 'Other', 'A variety of belief systems.', - this.relOtherShare * this.worldPopulation)); + 'other', 'others', 'A variety of belief systems.', + (this.cfgReligion.other ?? 0) * this.worldPopulation)); state.addResource(ResourceKey.atheism, new Religion( - 'Non-Religious', 'Atheists and agnostics.', - this.relNoneShare * this.worldPopulation)); + 'atheist', 'atheists', 'Atheists and agnostics.', + (this.cfgReligion.atheism ?? 0) * this.worldPopulation)); // add jobs state.addResource(ResourceKey.pastors, new Pastor()); @@ -126,7 +127,7 @@ class GameConfig { state.addResource(ResourceKey.houses, new House(this)); state.addResource(ResourceKey.churches, new Church(this)); state.addResource(ResourceKey.compounds, new Compound(this)); - state.addResource(ResourceKey.megaChurches, new MegaChurch(this)); + state.addResource(ResourceKey.megaChurches, new Megachurch(this)); // add research state.addResource(ResourceKey.buildingPermit, new BuildingPermit(this)); @@ -136,26 +137,4 @@ class GameConfig { return state; } - - public formatNumber (num: number): string { - 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' }, - ]; - const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; - let item: UnitLookup | undefined; - for (item of lookup.slice().reverse()) { - if (num >= item.value) break; - } - return item !== undefined - ? (num / item.value).toFixed( - this.numberFormatDigits).replace(rx, '$1') + item.symbol - : num.toFixed(this.numberFormatDigits).replace(rx, '$1'); - } } diff --git a/src/model/GameState.ts b/src/model/GameState.ts index fe90da2..933c1c5 100644 --- a/src/model/GameState.ts +++ b/src/model/GameState.ts @@ -1,3 +1,5 @@ +/// + class GameState { public readonly config: GameConfig; @@ -69,15 +71,17 @@ class GameState { } } - public performClick (resourceKey: ResourceKey): void { + public performAction (resourceKey: ResourceKey, actionIndex: number): void { const resource = this._resources[resourceKey]; - if (resource === undefined || !resource.isUnlocked(this)) return; + if (resource === undefined || resource.userActions === undefined + || actionIndex > resource.userActions.length + || !resource.isUnlocked(this)) return; - if (resource.clickAction !== undefined) { - resource.clickAction(this); - for (const callback of this.onResourceClick) { - callback(); - } + const action = resource.userActions[actionIndex]; + + action.performAction(this); + for (const callback of this.onResourceClick) { + callback(); } } diff --git a/src/model/Utils.ts b/src/model/Utils.ts new file mode 100644 index 0000000..a50dbec --- /dev/null +++ b/src/model/Utils.ts @@ -0,0 +1,23 @@ +const numberFormatDigits = 1; + +function formatNumber (num: number): string { + 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' }, + ]; + const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; + let item: UnitLookup | undefined; + for (item of lookup.slice().reverse()) { + if (num >= item.value) break; + } + return item !== undefined + ? (num / item.value).toFixed( + numberFormatDigits).replace(rx, '$1') + item.symbol + : num.toFixed(numberFormatDigits).replace(rx, '$1'); +} diff --git a/src/model/resource/BuildingPermit.ts b/src/model/resource/BuildingPermit.ts index 97f76f5..37fa910 100644 --- a/src/model/resource/BuildingPermit.ts +++ b/src/model/resource/BuildingPermit.ts @@ -2,7 +2,9 @@ class BuildingPermit extends Research { constructor (config: GameConfig) { - super('Building Permit', + super( + 'building permit', + 'building permits', 'Unlocks several new buildings you can build outside of your compounds.'); this.cost.money = config.cfgInitialMax.buildingPermit; } diff --git a/src/model/resource/Church.ts b/src/model/resource/Church.ts index 6e4e36b..40fbb2b 100644 --- a/src/model/resource/Church.ts +++ b/src/model/resource/Church.ts @@ -2,8 +2,10 @@ class Church extends Infrastructure { constructor (config: GameConfig) { - super('Churches', - `Preaching grounds for ${config.formatNumber(config.cfgCapacity.churches?.pastors ?? 0)} pastors.`); + super( + 'church', + 'churches', + `Preaching grounds for ${formatNumber(config.cfgCapacity.churches?.pastors ?? 0)} pastors.`); this.cost.money = config.cfgInitialCost.churches; this._costMultiplier.money = config.cfgCostMultiplier.churches; } diff --git a/src/model/resource/Compound.ts b/src/model/resource/Compound.ts index f1354c0..7e9e16f 100644 --- a/src/model/resource/Compound.ts +++ b/src/model/resource/Compound.ts @@ -2,7 +2,9 @@ class Compound extends Infrastructure { constructor (config: GameConfig) { - super('Compounds', + super( + 'compound', + 'compounds', 'Provides space for tents, houses, and churches and a place to hide more money.'); this.cost.money = config.cfgInitialCost.compounds; this._costMultiplier.money = config.cfgCostMultiplier.compounds; diff --git a/src/model/resource/Credibility.ts b/src/model/resource/Credibility.ts index f682422..6315a54 100644 --- a/src/model/resource/Credibility.ts +++ b/src/model/resource/Credibility.ts @@ -3,7 +3,8 @@ class Credibility extends Passive { constructor (config: GameConfig) { super( - 'Credibility', + 'credibility', + 'credibilities', 'Affects your ability to recruit and retain followers.'); this.value = config.cfgPassiveMax; } diff --git a/src/model/resource/CryptoCurrency.ts b/src/model/resource/CryptoCurrency.ts index 190f7b3..716b2a7 100644 --- a/src/model/resource/CryptoCurrency.ts +++ b/src/model/resource/CryptoCurrency.ts @@ -2,7 +2,9 @@ class CryptoCurrency extends Purchasable { constructor (config: GameConfig) { - super('Faithcoin', + super( + 'FaithCoin', + 'FaithCoins', "A crypto coin that can't be spent directly, but provides a steady stream of passive income."); this.cost.money = config.cfgInitialCost.cryptoCurrency; this._costMultiplier.money = config.cfgCostMultiplier.cryptoCurrency; diff --git a/src/model/resource/PlayerOrg.ts b/src/model/resource/Follower.ts similarity index 87% rename from src/model/resource/PlayerOrg.ts rename to src/model/resource/Follower.ts index 44e41c0..7d5c746 100644 --- a/src/model/resource/PlayerOrg.ts +++ b/src/model/resource/Follower.ts @@ -1,14 +1,24 @@ /// -class PlayerOrg implements IResource { +class Follower implements IResource { public readonly resourceType = ResourceType.religion; - public readonly name = 'Player'; + public readonly singularName = 'follower'; + public readonly pluralName = 'followers'; public readonly description = 'In you they trust.'; public readonly valueInWholeNumbers = true; - public readonly clickText = 'Recruit'; - public readonly clickDescription = 'Gather new followers.'; public value = 0; + public userActions: ResourceAction[] = [ + { + name: 'Recruit', + description: 'Gather new followers.', + isEnabled: (state: GameState): boolean => this.value < this.max(state), + performAction: (state: GameState): void => { + this._recruitFollower(state); + }, + }, + ]; + private _timeSinceLastLost = 0; private _lastRecruitmentLog = 0; private _followerSources: ResourceNumber = { }; @@ -37,27 +47,6 @@ class PlayerOrg implements IResource { return inc; } - public clickAction (state: GameState): void { - // don't exceed max - if (this.value >= this.max(state)) { - state.log('You have no room for more followers.'); - return; - } - - // chance to fail increases as credibility decreases - const creds = state.resource.credibility; - if (creds?.max !== undefined) { - const ratio = 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 - this.addValue(1, state); - } - public addValue (amount: number, state: GameState): void { const oldValue = this.value; this.value += amount; @@ -124,12 +113,12 @@ class PlayerOrg implements IResource { const followers = this._followerDests[rkey]; if (religion !== undefined && followers !== undefined) { if (msg !== '') msg += ', '; - msg += `${state.config.formatNumber(followers)} to ${religion.name}`; + msg += `${formatNumber(followers)} to ${religion.pluralName}`; total += followers; delete this._followerDests[rkey]; } } - state.log(`You lost ${state.config.formatNumber(total)} followers: ${msg}`); + state.log(`You lost ${formatNumber(total)} followers: ${msg}`); } if (Object.keys(this._followerSources).length > 0) { let msg = ''; @@ -141,17 +130,38 @@ class PlayerOrg implements IResource { if (religion !== undefined && followers !== undefined) { if (msg !== '') msg += ', '; msg += - `${state.config.formatNumber(followers)} from ${religion.name}`; + `${formatNumber(followers)} from ${religion.pluralName}`; total += followers; delete this._followerSources[rkey]; } } - state.log(`You gained ${state.config.formatNumber(total)} followers: ${msg}`); + state.log(`You gained ${formatNumber(total)} followers: ${msg}`); } this._lastRecruitmentLog = state.now; } } + private _recruitFollower (state: GameState): void { + // don't exceed max + if (this.value >= this.max(state)) { + state.log('You have no room for more followers.'); + return; + } + + // chance to fail increases as credibility decreases + const creds = state.resource.credibility; + if (creds?.max !== undefined) { + const ratio = 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 + this.addValue(1, state); + } + private _getRandomReligion ( state: GameState): [ResourceKey, IResource] | null { const religs = [ResourceKey.christianity, ResourceKey.islam, diff --git a/src/model/resource/House.ts b/src/model/resource/House.ts index 835bdca..bb9b846 100644 --- a/src/model/resource/House.ts +++ b/src/model/resource/House.ts @@ -2,8 +2,10 @@ class House extends Infrastructure { constructor (config: GameConfig) { - super('Houses', - `Provides room to house ${config.formatNumber(config.cfgCapacity.houses?.playerOrg ?? 0)} followers.`); + super( + 'house', + 'houses', + `Provides room to house ${formatNumber(config.cfgCapacity.houses?.playerOrg ?? 0)} followers.`); this.cost.money = config.cfgInitialCost.houses; this._costMultiplier.money = config.cfgCostMultiplier.houses; } diff --git a/src/model/resource/IResource.ts b/src/model/resource/IResource.ts index 36a0186..681001b 100644 --- a/src/model/resource/IResource.ts +++ b/src/model/resource/IResource.ts @@ -1,55 +1,19 @@ -enum ResourceType { - religion = 'religion', - job = 'job', - consumable = 'consumable', - infrastructure = 'infrastructure', - research = 'research', - passive = 'passive', -} - -enum ResourceKey { - playerOrg = 'playerOrg', - christianity = 'christianity', - islam = 'islam', - hinduism = 'hinduism', - buddhism = 'buddhism', - sikhism = 'sikhism', - judaism = 'judaism', - other = 'other', - atheism = 'atheism', - pastors = 'pastors', - money = 'money', - cryptoCurrency = 'cryptoCurrency', - tents = 'tents', - houses = 'houses', - churches = 'churches', - compounds = 'compounds', - buildingPermit = 'buildingPermit', - megaChurches = 'megaChurches', - credibility = 'credibility', -} - -type ResourceNumber = { [key in ResourceKey]?: number }; +/// interface IResource { readonly resourceType: ResourceType; - readonly name: string; + readonly singularName: string; + readonly pluralName: string; readonly description: string; readonly valueInWholeNumbers: boolean; readonly value: number; readonly cost?: ResourceNumber; - readonly clickText?: string; - readonly clickDescription?: string; - // readonly altClickText?: string; - // readonly altClickDescription?: string; - max?: (state: GameState) => number; inc?: (state: GameState) => number; - clickAction?: (state: GameState) => void; - // altClickAction (state: GameState): void; advanceAction?: (time: number, state: GameState) => void; + userActions?: ResourceAction[]; addValue: (amount: number, state: GameState) => void; isUnlocked: (state: GameState) => boolean; diff --git a/src/model/resource/Job.ts b/src/model/resource/Job.ts index 77b965a..fc7b7a2 100644 --- a/src/model/resource/Job.ts +++ b/src/model/resource/Job.ts @@ -3,39 +3,34 @@ abstract class Job implements IResource { public readonly resourceType = ResourceType.job; public readonly valueInWholeNumbers = true; - public readonly clickText = 'Hire'; - public readonly clickDescription = 'Promote one of your followers.'; public value = 0; public readonly cost: ResourceNumber = { }; public max?: (state: GameState) => number = undefined; public inc?: (state: GameState) => number = undefined; + public userActions: ResourceAction[] = [ + { + name: 'Hire', + description: 'Promote one of your followers.', + isEnabled: (state: GameState): boolean => + (this.max === undefined || this.value < this.max(state)) + && this.value < this._availableJobs(state), + performAction: (state: GameState): void => { + this._promoteFollower(state); + }, + }, + ]; + protected _costMultiplier: { [key in ResourceKey]?: number } = { }; protected _isUnlocked = false; constructor ( - public readonly name: string, + public readonly singularName: string, + public readonly pluralName: string, public readonly description: string ) { } - public clickAction (state: GameState): void { - if (this._availableJobs(state) <= 0) { - state.log('You have no unemployed followers to promote.'); - return; - } - if (this.max !== undefined && this.value < this.max(state) - && state.deductCost(this.cost)) { - this.addValue(1); - state.log(this._hireLog(1, state)); - for (const key in this._costMultiplier) { - const rkey = key; - this.cost[rkey] = - (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1); - } - } - } - public addValue (amount: number): void { this.value += amount; if (this.value < 0) this.value = 0; @@ -81,6 +76,23 @@ abstract class Job implements IResource { } protected _hireLog (amount: number, _state: GameState): string { - return `You hired ${amount} x ${this.name}.`; + return `You hired ${amount} x ${this.pluralName}.`; + } + + private _promoteFollower (state: GameState): void { + if (this._availableJobs(state) <= 0) { + state.log('You have no unemployed followers to promote.'); + return; + } + if (this.max !== undefined && this.value < this.max(state) + && state.deductCost(this.cost)) { + this.addValue(1); + state.log(this._hireLog(1, state)); + for (const key in this._costMultiplier) { + const rkey = key; + this.cost[rkey] = + (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1); + } + } } } diff --git a/src/model/resource/MegaChurch.ts b/src/model/resource/Megachurch.ts similarity index 76% rename from src/model/resource/MegaChurch.ts rename to src/model/resource/Megachurch.ts index 26f29e7..4ac019b 100644 --- a/src/model/resource/MegaChurch.ts +++ b/src/model/resource/Megachurch.ts @@ -1,9 +1,11 @@ /// -class MegaChurch extends Infrastructure { +class Megachurch extends Infrastructure { constructor (config: GameConfig) { - super('MegaChurches', - `Room for ${config.formatNumber(config.cfgCapacity.megaChurches?.pastors ?? 0)} pastors`); + super( + 'megachurch', + 'megachurches', + `Room for ${formatNumber(config.cfgCapacity.megaChurches?.pastors ?? 0)} pastors`); this.cost.money = config.cfgInitialCost.megaChurches; this._costMultiplier.money = config.cfgCostMultiplier.megaChurches; } diff --git a/src/model/resource/Money.ts b/src/model/resource/Money.ts index 8654cf0..cb3bb5b 100644 --- a/src/model/resource/Money.ts +++ b/src/model/resource/Money.ts @@ -8,9 +8,12 @@ class Money extends Purchasable { constructor ( public value: number ) { - super('Money', 'Used to purchase goods and services.'); - this.clickText = 'Collect Tithes'; - this.clickDescription = 'Voluntary contributions from followers.'; + super( + 'money', + 'moneys', + 'Used to purchase goods and services.', + 'Collect Tithes', + 'Voluntary contributions from followers.'); this.valueInWholeNumbers = false; this._isUnlocked = true; } @@ -54,6 +57,6 @@ class Money extends Purchasable { protected _purchaseLog (amount: number, state: GameState): string { const followers = state.resource.playerOrg?.value ?? 0; - return `You collected $${state.config.formatNumber(amount)} from ${state.config.formatNumber(followers)} followers.`; + return `You collected $${formatNumber(amount)} from ${formatNumber(followers)} followers.`; } } diff --git a/src/model/resource/Passive.ts b/src/model/resource/Passive.ts index ef3f054..841b8c0 100644 --- a/src/model/resource/Passive.ts +++ b/src/model/resource/Passive.ts @@ -8,7 +8,8 @@ abstract class Passive implements IResource { public advanceAction?: (time: number, state: GameState) => void = undefined; constructor ( - public readonly name: string, + public readonly singularName: string, + public readonly pluralName: string, public readonly description: string ) { } diff --git a/src/model/resource/Pastor.ts b/src/model/resource/Pastor.ts index cdc9fc9..0178e5a 100644 --- a/src/model/resource/Pastor.ts +++ b/src/model/resource/Pastor.ts @@ -4,7 +4,9 @@ class Pastor extends Job { private _timeSinceLastTithe = 0; constructor () { - super('Pastors', + super( + 'pastor', + 'pastors', 'Collect tithings for you and recruit new members from other faiths automatically.'); } @@ -38,7 +40,7 @@ class Pastor extends Job { collected = money.max(state) - money.value; if (collected > 0) { money?.addValue(collected, state); - state.log(`Your pastors collected $${state.config.formatNumber(collected)} in tithings from ${state.config.formatNumber(tithed)} followers.`); + state.log(`Your pastors collected $${formatNumber(collected)} in tithings from ${formatNumber(tithed)} followers.`); } this._timeSinceLastTithe = 0; } diff --git a/src/model/resource/Purchasable.ts b/src/model/resource/Purchasable.ts index 3195b5b..c5fc55c 100644 --- a/src/model/resource/Purchasable.ts +++ b/src/model/resource/Purchasable.ts @@ -3,39 +3,36 @@ abstract class Purchasable implements IResource { public readonly resourceType: ResourceType = ResourceType.consumable; public valueInWholeNumbers = true; - public clickText = 'Purchase'; - public clickDescription = 'Purchase'; public value = 0; public readonly cost: ResourceNumber = { }; public inc?: (state: GameState) => number = undefined; public max?: (_state: GameState) => number = undefined; + public userActions: ResourceAction[] = [ + { + name: this._purchaseButtonText, + description: this._purchaseDescription, + isEnabled: (state: GameState): boolean => + (this.max === undefined || this.value < this.max(state)) + && state.isPurchasable(this.cost), + performAction: (state: GameState): void => { + this._purchase(state); + }, + }, + ]; + protected _costMultiplier: ResourceNumber = { }; protected _isUnlocked = false; constructor ( - public readonly name: string, - public readonly description: string + public readonly singularName: string, + public readonly pluralName: string, + public readonly description: string, + private readonly _purchaseButtonText: string = 'Purchase', + private readonly _purchaseDescription: string = `Buy a ${singularName}.`, ) { } - - public clickAction (state: GameState): void { - if (this.max !== undefined && this.value >= this.max(state)) return; - if (state.deductCost(this.cost)) { - const amount = this._purchaseAmount(state); - if (amount > 0) { - this.value += amount; - state.log(this._purchaseLog(amount, state)); - for (const key in this._costMultiplier) { - const rkey = key; - this.cost[rkey] = - (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1); - } - } - } - } - public addValue (amount: number, _state: GameState): void { this.value += amount; } @@ -56,6 +53,22 @@ abstract class Purchasable implements IResource { } protected _purchaseLog (amount: number, _state: GameState): string { - return `You purchased ${amount} x ${this.name}.`; + return `You purchased ${amount} x ${this.pluralName}.`; + } + + private _purchase (state: GameState): void { + if (this.max !== undefined && this.value >= this.max(state)) return; + if (state.deductCost(this.cost)) { + const amount = this._purchaseAmount(state); + if (amount > 0) { + this.value += amount; + state.log(this._purchaseLog(amount, state)); + for (const key in this._costMultiplier) { + const rkey = key; + this.cost[rkey] = + (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1); + } + } + } } } diff --git a/src/model/resource/Religion.ts b/src/model/resource/Religion.ts index bc32b6c..eb95c0c 100644 --- a/src/model/resource/Religion.ts +++ b/src/model/resource/Religion.ts @@ -5,7 +5,8 @@ class Religion implements IResource { public readonly valueInWholeNumbers = true; constructor ( - public readonly name: string, + public readonly singularName: string, + public readonly pluralName: string, public readonly description: string, public value: number, ) { } diff --git a/src/model/resource/Research.ts b/src/model/resource/Research.ts index 4f4567a..d303522 100644 --- a/src/model/resource/Research.ts +++ b/src/model/resource/Research.ts @@ -5,13 +5,17 @@ abstract class Research extends Purchasable { public inc = undefined; constructor ( - public readonly name: string, + public readonly singularName: string, + public readonly pluralName: string, public readonly description: string ) { - super(name, description); + super( + singularName, + pluralName, + description, + 'Learn', + 'Complete this research.'); this.value = 0; - this.clickText = 'Learn'; - this.clickDescription = 'Complete this research.'; } public max: (state: GameState) => number = (_state) => 1; diff --git a/src/model/resource/SharedTypes.ts b/src/model/resource/SharedTypes.ts new file mode 100644 index 0000000..471f195 --- /dev/null +++ b/src/model/resource/SharedTypes.ts @@ -0,0 +1,41 @@ +/// + +enum ResourceType { + religion = 'religion', + job = 'job', + consumable = 'consumable', + infrastructure = 'infrastructure', + research = 'research', + passive = 'passive', +} + +enum ResourceKey { + playerOrg = 'playerOrg', + christianity = 'christianity', + islam = 'islam', + hinduism = 'hinduism', + buddhism = 'buddhism', + sikhism = 'sikhism', + judaism = 'judaism', + other = 'other', + atheism = 'atheism', + pastors = 'pastors', + money = 'money', + cryptoCurrency = 'cryptoCurrency', + tents = 'tents', + houses = 'houses', + churches = 'churches', + compounds = 'compounds', + buildingPermit = 'buildingPermit', + megaChurches = 'megaChurches', + credibility = 'credibility', +} + +type ResourceNumber = { [key in ResourceKey]?: number }; + +type ResourceAction = { + name: string; + description: string; + isEnabled: (state: GameState) => boolean; + performAction: (state: GameState) => void; +}; diff --git a/src/model/resource/Tent.ts b/src/model/resource/Tent.ts index 86ea24b..d6d3e41 100644 --- a/src/model/resource/Tent.ts +++ b/src/model/resource/Tent.ts @@ -2,8 +2,10 @@ class Tent extends Infrastructure { constructor (config: GameConfig) { - super('Tents', - `Provides room to house ${config.formatNumber(config.cfgCapacity.tents?.playerOrg ?? 0)} followers.`); + super( + 'tent', + 'tents', + `Provides room to house ${formatNumber(config.cfgCapacity.tents?.playerOrg ?? 0)} followers.`); this.cost.money = config.cfgInitialCost.tents; this._costMultiplier.money = config.cfgCostMultiplier.tents; } diff --git a/src/render/DebugRenderer.ts b/src/render/DebugRenderer.ts index a16adaa..2616bc3 100644 --- a/src/render/DebugRenderer.ts +++ b/src/render/DebugRenderer.ts @@ -53,17 +53,21 @@ class DebugRenderer implements IRenderer { let content = ` - ${this._escape(resource.name)}
+ ${this._escape(resource.pluralName)}
`; - if (resource.clickText !== undefined - && resource.clickDescription !== undefined) { - content += `
- `; + if (resource.userActions !== undefined) { + content += '
'; + for (let i = 0; i < resource.userActions.length; i++) { + const action = resource.userActions[i]; + content += ``; + } } if (resource.cost !== undefined && Object.keys(resource.cost).length !== 0) { @@ -71,11 +75,14 @@ class DebugRenderer implements IRenderer { } el.innerHTML = content; resContainer.appendChild(el); - if (resource.clickAction !== undefined) { - const btn = el.getElementsByClassName('resource-btn')[0]; - btn.addEventListener('click', (): void => { - state.performClick(rkey); - }); + if (resource.userActions !== undefined) { + for (let i = 0; i < resource.userActions.length; i++) { + const action = resource.userActions[i]; + const btn = document.getElementById(`resource-btn-${rkey}-${i}`); + btn?.addEventListener('click', (): void => { + state.performAction(rkey, i); + }); + } } } // create tools footer @@ -103,22 +110,25 @@ class DebugRenderer implements IRenderer { const value = resource.valueInWholeNumbers ? Math.floor(resource.value) : resource.value; - elV.innerHTML = state.config.formatNumber(value); + elV.innerHTML = formatNumber(value); elT.innerHTML = resource.max !== undefined - ? ` / ${state.config.formatNumber(resource.max(state))}` + ? ` / ${formatNumber(resource.max(state))}` : ''; - const elB = el.getElementsByClassName('resource-btn'); - if (elB.length > 0) { - const enabled = state.isPurchasable(resource.cost) - && (resource.max === undefined - || resource.value < resource.max(state)); - if (enabled) elB[0].removeAttribute('disabled'); - else elB[0].setAttribute('disabled', 'disabled'); + if (resource.userActions !== undefined) { + for (let i = 0; i < resource.userActions.length; i++) { + const elB = document.getElementById(`resource-btn-${rkey}-${i}`); + if (elB === null) continue; + if (resource.userActions[i].isEnabled(state)) { + elB.removeAttribute('disabled'); + } else { + elB.setAttribute('disabled', 'disabled'); + } + } } if (resource.inc !== undefined && resource.inc(state) > 0) { const elI = el.getElementsByClassName('resource-inc')[0]; elI.innerHTML = - ` +${state.config.formatNumber(resource.inc(state))}/s`; + ` +${formatNumber(resource.inc(state))}/s`; } if (this._handleClick) { const elC = el.getElementsByClassName('resource-cost'); @@ -154,10 +164,10 @@ class DebugRenderer implements IRenderer { if (resource.cost?.[rkey] !== undefined) { if (cost !== '') cost += ', '; if (rkey === ResourceKey.money) { - cost += `$${state.config.formatNumber(resource.cost[rkey] ?? 0)}`; + cost += `$${formatNumber(resource.cost[rkey] ?? 0)}`; } else { - cost += `${state.config.formatNumber(resource.cost[rkey] ?? 0)} - ${state.resource[rkey]?.name ?? rkey}`; + cost += `${formatNumber(resource.cost[rkey] ?? 0)} + ${state.resource[rkey]?.pluralName ?? rkey}`; } } }