cookie clicker but bigger
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

648 行
15 KiB

  1. "use strict";
  2. let belongings = {};
  3. let ownedUpgrades = {};
  4. let effects = {};
  5. let remainingUpgrades = [];
  6. let resources = {};
  7. let updateRate = 60;
  8. let currentProductivity = {};
  9. let lastTime = 0;
  10. function calculateProductivity() {
  11. let productivity = 0;
  12. for (const [key, value] of Object.entries(belongings)) {
  13. productivity += productivityOf(key);
  14. }
  15. for (let effect of effects["prod-all"]) {
  16. if (ownedUpgrades[effect.parent]) {
  17. productivity = effect.apply(productivity);
  18. }
  19. }
  20. return productivity;
  21. }
  22. // here's where upgrades will go :3
  23. function productivityMultiplierOf(type) {
  24. let base = 1;
  25. for (let effect of effects["prod"]) {
  26. if (ownedUpgrades[effect.parent] && effect.target == type) {
  27. base = effect.apply(base);
  28. }
  29. }
  30. for (let effect of effects["helper"]) {
  31. if (ownedUpgrades[effect.parent] && effect.helped == type) {
  32. base = effect.apply(base, belongings[effect.helper].count);
  33. }
  34. }
  35. return base;
  36. }
  37. function productivityOf(type) {
  38. let baseProd = buildings[type].prod;
  39. let prod = baseProd * productivityMultiplierOf(type);
  40. return prod * belongings[type].count;
  41. }
  42. function costOfBuilding(type) {
  43. let baseCost = buildings[type].cost
  44. let countCost = baseCost * Math.pow(1.15, belongings[type].count);
  45. return Math.round(countCost);
  46. }
  47. function buyBuilding(type) {
  48. let cost = costOfBuilding(type);
  49. if (resources.food >= cost) {
  50. belongings[type].count += 1;
  51. resources.food -= cost;
  52. }
  53. updateProductivity();
  54. }
  55. // update stuff
  56. function updateDisplay() {
  57. let newTime = performance.now();
  58. let delta = newTime - lastTime;
  59. lastTime = newTime;
  60. updateProductivity();
  61. addResources(delta);
  62. displayResources();
  63. displayBuildings();
  64. displayUpgrades();
  65. setTimeout(updateDisplay, 1000/updateRate);
  66. }
  67. function updateProductivity() {
  68. currentProductivity["food"] = calculateProductivity();
  69. }
  70. function addResources(delta) {
  71. for (const [resource, amount] of Object.entries(currentProductivity)) {
  72. resources[resource] += amount * delta / 1000;
  73. }
  74. }
  75. function displayResources() {
  76. document.title = "Gorge - " + round(resources.food) + " food";
  77. replaceChildren(document.querySelector("#resource-list"), renderResources());
  78. }
  79. function renderResources() {
  80. let list = [];
  81. for (const [key, value] of Object.entries(resources)) {
  82. let line1 = render(value, 3, 0) + " " + resourceTypes[key].name;
  83. let line2 = render(currentProductivity[key], 1, 1) + " " + resourceTypes[key].name + "/sec";
  84. list.push({"text": line1, "class": "resource-quantity"});
  85. list.push({"text": line2, "class": "resource-rate"});
  86. }
  87. return renderLines(list);
  88. }
  89. function displayBuildings() {
  90. for (const [key, value] of Object.entries(belongings)) {
  91. if (!belongings[key].visible) {
  92. if (resources.food * 10 >= costOfBuilding(key)) {
  93. unlockBuilding(key);
  94. } else {
  95. continue;
  96. }
  97. belongings[key].visible = true;
  98. document.querySelector("#building-" + key).classList.remove("hidden");
  99. }
  100. let button = document.querySelector("#building-" + key);
  101. let name = document.querySelector("#building-" + key + " > .building-button-name");
  102. let cost = document.querySelector("#building-" + key + " > .building-button-cost");
  103. name.innerText = value.count + " " + (value.count == 1 ? buildings[key].name : buildings[key].plural);
  104. cost.innerText = render(costOfBuilding(key)) + " food";
  105. if (costOfBuilding(key) > resources.food) {
  106. button.classList.add("building-button-disabled");
  107. cost.classList.add("building-button-cost-invalid");
  108. } else {
  109. button.classList.remove("building-button-disabled");
  110. cost.classList.add("building-button-cost-valid");
  111. }
  112. }
  113. }
  114. function canAfford(cost) {
  115. for (const [resource, amount] of Object.entries(cost)) {
  116. if (resources[resource] < amount) {
  117. return false;
  118. }
  119. }
  120. return true;
  121. }
  122. function spend(cost) {
  123. for (const [resource, amount] of Object.entries(cost)) {
  124. resources[resource] -= amount;
  125. }
  126. }
  127. function displayUpgrades() {
  128. for (let id of remainingUpgrades) {
  129. let button = document.querySelector("#upgrade-" + id);
  130. if (ownedUpgrades[id]) {
  131. button.style.display = "none";
  132. continue;
  133. }
  134. if (upgradeReachable(id)) {
  135. button.classList.remove("hidden");
  136. } else {
  137. button.classList.add("hidden");
  138. }
  139. if (upgradeAvailable(id)) {
  140. button.classList.remove("upgrade-button-inactive");
  141. } else {
  142. button.classList.add("upgrade-button-inactive");
  143. }
  144. }
  145. // now we throw out stuff
  146. for (let i = remainingUpgrades.length-1; i >= 0; i--) {
  147. if (ownedUpgrades[remainingUpgrades[i]]) {
  148. remainingUpgrades.splice(i, 1);
  149. }
  150. }
  151. }
  152. function buyUpgrade(id) {
  153. if (ownedUpgrades[id]) {
  154. return;
  155. }
  156. let upgrade = upgrades[id];
  157. if (!upgradeAvailable(id)) {
  158. return;
  159. }
  160. spend(upgrade.cost);
  161. ownedUpgrades[id] = true;
  162. }
  163. function eatMicro() {
  164. resources.food += productivityMultiplierOf("micro");
  165. }
  166. // setup stuff lol
  167. // we'll initialize the dict of buildings we can own
  168. function setup() {
  169. // create static data
  170. createTemplateUpgrades();
  171. // prepare dynamic stuff
  172. initializeData();
  173. createButtons();
  174. createDisplays();
  175. registerListeners();
  176. load();
  177. unlockAtStart();
  178. }
  179. function unlockAtStart() {
  180. unlockBuilding("micro");
  181. for (const [key, value] of Object.entries(belongings)) {
  182. if (belongings[key].visible) {
  183. unlockBuilding(key);
  184. }
  185. }
  186. }
  187. function unlockBuilding(id) {
  188. belongings[id].visible = true;
  189. document.querySelector("#building-" + id).classList.remove("hidden");
  190. }
  191. function initializeData() {
  192. for (const [key, value] of Object.entries(buildings)) {
  193. belongings[key] = {};
  194. belongings[key].count = 0;
  195. belongings[key].visible = false;
  196. }
  197. for (const [key, value] of Object.entries(resourceTypes)) {
  198. resources[key] = 0;
  199. currentProductivity[key] = 0;
  200. }
  201. for (const [id, upgrade] of Object.entries(upgrades)) {
  202. ownedUpgrades[id] = false;
  203. for (let effect of upgrade.effects) {
  204. if (effects[effect.type] === undefined) {
  205. effects[effect.type] = [];
  206. }
  207. // copy the data and add an entry for the upgrade id that owns the effect
  208. let newEffect = {};
  209. for (const [key, value] of Object.entries(effect)) {
  210. newEffect[key] = value;
  211. }
  212. newEffect.parent = id;
  213. // unfortunate name collision here
  214. // I'm using apply() to pass on any number of arguments to the
  215. // apply() function of the effect type
  216. newEffect.apply = function(...args) { return effect_types[effect.type].apply.apply(null, [effect].concat(args)); }
  217. effects[effect.type].push(newEffect);
  218. }
  219. }
  220. }
  221. function registerListeners() {
  222. document.querySelector("#tasty-micro").addEventListener("click", eatMicro);
  223. document.querySelector("#save").addEventListener("click", save);
  224. document.querySelector("#reset").addEventListener("click", reset);
  225. }
  226. function createButtons() {
  227. createBuildings();
  228. createUpgrades();
  229. }
  230. function createBuildings() {
  231. let container = document.querySelector("#buildings-area");
  232. for (const [key, value] of Object.entries(buildings)) {
  233. let button = document.createElement("div");
  234. button.classList.add("building-button");
  235. button.classList.add("hidden");
  236. button.id = "building-" + key;
  237. let buttonName = document.createElement("div");
  238. buttonName.classList.add("building-button-name");
  239. let buttonCost = document.createElement("div");
  240. buttonCost.classList.add("building-button-cost");
  241. button.appendChild(buttonName);
  242. button.appendChild(buttonCost);
  243. button.addEventListener("mousemove", function(e) { buildingTooltip(key, e); });
  244. button.addEventListener("mouseleave", function() { buildingTooltipRemove(); });
  245. button.addEventListener("click", function() { buyBuilding(key); });
  246. button.addEventListener("click", function(e) { buildingTooltip(key, e); });
  247. container.appendChild(button);
  248. }
  249. }
  250. // do we have previous techs and at least one of each building?
  251. function upgradeReachable(id) {
  252. if (ownedUpgrades[id]) {
  253. return false;
  254. }
  255. if (upgrades[id].prereqs !== undefined ){
  256. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  257. if (type == "buildings") {
  258. for (const [building, amount] of Object.entries(reqs)) {
  259. if (belongings[building].count == 0) {
  260. return false;
  261. }
  262. }
  263. }
  264. else if (type == "upgrades") {
  265. for (let upgrade of reqs) {
  266. if (!ownedUpgrades[upgrade]) {
  267. return false;
  268. }
  269. }
  270. }
  271. }
  272. }
  273. return true;
  274. }
  275. function upgradeAvailable(id) {
  276. if (!upgradeReachable(id)) {
  277. return false;
  278. }
  279. if (!canAfford(upgrades[id].cost)) {
  280. return false;
  281. }
  282. if (upgrades[id].prereqs !== undefined) {
  283. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  284. if (type == "buildings") {
  285. for (const [building, amount] of Object.entries(upgrades[id].prereqs[type])) {
  286. if (belongings[building].count < amount) {
  287. return false;
  288. }
  289. }
  290. } else if (type == "productivity") {
  291. for (const [key, value] of Object.entries(reqs)) {
  292. if (currentProductivity[key] < value) {
  293. return false;
  294. }
  295. }
  296. }
  297. }
  298. }
  299. return true;
  300. }
  301. function createUpgrades() {
  302. let container = document.querySelector("#upgrades-list");
  303. for (const [key, value] of Object.entries(upgrades)) {
  304. remainingUpgrades.push(key);
  305. let button = document.createElement("div");
  306. button.classList.add("upgrade-button");
  307. button.classList.add("hidden");
  308. button.id = "upgrade-" + key;
  309. let buttonName = document.createElement("div");
  310. buttonName.classList.add("upgrade-button-name");
  311. buttonName.innerText = value.name;
  312. button.appendChild(buttonName);
  313. button.addEventListener("mousemove", function(e) { upgradeTooltip(key, e); });
  314. button.addEventListener("mouseleave", function() { upgradeTooltipRemove(); });
  315. button.addEventListener("click", function() { buyUpgrade(key); });
  316. container.appendChild(button);
  317. }
  318. }
  319. function createDisplays() {
  320. // nop
  321. }
  322. function renderLine(line) {
  323. let div = document.createElement("div");
  324. div.innerText = line.text;
  325. if (line.valid !== undefined) {
  326. if (line.valid) {
  327. div.classList.add("cost-met");
  328. } else {
  329. div.classList.add("cost-unmet");
  330. }
  331. }
  332. if (line.class !== undefined) {
  333. for (let entry of line.class.split(",")) {
  334. div.classList.add(entry);
  335. }
  336. }
  337. return div;
  338. }
  339. function renderLines(lines) {
  340. let divs = [];
  341. for (let line of lines) {
  342. divs.push(renderLine(line));
  343. }
  344. return divs;
  345. }
  346. function renderCost(cost) {
  347. let list = [];
  348. list.push({
  349. "text": "Cost:"
  350. });
  351. for (const [key, value] of Object.entries(cost)) {
  352. list.push({
  353. "text": render(value,0) + " " + resourceTypes[key].name,
  354. "valid": resources[key] >= value
  355. });
  356. }
  357. return renderLines(list);
  358. }
  359. function renderPrereqs(prereqs) {
  360. let list = [];
  361. if (prereqs === undefined) {
  362. return renderLines(list);
  363. }
  364. list.push({
  365. "text": "Own:"
  366. });
  367. for (const [key, value] of Object.entries(prereqs)) {
  368. if (key == "buildings") {
  369. for (const [id, amount] of Object.entries(prereqs.buildings)) {
  370. list.push({
  371. "text": buildings[id].name + " x" + render(amount,0),
  372. "valid": belongings[id].count >= amount
  373. });
  374. }
  375. } else if (key == "productivity") {
  376. for (const [id, amount] of Object.entries(prereqs.productivity)) {
  377. list.push({
  378. "text": render(amount,0) + " " + resourceTypes[id].name + "/s",
  379. "valid": currentProductivity[id] >= amount
  380. });
  381. }
  382. }
  383. }
  384. return renderLines(list);
  385. }
  386. function renderEffects(effectList) {
  387. let list = [];
  388. for (let effect of effectList) {
  389. list.push({"text": effect_types[effect.type].desc(effect)});
  390. }
  391. return renderLines(list);
  392. }
  393. function fillTooltip(type, field, content) {
  394. let item = document.querySelector("#" + type + "-tooltip-" + field);
  395. if (typeof(content) === "string") {
  396. item.innerText = content;
  397. } else {
  398. replaceChildren(item, content);
  399. }
  400. }
  401. function upgradeTooltip(id, event) {
  402. let tooltip = document.querySelector("#upgrade-tooltip");
  403. tooltip.style.setProperty("display", "inline-block");
  404. fillTooltip("upgrade", "name", upgrades[id].name);
  405. fillTooltip("upgrade", "desc", upgrades[id].desc);
  406. fillTooltip("upgrade", "effect", renderEffects(upgrades[id].effects));
  407. fillTooltip("upgrade", "cost", renderCost(upgrades[id].cost));
  408. fillTooltip("upgrade", "prereqs", renderPrereqs(upgrades[id].prereqs));
  409. let yOffset = tooltip.parentElement.getBoundingClientRect().y;
  410. let yTrans = Math.round(event.clientY - yOffset);
  411. tooltip.style.setProperty("transform", "translate(-220px, " + yTrans + "px)");
  412. }
  413. function upgradeTooltipRemove() {
  414. let tooltip = document.querySelector("#upgrade-tooltip");
  415. tooltip.style.setProperty("display", "none");
  416. }
  417. function prodSummary(id) {
  418. let list = [];
  419. list.push(
  420. {"text": "Each " + buildings[id].name + " produces " + round(productivityMultiplierOf(id) * buildings[id].prod,1) + " food/sec"}
  421. );
  422. list.push(
  423. {"text": "Your " + render(belongings[id].count) + " " + (belongings[id].count == 1 ? buildings[id].name + " is": buildings[id].plural + " are") + " producing " + round(productivityOf(id),1) + " food/sec"}
  424. );
  425. let percentage = round(100 * productivityOf(id) / currentProductivity["food"], 2);
  426. if (isNaN(percentage)) {
  427. percentage = 0;
  428. }
  429. list.push(
  430. {"text": "(" + percentage + "% of all food)"}
  431. );
  432. return renderLines(list);
  433. }
  434. function buildingTooltip(id, event) {
  435. let tooltip = document.querySelector("#building-tooltip");
  436. tooltip.style.setProperty("display", "inline-block");
  437. fillTooltip("building", "name", buildings[id].name);
  438. fillTooltip("building", "desc", buildings[id].desc);
  439. fillTooltip("building", "cost", render(costOfBuilding(id)) + " food");
  440. fillTooltip("building", "prod", prodSummary(id));
  441. let yOffset = tooltip.parentElement.getBoundingClientRect().y;
  442. let xPos = tooltip.parentElement.getBoundingClientRect().x - 450;
  443. let yPos = event.clientY;
  444. tooltip.style.setProperty("transform", "translate(" + xPos + "px, " + yPos + "px)")
  445. }
  446. function buildingTooltipRemove() {
  447. let tooltip = document.querySelector("#building-tooltip");
  448. tooltip.style.setProperty("display", "none");
  449. }
  450. window.onload = function() {
  451. setup();
  452. lastTime = performance.now();
  453. setTimeout(updateDisplay, 1000/updateRate);
  454. }
  455. function save() {
  456. let storage = window.localStorage;
  457. storage.setItem("save-version", "0.0.1");
  458. storage.setItem("ownedUpgrades", JSON.stringify(ownedUpgrades));
  459. storage.setItem("resources", JSON.stringify(resources));
  460. storage.setItem("belongings", JSON.stringify(belongings));
  461. }
  462. function load() {
  463. let storage = window.localStorage;
  464. if (!storage.getItem("save-version")) {
  465. return;
  466. }
  467. let newOwnedUpgrades = JSON.parse(storage.getItem("ownedUpgrades"));
  468. for (const [key, value] of Object.entries(newOwnedUpgrades)) {
  469. ownedUpgrades[key] = value;
  470. }
  471. let newResources = JSON.parse(storage.getItem("resources"));
  472. for (const [key, value] of Object.entries(newResources)) {
  473. resources[key] = value;
  474. }
  475. let newBelongings = JSON.parse(storage.getItem("belongings"));
  476. for (const [key, value] of Object.entries(newBelongings)) {
  477. belongings[key] = value;
  478. }
  479. }
  480. function reset() {
  481. window.localStorage.clear();
  482. }