The Android likes dice – 10,000 version 1.5 released

Hi all,

I’ve released a new version of the 10,000 – The Dice Game app on Google Play. This release features a few things.

  • You can now play against the computer
  • German translation
  • Better randomness of the dice rolls

The option to play against the computer is something that has been on my mind for quite some time and I’m happy to say that the result is now here. The AI proved to be a bit more challenging to implement than I first thought, but from my time in the computer industry I was hardly surprised. Things are in my opinion never straight forward 😉 Nevertheless I’m quite pleased with the result.
I’ve aimed for the computer to be beatable but not a walkover, relying on a set of simple rules to govern its behaviour. I also tried to make it animated so you can follow the steps it takes when playing and who knows, maybe you’ll even get a new trick or two. I hope you’ll enjoy it!

Happy rolling 🙂

Building an AI (part 2)

This is part 2 of a series on building an AI for the dice game “10.000”. To read part 1 go here.

 

The mission

My goal is that at the end of this post I’ll have a set of formal rules that can be coded into the AI. I want to know what the odds are for scoring when rolling any number of dice and how many points I can expect to score. I also want to make a simple decision tree for when to roll and when not to roll.

 

Calculating the odds

Since I’m no genius when it comes to statistics, I read a few articles on the dice probabilities at different rolls. For those of you that are interested you can find some mathematics explained here, here and here. These articles give us a basis for determining what the chances are of not being able to score points on a given roll.

