DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

Ones Are Wild, So p Is 1/3 and Not 1/6 — and That One Number Decides Whether Your Bot Bluffs

Sixty-five games into building one a day, every board had been fully visible. Chess, Hex, Quoridor, and — as of yesterday — Backgammon, which added chance but still landed its dice face-up.

Liar's Dice removes that. You cannot see your opponent's hand, so the state is not a position you can evaluate. It is a belief, and the only evidence you get is what your opponent was willing to bid.

Playable, with every probability computed live: https://dev48.infy.uk/game/day66-liars-dice.html

The number the whole game runs on

A bid says "at least k dice across the table show face f". You can see your own hand, so the question is about the n dice you cannot:

function atLeast(n, j, p){
  if (j <= 0) return 1;          // already satisfied - NOT a tail sum from a negative index
  if (j > n) return 0;
  let total = 0;
  for (let i = j; i <= n; i++) total += choose(n, i) * Math.pow(p, i) * Math.pow(1 - p, n - i);
  return total;
}
Enter fullscreen mode Exit fullscreen mode

And p is 1/3, not 1/6, because ones are wild — a die counts toward f if it shows f or shows a 1.

Use 1/6 and every probability roughly halves. The practical effect is not a small mis-estimate: you get a bot that never bluffs, because every bid above the obvious looks impossible, and calls almost immediately. Invisible in the code, obvious in the play.

The exception is a bid on ones — ones are not wild for themselves, so there p really is 1/6. That asymmetry is also why the raise rules for ones differ: switching to ones lets you halve the count (rounded up), switching away costs double plus one. Both conversions are what hobby implementations drop.

Condition on your own hand, not the table

const mine = countFace(myHand, bid.face);
const needed = bid.count - mine;              // only THIS many from the unseen dice
return atLeast(unseenCount, needed, pFace(bid.face));
Enter fullscreen mode Exit fullscreen mode

Three lines, and they are the difference between a bot that plays and one that recites table odds. Holding two 4s, a bid of 2×4 is already true — probability exactly 1, returned rather than computed, because a tail sum from a negative index is a silent zero in a lot of implementations.

Exact, then checked against real rolls

The closed form is verified against 200,000 actual rolls for every (n, k) pair: worst error 0.0022 at p=1/3, 0.0019 at p=1/6. A sampled estimate would be off by about that much on every call, and those errors compound across a whole game of decisions.

Three of the four failures were my tests

Verification caught four, and only one was the engine:

  • I asserted a forced single die is always the larger. It is only the larger when both dice are individually playable — if the larger has no legal move at all, playing the smaller is forced, not a violation.
  • My blot fixture added a checker instead of moving one, so a conservation check was failing the fixture.
  • I asserted a bid could not raise itself, which was the one that actually found something.

Writing an assertion from memory rather than from the rulebook is its own category of bug.

Part of a from-scratch series — one game a day, vanilla JS, one file, offline: https://dev48.infy.uk/gamefromzero.php

Top comments (0)