Earlier this week I posted about my PHP spades project for automated testing of bidding and playing strategies. In that post I highlighted my use of the strategy design pattern to make it easy to test a variety of approaches to the game; however, I didn’t provide much structural detail. Lucky you, as it turns out, because the structure I was using at the time was far from ideal.
My overall idea for running the tests was to be able to use a very thin controller script, something along these lines:
1 2 3 4 5 6
This simple “play-at-once” approach is very easy to call, as it leaves every last bit of the game’s business logic to the model layer. Unfortunately, it doesn’t leave a lot of room for flexibility, particularly in two areas:
- Since the entire game happens in a single function call, it’s impossible for the controller layer to keep track of the game’s progress for output and/or logging purposes.
- It’s also impossible for the controller layer to allow user input and thereby facilitate the introduction of human players.
So, today I spent a good chunk of time re-architecting the game structure. In terms of tracking progress, I started to think in terms of atomicity: what’s the smallest sequential part of a game of spades? Well, let’s break it down. A game in its entirety consists of as many hands as necessary to get one team to 400 points without a tie. Each hand consists of each player bidding once, followed by as many tricks as necessary to lay down the entire deck. Each trick consists of every player laying down a single card in sequence. Tricks are won by the team who played the highest card (spades being higher than other suits), and hands are scored based on the number of tricks each player bid on winning.
The most atomic parts of all that are individual players’ bids and plays. So, if the controller layer was to be able to track the progress of the game, it would need to be able to check in after each of those events to see what happened. This led to the following “play-by-play” structure:
1 2 3 4 5 6 7 8 9 10 11 12 13
The nice thing about this is that, although there’s quite a bit going on behind the scenes, the controller layer really only needs to be aware of the Spades_Game, Spades_Player, and Spades_Strategy_X classes. As long as the game object returns a player, the controller layer knows that there’s more game to be played.
So what’s going on internally? Well, Spades_Game is basically a facade managing a sequence of Spades_Hand objects, which in turn manage a sequence of Spades_Trick objects. Take a look:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Notice that, at this point, there isn’t any logic regarding the phase we’re in (bidding or playing); since the two phases each occur once per hand, it makes more sense to manage them within the Spades_Hand class. Each phase is handled by separate logic, so Spades_Hand::getNextPlayer() makes use of a couple of protected methods to keep things distinct:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
Notice that the _getNextPlayer() half of the procedure is extremely similar to Spades_Game::getNextPlayer(); we’re still passing the buck along the chain to a smaller unit of play…the Spades_Trick instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
All these layers taken together make it possible for the controller layer to always have access to the next player in sequence, without having to know much of anything about that sequence.
One nice side bonus of this structure is that it would be dead simple for the controller layer to identify certain players as user-controlled, injecting their input into the system in the $game->acceptBid() and $game->acceptPlay() methods.
The next step in all of this, I suppose, will be to develop a simple web interface for viewing the results, and possibly trying to beat all these wonderful computer players.