Bulls Cows API

A Cloudflare Workers API for Bulls and Cows, with machine solving, human sessions, PVP play, dynamic strategies, and precomputed decision trees.

/api/meta 3-6 digits unique digits HTTP + WebSocket JS / WASM engine

Capabilities

Machine solving

Submit guess + A/B history, and the server filters candidates and returns the next guess. Dynamic mode can start from any valid history; tree mode follows a fixed first guess and a precomputed tree. Dynamic mode supports engine:"js" and engine:"wasm". Run endpoints can simulate a full solve against a provided secret.

Human sessions

The server generates a random secret and stores it in an AES-GCM encrypted session token. No database is required.

PVP play

The user provides a human secret, both sides take turns guessing, and the server validates feedback for the computer's guesses.

Two-player duel

Two human players submit their secrets, then each guesses the other secret. The server computes both feedback streams and compares attempt counts.

Precomputed assets

Candidate codebooks and decision trees are stored as Workers Static Assets and loaded lazily through the ASSETS binding.

Strategies

strategymodesdescription
first_remainingdynamic, treeAfter filtering, choose the lowest indexed remaining candidate.
minimax_worst_bucketdynamic, treeMinimize the largest feedback bucket for a more stable worst-case path.
expected_sizedynamic, treeIrving, 1978. Minimize expected remaining set size, equivalent to minimizing the sum of squared bucket sizes.
feedback_countdynamic, treeKooi, 2005. Maximize the number of non-empty feedback buckets.
optimaltreeBuilt-in optimal trees for three and four digits. The fixed first guesses are 012 and 0123.

Use /api/strategies for the full strategy list.

Execution Engines

enginescopedescription
jsdynamicThe default TypeScript path. It is kept as the compatibility path and is useful for ordinary calls and comparison tests.
wasmdynamicThe Rust packed hot path. Candidate filtering and strategy scoring run inside WASM, which is useful for heavier strategies such as minimax_worst_bucket, expected_size, and feedback_count.

tree mode reads a precomputed binary tree and does not use an engine. For machine solving, pass engine:"wasm". For PVP, pass computerEngine:"wasm" or engine:"wasm".

HTTP Examples

Feedback

curl -X POST https://bulls-cows-api.lzray.cloud/api/feedback \
	-H "content-type: application/json" \
	-d '{"n":4,"secret":"1234","guess":"1324"}'

Dynamic machine solving

curl -X POST https://bulls-cows-api.lzray.cloud/api/solve/next \
	-H "content-type: application/json" \
	-d '{"n":4,"mode":"dynamic","engine":"wasm","strategy":"expected_size","history":[{"guess":"0123","a":1,"b":1}],"options":{"allowFallback":true,"exactThreshold":3000}}'

Optimal tree

curl -X POST https://bulls-cows-api.lzray.cloud/api/solve/next \
	-H "content-type: application/json" \
	-d '{"n":4,"mode":"tree","strategy":"optimal","history":[{"guess":"0123","a":1,"b":1}]}'
curl -X POST https://bulls-cows-api.lzray.cloud/api/solve/next \
	-H "content-type: application/json" \
	-d '{"n":3,"mode":"tree","strategy":"optimal","history":[{"guess":"012","a":0,"b":0}]}'

Run a tree simulation

curl -X POST https://bulls-cows-api.lzray.cloud/api/solve/run-tree \
	-H "content-type: application/json" \
	-d '{"n":4,"secret":"1234","strategy":"optimal"}'

Run a dynamic simulation from completed guesses

curl -X POST https://bulls-cows-api.lzray.cloud/api/solve/run-dynamic \
	-H "content-type: application/json" \
	-d '{"n":4,"secret":"1234","strategy":"expected_size","engine":"wasm","history":[{"guess":"0123","a":0,"b":3}],"options":{"allowFallback":true,"exactThreshold":3000}}'

Human session

curl -X POST https://bulls-cows-api.lzray.cloud/api/human/start \
	-H "content-type: application/json" \
	-d '{"n":4}'
curl -X POST https://bulls-cows-api.lzray.cloud/api/human/guess \
	-H "content-type: application/json" \
	-d '{"sessionToken":"...","guess":"1234"}'

Two-player duel

curl -X POST https://bulls-cows-api.lzray.cloud/api/duel/start \
	-H "content-type: application/json" \
	-d '{"n":3,"playerASecret":"012","playerBSecret":"345","playerAName":"Ada","playerBName":"Ben"}'
curl -X POST https://bulls-cows-api.lzray.cloud/api/duel/turn \
	-H "content-type: application/json" \
	-d '{"sessionToken":"...","playerAGuess":"345","playerBGuess":"012"}'

WebSocket

Machine solving /ws/solve

const ws=new WebSocket("wss://bulls-cows-api.lzray.cloud/ws/solve");
ws.onmessage=ev=>console.log(JSON.parse(ev.data));
ws.onopen=()=>{
	ws.send(JSON.stringify({
		id:"m1",
		type:"next",
		payload:{
			n:4,
			mode:"dynamic",
			engine:"wasm",
			strategy:"feedback_count",
			history:[{guess:"0123",a:1,b:1}],
			options:{allowFallback:true,exactThreshold:3000}
		}
	}));
};

PVP /ws/pvp

const ws=new WebSocket("wss://bulls-cows-api.lzray.cloud/ws/pvp");
ws.onmessage=ev=>console.log(JSON.parse(ev.data));
ws.onopen=()=>{
	ws.send(JSON.stringify({
		id:"start",
		type:"start",
		payload:{n:4,humanSecret:"1234",computerStrategy:"minimax_worst_bucket",computerMode:"dynamic",computerEngine:"wasm"}
	}));
};
// Later turns can omit sessionToken because the connection keeps the latest token.
ws.send(JSON.stringify({
	id:"turn1",
	type:"turn",
	payload:{humanGuess:"5678",computerFeedback:{a:1,b:1}}
}));

Duel /ws/duel

const ws=new WebSocket("wss://bulls-cows-api.lzray.cloud/ws/duel");
ws.onmessage=ev=>console.log(JSON.parse(ev.data));
ws.onopen=()=>{
	ws.send(JSON.stringify({
		id:"start",
		type:"start",
		payload:{n:3,playerASecret:"012",playerBSecret:"345",playerAName:"Ada",playerBName:"Ben"}
	}));
};
ws.send(JSON.stringify({
	id:"turn1",
	type:"turn",
	payload:{playerAGuess:"345",playerBGuess:"012"}
}));

Error Codes

All HTTP errors return {"ok":false,"error":{"code":"...","message":"...","details":...}}. WebSocket errors use the same shape.

Use /api/errors for the complete error list.

INVALID_GUESS

The guess has the wrong length, contains non-digits, or repeats a digit.

INVALID_FEEDBACK

The A/B feedback is impossible, such as 3A1B for a four-digit game.

INCONSISTENT_HISTORY

The history contradicts itself and leaves no possible secret.

NEED_TREE_OR_APPROX

The exact dynamic strategy exceeded the threshold. Use a precomputed tree or allow fallback.