In the following I’ll be listing tables showing the number of combinations that score points and the probability of these when rolling X dice (the columns “# successes” and “Success %” respectively). Conversely I’ve also listed the chance of not rolling points (the “Failure %” column). In the first table I’ll assume no 3-of-a-kind has been rolled or what I’d like to call basic probability.

The total number of combinations is calculated as 6^x, where x is the number of dice rolled.

Basic probability

Dice # Successes Success % Failure %
6 45.576 97,69 % 2,31 %
5 7.176 92,28 % 7,72 %
4 1.092 84,26 % 15,74 %
3 156 72,22 % 27,78 %
2 20 55,56 % 44,44 %
1 2 33,33 % 66,67 %

 

When rolling 1, 2 or 3 dice it also becomes relevant if there has been rolled 3-of-a-kind previously, since it increases your odds of rolling dice that can score points. In the second table I’ll assume that there has been rolled 3-of-a-kind of 2’s, 3’s, 4’s or 6’s, since these affect the odds.

3-of-a-kind already rolled

Dice # Successes Success % Failure %
3 192 88,89 % 11,11 %
2 27 75,00 % 25,00 %
1 3 50,00 % 50,00 %

 

Diving deeper into the combinations

These odds can be divided further into the different combinations of dice. I’ll list a separate table for every number of dice rolled, sorted by their success probability in ascending order. The column “Combinations” is the name of the combination you scored. “# Successes” and “Success %” express the same as before.

The row “Two 3-of-a-kind” have been included since we use all the dice and unlock all 6 on successive rolls. The row “1 or 5” is for the cases when none of the other combinations occurs.

 

Rolling 6 dice

Combinations # Successes Success %
6-of-a-kind 6 0,01 %
5-of-a-kind 180 0,39 %
Two 3-of-a-kind 300  0,64 %
Straight 720  1,54 %
Three pairs 1.800  3,86 %
4-of-a-kind 2.250  4,82 %
3-of-a-kind 14.400  30,86 %
1 or 5 25.920  55,56 %

 

If you sum up “# Successes” you’ll luckily end up with 45.576, which is the same number as listed in the basic probability table above. In the next two tables the process is repeated for four or five dice.

 

Rolling 5 dice

Combinations # Successes Success %
5-of-a-kind 6 0,08 %
4-of-a-kind 150 1,93 %
3-of-a-kind 1.500 19,29 %
1 or 5 5.520 70,99 %

 

Rolling 4 dice

Combinations # Successes Success %
4-of-a-kind 6 0,46 %
3-of-a-kind 120 9,26 %
1 or 5 966 74,54 %

 

The next three tables are for rolling one, two or three dice without having rolled a 3-of-a-kind previously.

 

Rolling 3 dice

Combinations # Successes Success %
3-of-a-kind 6 2,78 %
1 or 5 150 69,44 %

 

Rolling 2 dice

Combinations # Successes Success %
1 or 5 20 55,56 %

 

Rolling 1 dice

Combinations # Successes Success %
1 or 5 2 33,33 %

 

You could also be rolling one, two or three dice when you’ve already rolled 3-of-a-kind. The following tables shows, where I’ve added the row “Add to 3-of-a-kind” for this. Remember the odds only change when the 3-of-a-kind is 2’s, 3’s 4’s or 6’s.

 

Rolling 3 dice with 3-of-a-kind already rolled

Combinations # Successes Success %
3-of-a-kind 5 2,31 %
Add to 3-of-a-kind 91 42,13 %
1 or 5 96 44,44 %

 

Rolling 2 dice with 3-of-a-kind already rolled

Combinations # Successes Success %
Add to 3-of-a-kind 11 30,56 %
1 or 5 16 44,44 %

 

Rolling 1 dice with 3-of-a-kind already rolled

Combinations # Successes Success %
Add to 3-of-a-kind 1 16,67 %
1 or 5 2 33,33 %

 

This covers the odds for every combination of dice.

 

Weighting the odds

We now have some probabilities down that we can use to guide our decisions (and the AI’s) on whether or not we want to roll the dice at any given point. However if you just decided to roll based on the probability of scoring alone, you would not be playing optimally i.e. scoring the most points.

Enter Expected Value and Opportunity Cost.

So what is this?

Expected Value (or EV) is the value we statistically can anticipate getting over the long run from our roll. In this case the score we can add on average. The Opportunity Cost is the value we give up by rolling; being the points we could have banked instead of rolling.

How do we use that when deciding whether or not to roll?

What we basically want to be doing is weighting out the two, by saying if my opportunity cost greater than my EV, I shouldn’t roll. But why is this correct? Let’s take an example. You have one dice left and have already scored a 1, a 5 and 3-of-a-kind of 2’s. Your chance of rolling and scoring is 50%. You could either be rolling a 1 scoring 100, a 5 scoring 50 or a 2 scoring 200 more. This would give you an average score of 116,67, but only on 50% of your rolls, giving you an EV of 58,33. Your opportunity cost is your current unbanked points of 350 times the chance you’ll miss out on points on the next roll giving you 116,67. Since the opportunity cost is greater than the EV you should be banking instead of rolling here.

To put it simply:

If EV > Opportunity cost, then roll.

Otherwise bank the points.

This is not the complete answer though. It would be if we always banked after this roll, but we could choose to roll again and again. Therefore we need to factor in the EV of successive rolls, at least to some extent. But how do we do this, since you can score and therefore remove a different number of dice after each roll? I’m certain that smarter and more mathematically experienced people would be able to solve this puzzle, but I accepted that the heuristic I’m building will not guarantee that the optimal solution would be found every time, which I why I’ll believe I could employ a short cut here. Calculate the chance of having scored all six dice at the end of the roll when rolling any number of remaining dice. Then weight this by multiplying by the EV of rolling all six dice. This post is however getting quite lengthy, so I’ll save this part for now.

 

The Expected Value of a roll

Working on the combinations I listed earlier I will now map out the EV of every combination on every number of dice rolled. By doing this we get the last set of numbers we need to make an equation for when to roll and when to not. The assumption is that you’ll score the maximum number of dice.

 

Rolling 6 dice

Combinations EV
6-of-a-kind 2.000
5-of-a-kind 1.525
Two 3-of-a-kind 1.000
Straight 1.000
Three pairs 750
4-of-a-kind 1.050
3-of-a-kind 575
1 or 5 156

 

Rolling 5 dice

Combinations EV
5-of-a-kind 1.500
4-of-a-kind 1.025
3-of-a-kind 550
1 or 5 139

 

Rolling 4 dice

Combinations EV
4-of-a-kind 1.000
3-of-a-kind 525
1 or 5 121

 

Rolling 3 dice

Combinations EV
3-of-a-kind 500
1 or 5 105

 

Rolling 2 dice

Combinations EV
1 or 5 90

 

Rolling 1 dice

Combinations EV
1 or 5 75

 

Rolling 3 dice with 3-of-a-kind already rolled

Combinations EV
3-of-a-kind 500
Add to 3-of-a-kind 3-of-a-kind value * 108/91
1 or 5 105

 

The fraction in the “Add to 3-of-a-kind” is to account for doubles and triples. I counted these by hand.

 

Rolling 2 dice with 3-of-a-kind already rolled

Combinations EV
Add to 3-of-a-kind 3-of-a-kind value * 12/11
1 or 5 94

 

Rolling 1 dice with 3-of-a-kind already rolled

Combinations EV
Add to 3-of-a-kind 3-of-a-kind value
1 or 5 75

 

That should be enough numbers to feed into the equation.

 

What if any kind of special rules take effect when playing the last round?

During the last round we could use the equation, but it is not enough to bank any number of points. It only matters if your total score exceeds that of all of your opponents. A simple solution is to keep rolling until you have more points (both banked and unbanked) than your opponent.

Put simply:

If banked + unbanked points <= opponents score, then roll.

Otherwise bank

 

That should be enough to implement a set of rules for the AI.

Building an AI (part 1)

Hi again,

After being in hiatus for quite some time I’ve gotten back to programming a bit. I guess there is an ebb and flow with everything.

Back in November I was looking into how I could be Building an AI with ASyncTask, but I never got to feed the thing some rules with regards to how to play the game. This I aim to remedy now. Since this could get pretty lengthy I’m planning a short series as I go though the process of building an AI for the dice game “10.000”.

 

Choosing a method for problem solving

Building an AI can be done in different ways. I haven’t explored the topic that much, but I could see going about problem solving in different ways; neural networks or some other kinds of fuzzy logic, search algorithms or a basic heuristic. I do however have some programming experience with heuristics and I believe that, since we’re talking about a limited problem space like this a heuristic can do the job just fine.

One of the issues at hand is whether or not we want to get the optimal solution i.e. winning as fast as possible. I can see an argument for building an AI that can do this, but I don’t think it would be very fun to play against. The probability built into the game somewhat remedies this, but the AI should strike a balance between being challenging to play against and still being beatable. Another plus for using a heuristic is that they can be made to give us an answer in short time consistently, thus giving the user a better experience.

 

A basic set of rules

When implementing the heuristic I need to come up with some rules that it will consist of. In principle I want the AI mimics the process of a human player asking himself a set of questions at every decision point during his turn and answering them based on a predefined logic (as humans we have the advantage of being able to adjust our evaluation during a game, whereas my AI will have a fixed set of rules).

Questions you could be asking yourself?

  • What is the probability of being able to make points with X dices?
  • What is the reward for rolling the dices?
  • What is the risk for rolling the dices?
  • Is it the last round and do I have the most points?

Joining these together my heuristic becomes:

Rule A) If my chance of success times the reward for rolling the dices is bigger than the chance of failure times the risk, then roll the dice. Otherwise bank the points (if able)

