diff --git a/cmd b/cmd index 5e01db2..878300b 100755 --- a/cmd +++ b/cmd @@ -2,11 +2,14 @@ cmd=$1 -if [ "$cmd" = "build" ]; then +if [ "$cmd" = "lint" ]; then + tslint --project . +elif [ "$cmd" = "build" ]; then tslint --project . && tsc elif [ "$cmd" = "run" ]; then firefox public/index.html & else - echo "Usage: ./cmd build - lint and compile" + echo "Usage: ./cmd lint - lint" + echo " ./cmd build - lint and compile" echo " ./cmd run - run in firefox" fi diff --git a/public/css/debugger.css b/public/css/debugger.css index 873ec31..7cc7c98 100644 --- a/public/css/debugger.css +++ b/public/css/debugger.css @@ -1,11 +1,9 @@ -#irreligious-game, .resource-type-container { - clear: all; - overflow: auto; +.resource-type-container { + display: flex; } -div.resource { - float: left; +.resource { border: 2px solid black; - margin-right: 5px; - margin-bottom: 5px; padding: 5px 10px; + margin: 0 5px 5px 0; + flex-shrink: 0; } diff --git a/src/main.ts b/src/main.ts index 990aa85..d1d2a2b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,12 +3,13 @@ /// /// -let globalStartTime = 0; +let globalStartTime: number = 0; let globalTimeout: number = null; +let cycleLength: number = 250; function gameLoop (state: GameState, renderer: IRenderer): void { // figure out how much actual time has passed - const elapsedTime = globalStartTime > 0 + const elapsedTime: number = globalStartTime > 0 ? (new Date()).getTime() - globalStartTime : 0; renderer.render(state); @@ -16,17 +17,18 @@ function gameLoop (state: GameState, renderer: IRenderer): void { // run again in 1sec globalStartTime = (new Date()).getTime(); - globalTimeout = setTimeout(() => gameLoop(state, renderer), 1000); + globalTimeout = setTimeout((): void => + gameLoop(state, renderer), cycleLength); } // run with default config at startup -(() => { - const config = new GameConfig(); - const renderer = new DebugRenderer(); - const state = config.generateState(); +((): void => { + const config: GameConfig = new GameConfig(); + const renderer: IRenderer = new DebugRenderer(); + const state: GameState = config.generateState(); // re-run main loop immediately on user clicks - state.onResourceClick.push(() => { + state.onResourceClick.push((): void => { if (globalTimeout !== null) { clearTimeout(globalTimeout); gameLoop(state, renderer); @@ -34,5 +36,6 @@ function gameLoop (state: GameState, renderer: IRenderer): void { }); if (document.readyState !== 'loading') gameLoop(state, renderer); - else document.addEventListener('DOMContentLoaded', () => gameLoop(state, renderer)); + else document.addEventListener('DOMContentLoaded', (): void => + gameLoop(state, renderer)); })(); diff --git a/src/model/GameConfig.ts b/src/model/GameConfig.ts index b964afb..903b7fe 100644 --- a/src/model/GameConfig.ts +++ b/src/model/GameConfig.ts @@ -17,7 +17,7 @@ class GameConfig { public relNoneShare: number = 0.16; public generateState (): GameState { - const state = new GameState(); + const state: GameState = new GameState(); // create player organization state.addResource('plorg', new Religion( @@ -57,7 +57,7 @@ class GameConfig { this.relNoneShare * this.worldPopulation)); // add purchasable resources - state.addResource('money', new Money(0, 1000)); + state.addResource('money', new Money(0)); state.addResource('bonds', new Savings(0)); return state; diff --git a/src/model/GameState.ts b/src/model/GameState.ts index 79e4084..0169c34 100644 --- a/src/model/GameState.ts +++ b/src/model/GameState.ts @@ -16,8 +16,14 @@ class GameState { if (this._resources[rkey].advanceAction !== null) { this._resources[rkey].advanceAction(time, this); } - if (this._resources[rkey].inc > 0) { - this._resources[rkey].value += this._resources[rkey].inc * time / 1000; + const max: number | null = this._resources[rkey].max(this); + if (this._resources[rkey].inc(this) > 0 + && (max === null || this._resources[rkey].value < max)) { + this._resources[rkey].value += + this._resources[rkey].inc(this) * time / 1000; + } + if (max !== null && this._resources[rkey].value > max) { + this._resources[rkey].value = max; } } } diff --git a/src/model/resource/IResource.ts b/src/model/resource/IResource.ts index 1cf4dc9..9e9bb54 100644 --- a/src/model/resource/IResource.ts +++ b/src/model/resource/IResource.ts @@ -10,8 +10,8 @@ interface IResource { resourceType: ResourceType; value: number; - inc: number; - max?: number; + max: (state: GameState) => number | null; + inc: (state: GameState) => number | null; cost: { [key: string]: number }; isUnlocked: (state: GameState) => boolean; diff --git a/src/model/resource/Money.ts b/src/model/resource/Money.ts index 043c244..6972d10 100644 --- a/src/model/resource/Money.ts +++ b/src/model/resource/Money.ts @@ -3,14 +3,21 @@ class Money extends Purchasable { constructor ( public value: number, - public max: number ) { super('Money', 'Used to purchase goods and services.'); this.clickText = 'Beg'; this.clickDescription = 'Alms for the poor.'; + this._baseMax = 1000; } public isUnlocked (state: GameState): boolean { return true; } + + public inc (state: GameState): number { + let baseInc: number = 0; + // bonds give $1/s + baseInc += state.getResource('bonds').value; + return baseInc; + } } diff --git a/src/model/resource/Purchasable.ts b/src/model/resource/Purchasable.ts index 79aeb0b..c2f7bc6 100644 --- a/src/model/resource/Purchasable.ts +++ b/src/model/resource/Purchasable.ts @@ -1,27 +1,26 @@ /// abstract class Purchasable implements IResource { - public readonly resourceType = ResourceType.Infrastructure; - public readonly max?: number = null; + public readonly resourceType: ResourceType = ResourceType.Infrastructure; public value: number = 0; - public inc: number = 0; public clickText: string = 'Purchase'; public clickDescription: string = 'Purchase'; public cost: { [key: string]: number } = null; + protected _costMultiplier: { [key: string]: number } = null; + protected _baseMax: number | null = null; constructor ( public readonly name: string, public readonly description: string ) { } - public clickAction (state: GameState) { - if (this.max !== null && this.value >= this.max) return; + public clickAction (state: GameState): void { + if (this.max(state) !== null && this.value >= this.max(state)) return; if (state.deductCost(this.cost)) { this.value += 1; - this.purchaseEffect(state); if (this._costMultiplier !== null && Object.keys(this._costMultiplier !== null)) { for (const rkey of Object.keys(this._costMultiplier)) { @@ -31,6 +30,14 @@ abstract class Purchasable implements IResource { } } + public inc (state: GameState): number | null { + return null; + } + + public max (state: GameState): number | null { + return this._baseMax; + } + public advanceAction (time: number, state: GameState): void { return; } @@ -38,8 +45,4 @@ abstract class Purchasable implements IResource { public isUnlocked (state: GameState): boolean { return false; } - - protected purchaseEffect (state: GameState) { - return; - } } diff --git a/src/model/resource/Religion.ts b/src/model/resource/Religion.ts index 68f3462..1f97936 100644 --- a/src/model/resource/Religion.ts +++ b/src/model/resource/Religion.ts @@ -1,14 +1,15 @@ /// class Religion implements IResource { - public readonly resourceType = ResourceType.Religion; - public readonly max?: number = null; + public readonly resourceType: ResourceType = ResourceType.Religion; public readonly clickText: string = null; public readonly clickDescription: string = null; - public readonly clickAction: () => void = null; public readonly advanceAction: (time: number) => void = null; public readonly cost: { [key: string]: number } = null; - public readonly inc: number = 0; + + public readonly max: () => null = (): null => null; + public readonly inc: () => null = (): null => null; + public readonly clickAction: () => void = null; constructor ( public readonly name: string, diff --git a/src/model/resource/Savings.ts b/src/model/resource/Savings.ts index 6bf65a7..a4f9ee3 100644 --- a/src/model/resource/Savings.ts +++ b/src/model/resource/Savings.ts @@ -1,8 +1,7 @@ /// class Savings extends Purchasable { - public max?: number = null; - private _isUnlocked = false; + private _isUnlocked: boolean = false; constructor ( public value: number, @@ -21,7 +20,7 @@ class Savings extends Purchasable { return false; } - protected purchaseEffect (state: GameState) { - state.getResource('money').inc += 1; + protected purchaseEffect (state: GameState): void { + return; } } diff --git a/src/render/DebugRenderer.ts b/src/render/DebugRenderer.ts index ce6fc8d..767ff35 100644 --- a/src/render/DebugRenderer.ts +++ b/src/render/DebugRenderer.ts @@ -2,67 +2,87 @@ /// class DebugRenderer implements IRenderer { - private _initialized = false; - private _handleClick = true; + private _initialized: boolean = false; + private _handleClick: boolean = true; - public render (state: GameState) { + public render (state: GameState): void { if (!this._initialized) { - const container = document.getElementById('irreligious-game'); + const container: HTMLElement = + document.getElementById('irreligious-game'); this._initialized = true; - state.onResourceClick.push(() => this._handleClick = true); - const style = document.createElement('link'); + state.onResourceClick.push((): void => { + this._handleClick = true; + }); + const style: HTMLElement = document.createElement('link'); style.setAttribute('rel', 'stylesheet'); style.setAttribute('href', 'css/debugger.css'); - const head = document.getElementsByTagName('head')[0]; + const head: HTMLElement = document.getElementsByTagName('head')[0]; head.appendChild(style); // create containers for each resource type for (const item in ResourceType) { if (isNaN(Number(item))) { - const el = document.createElement('div'); + const el: HTMLElement = document.createElement('div'); el.id = `resource-container-${ResourceType[item]}`; el.className = 'resource-type-container'; container.appendChild(el); } } } - const rkeys = state.getResources(); + const rkeys: string[] = state.getResources(); for (const rkey of rkeys) { - const resource = state.getResource(rkey); - console.log(`getting container resource-container-${resource.resourceType}`); // tslint:disable-line - const container = document.getElementById(`resource-container-${resource.resourceType}`); + const resource: IResource = state.getResource(rkey); + const container: HTMLElement = document + .getElementById(`resource-container-${resource.resourceType}`); if (resource.isUnlocked(state)) { - let el = document.getElementById(`resource-details-${rkey}`); + let el: HTMLElement = document + .getElementById(`resource-details-${rkey}`); if (el === null) { el = document.createElement('div'); el.className = 'resource'; el.id = `resource-details-${rkey}`; - let content = ` - ${resource.name}
- + let content: string = ` + + ${resource.name}
+ + + `; if (resource.clickText !== null) { - content += `
`; + content += `
+ `; } - if (resource.cost !== null && Object.keys(resource.cost) !== null) { + if (resource.cost !== null + && Object.keys(resource.cost) !== null) { content += `
Cost: `; } el.innerHTML = content; container.appendChild(el); if (resource.clickAction !== null) { - const btn = el.getElementsByClassName('resource-btn')[0]; - btn.addEventListener('click', () => state.performClick(rkey)); + const btn: Element = + el.getElementsByClassName('resource-btn')[0]; + btn.addEventListener('click', (): void => + state.performClick(rkey)); } } - const elV = el.getElementsByClassName('resource-value')[0]; - const elT = el.getElementsByClassName('resource-max')[0]; + const elV: Element = + el.getElementsByClassName('resource-value')[0]; + const elT: Element = + el.getElementsByClassName('resource-max')[0]; elV.innerHTML = this.formatNumber(resource.value, 1); - elT.innerHTML = resource.max !== null ? ` / ${this.formatNumber(resource.max, 1)}` : ''; + elT.innerHTML = resource.max(state) !== null + ? ` / ${this.formatNumber(resource.max(state), 1)}` + : ''; if (this._handleClick) { - if (resource.inc > 0) { - const elI = el.getElementsByClassName('resource-inc')[0]; - elI.innerHTML = ` +${this.formatNumber(resource.inc, 1)}/s`; + if (resource.inc(state) > 0) { + const elI: Element = + el.getElementsByClassName('resource-inc')[0]; + elI.innerHTML = + ` +${this.formatNumber(resource.inc(state), 1)}/s`; } - const elC = el.getElementsByClassName('resource-cost'); + const elC: HTMLCollectionOf = + el.getElementsByClassName('resource-cost'); if (elC.length > 0) { elC[0].innerHTML = this.getCostStr(resource, state); } @@ -72,15 +92,16 @@ class DebugRenderer implements IRenderer { this._handleClick = false; } - private getCostStr (resource: IResource, state: GameState) { - let cost = ''; + private getCostStr (resource: IResource, state: GameState): string { + let cost: string = ''; for (const rkey of state.getResources()) { if (resource.cost[rkey] !== undefined) { if (cost !== '') cost += ', '; if (rkey === 'money') { cost += `$${this.formatNumber(resource.cost[rkey], 1)}`; } else { - cost += `${this.formatNumber(resource.cost[rkey], 1)} ${state.getResource(rkey).name}`; + cost += `${this.formatNumber(resource.cost[rkey], 1)} + ${state.getResource(rkey).name}`; } } } @@ -88,7 +109,8 @@ class DebugRenderer implements IRenderer { } private formatNumber (num: number, digits: number): string { - const lookup = [ + type vlookup = { value: number, symbol: string }; + const lookup: vlookup[] = [ { value: 1, symbol: "" }, { value: 1e3, symbol: "K" }, { value: 1e6, symbol: "M" }, @@ -97,8 +119,10 @@ class DebugRenderer implements IRenderer { { value: 1e15, symbol: "P" }, { value: 1e18, symbol: "E" } ]; - const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; - const item = lookup.slice().reverse().find((i) => num >= i.value); + const rx: RegExp = /\.0+$|(\.[0-9]*[1-9])0+$/; + const item: vlookup = + lookup.slice().reverse() + .find((i: vlookup): boolean => num >= i.value); return item ? (num / item.value).toFixed(digits).replace(rx, "$1") + item.symbol : num.toFixed(digits).replace(rx, "$1"); diff --git a/src/render/IRenderer.ts b/src/render/IRenderer.ts index ed0ce79..b4d6e6d 100644 --- a/src/render/IRenderer.ts +++ b/src/render/IRenderer.ts @@ -1,5 +1,5 @@ /// interface IRenderer { - render (state: GameState); + render (state: GameState): void; } diff --git a/tslint.json b/tslint.json index d1c81ab..fe04b0d 100644 --- a/tslint.json +++ b/tslint.json @@ -3,6 +3,13 @@ "rules": { "no-reference": false, "space-before-function-paren": true, + "triple-equals": true, + "max-line-length": [ + true, { + "limit": 75, + "check-strings": true + } + ], "whitespace": [ true, "check-branch", @@ -14,6 +21,16 @@ "check-type-operator", "check-preblock", "check-postbrace" + ], + "typedef": [ + true, + "call-signature", + "arrow-call-signature", + "parameter", + "arrow-parameter", + "property-declaration", + "variable-declaration", + "member-variable-declaration" ] } }