Skip to content

Instantly share code, notes, and snippets.

@xiagu
Last active November 29, 2021 06:49
Show Gist options
  • Save xiagu/a6aa37f1e9bf2994a03ea7dace959e70 to your computer and use it in GitHub Desktop.
Save xiagu/a6aa37f1e9bf2994a03ea7dace959e70 to your computer and use it in GitHub Desktop.
Quick scripts to help play kittens game because life is short
SECONDS = 1e3;
MINUTES = 60 * SECONDS;
HOURS = 60 * MINUTES;
VERBOSE_AUTO = true;
autocraftNames = ['steel', 'plate'];
mediumCraftNames = ['kerosene', 'thorium', 'eludium'];
slowCraftNames = ['beam', 'slab', 'parchment', 'manuscript'];
scienceCraftNames = ['parchment', 'manuscript', 'compendium'];
religionBuyNames = ['Black Pyramid', 'Black Nexus', 'Black Core'];
partialCraftNames = ['concrete', 'alloy', 'gear'];
autobuyNames = [
'Workshop',
'Hut',
'Log House',
// 'Unic. Pasture',
// 'Observatory',
'Lumber Mill',
'Mine',
'Library',
'Academy',
'Temple',
'Tradepost',
'Smelter',
'Reactor',
'Factory',
'Accelerator',
'Calciner',
'Mansion',
'Chapel',
'Broadcast Tower',
'Solar Farm',
'Hydro Plant',
// 'Harbour',
'Mint',
'Barn',
// 'Magneto',
// 'Steamworks',
];
spaceBuyNames = [
'Space Elevator', 'Lunar Outpost', 'Orbital Array', 'Research Vessel',
'Sunlifter', 'Space Beacon'
];
autoTradeNames = ['Leviathans'];
function getOrCreateAutomation() {
try {
return Automation;
} catch (e) {
return class Automation {
constructor(timeout, runFn) {
this.timeout = timeout;
this.lastRun = 0;
this.fn = runFn;
this.enabled = true;
this.switchBackTo = '';
this.switchTo = '';
}
run() {
this.lastRun = Date.now();
if (this.switchTo && this.switchBackTo) {
switchTab(this.switchTo);
setTimeout(() => {
this.fn();
setTimeout(() => switchTab(this.switchBackTo), 100);
}, 100);
} else {
this.fn();
}
}
timeLeft() {
return this.timeout - (Date.now() - this.lastRun);
}
disable() {
this.enabled = false;
}
enable() {
this.enabled = true;
}
};
}
}
Automation = getOrCreateAutomation();
function getOrCreateCraftAutomation() {
try {
return CraftAutomation;
} catch (e) {
return class CraftAutomation extends Automation {
constructor(timeout, craftNames) {
super(timeout, () => craftResourcesByName(window[this.craftNames]));
this.craftNames = craftNames;
}
toString() {
const enabledStr = this.enabled ? '[E]' : '[ ]';
const timeout = `[${this.timeout / SECONDS}s]`;
return `${enabledStr}${timeout} ${this.craftNames}:\n${
prettyPrintArr(window[this.craftNames])}`;
}
}
}
}
CraftAutomation = getOrCreateCraftAutomation();
try {
console.log(`Clearing runAllTimeout ${runAllTimeout}`);
clearTimeout(runAllTimeout);
} catch (e) {
}
automations = new Map();
automations.set('observe', new Automation(10 * SECONDS, () => {
const observeBtn = document.getElementById('observeBtn');
if (observeBtn) observeBtn.click();
}));
automations.set(
'trade', new Automation(100 * SECONDS, () => tradeByName(autoTradeNames)));
automations.set(
'buyBuilding',
new Automation(2 * SECONDS, () => buyBuildingsByName(autobuyNames)));
automations.set(
'buySpace',
new Automation(51 * SECONDS, () => buySpaceByName(spaceBuyNames)));
automations.get('buySpace').switchTo = 'Space';
automations.get('buySpace').switchBackTo = 'Trade';
automations.set(
'buyReligion',
new Automation(31 * SECONDS, () => buyReligionByName(religionBuyNames)));
automations.get('buyReligion').switchTo = 'Religion';
automations.get('buyReligion').switchBackTo = 'Trade';
automations.set(
'titaniumTrade',
new Automation(11 * SECONDS, () => tradeByName(['Zebras'])));
automations.get('titaniumTrade').switchTo = 'Trade';
automations.get('titaniumTrade').switchBackTo = 'Bonfire';
automations.set('feedElders', new Automation(1 * MINUTES, () => {
if (game.resPool.get('necrocorn').value > 0) {
const feedBtn =
Array
.from(document.querySelectorAll('.trade-race .btn'))
.find((el) => el.innerText.includes('Feed elders'));
if (feedBtn) feedBtn.click();
}
}));
automations.set('buildCraftCombo', new Automation(5 * SECONDS, () => {
buyBuildingsByName(autobuyNames).then(() => {
automations.get('slowCraft').run();
});
}));
automations.set('craft', new CraftAutomation(15 * SECONDS, 'autocraftNames'));
automations.set(
'mediumCraft', new CraftAutomation(3 * MINUTES, 'mediumCraftNames'));
automations.set(
'slowCraft', new CraftAutomation(7 * SECONDS, 'slowCraftNames'));
automations.set(
'scienceCraft', new CraftAutomation(15 * SECONDS, 'scienceCraftNames'));
automations.set(
'partialCraft', new CraftAutomation(30 * SECONDS, 'partialCraftNames'));
automations.get('partialCraft').fn = function() {
craftResourcesByNameTenPercent(window[this.craftNames]);
};
automations.set(
'hunt',
new Automation(
2 * SECONDS,
() => document.querySelector('#fastHuntContainer > a').click()));
automations.set(
'praise',
new Automation(
1 * MINUTES,
() => document.querySelector('#fastPraiseContainer > a').click()));
function craftResourcesByName(names) {
const resourceRows =
Array.from(document.querySelectorAll('.craftTable .resourceRow'));
names.forEach((name) => {
const row = resourceRows.find((row) => row.innerText.includes(name));
const target = row && row.querySelector('td:last-child a');
if (target) target.click();
});
}
function craftResourcesByNameTenPercent(names) {
const resourceRows =
Array.from(document.querySelectorAll('.craftTable .resourceRow'));
names.forEach((name) => {
const row = resourceRows.find((row) => row.innerText.includes(name));
const target = row && row.querySelector('td:nth-child(5) a');
if (target) target.click();
});
}
// Returns a Promise for when it's done trying to buy every building.
function buyBuildingsByName(names) {
const itr = names.entries();
const buildingButtonContainer = document.querySelector('.bldGroupContainer');
if (!buildingButtonContainer) return Promise.resolve();
let resolve;
const boughtAllPromise = new Promise((res, rej) => {
resolve = res;
});
const helper = () => {
const val = itr.next();
if (val.done) {
resolve();
return;
}
buyBuildingByName(val.value[1], buildingButtonContainer);
setTimeout(helper, 0);
};
helper();
return boughtAllPromise;
}
function buyBuildingByName(name, buttonContainer) {
const buildingButtons = Array.from(
buttonContainer.querySelectorAll('.btn:not(.disabled) .btnContent'));
const button = buildingButtons.find((btn) => btn.innerText.includes(name));
if (button) {
console.log(`Trying to buy ${name}`);
button.click();
}
}
function buyReligionByName(names) {
const religionButtons = Array.from(document.querySelectorAll(
'.panelContainer .btn:not(.disabled) .btnContent'));
names.forEach((name) => {
const button = religionButtons.find((btn) => btn.innerText.includes(name));
if (button) {
console.log(`Trying to buy ${name}`);
button.click();
}
});
}
function buySpaceByName(names) {
const spaceButtons = Array.from(document.querySelectorAll(
'.panelContainer .btn:not(.disabled) .btnContent'));
names.forEach((name) => {
const button = spaceButtons.find((btn) => btn.innerText.includes(name));
if (button) {
console.log(`Trying to buy ${name}`);
button.click();
}
});
}
function tradeByName(names) {
const tradeButtons = Array.from(document.querySelectorAll('.panelContainer'));
names.forEach((name) => {
const race = tradeButtons.find((btn) => btn.innerText.includes(name));
if (race) race.querySelector('.trade a:nth-child(2)').click();
});
}
runAllAutos = () => {
const now = Date.now();
for ([name, auto] of automations) {
// Only run if enabled
if (!auto.enabled) continue;
if (now - auto.lastRun > auto.timeout) {
if (VERBOSE_AUTO) console.log(`Running ${name}`);
try {
auto.run();
} catch (e) {
console.warn(`${name} failed`);
console.error(e);
}
}
}
runAllTimeout = setTimeout(runAllAutos, 500);
};
function hotkeys(e) {
switch (e.key) {
case 'B':
switchTab('Bonfire');
break;
case 'T':
switchTab('Trade');
break;
case 'R':
switchTab('Religion');
break;
case 'W':
switchTab('Workshop');
break;
case 'K': // Kittens
case 'V': // Village
document.querySelector('.tabsContainer a.tab:nth-of-type(2)').click();
break;
case 'S':
switchTab('Space');
break;
case 'C':
switchTab('Science');
break;
case 'E': // timE
switchTab('Time');
break;
}
}
document.addEventListener('keypress', (e) => hotkeys(e));
function switchTab(tabName) {
const tabs = Array.from(document.querySelectorAll('.tabsContainer a.tab'));
const target = tabs.find((x) => x.innerText.includes(tabName));
if (target) target.click();
}
function status() {
const enabled = [];
const disabled = [];
for ([name, auto] of automations) {
if (auto.enabled) {
enabled.push(name);
} else {
disabled.push(name);
}
}
console.log(`Enabled automations\n${prettyPrintArr(enabled)}`);
console.log(`Disabled automations\n${prettyPrintArr(disabled)}`);
const buyTimeout = automations.get('buyBuilding').timeout / SECONDS;
const buildingOn = automations.get('buyBuilding').enabled ? 'E' : ' ';
console.log(`[${buildingOn}][${buyTimeout}s] buyBuilding - autobuyNames:\n${
prettyPrintArr(autobuyNames)}`);
const spaceTimeout = automations.get('buySpace').timeout / SECONDS;
const spaceOn = automations.get('buySpace').enabled ? 'E' : ' ';
console.log(`[${spaceOn}][${spaceTimeout}s] buySpace - spaceBuyNames:\n${
prettyPrintArr(spaceBuyNames)}`);
console.log(automations.get('craft').toString());
console.log(automations.get('mediumCraft').toString());
console.log(automations.get('slowCraft').toString());
console.log(automations.get('scienceCraft').toString());
console.log(automations.get('partialCraft').toString());
}
function prettyPrintArr(arr) {
return `[${arr.join(', ')}]`;
}
function disable(name) {
automations.get(name).disable();
}
function enable(name) {
automations.get(name).enable();
}
function removeByName(arr, name) {
let i;
while ((i = arr.indexOf(name)) >= 0) {
arr.splice(i, 1);
}
return arr;
}
function setLategameTimeouts() {
automations.get('hunt').timeout = 1 * SECONDS;
automations.get('craft').timeout = 9 * SECONDS;
// automations.get('slowCraft').timeout = 5 * SECONDS;
automations.get('scienceCraft').timeout = 30 * SECONDS;
automations.get('mediumCraft').timeout = 10 * MINUTES;
automations.get('praise').timeout = 30 * SECONDS;
disable('slowCraft');
disable('buyBuilding');
disable('observe');
enable('mediumCraft');
enable('buildCraftCombo');
automations.get('buildCraftCombo').timeout = 5 * SECONDS;
}
function prepareForReset() {
disable('buyBuilding');
disable('buildCraftCombo');
disable('slowCraft');
disable('craft');
disable('mediumCraft');
disable('praise');
disable('scienceCraft');
disable('buySpace');
disable('buyReligion');
disable('trade');
}
function recalcTimeouts() {
// Praise
setTimeoutByResPercent('praise', 'faith', 0.45);
setTimeoutByResPercent('hunt', 'manpower', 0.45);
// Craft
setTimeoutForCraft('craft');
setTimeoutForCraft('slowCraft', 1);
}
function setTimeoutByResPercent(automation, res, waitFraction = .8) {
const newTimeout = waitFraction * secondsToCap(res);
const percentStr = `[${(waitFraction * 100).toFixed(1)}% ${res}]`;
console.log(
`Set ${automation} timeout to ${newTimeout.toFixed(1)}s ${percentStr}`);
automations.get(automation).timeout = newTimeout * SECONDS;
}
function setTimeoutForCraft(craftAutomation, waitFraction = .8) {
const craftNames = window[automations.get(craftAutomation).craftNames];
// Fastest capped resource of raw materials
const fastestOfMaterials = craftNames.map((craftTarget) => {
const mats = game.workshop.getCraft(craftTarget).prices.map((p) => p.name);
return fastestCapped(mats);
});
// Fastest of those
const fastest = fastestCapped(fastestOfMaterials);
setTimeoutByResPercent(craftAutomation, fastest, waitFraction);
}
function secondsToCap(resName) {
const res = game.resPool.get(resName);
const perSecond = 5 * game.getResourcePerTick(res.name, true);
return res.maxValue / perSecond;
}
function fastestCapped(resNames) {
if (resNames.length === 1) return resNames[0];
return resNames.map((r) => [r, secondsToCap(r)])
.filter(([name, capSecs]) => capSecs > 0)
.reduce((best, elem) => {
return best[1] < elem[1] ? best : elem;
})[0];
}
automations.get('buildCraftCombo').disable();
automations.get('buyBuilding').disable();
automations.get('observe').disable();
automations.get('partialCraft').disable();
automations.get('titaniumTrade').disable();
automations.get('mediumCraft').enable();
automations.get('slowCraft').enable();
automations.get('praise').enable();
automations.get('trade').enable();
automations.get('buySpace').enable();
automations.get('feedElders').enable();
automations.get('buyReligion').enable();
console.log(
`Loaded automations [${Array.from(automations.keys()).join(', ')}]`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment