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