Bulls and Cows Game
Code Battle
한글로 보기
Check the rules of the game, fill in the following functions, select the opponent, and click the Start Game button to start the battle.
The opponent and I each have their own number (3 digits with 3 different numbers), and it is a game to win by guessing the opponent's number first. If both sides guess at same time, the larger number wins.
When round is started, you need to return your number and predict opponent's number at the beginning of every turn. When turn is ended, you receive the results of comparing the opponent's number and the predicted number.
If the matching digits are in their right positions, they are 'bulls', if in different positions, they are 'cows'

My Code (JavaScript)

Opponents

Online battles are played between signed-in members. Sign in to join.
If you or the opponent's screen is not visible, the communication speed will be slower.
TURN
COUNT
0
ROUND
WIN
0
LOSE
0
GAME
WIN
0
LOSE
0

Game Log

Recent logs are displayed at the top.

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.