Skip to content

Calculator class

elliotks edited this page Feb 28, 2024 · 1 revision

The Calculator class is a strategic tool designed for Cookie Clicker, aimed at optimizing in-game decisions related to purchases of upgrades and buildings to maximize cookies per second (CPS). It utilizes a comprehensive schema to evaluate the cost-effectiveness and impact of potential acquisitions, factoring in the game's dynamic state and special cases through methods like cps_acc, ecps, and calc_bonus. By simulating purchases and calculating their efficiency, the class provides targeted recommendations with the find_best method, ensuring auto-buy will choose the best upgrade or building to buy.

Here is the documented Calculator class for a better understanding.

// Purpose: Defines the Calculator class. This class is responsible for calculating optimal game decisions,
// such as when to purchase upgrades or buildings based on their cost-effectiveness and impact on cookie production.
class Calculator {
  // Purpose: Initializes a new instance of the Calculator class. This is where the class's properties are initially set up.
  constructor() {
    // Purpose: Holds a schema that defines different types of game objects (like upgrades and buildings) and how to interact with them.
    // Each object in this array contains methods for adding or removing the object, calculating its price, and determining its lasting effect if any.
    // objects: A function that returns an array of game objects to be considered by the calculator.
    this.schema = [
      {
        // objects: A function that returns an array of game objects to be considered by the calculator.
        objects: function () {
          // Purpose: Filters the available upgrades in the store to include only those relevant for optimization calculations.
          // Filter Logic:
          // Excludes upgrades by ID if they are in an empty array (placeholder for potential exclusions).
          // Ignores upgrades from the "prestige" and "toggle" pools, since they are irrelevant for auto-buying.
          // Excludes upgrades already in the game's vault or the ACABM's upgrade vault to avoid recommending already considered or intentionally avoided upgrades.
          return Game.UpgradesInStore.filter(function (e) {
            return (
              [].indexOf(e.id) < 0 &&
              e.pool != "prestige" &&
              e.pool != "toggle" &&
              !Game.vault.includes(e.id) &&
              !ACABM.settings.upgradevault.includes(e.id)
            );
          });
        },
        // accessors: An object containing methods to manipulate or query the game objects:
        // add: A method to simulate purchasing or applying the object.
        // sub: A method to simulate selling or removing the object.
        // price: A method to return the object's cost.
        // lasting: A method to return the object's lasting effect duration.
        accessors: {
          add: function (e) {
            e.bought = 1; // Simulates purchasing the upgrade. Upgrades are considered "bought" when their bought property is set to 1.
          },
          sub: function (e) {
            e.bought = 0; // Simulates removing the upgrade. Upgrades are considered available for purchase when their bought property is set to 0.
          },
          price: function (e) {
            return e.basePrice; // Returns the upgrade's base price.
          },
          lasting: function (e) {
            return e.lasting; // Returns the upgrade's lasting effect duration.
          },
        },
      },
      {
        // objects: A function that returns an array of game objects to be considered by the calculator.
        objects: function () {
          // Purpose: Filters the game's buildings to exclude any that the user or ACABM settings have deemed irrelevant or unwanted for the current optimization strategy.
          // Filter Logic:
          // Excludes buildings by ID if they are in an empty array (placeholder for potential exclusions).
          // Excludes buildings in ACABM's buildings vault to avoid recommending
          return Game.ObjectsById.filter(function (e) {
            return (
              [].indexOf(e.id) < 0 &&
              !ACABM.settings.buildingvault.includes(e.id)
            );
          });
        },
        // accessors: An object containing methods to manipulate or query the game objects:
        // add: A method to simulate purchasing or applying the object.
        // sub: A method to simulate selling or removing the object.
        // price: A method to return the object's cost.
        // lasting: irrelevant for buildings, but required for consistency of `this.schema`.
        accessors: {
          add: function (e) {
            e.amount++; // Simulates purchasing the building. The building's amount is incremented to reflect the purchase.
          },
          sub: function (e) {
            e.amount--; // Simulates selling the building. The building's amount is decremented to reflect the sale.
          },
          price: function (e) {
            return e.price; // Returns the building's price.
          },
          lasting: function (e) {
            return e.lasting; // Unused for buildings, but required for consistency of `this.schema`.
          },
        },
      },
    ];
  }

  // Purpose: Calculates the "cps acceleration" or efficiency of purchasing an object based on the increase in cookies per second (CPS) it provides versus its cost.
  // Parameters:
  // base_cps: The baseline CPS before the purchase.
  // new_cps: The CPS after the purchase.
  // price: The cost of the object.
  // Returns: A numerical value representing the efficiency of the purchase.
  cps_acc(base_cps, new_cps, price) {
    return (base_cps * base_cps * (new_cps - base_cps)) / (price * price); // The formula for calculating the efficiency of a purchase.
  }

  // Purpose: Calculates the effective cookies per second (eCPS), considering factors like how much of your CPS is being 'sucked' by Wrinklers (a game mechanic).
  // Returns: The effective CPS after adjustments.
  ecps() {
    return Game.cookiesPs * (1 - Game.cpsSucked); // The effective CPS is the base CPS multiplied by the fraction of CPS not being 'sucked' by Wrinklers.
  }

  // Overview: Calculates the bonus or efficiency of acquiring specific items (upgrades/buildings), considering their impact on the effective CPS (ecps) and the cost.

  // Temporarily Overrides:
  // Game.Win: This method is overridden to prevent the game from triggering win conditions during calculations.
  // Game.CalculateGains: Temporarily modified to prevent changes to Game.cookiesPsRawHighest, ensuring calculations don't artificially inflate future CPS estimations.

  // Calculation Process:
  // Each potential purchase is temporarily enacted within the game's simulation to assess its impact on CPS, using the add and sub accessors to simulate the purchase and removal of each item.
  // The method evaluates the cost-effectiveness of each option based on a custom efficiency metric (cps_acc), which considers the cost, the base CPS, and the new CPS after the purchase.
  // Special cases, like garden upgrades (using lasting) or dragon upgrades (with a hardcoded price of 999), are accounted for with specific logic to ensure accurate valuation.

  // After temporarily overriding Game.Win and Game.CalculateGains, the calc_bonus method performs the following steps:
  // Iterate Over Potential Purchases: It iterates over a list of items (either upgrades or buildings) generated by the list_generator function,
  // applying a custom function to each item to evaluate its potential impact.

  // Price Adjustments for Special Cases:

  // Garden Upgrades: The price is adjusted based on a multiplier of the game's current CPS if the upgrade has a lasting effect. This adjustment accounts for upgrades that have benefits
  // extending over time rather than immediate CPS increases.

  // Dragon Upgrades: For upgrades with a hardcoded price of 999, a specific calculation is used, factoring in the game's unbuffed CPS and whether the player is close to maxing out
  // their dragon's levels. This ensures that the calculator values these unique upgrades accurately.

  // Simulate Purchase: Each item is "purchased" by calling its add method, and then Game.CalculateGains is called to update the game's CPS based on this simulated purchase.

  // Calculate Efficiency: The method calculates the item's efficiency using cps_acc, factoring in the base CPS, the new CPS after the simulated purchase, and the item's price.
  // This efficiency metric helps identify how beneficial the purchase would be.

  // Restore Game State: After evaluating an item, it is "sold" (simulation reversed) by calling its sub method, and the game state is restored to its original condition before moving on to the next item.

  // Return Results: The method returns an array of objects, each representing a potential purchase and including the item itself, its price, and its calculated efficiency.
  calc_bonus(item, list_generator, mouse_rate) {
    // Temporarily override Game.Win
    var funcGW = Game.Win;
    Game.Win = function () {}; // Temporarily replace with no-op function

    // Temporarily override Game.CalculateGains
    // Clone the necessary game state
    var originalState = {
      CalculateGains: Game.CalculateGains,
      cookiesPsRawHighest: Game.cookiesPsRawHighest,
      // Clone other necessary variables or states
    };

    // Temporarily override Game.CalculateGains with a version that does not modify cookiesPsRawHighest
    Game.CalculateGains = function () {
      var originalRawHighest = Game.cookiesPsRawHighest; // Store cookiesPsRawHighest before the call
      originalState.CalculateGains.call(Game); // Call the original CalculateGains
      Game.cookiesPsRawHighest = originalRawHighest; // Restore cookiesPsRawHighest after the call
    };

    var res = list_generator().map(
      function (e) {
        var lasting = this.item.lasting(e); // Garden Upgrade Calc
        var price = Math.round(this.item.price(e)); // Price of the item
        // -- Garden Upgrade Calc -- currently the only upgrades using lasting.
        if (lasting) {
          price = Math.round(price * Game.cookiesPs * 60); // Garden Upgrade Price Adjustment
        }
        // -- Dragon Upgrade Calc -- currently the only upgrades with price 999.
        if (price == 999) {
          price =
            Game.unbuffedCps *
            60 *
            30 *
            (Game.dragonLevel < Game.dragonLevels.length - 1 ? 1 : 0.1); // Dragon Upgrade Price Adjustment
        }

        this.item.add(e); // Simulate purchase
        Game.CalculateGains(); // Calculate the game's CPS based on the simulated purchase
        var cps = this.calc.ecps() + Game.computedMouseCps * this.rate; // Calculate the effective CPS after the purchase
        this.item.sub(e); // Remove the item from the simulation
        Game.CalculateGains(); // Restore the game's CPS to its original state
        return {
          obj: e, // The item itself
          price: price, // The price of the item
          acc: this.calc.cps_acc(this.base_cps, cps, price), // Calculate the efficiency of the purchase
        };
      }.bind({
        item: item, // The item being evaluated
        calc: this, // The calculator instance
        rate: mouse_rate, // The mouse rate
        base_cps:
          (Game.cookiesPs ? this.ecps() : 0.001) +
          Game.computedMouseCps * mouse_rate, // The base CPS
      })
    );

    Game.Win = funcGW; // Restore Game.Win
    Game.CalculateGains = originalState.CalculateGains; // Restore Game.CalculateGains
    Game.cookiesPsRawHighest = originalState.cookiesPsRawHighest; // Restore cookiesPsRawHighest

    return res;
  }

  // Purpose: To find the most cost-effective item (either an upgrade or a building) to purchase next.

  // Process:
  // Aggregate Potential Purchases: Combines the results of calc_bonus for both upgrades and buildings into a single pool of options.

  // zero_buy: A threshold calculated based on the square root of the product of cookies earned and the current CPS. This serves as a heuristic for determining when it
  // might not be worthwhile to invest in items with no immediate CPS gain (acc == 0).

  // Select Best Option: Iterates through the pool of options to find the one with the highest efficiency (acc), but also considers the zero_buy threshold to avoid recommending
  // purchases that wouldn't significantly impact the player's progress.

  // The find_best method synthesizes the detailed calculations and simulations performed by calc_bonus to provide a clear recommendation on what auto-buy should purchase next
  // for optimal game progression.
  find_best(mouse_rate) {
    var pool = [];
    var zero_buy = Math.sqrt(Game.cookiesEarned * Game.cookiesPs); // Zero Buy Threshold

    for (var i = 0; i < this.schema.length; i++)
      pool = pool.concat(
        this.calc_bonus(
          this.schema[i].accessors,
          this.schema[i].objects,
          mouse_rate || 0
        ) // Aggregate Potential Purchases
      );

    return pool.reduce(function (m, v) {
      return m.acc == 0 && m.price < zero_buy
        ? m
        : v.acc == 0 && v.price < zero_buy
        ? v
        : m.acc < v.acc
        ? v
        : m;
    }, pool[0]); // Select Best Option
  }
}
Clone this wiki locally