Tijd om iets nieuws te programmeren. Gamen vind ik leuk, automatiseren ook, dus een game automatiseren is natuurlijk top. Zie daar de CookieClicker. The name says it all – je moet klikken om cookies te krijgen. Als je die investeert krijg je nog meer cookies, enzovoort.
Eerst het klikken zelf maar automatiseren. Het script moet simpelweg klikken op het koekje. En het liefst heel snel 🙂
var settings = {
clicksPerSecond: 100
}
var timeout;
function clickCookies() {
clearTimeout(timeout);
clickCookie();
}
function clickCookie() {
document.getElementById('bigCookie').click();
timeout = setTimeout(clickCookie, 1000 / settings.clicksPerSecond);
}
clickCookies();
De functie clickCookies() trapt de boel af. Eerst wordt een al een eventueel lopend proces (van een run ervoor) gestopt, dan wordt een nieuwe gestart. De functie clickCookie() roept zichzelf aan. Hier zit een vertraging op, gebaseerd op het ingestelde aantal clicks per seconde. Tot 150 clicks/seconde gaat het op mijn machine zonder problemen, rond de 200 raakt Chrome over zijn toeren.

Tijd om naast het klikken, ook het bouwen te automatiseren. Eerst maar de meest simpele versie: gewoon alles proberen te bouwen, of er nu goed geld is of niet, of het nu de meest verstandige investering is of niet.
var settings = {
buildsPerSecond: 1
}
var timeoutBuilding;
function build() {
for(let i=0; i<17; i++) {
id = 'productName' + i;
document.getElementById(id).click();
}
timeoutBuilding= setTimeout(build, 1000 / settings.buildsPerSecond);
}
function keepBuilding() {
clearTimeout(timeoutBuilding);
build();
}
keepBuilding();
De functie keepBuilding() trapt hier de boel af en werkt op dezelfde manier als clickCookies(). De functie build() klikt een voor een op alle 17 gebouwen in een poging deze te kopen/upgraden. Om mijn laptop niet onnodig te belasten, laat ik dit proces maar 1 keer per seconde plaatsvinden.

Wanneer je eenmaal je eerste gebouw hebt gekocht, komen er per gebouw ook upgrades beschikbaar. Tijd om het script aan te vullen met een stuk dat deze upgrades automatisch koopt.
var settings = {
upgradesPerSecond: 1
}
var timeoutUpgrading;
function upgrade() {
let upgradeButton = document.getElementById('upgrade0');
if(upgradeButton != null) {
upgradeButton.click();
}
timeoutUpgrading = setTimeout(upgrade, 1000 / settings.upgradesPerSecond);
}
function keepUpgrading() {
clearTimeout(timeoutUpgrading);
upgrade();
}
keepUpgrading();
Dezelfde structuur in de code als bij het bouwen. Alleen het upgraden zelf is simpeler, omdat het script altijd alleen de eerste pakt, als die bestaat. De upgrade verdwijnt na aankoop, dus staat de volgende weer klaar.

In principe loopt nu alles geautomatiseerd, maar het kan nog wel een stuk slimmer. Bouwen wat je bouwen kunt betekent dat het goedkoopste gebouw de grootste kans heeft om geupgrade te worden. Maar vaak is het beter even te wachten en te sparen voor een upgrade van een duurder gebouw.
De beste upgrade is simpelweg de upgrade die het meeste bang for your bucks oplevert. Of te wel: de meeste extra clicks / upgrade prijs.

Deze informatie is beschikbaar in de tooltip van elk gebouw. In het voorbeeld hierboven: 0.2 extra cliks per 61 cookies. Ik bouw de functie build iets om:
function build() {
document.getElementById(
'productName' + getIdToBuild()
).click();
timeout = setTimeout(build, 1000 / settings.buildsPerSecond);
}
function getIdToBuild() {
let ratios = [];
for(let i=0; i<17; i++) {
ratios.push(
getRatioFromTooltip(i)
);
}
let index = ratios.indexOf(Math.max(...ratios));
return index;
}
Het script probeert het gebouw te upgraden met de beste prijs/opbrengst verhouding. Daarvoor berekent het alle verhoudingen voor de 17 gebouwen en heeft de hoogste terug.
<div style="min-width:350px;padding:8px;">
<div class="icon" style="float:left;margin-left:-8px;margin-top:-8px;background-position:0px 0px;"></div>
<div style="float:right;text-align:right;">
<span class="price">61</span>
</div>
<div class="name">Cursor</div>
<small>[owned : 10</small>]
<div class="line"></div>
<div class="description">Autoclicks once every 10 seconds.</div>
<div class="line"></div>
<div class="data">• each cursor produces <b>0.2</b> cookies per second<br>• 10 cursors producing <b>2</b> cookies per second (<b>50%</b> of total CpS)<br>• <b>138</b> cookies clicked so far</div>
</div>
Bovenstaand HTML is de tooltip van het voorbeeld eerder. Je ziet hier de prijs (61) en het aantal extra clicks (0.2) weer terug.
function getRatioFromTooltip(id) {
let tooltip = Game.ObjectsById[id].tooltip();
// Get price
tooltip = tooltip.slice(tooltip.search('price'));
tooltip = tooltip.slice(tooltip.search('>')+1);
let price = tooltip.slice(0, tooltip.search('<'));
price = price.replace(',', '');
// Get extra clicks per second (cps)
let cps = 0;
if(tooltip.search('<b>') > 0) {
tooltip = tooltip.slice(tooltip.search('<b>') + 3);
cps = tooltip.slice(0, tooltip.search('<'));
cps = parseFloat(cps.replace(/,/g, ''));
}
let ratio = cps / price;
return ratio;
}
Bovenstaand script loopt de HTML door, op zoek naar herkenbare stukken in de HTML, om eerst de prijs en vervolgens de extra clicks per seconde eruit te vissen.

Een nadeel: het script verwacht altijd een prijs en het aantal extra clicks, maar zoals je ziet is het aantal extra clicks niet beschikbaar als je het gebouw nog niet gekocht hebt.
Om het script toch te laten beginnen met het kopen van het eerste gebouw, bouw ik de functie getIdToBuild() iets om:
function getIdToBuild() {
let ratios = [];
for(let i=0; i<17; i++) {
ratios.push(
getRatioFromTooltip(i)
);
}
let maxRatio = Math.max(...ratios);
return maxRatio == 0 ? 0 : ratios.indexOf(Math.max(...ratios));
}
Nog altijd weigert het script te starten met bouwen. Er blijkt nog iets anders aan de hand:

De notatie van de prijs van de gebouwen verandert: van een getal (zoals bijvoorbeeld 130.000) naar een getal met een aanduiding (1.4 million).
// Multiply the price with the appropriate factor if necessary
if(price.search(' ') > 0) {
let factor = price.slice(price.search(' ') + 1);
var factors = [
'thousand',
'million',
'billion',
'trillion',
'quadrillion',
'quintillion',
'sextillion',
'septillion',
'octillion',
'nonillion'
];
let index = factors.indexOf(factor);
let multiplier = 10 ** ((index+1)*3);
price = price.slice(0, price.search(' '));
price = price * multiplier;
}
Dit stuk code voeg ik in bij de functie getRatioFromTooltip. Dit script checkt of er een extra aanduiding achter het getal staat. Zo ja, dan vermenigvuldigt het de prijs op basis van de genoemde factor.

Een probleem: alleen het eerste gebouw wordt gekocht en geupgrade. De extra clicks per seconde van het tweede gebouw is niet bekend, dus het script klikt daar nooit op. Da’s onhandig.
Wanneer is het dan verstandig om een nieuw gebouw te kopen? Eigenlijk zodra je het kunt betalen :). Een nieuw gebouw heeft namelijk altijd een betere prijs/extra clicks verhouding, dan een upgrade van een bestaand gebouw.
function build() {
// New building
let idFromFirstNewBuilding = getIdFromFirstNewBuilding();
if(idFromFirstNewBuilding != undefined) {
document.getElementById(
'productName' + idFromFirstNewBuilding
).click();
}
// Upgrade existing
document.getElementById(
'productName' + getIdWithBestRatio()
).click();
timeout = setTimeout(build, 1000 / settings.buildsPerSecond);
}
Eerst de functie build in tweeën gesplitst en getIdToBuild hernoemd naar getIdWithBestRatio. Het eerste deel zorgt voor een klik op het eerste de beste gebouw waarvan de prijs onbekend is.
function getIdFromFirstNewBuilding() {
for(let i=0; i<17; i++) {
let tooltip = Game.ObjectsById[i].tooltip();
if(tooltip.indexOf('owned : 0') != -1) {
return i;
}
}
return undefined;
}
Deze functie zoekt naar een nieuw gebouw, door alle tooltips langs te gaan en te checken of daarin de tekst ‘owned : 0’ voorkomt (zie eerder screenhot).

Met dit laatste stuk, is het script compleet. Ik vind het ook wel weer mooi geweest.
Tijd voor een kopje thee met, jawel, een koekje erbij.
De code is beschikbaar op https://github.com/gkruiger/cookieclicker.
Reacties door Gertjan Kruiger