Game Rules
Read the strategy guideOpponents
TURN
ROUND
GAME
Game Log
What to cross off when the result comes back
Bulls and cows is a game about collecting information. There are only 720 three-digit numbers with distinct digits, and every turn returns strikes and balls. A single result crosses off hundreds of candidates. Cross off nothing and you are still guessing among all 720. Below are three ways of crossing off, and the results each actually produced.
1. Ignore the result and keep guessing (the default sample)
This is what the starter code on this screen does. Every turn it makes a random three-digit number and only avoids ones it has already tried. Strikes and balls come back, and it does nothing with them.
RANDOM roughly even LEVEL 1·2·3 loses every game
Sweeping 720 numbers at random rarely lands inside 30 turns. It is even against RANDOM because neither side uses the results.
2. Cross off every candidate that disagrees
This is the fundamental move. Hold all 720, then test your last guess against each candidate. If that candidate were the answer, a specific result would have come back. If it differs from what you actually got, that candidate is not the answer. Cross it off.
012 → 1S 0B
One of 0, 1, 2 is in the answer in that exact position. Every candidate that disagrees goes.
345 → 0S 1B
One of 3, 4, 5 is there but in the wrong position. Even this one line cuts the field hard.
var left = null;
var lastGuess = null;
// All 720 three-digit numbers with distinct digits. 10 x 9 x 8.
function allNumbers() {
var out = [];
var a, b, c;
for (a = 0; a <= 9; a += 1) {
for (b = 0; b <= 9; b += 1) {
for (c = 0; c <= 9; c += 1) {
if (a !== b && b !== c && a !== c) {
out.push("" + a + b + c);
}
}
}
}
return out;
}
// [strikes, balls] for a guess against a candidate answer. Straight from the rules.
function judge(guess, secret) {
var strikes = 0, balls = 0;
var i, j;
for (i = 0; i < 3; i += 1) {
for (j = 0; j < 3; j += 1) {
if (guess.charAt(j) === secret.charAt(i)) {
if (i === j) { strikes += 1; } else { balls += 1; }
}
}
}
return [strikes, balls];
}
function onGameStart() {}
function onRoundStart() {
var pool = allNumbers();
left = allNumbers();
lastGuess = null;
// Pick my own number at random.
return pool[Math.floor(Math.random() * pool.length)];
}
function onTurnStart() {
lastGuess = left.length ? left[0] : "012";
return lastGuess;
}
// Drop every candidate that disagrees with the result just received.
// This is the fundamental move in this game.
function onTurnEnd(result) {
var got = result.result;
var kept = [];
var i, r;
for (i = 0; i < left.length; i += 1) {
r = judge(lastGuess, left[i]);
if (r[0] === got[0] && r[1] === got[1]) { kept.push(left[i]); }
}
left = kept;
}
function onRoundEnd(result) {}
function onGameEnd(result) {}
RANDOM wins every game LEVEL 1 roughly even LEVEL 2·3 loses every game
RANDOM: wins every game. LEVEL 1: even, because a few turns narrow the field to one or two. But LEVEL 2 and 3 still win every game. They are doing the same thing, and this is a race to guess first. At the same speed you do not win.
3. Choose which side to sweep first
Leave the crossing-off exactly as it is and change only **which of the remaining candidates you guess next**. The version below guesses the largest first. One function is all that changed.
// Guess the largest remaining candidate. Only the order changed; the
// filtering is identical. If the opponent has a habit in how it picks its own
// number, sweeping that side first saves several turns.
function onTurnStart() {
var best = left.length ? left[0] : "012";
var i;
for (i = 1; i < left.length; i += 1) {
if (parseInt(left[i], 10) > parseInt(best, 10)) { best = left[i]; }
}
lastGuess = best;
return lastGuess;
}
RANDOM wins every game LEVEL 1 roughly even LEVEL 2 has the edge LEVEL 3 mostly loses
That one difference moves LEVEL 2 from losing every game to having the edge: repeating 60 games ten times gives a win rate between 55% and 73%, median 65%. There is a lean in how it picks its own number, and sweeping that side first saves several turns. Against LEVEL 3 it barely helps: it targets one opponent's habit, so an opponent without that habit gives you nothing.
4. From here it is your turn
Two directions are open. One is a better guess: among the remaining candidates, pick the one that splits them most evenly by result, and your worst case shrinks. The other is untouched so far — the number onRoundStart returns is a strategy too. If you know the order an opponent sweeps in, you can pick a number that gets caught late. Paste the code above into the editor and start there.
How a battle runs
- Code battle is a battle between your code and opponent's code, and the language is JAVASCRIPT.
- Fill in the code, select the opponent (RANDOM, LEVEL1, LEVEL2, LEVEL3, Online opponent) and then press the game start button will start the match.
- 1 game consists of 31 rounds, 1 round will continue until one side wins or 30 turns(the larger number wins).
Bulls and cows has six hooks, and onRoundStart must return a three-digit string with distinct digits. That is your number for the round. Step 2's code contains all six, so it runs as-is. Step 3 shows only the one function that changed.
Results come from 60 games (1,860 rounds) against each opponent, and a test in the repository re-checks them against the real rule engine. All four opponents carry randomness, so results vary. At 25 games the same matchup swung between 52% and 76%, which is why it is 60.