Ex­po­nen­tial Idle Guides

Day 3: Fibon­acci Friend

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

Feel free to use the gloss­ary as needed.

Buon­giorno a tutti. It is dawn of the third day.

Across your win­dow, a man wear­ing a blue head scarf is wav­ing at you. You may re­cog­nise him from your maths text­books - his name is Le­onardo Bon­acci (maybe), also known as Fibon­acci! Today, we are go­ing to make a new up­grade based on his fam­ous Fibon­acci se­quence. But first, we will im­ple­ment pub­lic­a­tions for our the­ory.

Cau­tion: Be­fore head­ing into today’s con­tents, it is ad­vised to back up your save files some­where.

Pub­lic­a­tions #

Let’s think about our the­ory’s pro­gres­sion.

We have two up­grades, with their in­di­vidual per­son­al­it­ies. While this alone might make for some in­ter­est­ing con­ver­sa­tions within your player base, without some sort of re­set mech­anic - a staple of many idle games - you’ll be stuck tap­ping the same but­tons forever. And in Ex­po­nen­tial Idle, the re­set mech­anic avail­able to the­or­ies is called Pub­lic­a­tions.

Let’s in­tro­duce pub­lic­a­tions to our the­ory, by de­fin­ing sev­eral key func­tions:

const pubPower = 0.1;

var getPublicationMultiplier = (tau) => tau.pow(pubPower);

var getPublicationMultiplierFormula = (symbol) => `{${symbol}}^{${pubPower}}`;

var getTau = () => currency.value;

var getCurrencyFromTau = (tau) =>
[
    tau.max(BigNumber.ONE),
    currency.symbol
];

For this the­ory, the tau value shall simply be the max­imum cur­rency reached, with 1-to-1 con­ver­sion. Then, we shall define pub­lic­a­tion power as 0.1 (10%). This will be im­port­ant later, when we try to bal­ance the the­ory. For now, let’s simply un­der­stand that with 1-to-1 tau con­ver­sion, a pub­lic­a­tion power of 0.1 means that pub­lic­a­tions make up around 10% of rho’s grow­ing power, log­ar­ith­mic­ally speak­ing, while the up­grades make up the rest.

To en­able pub­lic­a­tions, let’s im­ple­ment our pub­lic­a­tion up­grade. It is a per­man­ent up­grade, so their iden­ti­fier set is dif­fer­ent from reg­u­lar up­grades, which means we can start from 0 again:

let init = () =>
{
    ...
    theory.createPublicationUpgrade(0, currency, BigNumber.from('1e7'));
}

Fi­nally, modify the tick func­tion to in­clude the pub­lic­a­tion bo­nus, and add a sec­ond­ary equa­tion for it:

var tick = (elapsedTime, multiplier) =>
{
    let dt = BigNumber.from(elapsedTime * multiplier);
    let bonus = theory.publicationMultiplier;
    currency.value += dt * bonus * getc1(c1.level) * getc2(c2.level) * (BigNumber.ONE + getf(f.level));
}

var getSecondaryEquation = () => `${theory.latexSymbol} = \\max\\rho`;

Done. Now we can pub­lish our the­ory and gain quicker pro­gress on the next run! Al­though… we don’t seem to be able to reach it eas­ily…

A gift from Fibon­acci #

The scarved man throws you a marble across the win­dow. You catch it swiftly. He throws an­other. And then two at the same time, and then three, then five… You real­ise what he wants from you, and so you sprint back to your desk and start im­ple­ment­ing his idea - the Fibon­acci se­quence.

Let’s define this up­grade as f (f), and set its iden­ti­fier to 2:

let f;

let init = () =>
{
    ...
    {
        f = theory.createUpgrade(2, currency, new ExponentialCost(200, 1.618034));
        let getDesc = (level) => `f = ${getf(level).toString(0)}`;
        f.getDescription = (amount) => Utils.getMath(getDesc(f.level));
        f.getInfo = (amount) => Utils.getMathTo(getDesc(f.level),
        getDesc(f.level + amount));
    }
}

Let’s also define the value of f for each level, which should be the n-th Fibon­acci num­ber. Re­mem­ber, the Fibon­acci num­bers are a type of re­cur­rence re­la­tion, in which a term is defined as the sum of pre­vi­ous terms:

let getf = (level) =>
{
    if(level == 0)
        return BigNumber.ZERO;
    if(level == 1)
        return BigNumber.ONE;

    return getf(level - 1) + getf(level - 2);
};

Fi­nally, change the tick func­tion and the primary equa­tion to im­ple­ment our new up­grade:

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

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

Re­mem­ber, mul­ti­plic­a­tions! #

Oops. It seems like we’ve caused all pro­gress to halt. Why is this the case?

Our cur­rency (rho) growth equa­tion is defined by c1×f. But it seems like since f equals zero on the 0th level, rho’s growth al­ways ends up be­ing zero. There are many ways to fix this, but we shall go with the chees­iest one: adding one. Let’s modify our rho equa­tion:

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

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

