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
| strategy | modes | description |
|---|---|---|
first_remaining | dynamic, tree | After filtering, choose the lowest indexed remaining candidate. |
minimax_worst_bucket | dynamic, tree | Minimize the largest feedback bucket for a more stable worst-case path. |
expected_size | dynamic, tree | Irving, 1978. Minimize expected remaining set size, equivalent to minimizing the sum of squared bucket sizes. |
feedback_count | dynamic, tree | Kooi, 2005. Maximize the number of non-empty feedback buckets. |
optimal | tree | Built-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
| engine | scope | description |
|---|---|---|
js | dynamic | The default TypeScript path. It is kept as the compatibility path and is useful for ordinary calls and comparison tests. |
wasm | dynamic | The 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_GUESSThe guess has the wrong length, contains non-digits, or repeats a digit.
INVALID_FEEDBACKThe A/B feedback is impossible, such as 3A1B for a four-digit game.
INCONSISTENT_HISTORYThe history contradicts itself and leaves no possible secret.
NEED_TREE_OR_APPROXThe exact dynamic strategy exceeded the threshold. Use a precomputed tree or allow fallback.