Rule B) If it is the last round and I haven’t got the most points, then keep rolling.

 

What remains to be done?

For now I’ve established a basic set of rules. To determine whether or not the AI should keep rolling I need to feed some numbers into rule A. These numbers I’ll try and calculate in my next post using statistical analysis.

 

That’s it for now 🙂

 

EDIT: To read part 2 go here.

Building an AI with ASyncTask

I’ve wanted to add an AI for the 10.000 game, since giving users a single player option seems like a necessity, if I want to retain them. I for one was getting a little bit tired of playing both sides, if I couldn’t find someone else to play against.

The game before implementing the AI
Up until now the game has been built around the assumption that only users generated input for the game. I didn’t really take any measures to incorporate alternative input methods that an AI could use.

Drawing the architecture of the app you’ll get:

UI –> Model

Needless to say I’ll put a little bit of thought into the architecture, before punching in any lines of code, since there’s no simple way to integrate it in the existing architecture. As the model stands now, it has a set of methods that you can use to manipulate it’s state with, giving me some flexibility.

Examples of this are:

public boolean canRoll()
public void performRoll()
public boolean performLock(int diceNo)

Caveat: Expanding the app with an AI
I could write an AI class, that could manipulate the model, using the available methods. However, if I did that;

  1. How would the AI know when to update and when not to?
  2. How would the UI know when to allow user input and when not to?

Using the Observer pattern (through the Java event model) enables me to publish events. With these events I can trigger the two classes and thus get them to do something, be it a calculation, a move etc. It doesn’t however limit them from performing actions, when any or every event is published. The Command pattern could probably be used in this regard, to give the model some uniform way of validating which actions are ok and which are not. That partly solves the problem, since it does not limit the AI would from performing a task and the UI would still enables user generated input.

The proposed changes so far
Updating the architecture with the AI gives me a diagram:

UI –> Model <– AI

It only states that the UI and AI should exist as separate objects and that they can independently make calls to the model. To ensure that only the allowed object can perform actions at a given time, the model needs to check who’s making the calls.

Caveat: Figuring out who invokes a method

The model could use Reflection to figure out who invoked a given method. Using Reflection does generate an overhead in resource consuption, compared to simpler solutions, and I’m not entirely convinced that it is secure. I base that assumption on this thread.

A simpler implementation would be to change all public method signatures to include a reference to the calling object as the first parameter. This must be filled out with a “this” reference, when any object tries to call a method, thus ensuring a check for legality of the performed call. The solution doesn’t gurantee a solution to the security issue 100%, since you could just pass a reference to another object, but I’ve decided there’s no need to bring out any bigger guns, when ther’s only a game of 10.000 at stake.

The aforementioned examples gets updated to:

public boolean canRoll(Object caller)
public boolean performRoll(Object caller)
public boolean performLock(Object caller, int diceNo)

Now we’re getting somewhere 🙂

Implementing the AI

Since the AI will be doing some computations and needs to be able to idle for a bit, so that the user the AI performed actions separately in the UI, it’s necessary to place this in a separate thread.

Caveat: AI and the UI thread
Implementing the AI (by extending the Thread class or implementing Runnable) doesn’t detach the AI from the UI. This means that when the AI goes into a wait state or does some computations, it’ll lock up the UI. This is a sure way of degrading the user experience and may even result in some ANRs. It should be obvious that you’ll want to avoid this.

The answer is using AsyncTask to detach the AI from the UI. The AsyncTask is part of Androids threading framework and solves the problem of locking your UI thread. An alternative could be using an IntentService, but with a litte careful programming I believe that an ASyncTask will solve my problem just fine.

One thing to be aware of though, is if your app gets reloaded, for instance if you rotate the screen, the AsyncTask will be killed. Program your solutions accordingly.

 

That pretty much wraps up my thoughts for how to expand the architecture in the 10.000 app. Be sure to check back for more information 🙂