Hur­ray! It works again.

A gift in re­turn #

Even with the new Fibon­acci up­grade, the the­ory still grows very slowly. Let’s give it and c1 an­other friend, c2 (c2), with an iden­ti­fier of 3, and grows ac­cord­ing to the powers of 2:

let c2;

let init = () =>
{
    ...
    {
        c2 = theory.createUpgrade(3, currency, new ExponentialCost(500, 3));
        let getDesc = (level) => `c_2 = ${getc2(level).toString(0)}`;
        c2.getDescription = (amount) => Utils.getMath(`c_2 = 2^{${c2.level}}`);
        c2.getInfo = (amount) => Utils.getMathTo(getDesc(c2.level),
        getDesc(c2.level + amount));
    }
}

let getc2 = (level) => BigNumber.TWO.pow(level);

c2’s value func­tion uses BigNum­ber’s nat­ive pow method, which raises it to the power of the ar­gu­ment. BigNum­ber has more of these meth­ods, such as abs, which will come in handy soon.

Let’s modify our equa­tion ac­cord­ingly:

var tick = (elapsedTime, multiplier) =>
{
    ...
    currency.value += dt * getc1(c1.level) * getc2(c2.level) * (BigNumber.ONE + getf(f.level));
}

var getPrimaryEquation = () => `\\dot{\\rho} = c_1c_2(1+f)`;

Hope­fully, with c2, we should get a bit stronger now. Note that you may see the c2 up­grade be­ing lis­ted be­low f on the screen, which is quite an­noy­ing. The or­der of up­grades is de­term­ined by the or­der in which they were de­clared in the code (their IDs do not mat­ter, so don’t try to change them). Let’s move the de­clar­a­tion block of c2 to make it show be­low c1 and above f:

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));
    }

    // Here!
    {
        c2 = theory.createUpgrade(3, currency, new ExponentialCost(500, 3));
        let getDesc = (level) => `c_2 = ${getc2(level).toString(0)}`;
        c2.getDescription = (amount) => Utils.getMath(`c_2 = 2^{${c2.level}}`);
        c2.getInfo = (amount) => Utils.getMathTo(getDesc(c2.level),
        getDesc(c2.level + amount));
    }

    {
        f = theory.createUpgrade(2, currency, new ExponentialCost(200, 1.618034));
        let getDesc = (level) => `f = ${getf(level).toString(0)}`;
        f.getDescription = (amount) => Utils.getMath(getDesc(f.level));
        f.getInfo = (amount) => Utils.getMathTo(getDesc(f.level),
        getDesc(f.level + amount));
    }

    // Not here.
}

Af­ter­math #

As you play the the­ory, you may no­tice that the game is start­ing to slow down. Spooky! What could be the cause? If you feel para­noid, let’s switch to an­other the­ory to run for now, and we will con­tinue with a solu­tion to­mor­row.

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, c2;
let f;

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));
    }

    {
        c2 = theory.createUpgrade(3, currency, new ExponentialCost(500, 3));
        let getDesc = (level) => `c_2 = ${getc2(level).toString(0)}`;
        c2.getDescription = (amount) => Utils.getMath(`c_2 = 2^{${c2.level}}`);
        c2.getInfo = (amount) => Utils.getMathTo(getDesc(c2.level),
        getDesc(c2.level + amount));
    }

    {
        f = theory.createUpgrade(2, currency, new ExponentialCost(200, 1.618034));
        let getDesc = (level) => `f = ${getf(level).toString(0)}`;
        f.getDescription = (amount) => Utils.getMath(getDesc(f.level));
        f.getInfo = (amount) => Utils.getMathTo(getDesc(f.level),
        getDesc(f.level + amount));
    }
    
    theory.createPublicationUpgrade(0, currency, BigNumber.from('1e7'));
}

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

let getc2 = (level) => BigNumber.TWO.pow(level);

let getf = (level) =>
{
    if(level == 0)
        return BigNumber.ZERO;
    if(level == 1)
        return BigNumber.ONE;

    return getf(level - 1) + getf(level - 2);
};

var tick = (elapsedTime, multiplier) =>
{
    let dt = BigNumber.from(elapsedTime * multiplier);
    let bonus = theory.publicationMultiplier;
    currency.value += dt * bonus * getc1(c1.level) * getc2(c2.level) * (BigNumber.ONE + getf(f.level));
}

var getPrimaryEquation = () => `\\dot{\\rho} = c_1c_2(1+f)`;

var getSecondaryEquation = () => `${theory.latexSymbol} = \\max\\rho`;

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

const pubPower = 0.1;

var getPublicationMultiplier = (tau) => tau.pow(pubPower);

var getPublicationMultiplierFormula = (symbol) => `{${symbol}}^{${pubPower}}`;

var getTau = () => currency.value;

var getCurrencyFromTau = (tau) =>
[
    tau.max(BigNumber.ONE),
    currency.symbol
];

init();