# King of the Hill - Liar's Dice

Liar's Dice is a fairly simple dice game. I've seen a few different variations of the rules, but here is the version I'm most familiar with:

• Each player starts with 5d6
• Except when tallying the dice at the end of a round, each player may see their own dice, but not those of any opponent
• At the start of any given round, all players roll whatever dice they currently have
• Then, one player (usually, this is either the winner of the previous round OR the player to the left of the player who started last time; we'll be using the former for this KotH; with a random player starting the first round) makes a guess about how many of a particular number are on the table (ONES ARE WILD)
• Bidding continues to the right, going higher each time (for instance; 3 fives, 3 sixes and 4 twos are all higher than 3 fours, but 3 threes is not; 4 ones is also higher but bidding on ones will probably put you at a disadvantage); until any player calls the player preceding them a liar
• At this point, all players reveal their dice and count the number of the last number bid on on the table altogether
• If the total is lower than the bid, the player who made the bid must give a die to the player who called them a liar, otherwise, the player who called the bidder a liar must give a die to the bidder (so the bidder wins if their are at least as many of that number as he had bid, there don't have to be the exact number)
• When you run out of dice, you lose
• The last player standing wins

For example:

Player one has 1,1,2,4,6
Player two has 1,2,2,3,5
Player three has 1,3,3,4,6
Player one: three sixes.
Player two: four twos.
Player three: four threes.
Player one: five twos.
Player two: six twos.
Player three: six threes.
Player one: six fours.
Player two: Liar!
They reveal their dice and count up the ones (because ones are wild) and the fours.
It turns out that there are, in fact, exactly six fours.
So player two gives player one a die.
They reroll and player one starts the next round.


You must write a bot to play this game. It must implement the following abstract java class:

public abstract class Player {
public Player() {}
public String toString() {
return this.getClass().getSimpleName();
}
public abstract String bid(int yourId, int[] diceEachPlayerHas, int[] yourDice, String[] bids);
}

• You must implement the bid method
• The first argument is your bot's current position in the turn order, the second is an array showing how many dice each player (including yourself) currently has, the third is an array showing the values currently shown on your own dice, and the fourth is an array of all bids made since the start of the current round - will have length 0 if you are making the first bid of the round
• The output should be either a string of the form "number face", or the string "Liar!" to call the previous bidder a liar.
• If your output is formatted illegally, you will be eliminated.
• You may override the toString method, but are not required to. However, you may not edit it in any way that interferes with the readability of the controller's output.
• You are permitted to call any other public methods of the controller, but not its main method.
• You may read and edit only files in the running directory prefixed with your bot's own name
• You are not permitted to take input from any other source
• Instance variables are reset at the start of each new game, but static variables are not.

Scoring

• One set of 1,000 games, with 3-5 players in each, will be simulated each time a bot is added (as soon as three or more bots have been submitted), scored as shown in the controller source (in any given game, you get 1 at the start of each of your turns, 10 each time you capture a die, and 1,000 bonus if you win); enforcing a limit of 5,000 TURNS (not rounds) each game.
• Your bot will be scored by its score from the latest set of games; plus ten times its vote score, if nonnegative. (The latter is unlikely to have a significant effect on the score)

The controller source can be found here.

# Scores as of 2015-06-19:

Badnomial: 434,924 + 6x10 = 424,984
Nobody: 282,329 + 6x10 = 282,389
StraightShooter: 265,205 + 5x10 = 265,255
MostlyHonestAbe: 158,958 + 4x10 = 158,998
The Pirate: 157,005 + 1x10 = 157,015
Statistician: 144,012 + 2x10 = 144,032
Fidelio: 49,973 + 2x10 = 49,993
Absurd Bot: 6,831
DrHouse: 2,638 + 3x10 = 2,668

• You should clarify that the output should be "2 3" and not "two threes" like your example shows. Also, is there a way in the controller to watch a single match? – Cain Jun 9 '15 at 17:50
• Not in the official version, but I'll post an alternate version that lets you do that. – SuperJedi224 Jun 11 '15 at 11:06
• @Geobits: If you want to. It will put you at a bit of a disadvantage if someone calls you though. – SuperJedi224 Jun 11 '15 at 20:02
• I assume the indices of the arrays are the "ids" of the players, so that diceEachPlayerHas[yourId] = your dice count, and bids[yourId] is your first bid (or null if it's your first turn). Is that correct? – Not that Charles Jun 11 '15 at 21:24
• I've seen games where some submissions play more games than others (Nobody: 414 games, Straight Shooter: 409 games). This isn't fair, can you please fix this? – CommonGuy Jun 17 '15 at 6:45

# Nobody

Tries to guess the dice from other players. Calls other bots liars if it doesn't know what to do.

Edit: Fixed a problem where Nobody would bid forever, never calling Liar.

public class Nobody extends Player{

@Override
public String bid(int myId, int[] diceEachPlayerHas, int[] myDice,
String[] bids) {
if (bids.length == 0)
return "1 2";
int wilds = 0;
int players = Controller.numPlayers();
double myKnowledge = (double)diceEachPlayerHas[myId]/Controller.diceInPlay();
double previousKnowledge = (double)diceEachPlayerHas[(myId-1+players)%players] / Controller.diceInPlay();
int[] dice = new int[5];
for (int i = 0; i < myDice.length; i++) {
if (myDice[i] == 1) {
wilds++;
} else {
dice[myDice[i]-2]++;
}
}
wilds = (int) (1/myKnowledge+wilds-1)+1;
for (int i = 2; i <= 6; i++) {
dice[i-2] += wilds;
}
String best = "0 0";
for (int i = 2; i <= 6; i++) {
if (Controller.isGreaterThan(dice[i-2] + " " + i, best)) {
best = dice[i-2] + " " + i;
}
}
if (Controller.isGreaterThan(best, bids[bids.length - 1])) {
return best;
}
if (previousKnowledge > 0.4) {
int prev = Integer.valueOf(bids[bids.length - 1].split(" ")[0]);
int prevFace = Integer.valueOf(bids[bids.length - 1].split(" ")[1]);
if (dice[prevFace - 2] +2 >= prev)
return (prev+1) + " " + bids[bids.length - 1].split(" ")[1];
}
return "Liar!";
}
}

• Your last set of updates really seem to have helped. – SuperJedi224 Jun 12 '15 at 15:21

Badnomial, the bot that makes bad decisions based on binomial distributions: Edit: Fixed a stupid mistake in the probability calculations, now accounts for next Bidder as well as previous.

    public class Badnomial extends Player{

public String bid(int myId, int[] diceEachPlayerHas, int[] myDice, String[] bids) {
int[] dieCounts = new int[7];
for(int i:myDice)
dieCounts[i]++;
for(int i=2; i<7; i++)
dieCounts[i] += dieCounts[1];

if(bids.length > 0)
{
String[] lastBid = bids[bids.length - 1].split(" ");
int bidCount = Integer.valueOf(lastBid[0]);
int bidDie = Integer.valueOf(lastBid[1]);
// Check if I hold a better bid
boolean betterBid = false;
int myBidDie;
int myBidCount;
int myHighestCount = 0;
int myHighDie = bidDie +1;

for(int i = 2; i < 7; i++) {
if(dieCounts[i] >= myHighestCount) {
myHighestCount = dieCounts[i];
myHighDie = i;
}
}
if((myHighestCount > bidCount) || ((myHighestCount == bidCount) && (myHighDie > bidDie))) {
betterBid = true;
myBidDie = myHighDie;
myBidCount = myHighestCount;
}

if(betterBid == false) {
int unknownDice = Controller.diceInPlay() - myDice.length;
int myDiceNeeded = bidCount - myHighestCount;
if(myHighDie <= bidDie)
myDiceNeeded++;
int previousBidder = myId - 1;
if(previousBidder < 0)
previousBidder = Controller.numPlayers() -1;
int bidderDiceNeeded = bidCount - dieCounts[bidDie] - (int)(diceEachPlayerHas[previousBidder]/3 +1);
int bidderUnknown = Controller.diceInPlay() - diceEachPlayerHas[previousBidder] -myDice.length;
int nextBidder = myId + 1;
if(nextBidder == Controller.numPlayers())
nextBidder = 0;
int nbDiceNeeded = myDiceNeeded - (int)(diceEachPlayerHas[nextBidder]/3 +1);
int nbUnknown = Controller.diceInPlay() - diceEachPlayerHas[nextBidder];
//float myChances = (unknownDice/3 - myDiceNeeded)/((float)unknownDice/9);
//float bidderChances = (bidderUnknown/3 - bidderDiceNeeded)/((float)bidderUnknown/9);
double myChances = 1 - cumBinomialProbability(unknownDice, myDiceNeeded -1);
double bidderChances;
if(bidderDiceNeeded > 0)
bidderChances = 1- cumBinomialProbability(bidderUnknown, bidderDiceNeeded -1);
else bidderChances = 1.0;
double nbChances;
if(nbDiceNeeded > 0)
nbChances = 1- cumBinomialProbability(nbUnknown, nbDiceNeeded -1 );
else nbChances = 1.0;
if(((myChances < .5) && (nbChances <.5)) || (bidderChances < .2))
return "Liar!";
}

return (bidCount+1) + " " + myHighDie;
}

return 2 + " " + 2;
}

private double cumBinomialProbability(int n, int k) {
double sum = 0;
for(int i = 0; i <=k; i++)
sum += binomialProbability(n, i);
return sum;
}

private double binomialProbability(int n, int k) {
double nfact = 1;
double dfact = 1;
int greater;
int lesser;
if((n-k) > k) {
greater = n - k;
lesser = k;
}
else {
greater = k;
lesser = n-k;
}
for(int i = greater+1; i <= n; i++)
nfact = nfact * i;
for(int i = 2; i <= lesser; i++)
dfact = dfact * i;
return (nfact/dfact)*(Math.pow((1.0/3), k))*Math.pow(2.0/3, (n-k));
}

}


It tries to determine whether it should bluff or call Liar based on estimated cumulative binomial distributions for itself and the previous and next bidders' chances of having their needed dice present.

Basically, it calls Liar if the previous Bidder is very likely to be a Liar or if it feels it that both it and the next Bidder are more likely lying than not.

• With these changes, Badnomial actually seems remotely competent vs the other bots. – InactionPotential Jun 19 '15 at 11:43

# Straight Shooter

He plays it straight and doesn't bluff. He's also naive enough to think that others do, too, so he never calls liar unless the bid goes over the total number of dice in play (minus his own dice that don't match the bid).

To be a bit more conservative than the exact expected number for each die, he doesn't count his own wilds, but assumes others have a uniform distribution. With the current four players, either he or MostlyHonestAbe came up first each time, with fairly close scores.

I'm assuming the minimum bid is 2 2. If a bid of one die (or bidding ones) is allowed, let me know so I can make that change.

public class StraightShooter extends Player{
public String toString(){return "Straight Shooter";}
public String bid(int me, int[] numDices, int[] dice, String[] bids){
int[] counts = new int[7];
double[] expected = new double[7];
int unknown = Controller.diceInPlay() - dice.length;
for(int i:dice)
counts[i]++;
for(int i=2;i<7;i++)
expected[i] = counts[i] + unknown / 3d;
int bidCount = 2;
int bidDie = 2;
if(bids.length > 0){
String[] lastBid = bids[bids.length-1].split(" ");
bidCount = Integer.valueOf(lastBid[0]);
bidDie = Integer.valueOf(lastBid[1])+1;
int possible = Controller.diceInPlay();
for(int i=2;i<7;i++)
if(i != bidDie)
possible -= counts[i];
if(bidCount > possible)
return "Liar!";

if(bidDie > 6){
bidDie = 2;
bidCount++;
}
}
double best = Double.MAX_VALUE;
int bestCount = bidCount;
int bestDie = bidDie;
for(int count=bidCount;count<=Controller.diceInPlay();count++){
for(int die=bidDie;die<7;die++){
double score = Math.abs(expected[die]-bidCount);
if(score < best){
best = score;
bestCount = count;
bestDie = die;
}
}
bidDie = 2;
}
return bestCount + " " + bestDie;
}
}

• This and MostlyHonestAbe are both so hesitant to lie or call liar, there's some games going to 2000 turns when I test haha. :P – Cain Jun 11 '15 at 21:05
• Same in mine. That's okay, though, because every turn is an extra point toward the final score. If I last 2000 turns and don't win, that's better than winning after 100 in my book ;) – Geobits Jun 11 '15 at 21:15
• I just had to go look at the scoring rules again. Whole new game XD – Cain Jun 11 '15 at 21:17
• Yea, with this scoring it seems like the optimum strategy might be to be as conservative as you can, and just rack up points. Maybe there's something better, but I can't see it. – Geobits Jun 11 '15 at 21:19
• I'm not sure it would make much difference. Being conservative would still be an advantage, just because you have a lower chance of losing a die. The reason more people don't play that way in real life is because it's just boring, but what is boredom to a bot? – Geobits Jun 11 '15 at 21:24

## MostlyHonestAbe

Abe makes conservative guesses about the rest of opponents die, and then then stays honest until he doesn't think there are enough dice to beat the current bid. At this point he bluffs once, then calls liar the next time.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;

public class MostlyHonestAbe extends Player{

final boolean debug = false;
boolean bluffedOnce = false;
PrintStream out;
@Override
public String bid(int myId, int[] diceEachPlayerHas, int[] myDice, String[] bids) {
try {
File f = new File("abe.log.txt");
out = new PrintStream(f);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
if(debug){
out = System.out;
}

//reset bluff counter on the first round
if(bids.length < diceEachPlayerHas.length){
bluffedOnce = false;
}

//Is it the first bid?
if(bids.length == 0){
out.println("I go first");
return lowestViableBid(1,1, myDice, diceEachPlayerHas, true);
}

out.println("Last bid = " + bids[bids.length - 1]);
out.print("My Dice = ");
for(int d : myDice){
out.print(d + ", ");
}
out.println();

//What was the last bid?
String[] lastBid = bids[bids.length -1].split(" ");
return lowestViableBid(Integer.parseInt(lastBid[1]), Integer.parseInt(lastBid[0]), myDice, diceEachPlayerHas, false);

}

//Lowest honest bid, or liar
private String lowestViableBid(int highestVal, int highestCount, int[] myDice, int[] otherDice, boolean firstTurn){

//Make a better array for the dice
//Include what the other players probably have
int wilds = numDie(1, myDice);
int[] diceCount = new int[6];
diceCount[0] = wilds;
int otherPlayerExpectedValue = 0;
for(int d : otherDice){
otherPlayerExpectedValue += d;
}
otherPlayerExpectedValue -= myDice.length;
out.println("Number of other dice = " + otherPlayerExpectedValue);
otherPlayerExpectedValue = otherPlayerExpectedValue / 4;
//Note: Other player expected value is biased low, counting wilds the number should be divided by 3.

out.println("playerExpectedVal = " + otherPlayerExpectedValue);
for(int i = 1; i < 6; i++){
diceCount[i] = numDie(i + 1, myDice) + wilds + otherPlayerExpectedValue;
}

//What's my array look like?
for(int i = 0; i < diceCount.length; i++){
out.println("diceVal = " + (i + 1) + ", diceCount = " + diceCount[i]);
}

//Can I bid the same number, but higher dice val?
for(int diceVal = highestVal + 1; diceVal <= 6; diceVal++){
if(diceCount[diceVal - 1] >= highestCount){
out.println("1.Returning " + highestCount + " " + diceVal);
return highestCount + " " + diceVal; }
}

for(int diceNum = highestCount + 1; diceNum <= myDice.length; diceNum++){
for(int diceVal = highestVal + 1; diceVal <= 6; diceVal++){
if(diceCount[diceVal - 1] == diceNum){
out.println("2.Returning " + (diceNum) + " " + diceVal);
return (diceNum) + " " + diceVal; }
}
}

if(firstTurn){ return "1 2"; }
//If this is the first time I'm out of my league, bluff a round before calling liar.
if(!bluffedOnce){
out.println("bluffing " + (highestCount + 1) + " " + highestVal);
bluffedOnce = true;
return (highestCount + 1) + " " + highestVal;
}
out.println("Returning Liar!");
//Well, wouldn't want to lie
return "Liar!";
}

private int numDie(int i, int[] myDice){
int result = 0;
for(int j : myDice){
if(i == j){ result++; }
}
return result;
}
}

• Are you kidding me? I was less than five minutes from posting HonestAbe. Now I have to think up a new name :P – Geobits Jun 11 '15 at 18:41
• Can't have a game with Liar in the name without an Abraham Lincoln reference somewhere. – Cain Jun 11 '15 at 21:11

# Dr. House

Everybody Lies!

public class DrHouse extends Player
{
public String bid(int yourId, int[] diceEachPlayerHas, int[] yourDice, String[] bids)
{
return "Liar!";
}
}

• I suggest adding special logic for when you have the first bid of the round. – SuperJedi224 Jun 15 '15 at 18:46
• @SuperJedi224 I imagine that the bot then considers the controller telling him that it is his turn to be a liar – Nathan Merrill Jun 15 '15 at 19:01
• Made my day lol – Rohan Jhunjhunwala Jul 17 '16 at 23:18

# Fidelio

This bot know that only his most recurrent value will lead him to victory, so he stick with it. He assume there's a portion of everyone's dice that is the same as his, if anyone bid more than that portion, he assume he's a liar.

public class Fidelio extends Player
{
final String LIAR ="Liar!";
@Override
public String bid(int yourId,
int[] diceEachPlayerHas,
int[] yourDice,
String[] bids)
{
int[] myDices = new int[6];
int valueToBid=1;
for(int i : yourDice)
myDices[i-1]++;
for(int i=2;i<myDices.length;i++)
if(myDices[i]>=myDices[valueToBid])
valueToBid=i;
if(bids.length==0)
return 2+" "+valueToBid;
int sum=0;
String[] lastBidString=bids[bids.length-1].split(" ");
int[] lastBid = new int[2];
lastBid[0] = Integer.parseInt(lastBidString[0]);
lastBid[1] = Integer.parseInt(lastBidString[1])-1;
for(int i : diceEachPlayerHas)
sum+=i;
sum-=yourDice.length;
if(lastBid[0]>sum/3+myDices[lastBid[1]]+myDices[0])
return LIAR;
if(lastBid[1]>= valueToBid)
{
if(lastBid[0]>=myDices[0]+myDices[valueToBid]+sum*2/5)
return LIAR;
return (lastBid[0]+1)+" "+myDices[valueToBid];
}
return lastBid[0]+" "+valueToBid;
}
}


I hope he will do some good work :).

• I'm getting an IndexOutOfBoundsException on line 13. Remember that arrays are 0-indexed in java. – SuperJedi224 Jun 15 '15 at 11:14
• Now I'm getting one on the other end on line 19, for an index of -1. It would appear that it was trying to read the last element from an empty array, you should include a check for that. – SuperJedi224 Jun 15 '15 at 11:55
• Fixed, the check if(bids.length==0) was done after I used bids... – Katenkyo Jun 15 '15 at 12:19
• Oh, I had just proposed another possible solution, but this will probably work as well. – SuperJedi224 Jun 15 '15 at 12:20
• Ah, so this suggested edit is no longer needed? – mbomb007 Jun 15 '15 at 21:03

# Statistician

You have 1/3 chance of having any number other than aces. One guy once tell me that not checking your dices and juste knowing the odds can make you win this game. EDIT : It was bidding too high. But it doesn't improve the score a lot.

public class Statistician extends Player{
public String toString(){return "Statistician";}
public String bid(int me, int[] numDices, int[] dice, String[] bids){
int totalDices = 0;
int currentBid, max;
for (int i : numDices)
totalDices += i;
max = totalDices/3;
if(bids.length>0){
currentBid = Integer.valueOf(bids[bids.length-1].split(" ")[0]);
if(currentBid>max)
return "Liar!";
}
return max+" 6";
}
}


# Absurd Bot

Makes the claim that all of the dice are 6's unless it can't. If the bot can't do that, it means that this is an impossible situation or nearly impossible situation. Because of this, it calls liar. I am curious as to how effective this bot will be.

public class AbsurdBot extends Player {
@Override
public String bid(int yourId, int[] diceEachPlayerHas,int[] yourDice,String[] bids)
{
String[] lastbid;
int a, b, d;
d = 0;
for (int dice : diceEachPlayerHas)
d += dice;
if (bids.length != 0)
{
lastbid = bids[bids.length-1].split(" ");
a = Integer.parseInt(lastbid[0]);
b = Integer.parseInt(lastbid[1]);
if (a > d || a == d && b == 6)
return "Liar!";
}
return d + " 6";
}
}

• As for how effective: Its main function seems to be handing dice to whichever player follows it :P – Geobits Jun 12 '15 at 14:06
• @Geobits I fixed the code. This is what happens when you try to jump into a programming language you haven't programmed in before... – frederick Jun 12 '15 at 14:12
• @Geobits Thanks for all the help. I think that this finally works properly now. Does it? (Java is confusing) – frederick Jun 12 '15 at 14:23
• Yea, it runs now. The strategy is insanely suicidal, though. It scores only ~2% of the next-lowest player. – Geobits Jun 12 '15 at 14:25
• @Geobits I never tried running it against the other players. Did you run it against the others? – frederick Jun 12 '15 at 14:29

# The Pirate

I made a few simple bots while testing the controller, and this is the only one that's really any good.

Will likely be improved upon later.

import java.util.Arrays;
import java.util.Scanner;

public class Pirate extends Player{
public Pirate() {
}
public String toString(){
return "The Pirate";
}
private String bid(int[] t,int tol){
int[]z=t.clone();
Arrays.sort(z);
int j=0;
for(int i=0;i<6;i++){
if(t[i]==z[5]){j=i;break ;}
}
return (tol+t[j])+" "+(j+1);
}
@Override
public String bid(int yourId, int[] diceEachPlayerHas, int[] yourDice,
String[] bids) {
int[] t=new int[6];
for(int i=0;i<yourDice.length;i++){
t[yourDice[i]-1]++;
}
for(int i=1;i<t.length;i++)t[i]+=t[0];
int tol=(Controller.diceInPlay()-yourDice.length)/4;
if(bids.length==0)return bid(t,1);
Scanner i=new Scanner(bids[bids.length-1]);
int x=i.nextInt(),y=i.nextInt();
i.close();
if(t[y-1]>x)return (t[y-1]+2)+" "+y;
int nd=Controller.diceInPlay();
if(x>nd+t[y-1]-yourDice.length)return "Liar!";
if(Controller.isGreaterThan(bid(t,tol), bids[bids.length-1])){
int z=Controller.valueOf(bids[bids.length-1]);
for(int j=1;j<=tol;j++)if(Controller.valueOf(bid(t,j))>z)return bid(t,j);
}
return "Liar!";
}
}