How do I program a 1v1 Liar's Dice AI?

0 favourites
From the Asset Store
Fully animated six sided dice, top down wooden tray and dice throwing cup
  • Sorry, I know this is an incredibly dense question, but I am struggling big time here and I don't know what else really to do at this point aside from hiring a person smarter than me to tackle this issue.

    So I am creating a game that, at its core, is a 1v1 game of Liar's Dice. For those who need an explaination, two players shake up 5 six sided die in a cup and place them on a table, hidden from each other's view. You may view your own hand, but you may not see the other player's hand. The starting player then wagers how many total dice are presenting a certain pip on the entire table. A wager of "Three 2s" would be claiming "There are at least three dice showing the 2-pip side".

    The next player then must either raise the wager, either by raising the number of wagered dice (Four 2s) or pips (Three 3s), or they may call the other player a Liar to reveal all hands on the table. If the player who calls is correct, then the liar loses a die. If the player who calls is incorrect, then the caller loses a die.

    Play continues until one player loses all of their dice.

    In this game, 1s count as wilds, meaning they represent any face. So a hand of 1, 1, 2, 3, 4 could call "three 3s" in absolute safety since the 1s represent the remaining 3s.

    So here's where I am getting stuck: every article that explains the math of this game just boggles my mind and I have no idea how to break this concept down. I am unsure how to program an AI for the enemy player if I can't understand the probability of the game itself. I struggled with high school math. I struggled with using Pi to calculate the tiles to be exploded in a fighting game I made two years ago.

    I have the player's hand loading into an array and the enemy hand loading into an array. I also have the enemy hand loading into a special array called "known dice" since the enemy player will cheat during the game to gain information they shouldn't normally have, but that's another day's problem for now, although I assume if I build this AI to work off of the "known dice" array now, then it will still work later when I add cheating.

    So Wikipedia breaks it down as "The expected quantity of any face value among a number of unknown dice is one-sixth the total unknown dice. A bid of the expected quantity (or twice the expected value when playing with wilds), rounded down, has a greater than 50% chance of being correct and the highest chance of being exactly correct."

    So this means first I have to take the number of unknown dice and divide it by 6. Then I take the result and double it, then round it down, and this will give a resut of a "probably true" bid if I include the known dice? Basically I need to create a conditional tree for how many unknown dice there are on the table, do that piece of math, and then compare it to what the player has claimed and go from there.... I think?

    Many of these articles are going into these massive formulas that I just can't wrap my head around. This one is basically Latin to me.

    Can anyone offer me some assistance here?

  • Does the AI have to play perfectly? If not, then just break down what you would do in any given situation in terms of available information and decision to be made. The key is to really identify why you make the choices you do and translate that into conditions.

    Otherwise, you're gonna need to understand the math behind it or find someone to do it for you.

  • End goal is to have the AI begin play overconfident and sloppy and then tighten up over a few lost rounds til it plays perfectly.

    In that case I feel it's better to understand the math and create the perfect play, then engineer mistakes into it.

    Do I like... I dunno, get a math tutor to explain it? Or a professor?

  • The easiest solution is to give the AI the ability to see player's numbers.

    When it knows exactly how many 2s on the table, it can't lose. So all you need to do is to add some randomness to its decisions.

    Say, a 30% chance of bluffing, 20% chance of saying "lie" when the wager is actually correct etc.

    It won't be the smartest AI, but many players probably won't notice that it's cheating.

  • What aspect of the math do you need help with specifically?

    Calculating the probability that that the other player’s dice have at least n dice of a certain number?

    Say the other player claims 5 twos. And you have 2 twos, then you’d only need to calculate the probability that the other player has at least 3 twos. (Since 5-2=3)

    After that if the probability is too low you can call the other player a liar.

    The only other action is making another claim. For that you’d take one of the numbers on your dice, count the number of dice you have with that value and add 1,2,3,4 or 5 to it. It just depends how risky you want to be.

    Anyways you want a way to get the probability that the other player has at least n dice with a certain value. N is the number of dice.

    The math way to do it is with “probability math”. You can find some nice tutorials online that explain that well. That’s the only math stuff that that article you linked uses for the most part. And really the only strange math operation is the “!” Whitch is called a factorial. For example 4!=4*3*2*1

    If you don’t like math then you can also brute force it. Just try all possible dice rolls and count the number of them with at least n dice with a certain value. For 5 dice that’s 6^5 or 7776 possible rolls. No issue to do with a loop.

    In a similar vein you could find an approximate probability by only sampling some of the possible rolls. Basically roll all 5 dice 1000 times randomly, and count the number of them with n dice of a certain value.

    Another way is to not count or calculate anything. Just have the probabilities pre calculated in an array and reference that.

  • I decided to test the brute force method. It's plenty fast but I'm not sure it's simpler than just using a formula. Guess I'll try making a formula that does the same thing with probability math later.

    I ended up building a full game to test it. You control both players and the ai provides a probability by each button based on guessing the other player's dice. To play as a player don't look at the other player's dice, and to play as an ai press buttons based on the probability by it.

    I probably fudged the rules a bit. I'm assuming you can increase the pip or count by more than one, and the first player gets to start at any pip or count they like. I just had it start at "1 two".

    Anyways, it's not the cleanest or most readable but it was meant to test calculating the probability and seeing how the ai could use that to make a decision. It seems like a viable way to go about it.

    dropbox.com/scl/fi/lqfpg3ur6idb09b50oeup/liars_dice.capx

    Now lets look at the math way to do it. That article you posted gets you most of the way there...

    K = (number of dice in the claim) - (number of dice in the player's hand that match the pip or is wild (one))
    N = (number of dice that the other player has)
    Probability = 0
    
    compare: K=0
    -- set probability to 1
    else
    compare: K<=N
    Repeat K times
    -- add factorial(N)/(factorial(loopindex+1)*factorial(N-loopindex-1))*((1/3)^(loopindex+1))*((2/3)^(N-loopindex-1)) to probability
    
    function factorial(num)
    -- return (num=0 | num=1)?1:factorial(num-1)

    Here's the same thing in formula form in case I made typos.

    Anyways, hopefully some of that helps.

  • Wow, this kind of blows my mind, honestly. I had read a few things on how to do this and came back to see if any tips had been posted before I got into it, but this example project not only eclipses my math skills but also is written far more beautifully and efficiently than I have ever done. I feel like a caveman on multiple fronts, haha.

    I am looking inside of it now to see what I didn't know that I didn't know. I didn't even know "Pick by Evaluate" existed.

  • Thanks but I often combine too many things together so it’s not super readable to others. I also did some ugly shortcuts toward the end which made it even less readable. But my goal was to test my ideas instead of just talk about them. If any part of it is useful that’s a good thing.

    You’ll probably want to do it in your own style in a way you understand.

    By far the easiest way to go about the ai would be with simple math and manually inputting the probability numbers.

    Count = claimCount - (number of ones) - (number of dice=claimPip)

    If (number of other players dice)=5

    If count =5

    — set probability to 0.045

    … and so on for ever combination of dice count and count. In a more compact way you can have all the probability numbers in an array and you can access it with one event. That link you posted has a table of some of the probabilities. Or you can calculate them with that formula.

    An amusing side effect of writing the probabilities down manually is you could just use arbitrary numbers and you’d change the ai’s decision making.

  • So, I was able to connect with a programming buddy and he was able to walk through the process with me in a way that I could barely sort of grasp. I am now working on trying to create the formula within Construct since he was able to show me how he would do it in Python, and while he doesn't understand C3, he knows Java and Python. I have a semi-decent handle on C3, but I struggle with coding langauges.

    I have now discovered what you meant by "factorial", being like 5! meaning the result of 5*4*3*2*1. In Python, this would just be the result of "5!", but Construct, as far as I can tell, doesn't have a built in ability to do factorials. So my friend said I needed to write a function for a factorial in order to call that function and drop it into my event sheet.

    I have made a simple project where I have a number entry box and a button to click. Typing a number into the box and clicking the button should return the factorial.

    However, I am now discovering that I didn't know as much about functions as I thought I did previously. I have used functions as events in the past without a need to return a value, mostly for things like the player being hurt and subtracting health, temporary invincibility on hit and stuff like that. I had to learn how to even call a function with a return value, since it no longer appears in the event list. Since I got THAT working, my function only half works and I am a bit stuck now.

    When the program is started, if you type any number 1 or lower, it returns the same number as a result in the text. If you, at any time, type a number larger than 1, the program freezes up and no longer works.

    So I have a few things that I might be doing wrong. When I call my function using "Set Result to Functions.FactorCalculation(0)"... what does the "0" represent? That's the first item in the list of parameters that I defined, right?

    Where does the return value go? I can't seem to find anything in the Functions.FactorCalculation expression that represents the return value itself. I'm not quite sure how to actually access whatever is being returned from the expression. I feel that I may be simply misunderstanding how to use a function with a return value.

  • That works, I appreciate it. I am trying to make sure I understand this correctly so I can use it in the future rather than just copying someone else's work.

    When you use the expression Function.Factorial(5), this calls the function and sends the number 5 into the "N" parameter of the function.

    I physically wrote down the math step by step 5 times to understand how the loop works. I get it now, I think. 1 * (5 - 0) = 5, sets it to output. 5 * (5 - 1) = 20. Sets it to output, and so on.

    Once the loop is complete, it then takes output and makes it the return. The return is then returned to the expression that we typed earlier and inserts it into the parameter parenthesis, so that it basically says "Set result to Function.Factorial(120)". return is then set to whatever is inside the parenthesis.

    Am I getting it, roughly?

  • When a return type (when creating the function) and value (return value action) is set, the function can be used as an expression, where it will return the return value when used.

    = 5!

    = 5 * 4 * 3 * 2 * 1

    = (5-0) * (5-1) * (5-2) * (5-3) * (5-4)

    Where n=5 and loopindex is the number of times the loop has repeated, starting from 0. Each iteration of the loop is multiplied by the result of the previous one, stored in output. We're starting with output=1, so the first iteration of the loop is 1*(5-0) (1*n-loopindex), which results in 5. The next loop will be 5*(5-1), and so on.

    Once the loop is complete, it then takes output and makes it the return. The return is then returned to the expression that we typed earlier and inserts it into the parameter parenthesis, so that it basically says "Set result to Function.Factorial(120)". return is then set to whatever is inside the parenthesis.

    To clarify, it doesn't really replace and insert into the parenthesis. The number in the parenthesis is the parameter used to run the function. The entire "Function.Factorial(5)" function, as an expression, equals (or is replaced by, if you want to use that wording) whatever is set as the return value inside the function.

    So in the end that action is in effect "Set result to 120". Note this is different than "Set result to Function.Factorial(120)", which would be... a very big number.

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • Once the function is finished, the local number "output" clears out and returns to 1 the next time the function is run, right? It doesn't clear out during the loops since the function hasn't yet finished.

  • A local variable/number/value is not normally held in memory outside of the event block it is used in, so yes it resets to 1 each time. This is a key property of how local variables work as opposed to global variables. Note that this can be overwritten with the "static" option set, as described in the manual.

    construct.net/en/make-games/manuals/construct-3/interface/dialogs/event-variable

  • To clarify, it doesn't really replace and insert into the parenthesis. The number in the parenthesis is the parameter used to run the function. The entire "Function.Factorial(5)" function, as an expression, equals (or is replaced by, if you want to use that wording) whatever is set as the return value inside the function.

    So in the end that action is in effect "Set result to 120". Note this is different than "Set result to Function.Factorial(120)", which would be... a very big number.

    Ahhh ok that makes more sense.

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)