Mathematical Personality

The AI is really quite good at playing the game, and whilst changing the number of cards that remain flipped scales difficulty, the AI is far better at finding patterns - and has a fairly consistent skill level, I’ve never seen it attempt to play any hand that does not utilize all 5 cards. It also has quite a gung-ho approach to the game, playing flipped cards far too riskily - if you play visible cards only, it’ll fail because it thinks every set of 5 hidden cards is a Royal Flush.

Basically, it ranks every potential hand by it’s score - which is a product of the cards in the hand, and a multiplier for the type of hand. A high card attracts a multiplier of 1, a royal flush much higher (100 at the time of writing).

One of the ambitions for the game is a quest mode, where you defeat the titular Poker Kingdoms, I envision each card in the desk as a node in that quest, but there’s not enough variety in the game for 52 games, I could make some obstacles on the board, but once you understand the mechanics of the game, layout really won’t make that much challenge.

So I’m experimenting with giving the AI a personality - at least a difference in the strategy the AI plays with. The simplest, and most obvious given that you’re playing against members of the deck is to make them preference certain cards - each card preferences its own face, and suit higher (4 of spades is more likely to pick 4, and spades) - the more advanced the card, the less it will preference itself (being a better ‘player’).

The other factor that needs to be dealt with is hidden-cards, vs jokers, to the AI they appear identical, as a result of how they’re dealt with by the AI, but playing a Joker is less risky than playing a flipped card, with that comes a lower reward. So, each hand generates a risk state, from 0 to 100% risk (20% for each flipped card). This can be used to influence AI players as either a positive or negative value, conservative players would take a penalty, but daring players can add this as a bonus.

Finally, each personality can receive a custom table for hand multipliers, I can heavily weight basic hands early game, and as the player progressed, shift that weighting closer to real values as the player approaches later stages in the game - perhaps throwing in some curve-balls on the way.

Advanced programming is for dummies

There was an overwhelming sense of success getting a computer to play the game against me (and against itself), I’ve never really worked on AI, particularly beyond reactive states. But waiting for more than a minute each time the computer needed to make a move was not acceptable, or remotely fun. Especially when it would sometimes die, because it would not take risks.

The risk taking problem was easy to solve - if you treat flipped cards as jokers, the computer will appear to take ‘guesses’ - this does create almost the opposite effect, as finding 5 jokers in a row, the computer assumes it is a Royal Flush, and always takes the shot. But it places the AI in a state where it can play against itself indefinitely.

To get any use out of it, however, the speed issue needs to be fixed. The goal was to allow the game to run at the target framerate consistently, so any on-screen effects were not blocked by the CPU spikes. To measure this, I placed an animated poker chip on the screen, so I could see visually how things looked as well as reading the metric.

Unity 5 brought the profiling tools to the public, this was a great chance to use them, and was another example of using a known project, to test new tools. The basics were pretty simple, play the game, and watch the performance graph, when it spikes, you can break down your function calls, and see how much time you’re wasting.

Caching helped a lot, I was calling lots of functions repeatedly sometimes 4 or 5 times for each walker, for 11 hands, and 17,000 possible walks, none of these looked slow, but running them millions of times was causing me a huge hit. The second big loss of speed was using Collections, lists and dictionaries are convenient to work with, but come with a lot of baggage. I took each function and rewrote it to work with pure, fixed size arrays. This gained me a huge speed increase.

Finally, a rework of the logic - it dawned on me that the original design for AI was flawed, there can only be one possible hand for each walk, and each walk is a hand (even if it’s just a high-card). So rather than regenerating the walks ever turn, I generated the finite set of walks, and tested the current hand of each. This got the processing time down to a fraction of a second, perfectly acceptable to play against as a human, I was wasting more time animating cards than running AI. But it still caused a glitch.

Rather than go back down the complicated route of threading, I built a quick hack -

private float maxRun = 1.0f / 100f;

float frameStart = UnityEngine.Time.realtimeSinceStartup;
float realTime = UnityEngine.Time.realtimeSinceStartup;
int position = 0;

while (realTime - frameStart < maxRun) {
realTime = UnityEngine.Time.realtimeSinceStartup;

Basically, at every frame, grab the start time, and start processing the list of walks, until the time elapsed is more than 1/100th of a second, and move on, and come back to that point next frame. The result is spikes in CPU usage, but no noticable frame rate drop (at 60fps). My spinning poker chip span smoothly through the whole event. Again, a simple script had done away with the problem with far less drama than more advanced methods.

The AI is mechanically sound, but needs some tweaking to make it fair, and give it some variance.

AI think AI can

Poker is a fairly large problem set, but not as large as actually playing poker (or a derivative). Thankfully, Poker Kingdoms does not have be able to watch the player for bluffs and other non-mechanical game devices that exist in the traditional game.

What it does have to do, is scan the board for a playable hands, and rank them. On the surface, this seems like a similar problem to games like Puzzle Quest, scan the board and look for a pattern, and make a play. Puzzle Quest has 3 valid patterns (2, 2x1, 2x2) to search for. Poker Kingdoms has 11, and, by nature of the game, they are each 5 tiles, and, as raised in the early parts, can exist in any set of 5 congruous tiles.

The developers of Puzzle Quest insist that there is no solid AI behind their game, it doesn't plan its choices, and possibly doesn't even rank them if multiples exist. The latter can’t work in Poker Kingdoms, every possible set of 5 cards produces a valid hand, but a High Card hand is not worth very much at all.

For the curious, the 5x5 board which Poker Kingdoms seems to play best on, has just a bit more than 17,000 available moves at any point in time - some cards can be hidden, so we need to find out all possible results, not just all actual results as could be done in Puzzle Quest. That’s

My initial attempt tried to be elegant, for each hand type, it would scan the board, and start a ‘walker’. Walkers collected a set of points, testing each available move from the last point, and spawning a new walker to follow that path.

0>10<10 10 1
2 32 32>32<3

0 10 10>10<1
2>32<32 32 3

0 10 10<10>1
2<32>32 32 3

0,1,3,2 && 0,2,3,1
1,0,2,3 && 1,3,2,0
2,0,1,3 && 2,3,1,0
3,1,0,2 && 3,2,0,1
  • this is a subset of actual generated walks, I’ve ignored diagonal steps.

As these walker progressed, they would check against their respective hands, and stop spawning if they were no longer valid. If they reached 5 valid cards, they’d add a selection to a growing list of valid moves, and eventually return the move that scored the most points.

This worked.

The computer could walk the board and find visible hands.

Aside: part of my encapsulation means flipped cards are invisible to every other element of the game, the representation of face/suit is private, and the getter reads the flipped bool. I would have to go out of my way to let the computer cheat.

Because it couldn't read flipped cards, it could break if there was no sets of 5 congruous cards, and it took no risks (playing a flipped card scores higher).

It was also game-breaking slow. On a high end machine, it was taking over a minute to scan the board in some cases - and even set as a background thread, this would stall the game play for far too long - I wasn't building Civilization here, without the background thread, the game could lock up for long enough for Windows to think it had failed.