Last active
July 8, 2025 03:56
-
-
Save Krinkle/a170d6720034239a0f53f095d12d58a3 to your computer and use it in GitHub Desktop.
Matt Gray's 2025 Conga on onemillionchessboards.com
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// One time: | |
// * Open the web inspector. | |
// * Paste the below code in the console. | |
// | |
// Usage: | |
// 1. Find a white pawn at the front of one of the conga lines in column 3425. | |
// Matt started around 3425,4464 | |
// and as of 6 Jul 2025 we have migrated north until 3425,3740. | |
// 2. Click on the white pawn so that it is selected (yellow) and gets a target (blue) | |
// in front of it. | |
// 3. Run choochoo() from the console, which will move the pawn north upto 100 squares. | |
// | |
// If it stops and is still within view but not selected, simply run choochoo() again. | |
// | |
// If it fails and is out of view, find it again, select it, and | |
// run choochoo(). | |
// | |
// If your pawn joined the end of another conga line, congrats! | |
// Time to find another pawn, and go to step 1. | |
// | |
// ------- | |
// | |
// Copyright 2025 Timo Tijhof <https://timotijhof.net/congajs> | |
// This is free and unencumbered software released into the public domain. | |
// | |
// Changelog: | |
// * v1 (2025-07-05): Initial release at <https://timotijhof.net/congajs>, | |
// in response to https://chaos.social/@mattgrayyes/114800817697031071. | |
// * v2 (2025-07-06): Optimization: Reduce DELAY_AFTER_NAVUP from 300ms to 250ms. | |
// * v3 (2025-07-06): Optimization: Reduce DELAY_AFTER_RESELECT from 300ms to 200ms. | |
// * v4 (2025-07-06): Add error messages. | |
// * v5 (2025-07-06): Fix: Increase DELAY_AFTER_NAVUP back up to 300ms because apparently | |
// on bigger screens (or lower zoom level) it takes longer causing us | |
// to stop after just a one one northward move. | |
// * v6 (2025-07-06): Make resuming easier by selecting the inspected pawn first. | |
// * v7 (2025-07-06): Make script even easier, by inverting the approach. We now auto-inspect | |
// the selected pawn, instead of auto-selecting the inspected pawn. | |
// No more reliance on using the Inspector dev in the browser console, | |
// and thus no more $0 variable. | |
// * v8 (2025-07-07): Optimization: Batch four moves together to reduce post-navup delays. | |
function choochoo() { | |
'use strict'; | |
// A selected pawn has "--axe-transform" set to scale(1.2) instead of scale(1). | |
const SEL_WHITE_PAWN = 'button.sc-dVBluf.dmuknz[style*="--axe-transform: scale(1.2)"]:has(img[src="/pieces/white-processed/pawn.png"])'; | |
const SEL_TARGET = 'button.sc-fLDLck.hJsSaA'; | |
const SEL_BOARD_UP = 'button:has(svg.lucide-circle-arrow-up)'; | |
const REPEATS = 300; | |
const DELAY_AFTER_NAVUP = 300; | |
const DELAY_AFTER_MOVE = 300; | |
const DELAY_AFTER_RESELECT = 200; | |
class CongaJsError extends Error { | |
constructor(message) { | |
super(message); | |
this.name = 'CongaJsError'; | |
} | |
} | |
class CongaJsNoTarget extends Error {} | |
const pawn = document.querySelector(SEL_WHITE_PAWN); | |
if (!pawn) { | |
throw new CongaJsError('Select a white pawn before running choochoo()'); | |
} | |
if (!pawn.isConnected) { | |
throw new CongaJsError('Pawn element has been recycled. Find it, inspect it, and run choochoo() again.'); | |
} | |
const boardUp = document.querySelector(SEL_BOARD_UP); | |
if (!boardUp) { | |
throw new CongaJsError('Failed to locate "board up" button on the screen.'); | |
} | |
// Rather than scheduling a large number of spaced out timeouts all at once, | |
// we use a Promise chain. This has a number of benefits: | |
// - The order is guruanteed. | |
// - Over time, race conditions and freezes will cause our time predictiom to be off | |
// eventually causing us to select targets that don't exist yet. By chaining the timeouts | |
// one at a time, we're never more than a few millisecond out of sync, which we can account for. | |
// - We can catch errors and thus naturally cancel any remaining actions else. | |
// This is important because errors will sometimes happen, for example, after each move, | |
// blue target squares are inserted, which takes a short amount of time. Sometimes this | |
// takes longer than we expect, and then we can't click the target and so we fail. | |
// At this point, we don't want to continue as otherwise we'll move the board up too far, | |
// and we'll loose our pawn $0 reference. The game recycles thus button 1:1 for a given | |
// logical game piece, but once it is out of view, if we navigate back to it, | |
// a new button is created to represent that pawn. By catching the errors, you can | |
// simply run the snippet again to continue (once you've selected the pawn). | |
// - We can apply the delay to *after* an action, rather then before it, | |
// so that the loop below is more intuitive (rather than scheduling each action based | |
// on the mandatory delay from the previous type of action, we can declare them together). | |
function delayWithCatch(ms, fn) { | |
return Promise.try(fn) | |
.then(() => { | |
return new Promise((resolve) => { | |
setTimeout(resolve, ms); | |
}); | |
}); | |
} | |
function moveUp() { | |
// If there are black pawns ahead (north-west or north-east) there will | |
// be multiple targets. The game draws the northward option first, and | |
// the any diagonal/alternative options, so just select the first one. | |
const target = document.querySelectorAll(SEL_TARGET)[0]; | |
if (!target) { | |
throw new CongaJsError('Pawn has no available targets. Find another and go again!'); | |
} | |
// If there are only diagnoal/alternative options, that will be first. | |
// This happens if we reached the back of the next conga line, and there's | |
// also a black pawn nearby. Don't take that, instead stop here. | |
// Confirm by comparing x coord of target to x coord of pawn. | |
const targetX = target?.style?.getPropertyValue('--x'); | |
if (!targetX) { | |
throw new CongaJsError('Could not find X coord of target'); | |
} | |
if (!pawn.style.transform.includes(targetX)) { | |
throw new CongaJsError('Stopped. The only target would abandon our conga line. Find another and go again!'); | |
} | |
target.click(); | |
} | |
let pChain = Promise.resolve(); | |
for (let i = 0; i < REPEATS; i++) { | |
let retry = false; | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_MOVE, () => moveUp())); | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_RESELECT, () => pawn.click())); | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_MOVE, () => moveUp())); | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_RESELECT, () => pawn.click())); | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_MOVE, () => moveUp())); | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_RESELECT, () => pawn.click())); | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_MOVE, () => moveUp())); | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_RESELECT, () => pawn.click())); | |
// Optimization: Reduce post-navup delay by batching four moves together. | |
pChain = pChain.then(delayWithCatch.bind(null, DELAY_AFTER_NAVUP, () => { | |
boardUp.click(); | |
boardUp.click(); | |
boardUp.click(); | |
boardUp.click(); | |
})); | |
} | |
pChain.catch((e) => { | |
if (e instanceof CongaJsError) { | |
console.warn(e.toString()); | |
} else { | |
console.debug(e.name + ': ' + e.message); | |
console.warn(new CongaJsError('Stopped. Pawn blocked or lost selection. Find one and go again!').toString()); | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment