This has broken quite a bit, since many parts of the game expect a scalar instead of a whole object. I have also been deep freezing constant configuration data and, generally, trying to make as much immutable as possibletags/v0.0.6
| @@ -2,168 +2,249 @@ | |||||
| const resourceTypes = { | const resourceTypes = { | ||||
| "food": { | "food": { | ||||
| name: "Food" | |||||
| name: "food" | |||||
| } | } | ||||
| } | } | ||||
| deepFreeze(resourceTypes); | |||||
| const buildings = { | const buildings = { | ||||
| "micro": { | "micro": { | ||||
| "name": "Micro", | "name": "Micro", | ||||
| "plural": "Micros", | "plural": "Micros", | ||||
| "desc": "A tasty, squirmy treat.", | "desc": "A tasty, squirmy treat.", | ||||
| "cost": 1e1, | |||||
| "prod": 1e-1 / 1, | |||||
| "cost": { | |||||
| "food": 1e1 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e-1 / 1 | |||||
| }, | |||||
| "icon": "fa-universal-access" | "icon": "fa-universal-access" | ||||
| }, | }, | ||||
| "anthro": { | "anthro": { | ||||
| "name": "Anthro", | "name": "Anthro", | ||||
| "plural": "Anthros", | "plural": "Anthros", | ||||
| "desc": "Something more substantial to sate your hunger.", | "desc": "Something more substantial to sate your hunger.", | ||||
| "cost": 1e2, | |||||
| "prod": 1e0 / 1.1, | |||||
| "cost": { | |||||
| "food": 1e2 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e0 / 1.1 | |||||
| }, | |||||
| "icon": "fa-male" | "icon": "fa-male" | ||||
| }, | }, | ||||
| "car": { | "car": { | ||||
| "name": "Car", | "name": "Car", | ||||
| "plural": "Cars", | "plural": "Cars", | ||||
| "desc": "Crunchy shell, tasty center.", | "desc": "Crunchy shell, tasty center.", | ||||
| "cost": 1.2e3, | |||||
| "prod": 1e1 / 1.2, | |||||
| "cost": { | |||||
| "food": 1.2e3 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e1 / 1.2 | |||||
| }, | |||||
| "icon": "fa-car" | "icon": "fa-car" | ||||
| }, | }, | ||||
| "bus": { | "bus": { | ||||
| "name": "Bus", | "name": "Bus", | ||||
| "plural": "Buses", | "plural": "Buses", | ||||
| "desc": "Probably the worst place to be when a macro is aroud.", | "desc": "Probably the worst place to be when a macro is aroud.", | ||||
| "cost": 1.4e4, | |||||
| "prod": 1e2 / 1.3, | |||||
| "cost": { | |||||
| "food": 1.4e4 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e2 / 1.3 | |||||
| }, | |||||
| "icon": "fa-bus" | "icon": "fa-bus" | ||||
| }, | }, | ||||
| "house": { | "house": { | ||||
| "name": "House", | "name": "House", | ||||
| "plural": "Houses", | "plural": "Houses", | ||||
| "desc": "Home sweet home - but it doesn't taste sweet?", | "desc": "Home sweet home - but it doesn't taste sweet?", | ||||
| "cost": 1.6e5, | |||||
| "prod": 1e3 / 1.4, | |||||
| "cost": { | |||||
| "food": 1.6e5 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e3 / 1.4 | |||||
| }, | |||||
| "icon": "fa-home" | "icon": "fa-home" | ||||
| }, | }, | ||||
| "apartment": { | "apartment": { | ||||
| "name": "Apartment", | "name": "Apartment", | ||||
| "plural": "Apartments", | "plural": "Apartments", | ||||
| "desc": "More snacks, less packaging.", | "desc": "More snacks, less packaging.", | ||||
| "cost": 1.8e6, | |||||
| "prod": 1e4 / 1.5, | |||||
| "cost": { | |||||
| "food": 1.8e6 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e4 / 1.5 | |||||
| }, | |||||
| "icon": "fa-building" | "icon": "fa-building" | ||||
| }, | }, | ||||
| "block": { | "block": { | ||||
| "name": "Block", | "name": "Block", | ||||
| "plural": "Blocks", | "plural": "Blocks", | ||||
| "desc": "A whole pile of buildings.", | "desc": "A whole pile of buildings.", | ||||
| "cost": 2e7, | |||||
| "prod": 1e5 / 1.6, | |||||
| "cost": { | |||||
| "food": 2e7 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e5 / 1.6 | |||||
| }, | |||||
| "icon": "fa-warehouse" | "icon": "fa-warehouse" | ||||
| }, | }, | ||||
| "town": { | "town": { | ||||
| "name": "Town", | "name": "Town", | ||||
| "plural": "Towns", | "plural": "Towns", | ||||
| "desc": "'Tourist trap' has never been this literal.", | "desc": "'Tourist trap' has never been this literal.", | ||||
| "cost": 2.2e8, | |||||
| "prod": 1e6 / 1.7, | |||||
| "cost": { | |||||
| "food": 2.2e8 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e6 / 1.7 | |||||
| }, | |||||
| "icon": "fa-store" | "icon": "fa-store" | ||||
| }, | }, | ||||
| "city": { | "city": { | ||||
| "name": "City", | "name": "City", | ||||
| "plural": "Cities", | "plural": "Cities", | ||||
| "desc": "Please no sitty on our city.", | "desc": "Please no sitty on our city.", | ||||
| "cost": 2.4e9, | |||||
| "prod": 1e7 / 1.8, | |||||
| "cost": { | |||||
| "food": 2.4e9 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e7 / 1.8 | |||||
| }, | |||||
| "icon": "fa-city" | "icon": "fa-city" | ||||
| }, | }, | ||||
| "metro": { | "metro": { | ||||
| "name": "Metropolis", | "name": "Metropolis", | ||||
| "plural": "Metropolises", | "plural": "Metropolises", | ||||
| "desc": "A big ol' city. Tasty, too.", | "desc": "A big ol' city. Tasty, too.", | ||||
| "cost": 2.6e10, | |||||
| "prod": 1e8 / 1.9, | |||||
| "cost": { | |||||
| "food": 2.6e10 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e8 / 1.9 | |||||
| }, | |||||
| "icon": "fa-landmark" | "icon": "fa-landmark" | ||||
| }, | }, | ||||
| "county": { | "county": { | ||||
| "name": "County", | "name": "County", | ||||
| "plural": "Counties", | "plural": "Counties", | ||||
| "desc": "Why salt the land when you can slurp it?", | "desc": "Why salt the land when you can slurp it?", | ||||
| "cost": 2.8e11, | |||||
| "prod": 1e9 / 2, | |||||
| "cost": { | |||||
| "food": 2.8e11 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e9 / 2 | |||||
| }, | |||||
| "icon": "fa-map" | "icon": "fa-map" | ||||
| }, | }, | ||||
| "state": { | "state": { | ||||
| "name": "State", | "name": "State", | ||||
| "plural": "States", | "plural": "States", | ||||
| "desc": "The United States is made up of...43 states - no, 42...", | "desc": "The United States is made up of...43 states - no, 42...", | ||||
| "cost": 3e12, | |||||
| "prod": 1e10 / 2.1, | |||||
| "cost": { | |||||
| "food": 3e12 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e10 / 2.1 | |||||
| }, | |||||
| "icon": "fa-map-signs" | "icon": "fa-map-signs" | ||||
| }, | }, | ||||
| "country": { | "country": { | ||||
| "name": "Country", | "name": "Country", | ||||
| "plural": "Countries", | "plural": "Countries", | ||||
| "desc": "One nation, under paw.", | "desc": "One nation, under paw.", | ||||
| "cost": 3.2e13, | |||||
| "prod": 1e11 / 2.2, | |||||
| "cost": { | |||||
| "food": 3.2e13 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e11 / 2.2 | |||||
| }, | |||||
| "icon": "fa-flag" | "icon": "fa-flag" | ||||
| }, | }, | ||||
| "continent": { | "continent": { | ||||
| "name": "Continent", | "name": "Continent", | ||||
| "plural": "Continents", | "plural": "Continents", | ||||
| "desc": "Earth-shattering appetite!", | "desc": "Earth-shattering appetite!", | ||||
| "cost": 3.4e14, | |||||
| "prod": 1e12 / 2.3, | |||||
| "cost": { | |||||
| "food": 3.4e14 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e12 / 2.3 | |||||
| }, | |||||
| "icon": "fa-mountain" | "icon": "fa-mountain" | ||||
| }, | }, | ||||
| "planet": { | "planet": { | ||||
| "name": "Planet", | "name": "Planet", | ||||
| "plural": "Planets", | "plural": "Planets", | ||||
| "desc": "Earth appetite!", | "desc": "Earth appetite!", | ||||
| "cost": 3.6e15, | |||||
| "prod": 1e13 / 2.4, | |||||
| "cost": { | |||||
| "food": 3.6e15 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e13 / 2.4 | |||||
| }, | |||||
| "icon": "fa-globe-europe" | "icon": "fa-globe-europe" | ||||
| }, | }, | ||||
| "solar-system": { | "solar-system": { | ||||
| "name": "Solar System", | "name": "Solar System", | ||||
| "plural": "Solar Systems", | "plural": "Solar Systems", | ||||
| "desc": "Earths appetite!", | "desc": "Earths appetite!", | ||||
| "cost": 3.8e16, | |||||
| "prod": 1e14 / 2.5, | |||||
| "cost": { | |||||
| "food": 3.8e16 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e14 / 2.5 | |||||
| }, | |||||
| "icon": "fa-meteor" | "icon": "fa-meteor" | ||||
| }, | }, | ||||
| "galaxy": { | "galaxy": { | ||||
| "name": "Galaxy", | "name": "Galaxy", | ||||
| "plural": "Galaxies", | "plural": "Galaxies", | ||||
| "desc": "In a galaxy far, far down your gullet...", | "desc": "In a galaxy far, far down your gullet...", | ||||
| "cost": 4.0e17, | |||||
| "prod": 1e15 / 2.6, | |||||
| "cost": { | |||||
| "food": 4.0e17 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e15 / 2.6 | |||||
| }, | |||||
| "icon": "fa-sun" | "icon": "fa-sun" | ||||
| }, | }, | ||||
| "universe": { | "universe": { | ||||
| "name": "Universe", | "name": "Universe", | ||||
| "plural": "Universes", | "plural": "Universes", | ||||
| "desc": "Into the you-verse.", | "desc": "Into the you-verse.", | ||||
| "cost": 4.2e18, | |||||
| "prod": 1e16 / 2.7, | |||||
| "cost": { | |||||
| "food": 4.2e18 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e16 / 2.7 | |||||
| }, | |||||
| "icon": "fa-asterisk" | "icon": "fa-asterisk" | ||||
| }, | }, | ||||
| "multiverse": { | "multiverse": { | ||||
| "name": "Multiverse", | "name": "Multiverse", | ||||
| "plural": "Multiverses", | "plural": "Multiverses", | ||||
| "desc": "This is getting very silly.", | "desc": "This is getting very silly.", | ||||
| "cost": 4.4e19, | |||||
| "prod": 1e17 / 2.8, | |||||
| "cost": { | |||||
| "food": 4.4e19 | |||||
| }, | |||||
| "prod": { | |||||
| "food": 1e17 / 2.8 | |||||
| }, | |||||
| "icon": "fa-infinity" | "icon": "fa-infinity" | ||||
| } | } | ||||
| } | } | ||||
| deepFreeze(buildings); | |||||
| const effect_types = { | const effect_types = { | ||||
| "prod": { | "prod": { | ||||
| "apply": function (effect, productivity) { | "apply": function (effect, productivity) { | ||||
| return productivity * effect.amount; | |||||
| scaleCost(productivity, effect); | |||||
| }, | }, | ||||
| "desc": function (effect) { | "desc": function (effect) { | ||||
| return round(effect.amount, 2) + "x food production from " + buildings[effect.target].plural; | return round(effect.amount, 2) + "x food production from " + buildings[effect.target].plural; | ||||
| @@ -171,7 +252,7 @@ const effect_types = { | |||||
| }, | }, | ||||
| "prod-all": { | "prod-all": { | ||||
| "apply": function (effect, productivity) { | "apply": function (effect, productivity) { | ||||
| return productivity * effect.amount; | |||||
| scaleCost(productivity, effect); | |||||
| }, | }, | ||||
| "desc": function (effect) { | "desc": function (effect) { | ||||
| return round((effect.amount - 1) * 100) + "% increase to food production"; | return round((effect.amount - 1) * 100) + "% increase to food production"; | ||||
| @@ -208,6 +289,8 @@ const effect_types = { | |||||
| } | } | ||||
| } | } | ||||
| deepFreeze(effect_types); | |||||
| let upgrades = { | let upgrades = { | ||||
| } | } | ||||
| @@ -219,6 +302,7 @@ function createTemplateUpgrades() { | |||||
| createHelperUpgrades(); | createHelperUpgrades(); | ||||
| createClickVictimUpgrades(); | createClickVictimUpgrades(); | ||||
| createPowerupFreqUpgrades(); | createPowerupFreqUpgrades(); | ||||
| deepFreeze(upgrades); | |||||
| } | } | ||||
| const prodUpgradeCounts = [1, 5, 10, 25, 50, 75, 100]; | const prodUpgradeCounts = [1, 5, 10, 25, 50, 75, 100]; | ||||
| @@ -13,6 +13,7 @@ const resources = {}; | |||||
| let updateRate = 30; | let updateRate = 30; | ||||
| const currentProductivity = {}; | const currentProductivity = {}; | ||||
| let clickBonus = 0; | let clickBonus = 0; | ||||
| let clickVictim = "micro"; | let clickVictim = "micro"; | ||||
| @@ -66,7 +67,10 @@ function addPowerup(powerup) { | |||||
| updateAll(); | updateAll(); | ||||
| } | } | ||||
| function applyGlobalProdBonuses(productivity) { | |||||
| function getGlobalProdBonus() { | |||||
| let productivity = 1; | |||||
| for (let effect of effects["prod-all"]) { | for (let effect of effects["prod-all"]) { | ||||
| if (ownedUpgrades[effect.parent]) { | if (ownedUpgrades[effect.parent]) { | ||||
| @@ -78,10 +82,10 @@ function applyGlobalProdBonuses(productivity) { | |||||
| } | } | ||||
| function calculateProductivity() { | function calculateProductivity() { | ||||
| let productivity = 0; | |||||
| let productivity = makeCost(); | |||||
| for (const [key, value] of Object.entries(belongings)) { | for (const [key, value] of Object.entries(belongings)) { | ||||
| productivity += productivityOf(key); | |||||
| productivity = addCost(productivity, productivityOf(key)); | |||||
| } | } | ||||
| return productivity; | return productivity; | ||||
| @@ -110,27 +114,42 @@ function productivityMultiplierOf(type) { | |||||
| } | } | ||||
| function productivityOf(type) { | function productivityOf(type) { | ||||
| let baseProd = buildings[type].prod; | |||||
| let baseProd = makeCost(buildings[type].prod); | |||||
| let prod = baseProd * productivityMultiplierOf(type); | |||||
| baseProd.food *= productivityMultiplierOf(type); | |||||
| prod = applyGlobalProdBonuses(prod); | |||||
| baseProd.food *= getGlobalProdBonus(); | |||||
| baseProd.food *= belongings[type].count; | |||||
| return baseProd; | |||||
| } | |||||
| return prod * belongings[type].count; | |||||
| function makeCost(source) { | |||||
| const empty = mapObject(resourceTypes, () => 0); | |||||
| Object.preventExtensions(empty); | |||||
| return {...empty, ...source}; | |||||
| } | |||||
| function addCost(cost1, cost2) { | |||||
| return Object.keys(resourceTypes).reduce((o, k) => ({ ...o, [k]: cost1[k] + cost2[k]}), {}); | |||||
| } | |||||
| function scaleCost(cost, scale) { | |||||
| return Object.keys(resourceTypes).reduce((o, k) => ({ ...o, [k]: cost[k] * scale}), {}); | |||||
| } | } | ||||
| function costOfBuilding(type, count = 1) { | function costOfBuilding(type, count = 1) { | ||||
| let total = 0; | |||||
| let total = makeCost(); | |||||
| while (count > 0) { | while (count > 0) { | ||||
| let baseCost = buildings[type].cost; | |||||
| let countCost = baseCost * Math.pow(1.15, belongings[type].count + count - 1); | |||||
| total += countCost; | |||||
| let baseCost = makeCost(buildings[type].cost); | |||||
| baseCost.food *= Math.pow(1.15, belongings[type].count + count - 1); | |||||
| total = addCost(total, baseCost); | |||||
| count--; | count--; | ||||
| } | } | ||||
| return Math.round(total); | |||||
| return mapObject(total, round); | |||||
| } | } | ||||
| function buildingCount() { | function buildingCount() { | ||||
| @@ -148,9 +167,9 @@ function buyBuilding(type, e) { | |||||
| let cost = costOfBuilding(type, count); | let cost = costOfBuilding(type, count); | ||||
| if (resources.food >= cost) { | |||||
| if (canAfford(cost)) { | |||||
| spend(cost); | |||||
| belongings[type].count += count; | belongings[type].count += count; | ||||
| resources.food -= cost; | |||||
| } | } | ||||
| updateProductivity(); | updateProductivity(); | ||||
| @@ -181,7 +200,7 @@ function updateDisplay() { | |||||
| } | } | ||||
| function updateProductivity() { | function updateProductivity() { | ||||
| currentProductivity["food"] = calculateProductivity(); | |||||
| Object.assign(currentProductivity, calculateProductivity()); | |||||
| activePowerups.forEach(entry => { | activePowerups.forEach(entry => { | ||||
| const powerup = entry.powerup; | const powerup = entry.powerup; | ||||
| @@ -193,11 +212,7 @@ function updateProductivity() { | |||||
| belongings: belongings | belongings: belongings | ||||
| }; | }; | ||||
| console.log(currentProductivity); | |||||
| powerup.effect(state); | powerup.effect(state); | ||||
| console.log(currentProductivity); | |||||
| }) | }) | ||||
| } | } | ||||
| @@ -233,7 +248,9 @@ function displayBuildings() { | |||||
| for (const [key, value] of Object.entries(belongings)) { | for (const [key, value] of Object.entries(belongings)) { | ||||
| if (!belongings[key].visible) { | if (!belongings[key].visible) { | ||||
| if (resources.food * 10 >= costOfBuilding(key)) { | |||||
| if (resources.food * 10 >= costOfBuilding(key).food) { | |||||
| unlockBuilding(key); | |||||
| } if (belongings[key].count > 0) { | |||||
| unlockBuilding(key); | unlockBuilding(key); | ||||
| } else { | } else { | ||||
| continue; | continue; | ||||
| @@ -247,15 +264,17 @@ function displayBuildings() { | |||||
| let name = document.querySelector("#building-" + key + " > .building-button-name"); | let name = document.querySelector("#building-" + key + " > .building-button-name"); | ||||
| let cost = document.querySelector("#building-" + key + " > .building-button-cost"); | let cost = document.querySelector("#building-" + key + " > .building-button-cost"); | ||||
| const buildingCost = costOfBuilding(key, count); | |||||
| name.innerText = value.count + " " + (value.count == 1 ? buildings[key].name : buildings[key].plural); | name.innerText = value.count + " " + (value.count == 1 ? buildings[key].name : buildings[key].plural); | ||||
| cost.innerText = render(costOfBuilding(key, count)) + " food"; | |||||
| cost.innerText = render(buildingCost.food) + " food"; | |||||
| if (costOfBuilding(key, count) > resources.food) { | |||||
| button.classList.add("building-button-disabled"); | |||||
| cost.classList.add("building-button-cost-invalid"); | |||||
| } else { | |||||
| if (canAfford(buildingCost)) { | |||||
| button.classList.remove("building-button-disabled"); | button.classList.remove("building-button-disabled"); | ||||
| cost.classList.add("building-button-cost-valid"); | cost.classList.add("building-button-cost-valid"); | ||||
| } else { | |||||
| button.classList.add("building-button-disabled"); | |||||
| cost.classList.add("building-button-cost-invalid"); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -375,7 +394,7 @@ function buyUpgrade(id, e) { | |||||
| } | } | ||||
| function eatPrey() { | function eatPrey() { | ||||
| const add = buildings[clickVictim]["prod"] * 10 * productivityMultiplierOf(clickVictim) + clickBonus; | |||||
| const add = buildings[clickVictim]["prod"].food * 10 * productivityMultiplierOf(clickVictim) + clickBonus; | |||||
| resources.food += add; | resources.food += add; | ||||
| return add; | return add; | ||||
| } | } | ||||
| @@ -938,7 +957,7 @@ function buildingTooltip(id, event) { | |||||
| fillTooltip("building", "name", (count != 1 ? count + "x " : "") + buildings[id].name); | fillTooltip("building", "name", (count != 1 ? count + "x " : "") + buildings[id].name); | ||||
| fillTooltip("building", "desc", buildings[id].desc); | fillTooltip("building", "desc", buildings[id].desc); | ||||
| fillTooltip("building", "cost", render(costOfBuilding(id, count)) + " food"); | |||||
| fillTooltip("building", "cost", render(costOfBuilding(id, count).food) + " food"); | |||||
| fillTooltip("building", "prod", prodSummary(id)); | fillTooltip("building", "prod", prodSummary(id)); | ||||
| let xPos = tooltip.parentElement.getBoundingClientRect().x - 450; | let xPos = tooltip.parentElement.getBoundingClientRect().x - 450; | ||||
| @@ -3,6 +3,9 @@ function render(val, places = 1, smallPlaces = 0) { | |||||
| } | } | ||||
| function numberText(val, places = 1, smallPlaces = 0) { | function numberText(val, places = 1, smallPlaces = 0) { | ||||
| if (isNaN(val)) { | |||||
| throw new RangeError("Invalid number: " + val); | |||||
| } | |||||
| if (val < 1000) { | if (val < 1000) { | ||||
| return round(val, smallPlaces); | return round(val, smallPlaces); | ||||
| } | } | ||||
| @@ -18,3 +18,24 @@ function removeChildren(element) { | |||||
| function round(val, places = 0) { | function round(val, places = 0) { | ||||
| return Math.round(val * Math.pow(10, places)) / Math.pow(10, places); | return Math.round(val * Math.pow(10, places)) / Math.pow(10, places); | ||||
| } | } | ||||
| function mapObject(obj, func) { | |||||
| return Object.keys(obj).reduce((o, k) => ({ ...o, [k]: func(obj[k])}), {}); | |||||
| } | |||||
| function deepFreeze(object) { | |||||
| // Retrieve the property names defined on object | |||||
| var propNames = Object.getOwnPropertyNames(object); | |||||
| // Freeze properties before freezing self | |||||
| for (let name of propNames) { | |||||
| let value = object[name]; | |||||
| object[name] = value && typeof value === "object" ? | |||||
| deepFreeze(value) : value; | |||||
| } | |||||
| return Object.freeze(object); | |||||
| } | |||||