I recently wrote a game engine for the card game “Pit” and posted it to my Github.
The Card Game
Pit is a commodities trading card game. It’s quite fun, and good for groups up to eight. Even though I only recently learned of it, the game has actually been around since 1904, when it was first sold by Parker Brothers. The game play involves trading cards with other players until you complete a set of nine of the same card, at which point you yell out (or ring the bell) and declare that you have cornered the market and won the round. Trading consists of yelling out offers and making deals to exchange cards (blind, and always for the same number of cards) with other players. It can be quite hectic, and is probably something like an actual commodities trading floor.
The Game Engine
I wrote a game engine (actually two) for Pit in Python. The idea is for people to write Player classes and have them compete against each other. The game engine manages the cards, rounds, scoring and other aspects of the game.
Disclaimer: I wrote a basic player and have run hundreds of game simulations (and even have unit tests!) but I wouldn’t say it’s thoroughly debugged. If you check out the code and find any issues, let me know. Or submit a patch
Thanks for reading, and definitely send along any feedback. It’s always appreciated.
This is the version that a couple of my coworkers (and I) are planning to use to test out different Player algorithms. Here’s a run-down of the game flow…
- Cards are dealt, players after dealer get 10 cards (dealer rotates, just as in the live game)
- Game loop starts, processing one cycle of the game at a time until someone wins
- Game Cycle:
- Players are asked for an action (make offer, respond to offer, etc.)
- Actions are randomized and then processed one by one
- Cards update, notifications sent after each action is performed
During each cycle, active players may submit a single action to perform. Those actions include:
- Make an offer – call out a number of cards to trade
- Respond to an offer – propose a trade to specific player
- Ring the bell – declare you have won this round
- Pass – do nothing
Notice that “confirm a trade” is not listed. That happens outside of the normal game flow, and is triggered immediately whenever a player sends an offer response to another player. This does allow players to perform more than one action per round, but is somewhat realistic (people tend to hear their own name called, regardless of other distractions) and makes the game bookkeeping easier.
Game actions are not free. Making an offer, responding, or participating in a trade take a fixed number of cycles. During this time a player will not be queried by the game engine for any new actions, nor will the player be notified of actions made (or trades confirmed) by other players. The one exception to this rule is that players will still be notified of direct responses to any of their prior offers, and they may still confirm them, resulting in another trade (and yet another delay of a few game cycles).
I wrote the async version second, and it was meant to be more realistic, closer to how people actually play the game. The idea was to break away from the concept of fixed cycles and order of operations. The game engine and each player run in their own separate (and multithreaded) Python process and communicate with each other via dedicated pipes and a single action queue. The code is a bit more complicated than the synchronous version, and a group of my basic players needs around 30 seconds to complete a single game (the sync version takes a couple tenths of a second).
In the course of writing & testing the async version, I realized that it’s not necessarily that much more realistic. I still had to alter reality in several ways to make the task easier on myself. For example, all communication routes through the game engine. In the real game, everyone is an independent agent and there is no central control.
Perhaps I’ll write a third version some day. In any case, I may write another post with more details about this version, but the current plan is to focus on the simpler (and faster) synchronous version. We want to run hundreds or thousands of trials to see whose player code is best, and I’m not sure the async version is suitable for that in its current state.