More GKMinmaxStrategist

With WeGame Corp, I’m redoing a game I wrote under Cetuscript called Krok. Krok is my version of Crokinole, a Canadian favourite. The rules in Krok are basically the same as Crokinole with some, in my opinion, minor tweaks.

My original game allowed up to 4 players to play either by sharing a device or by connecting through Game Center. I’m not sure if this rewrite will have network play. The jury is still out on that and the deliberations might become an article on this blog. However, the upcoming version will have bots to play against. So, if you can’t find any friends to play Krok with, you’ll still have the bots.

Playing Krok requires the evaluation of a starting point for your puck, a target for your puck, the angle to hit the target, and how hard to hit your puck. And all that boils down determining a position and a vector — four floating point values.

I used the GKMinmaxStrategist for Hexin, a two player chess like game. It was pretty easy to use. When I was thinking about how to do the AI for Krok, I thought about using a GKStrategist. The GKMonte​Carlo​Strategist seemed like it would be good. It advertises to be able to search a large space of alternatives by taking a random samples. However, it seems to be only looking for the win. I still needed to assess each random sample for its value.

That brought me back to the GKMinmaxStrategist. Of the four characteristics listed in that link, Krok provides “Sequential”, “Adversarial”, and “Perfect Information”. The game isn’t “Deterministic.” 3 out of 4 is pretty good.

Because the game isn’t deterministic, in fact even the AI players will execute their moves with error, there isn’t any point in searching far ahead. The maxLookAheadDepth is set to 1. I also set the randomSource to GKARC4RandomSource().

The GKMinmaxStrategist relies on evaluating all the possible moves for a given game configuration. With Krok, the number possible shots (moves) is infinite, or as many as you can generate with 4 doubles. In the gameModelUpdates(for:) method, I generate a large number of random and specific shot samples and then evaluate the shot in apply(_:) and score(for:).

A number of starting points are picked at random. For each starting point, each possible target is considered. For each possible target, ending points are determined at random. For each pair of starting point and ending point, the velocity is also randomly assigned.

Starting/ending points are also filtered out by obstruction. If the puck can’t travel between the start and the end points without hitting another puck or one of the 8 pegs, that pair is discarded.

Thus we have a number of random starting points and velocity vectors to consider. For each set, the resting positions for the shot puck and the target puck are calculated. For this calculation, I make a number of assumptions and simplifications. What I want to get to is a good guess at where the pucks will end up. I didn’t go into exhaustive detail because I didn’t want to do the math and I didn’t think the gain in precision would be worth it. After all, the actual shot will have an error applied to it. A good guess is good enough.

Where the shot puck and the target puck end up determines the value of the shot. The shot puck’s value is further modified by an estimation on how easy the shot would be. The harder the shot, the less valuable the shot is. While the value for the target puck isn’t modified by how hard the shot would be, it is the difference between the value of the target’s original position and the value of the predicted resting position.

The Shot, or GKGameModelUpdate structure, contains the starting position, the velocity of the shot, the value for the shooter, and an optional value for the target. It is optional because sometimes there is no target to shoot at.

In apply(_:), the active player’s score is modified by the value for the shooter and the opponent who has the target puck gets their score modified by the optional value for the target. In score(for:), the board score is calculated and return for the value of the board for the particular player the method was called for.

I’m doing the physics in gameModelUpdates(for:) rather than apply(_:) mostly for convenience. The data needed to calculate the core of the shot, the position and velocity, are all found in generating the shot in gameModelUpdates(for:). By taking the calculations a step further, the work to be done in apply(_:) is very simple. However, if I feel the need to evaluate the results of a shot in greater detail, I will move the calculations to apply(_:).

The benefit of using GKMinmaxStrategist is that it provides an easy to use framework for evaluating a large number of randomly generated samples. My coding contributions are confined to what is model specific. The strategist takes care of the rest.