It can identify more than half of all colors in just 2 guesses. If you edit the code to minimize the size of the largest bucket instead, you get an algorithm that has a slightly worse average case, but never needs more than 4 guesses.
Paste it in the site's browser console to try.
function solve() {
let possibleTargets = new Array(0x1000).fill(0).map((_, i) => i);
while (true) {
// for each possible guess, calculate the score-buckets
const guessResults = possibleTargets.map((guess) => { // for better brute-force, iterate through all colors, but this performs better (probably because we don't reward for guessing correctly in the current turn)
const buckets = new Array(101).fill(0).map(() => []);
for (const possibleTarget of possibleTargets) {
buckets[calcScore(possibleTarget, guess)].push(possibleTarget);
}
return { guess, buckets };
});
// find guess with lowest variance
const best = guessResults.sort((a, b) => calcVariance(a.buckets) - calcVariance(b.buckets))[0];
// make the guess & update possible targets
const sc = makeGuess(best.guess);
if (sc >= 100) return "Success!";
possibleTargets = best.buckets[sc];
}
}
function calcVariance(buckets) { const maxBucketSize = Math.max(...buckets.map(b => b.length)); return buckets.reduce((acc, b) => acc + (b.length - maxBucketSize) ** 2, 0); }
function deconstruct(color) { return [(color & 0xF00) >> 8, (color & 0x0F0) >> 4, (color & 0x00F)]; }
function calcScore(target, guess) { return score(...deconstruct(target), ...deconstruct(guess)); }
function makeGuess(guess) { [rr, gg, bb] = deconstruct(guess); submitInput(); return score(r, g, b, rr, gg, bb); };
solve();
I'd like to try out a few alternatives in place of your variance function. Something like b.length*log(b.length) to estimate the expected number of guesses, and perhaps using a version of log closer to ceil(log(x)/log(100)).
Regarding computing it for all targets, I optimized it by precomputing the value of guessResults in the first iteration, since it's always the same (no matter the target color), which saves most of the computation. I removed the optimization so the code wouldn't be so long here.