Ex­po­nen­tial Idle Guides

Day 2: A The­ory of Your Own

Guide writ­ten by prop. Con­tri­bu­tions from the Amaz­ing Com­munity.

Feel free to use the gloss­ary as needed.

Good morn­ing. It is dawn of the second day.

Today, we’re go­ing to cre­ate a simple the­ory with a couple of up­grades, and un­til the end of this week, we will be flesh­ing it out, bit by bit. By the end, hope­fully it will be one to be proud of.

Let’s start by cre­at­ing a folder in the SDK dir­ect­ory. Any name would do. In­side the folder, cre­ate a new file named my_­the­ory.js. This shall be your first the­ory.

Note: Within the com­munity, the term ‘vari­able’ is used for vari­ables in an equa­tion. However, in this guide, they will be called ‘up­grades’, as not only are they ca­non­ic­ally called this way in the API, the term ‘vari­able’ is used to refer to vari­ables in pro­gram­ming. When re­fer­ring to an up­grade’s value, the word ‘term’ or ‘factor’ shall be used, de­pend­ing on the equa­tion.

Note: I will be us­ing el­lipses (…) to trun­cate any code that was already pre­vi­ously writ­ten. Do not blindly copy these!

I know. I wish I could say ‘vari­ables’ all the time too.

The first ritual #

Let’s start with the ne­ces­sit­ies. Im­port the fol­low­ing mod­ules:

import { BigNumber } from '../api/BigNumber';
import { ExponentialCost, FreeCost } from '../api/Costs';
import { theory } from '../api/Theory';
import { Utils } from '../api/Utils';

Next, is the ba­sic in­form­a­tion for this the­ory. Aside from id, you are free to re­name everything else. If you re­name the ID and send to the game, it will show up as a com­pletely new the­ory.

var id = 'my_theory';
var name = 'My Theory';
var description = 'The one and only.';
var authors = 'Stuart Clickus';

The first up­grade #

Now, let’s de­clare a few ba­sic things: a cur­rency, and your first up­grade, a clicker! You want the clicker to give you ex­actly 1 unit of cur­rency whenever you level it up.

First, let’s cre­ate a cur­rency with the­ory.cre­ate­Cur­rency. No ar­gu­ments are provided, so the de­fault sym­bol of rho (ρ) is used.

let currency;

let init = () =>
{
    currency = theory.createCurrency();
}

Not that dif­fi­cult, right? Let’s next de­clare the clicker, us­ing the­ory.cre­ateUp­grade. This func­tion takes 3 ar­gu­ments:

let currency;
let clicker;

let init = () =>
{
    currency = theory.createCurrency();

    {
        clicker = theory.createUpgrade(0, currency, new FreeCost);
        clicker.description = Utils.getMath('\\rho \\leftarrow \\rho + 1');
        clicker.info = 'Increase currency by 1.';
    }
}

By the way, al­though right now we don’t need any vari­ables in­side an en­closed scope that would war­rant an ex­tra code block, let’s make it a habit, in case that’s needed in the fu­ture.

For its de­scrip­tion and info, since we don’t need to up­date them, we will as­sign a string to two prop­er­ties of the clicker: de­scrip­tion and info. Their ‘dy­nam­ic’ coun­ter­parts are get­De­scrip­tion and get­Info re­spect­ively, which can only be as­signed to ar­row func­tions.

In the de­scrip­tion, we’d want to make it look ‘math­sy’, so we are go­ing to use the ar­row syn­tax for as­sign­ment: ρρ+1. For the LaTeX syn­tax, we use Utils.get­Math to wrap a nor­mal string in a pair of \( and \) for in­line LaTeX dis­play. You can use these mark­ers nor­mally without this func­tion as well.

Pro­gram­mat­ic­ally though, it would look something more like this: cur­rency.value += 1. In fact, this is what we would like to trig­ger every time we buy the clicker. And for that, we can use the up­grade’s bought trig­ger:

clicker.bought = (amount) => currency.value += 1;

Al­though the cur­rency’s value is a BigNum­ber, keep in mind that BigNum­bers can also inter-op­er­ate with (nor­mal) num­bers: ad­di­tion, sub­trac­tion, mul­ti­plic­a­tion, di­vi­sion, etc. In all cases, the res­ult is al­ways go­ing to be a BigNum­ber, ex­cept for when a NaN could be triggered.

let currency;
let clicker;

let init = () =>
{
    currency = theory.createCurrency();

    {
        clicker = theory.createUpgrade(0, currency, new FreeCost);
        clicker.description = Utils.getMath('\\rho \\leftarrow \\rho + 1');
        clicker.info = 'Increase currency by 1.';
        clicker.bought = (amount) => currency.value += 1;
    }
}

Hav­ing a but­ton is nearly enough, ex­cept that you’d also want to tell the play­ers to click it, and in­tro­duce some visual feed­back when they do. So let’s define a primary equa­tion, and feed some graph co­ordin­ates to the game:

var getPrimaryEquation = () => `\\text{Click the clicker!}`;

var get2DGraphValue = () => currency.value.sign *
(BigNumber.ONE + currency.value.abs()).log10().toNumber();

As the primary equa­tion auto­mat­ic­ally as­sumes the LaTeX maths mode, any nor­mal text must be wrapped around a \\text{} com­mand.

The graph for now will be dis­play­ing the cur­rency’s value on a log­ar­ithmic scale. An ab­so­lute value is used, since you can’t have log­ar­ithm of a neg­at­ive num­ber. It is then ad­ded to 1 be­fore log­ging, since the log­ar­ithm of 0 is minus in­fin­ity, and the log­ar­ithm of any­thing between 0 and 1 is neg­at­ive, thus, we don’t want the graph to jump around or be­come un­defined when the cur­rency value is between -1 and 1. Fi­nally, as the 2D graph co­ordin­ates use num­bers, we con­vert the BigNum­ber to a num­ber us­ing toNum­ber.

Fi­nally, don’t for­get to call init at the end of the file:

init();

Now, send it to the SDK! You can see that whenever you buy the clicker, the cur­rency in­creases, and the graph also goes up!

The second up­grade #

That was great! Click­ing that but­ton a hun­dred times surely lif­ted my spirit! But would I like to do it forever? One of the good feel­ings someone can get from an idle game is when they can fi­nally stop do­ing something they pre­vi­ously had to do be­grudgingly. So let’s define a new up­grade c1 (c1) that will out­class this clicker, and set its iden­ti­fier to 1:

let c1;

let init = () =>
{
    ...
    {
        c1 = theory.createUpgrade(1, currency, new ExponentialCost(10, 1));
    }
}

This time, we’ll be us­ing an ex­po­nen­tially scal­ing cost. The ar­gu­ments for an Ex­po­nen­tial­Cost con­structor are as fol­lows:

We are also go­ing to define the getc1 func­tion to get c1’s power, by us­ing get­Step­wisePower­Sum:

let getc1 = (level) => Utils.getStepwisePowerSum(level, 2, 5, 0);

A step­wise power sum is a power sum that has been stretched so that each term ap­pears mul­tiple times. With ref­er­ence to the power sum of base 2:

1+2+22+23+24+25+26+...,

a step­wise power sum of base 2 and length 3 re­peats each power 3 times:

1+1+1+2+2+2+22+22+22+...

Each up­grade level simply adds a term to the sum. Pro­gram­mat­ic­ally, ob­tain­ing the value of a step­wise power sum re­quires four ar­gu­ments:

All step­wise up­grades in the base game are of base 2 and length 10, but cus­tom the­ory de­velopers of­ten try to ex­per­i­ment with them. This time, we’re us­ing base 2 and length 5, which scales twice as fast as base 2 and length 10.

Now, let’s as­sign the de­scrip­tion and info to c1. As c1’s value changes every time the up­grade is bought, we will be us­ing get­De­scrip­tion and get­Info so these texts will peri­od­ic­ally up­date, around 10 times per second.

let init = () =>
{
    ...
    {
        c1 = theory.createUpgrade(1, currency, new ExponentialCost(10, 1));
        let getDesc = (level) => `c_1 = ${getc1(level).toString(0)}`;
        c1.getDescription = (amount) => Utils.getMath(getDesc(c1.level));
        c1.getInfo = (amount) => Utils.getMathTo(getDesc(c1.level),
        getDesc(c1.level + amount));
    }
}

Note: An ar­row func­tion like this, if they’re as­signed to an ob­ject’s prop­erty, of­ten means to ‘up­date 10 times per second’. Glob­ally defined func­tions such as init, nor­mally don’t get called 10 times per second (al­though there are ex­cep­tions, such as tick).

For the de­scrip­tion, we want to show something like c1=x, and for the info, we want to show the change in power when we level up the up­grade as c1=xc1=y. Since there is a com­mon part in these two texts, c1=..., we will be cre­at­ing a re­useable func­tion get­Desc to shorten the code.

This func­tion here re­turns a spe­cial type of string: a tem­plate lit­eral, which are wrapped by back­ticks (`). Tem­plate lit­er­als have two spe­cial powers: multi-line strings, and format­ting. By us­ing the syn­tax ${} in­side a tem­plate lit­eral, and put an ex­pres­sion in­side the brack­ets, we can format the string based on the ex­pres­sion’s res­ult. In this case, the string will take whatever getc1(level).to­String(0) (string rep­res­ent­a­tion of c1 with no decim­als) eval­u­ates to. If the value of c1 is 9, the re­turned string will be ‘c_1 = 9’, for in­stance.

In the as­sign­ment of get­Info, we also see a new util­ity be­ing used: Utils.get­MathTo, which is a doubled ver­sion of Utils.get­Math, with a right-fa­cing ar­row in the middle. We’ll be us­ing it to gen­er­ate our c1=xc1=y.

Fi­nally, let’s im­ple­ment c1’s ef­fect so that we won’t be wast­ing money on it. We will be us­ing tick to in­cre­ment the cur­rency ac­cord­ing to the c1 term:

var tick = (elapsedTime, multiplier) =>
{
    let dt = BigNumber.from(elapsedTime * multiplier);
    currency.value += dt * getc1(c1.level);
}

Let’s change the primary equa­tion to re­flect this change:

var getPrimaryEquation = () => `\\dot{\\rho} = c_1`;

The dot nota­tion was defined by Isaac New­ton as a de­riv­at­ive of a vari­able with re­spect to time (t). In-game, we can simply treat c1 as the cur­rency’s ‘ve­lo­city’.

Now, load it through the SDK! You can now see the new c1 up­grade avail­able for pur­chase, and that it can af­fect the cur­rency.

Af­ter­math #

This the­ory is still go­ing pretty slowly. I bet you’re temp­ted to hack in some more cur­rency. I shall see you to­mor­row, where we will be im­ple­ment­ing even more up­grades for the the­ory!

Mean­while, the source code after today’s work can be found here:

import { BigNumber } from '../api/BigNumber';
import { ExponentialCost, FreeCost } from '../api/Costs';
import { theory } from '../api/Theory';
import { Utils } from '../api/Utils';

var id = 'my_theory';
var name = 'My Theory';
var description = 'The one and only.';
var authors = 'Stuart Clickus';

let currency;
let clicker;
let c1;

let init = () =>
{
    currency = theory.createCurrency();

    {
        clicker = theory.createUpgrade(0, currency, new FreeCost);
        clicker.description = Utils.getMath('\\rho \\leftarrow \\rho + 1');
        clicker.info = 'Increases currency by 1';
        clicker.bought = (amount) => currency.value += 1;
    }

    {
        c1 = theory.createUpgrade(1, currency, new ExponentialCost(10, 1));
        let getDesc = (level) => `c_1 = ${getc1(level).toString(0)}`;
        c1.getDescription = (amount) => Utils.getMath(getDesc(c1.level));
        c1.getInfo = (amount) => Utils.getMathTo(getDesc(c1.level),
        getDesc(c1.level + amount));
    }
}

let getc1 = (level) => Utils.getStepwisePowerSum(level, 2, 5, 0);

var tick = (elapsedTime, multiplier) =>
{
    let dt = BigNumber.from(elapsedTime * multiplier);
    currency.value += dt * getc1(c1.level);
}

var getPrimaryEquation = () => `\\dot{\\rho} = c_1`;

var get2DGraphValue = () => currency.value.sign *
(BigNumber.ONE + currency.value.abs()).log10().toNumber();

init();