whist
whist copied to clipboard
Whist is a trick-taking, real-time multiplayer card game.
Table of Contents
- Whist
- Features
- Setup instructions
- High level overview
- Code Architecture
- Controllers
- Views
- Models
- AI computer opponents
- Multiplayer
- Software design pattern examples
- Code Architecture
- Code metrics
- [App statistics](#app statistics)
- Used libraries & code
Whist
Whist is a trick-taking card game with many variations. The one implemented here is very similar to Oh Hell. You can download the app from the Google Play Store. Please note that the code & documentation presented here are not up to date, as many more features have been added.
Features
- Single player against computer opponents
- (Android only) Real-time multiplayer using Google Play Games Services (invite friends and/or play with up to 3 random opponents)
- (Android only) Achievements and Leaderboards
- Statistics, game settings & variants
Planned future work: tests, iOS app, app invites & deep links, more battery efficient computer opponents, better graphics, in app purchases, and more.
I decided to open-source my code in order to showcase my work and get valuable feedback. Below, I present a high-level overview of the app --- feel free to contact me or dig in the code for more details. My CV can be found here.
Note: Everything in here is 100% my own work, except where specifically indicated below. This includes the UI design and all graphic assets. I am not very proud of my design skills, so please be lenient with the visual aspect of the game.
Setup instructions
In case you want to build and run the project yourself, you need to:
- Follow the instructions from here, to import an existing LibGDX, gradle based project.
- Implement the function
getStoragePassword()inConfig.java, for example by simply returning a string. - Create your own implementation of card shuffling in
Dealer.java, for example by callingcards.shuffle(). - Setup Google Games Services as explained here.
- Modify the ids.xml file, with the application ID and achievement/leaderboards IDs you obtained in step (4).
- Modify the signing configurations in the android
gradle.buildfile, with your own keyAlias, keyPassword, storeFile and storePassword (both for the debug and release configurations).
High level overview
The app is built on top of the LibGDX game development framework and it is written in Java. Currently, it compiles for Android and Desktop, and iOS. Platform specific code is placed in the android, desktop and ios directories, whereas all platform independent code is
placed in the core package. It also uses a customised version (to fit my own needs) of the StageBuilder library, a project for building LibGDX stages (screens) from xml files. Please have a look at my other repository, where I supply an Adobe Illustrator javascript which automatically exports graphical assets and .xml files for use with StageBuilder. Once the Illustrator file is updated, the UI of the app can be updated using two clicks, with no code modification needed whatsoever.
One of the primary goals when I decided to write this game from scratch, was to create my own library, tools and workflows. This would allow me to quickly build new games in the future. As such, my top priorities were code re-usability, maintainability, testability and minimal dependencies.
Code Architecture
The structure follows the Model View Presenter (MVP) architecture. That said, please do not be confused: all Presenters extend the Controller abstract class, and follow the XxxController naming convention (after the Model View Controller (MVC) architecture which is very similar).
In the following UML class diagrams, many classes are omitted for brevity reasons, including all XxxModel classes (many are shown as fields). Moreover, only a selection of the members of each class is shown to save space.
Tip: You might prefer to navigate the diagrams whilst reading the descriptions below them.
Controllers

All controllers inherit from the Controller abstract class, which allows the communication between them.
The diagram is color coded as follows:
1. App entry point (green)
The AppController class is the app entry point. It implements the IApplication interface, which defines methods for the app lifecycle events (e.g. onCreate(), render(), onDispose() etc.).
In addition, AppController is a CompositeController, and is the root of the controllers object graph. In other words, all other controllers are its children or grand-children.
Note: The proposed structure scheme makes it easy to write unit tests. Each controller can be easily swapped for a stub, allowing the independent testing of each one of them. At the same time, communication between them is easy without dependency injections.
2. First level controllers (blue)
The name of each controller (Assets, Storage, CardController etc.) and the members of the interfaces they implement pretty much sum up their responsibilities.
The most interesting one is the composite controller ScreenDirector. It creates the root of all View objects: this is represented by LibGDX's Stage object. Moreover, it creates three ScreenControllers: the LoadingController, the MenuController and the GameController, and activates the appropriate one according to the state of the app.
3. Screen controllers (red)
These are the presenters which handle the creation and the management of their Views.
- The
LoadingControllerupdates theLoadingView - The
MenuControllerupdates theMenuScreenand gets notified about input events. - The
GameControllerupdates theGameScreenand gets notified about input & game events.
The GameController delegates game actions to the concrete implementations of the IWhistGameController interface.
4. Game controllers (yellow)
All concrete implementations the the abstract class WhistGameController make up the Whist-specific game controllers:
GameStateControllerDealerPlayerControllerBoardController
Note: The
GameSimulator,WhistExecutorService, andThreadedGameModelPoolare not controller objects. They are just there to show how thePlayerControllerimplements the bots.
Views

All UI elements derive from LibGDX's scene2d Actor class, a graph node that knows how to draw itself on the screen.
LibGDX's scene2d "is a 2D scene graph for building applications and UIs using a hierarchy of actors"
Views are composite actors (they extend Group). They can be built synchronously or asynchronously from xml layout files using StageBuilder. A view is only built synchronously only when it is required immediately. Otherwise, as with every other computationally expensive task, most of the work is performed on a background thread. Execution returns to the UI thread using the command software design pattern whenever required (e.g. for openGL texture binding calls).
Screens
Screens are composite Views. They group UI elements expected to be shown together --- their names are self-explanatory: MenuScreen and GameScreen.
In addition:
- they are
Viewfactories: Given the view's .xml filenames, they instantiate the appropriate sub-classes ofView. - they manage the lifecycle of all the
Views they construct - they are Facades to the UI for the
IScreenControllers. Most of the communication between views and presenters goes through them.
In the UML class diagram, Views created and managed by the MenuScreen are shown in purple. Those managed by the GameScreen are shown in red.
Models
Models expose methods to update and query their internal states, having no business logic (apart from some input validation when updating). They can be serialised to store them or transmit them through network calls. Most of them are placed in the models package.
AI computer opponents
The game wouldn't be complete without a single player mode against computer opponents. I needed a quick hack to implement this functionality, but simple heuristic rules would make the game boring.
"Although the rules are extremely simple, there is enormous scope for scientific play." [Wikipedia]
Thus, I implemented a simulation-based algorithm, that allows the computer to play the game with no training or prior input.
Although this algorithm is not suitable for a mobile application (... I guess users expect card games to use less battery!), makes the game fun because it is hard to beat. I challenge you to win the bots!
It is multithreaded
Uses an ExecutorService to create a fixedThreadPool (see WhistExecutorService). Synchronisation is achieved using ReentrantReadWriteLocks.
... and recursive
The game is simulated recursively. Whenever a recursive call is made, a read lock is obtained to see whether there are any idling threads. If so, the caller tries to obtain a write lock on the number of running threads: if successful, the number of active threads is incremented, and the simulation continues as a new task on the new thread. Otherwise the current thread continues normally.
... and uses Pools to reduce the frequency of garbage collections
Every time a game action is simulated, a new SimGameModel is created. Instead of creating a new object every time, SimGameModels are recycled whenever they are no longer required. To make matters simpler, one pool is used per thread. See ThreadedGameModelPool.
Note: Due to the nature of the game, the number of
SimGameModels space quickly explodes, making it impossible to simulate all possible outcomes. To tackle this problem, some heuristic rules are used to limit the number of simulations per card, when dealing more than 6 to each player.
Multiplayer
The Google Play Games Services API was used to implement real-time multiplayer functionality across devices and users. All of the API specific code can be found in the google package. It's a high level API, so it was very easy to implement the following:
- Inviting friends to play against them, or playing against random opponents (or a mix of the two)
- Handling invitations and notifying the user
- Handling room life-cycle events
- Creating and matching opponents for different game variants (such as the bet amount)
Interesting bits of my own code can be found in the MultiplayerMessage class (e.g. using a Pool to recycle messages and multiplexing information into single bytes to conserve network usage) and in the Messenger class, whereby an inbox and an outbox are used to properly handle Messages received in the wrong order, or requesting messages that have been dropped to be re-sent. The BaseGameUtils, GameHelper and GameHelperUtils classes where obtained from Google's samples, and where slightly modified to the needs of the game.
Software design pattern examples
One example for each of the following software design patterns is given below.
-
Behavioral
-
Observer
The
IStatisticscontroller is an observable which notifies the attached observers (e.g theUserView,StatisticsViewandCoinsView) when theStatisticsModelchanges. -
Command
Whenever a task finishes on a background thread, this pattern is used to return to the main UI thread. Concrete commands are encapsulated in
Runnableobjects and are submitted for execution using theGdx.app.postRunnable()utility function. -
Mediator
Classes implementing the
IScreenControllerinterface are concrete mediators: they handle the interaction between UI elements and their corresponding model representations. In other words,ScreenControllers update theScreens, and are informed by theScreens about user events to update the models (Screens inherit fromEventListener). -
Memento
This pattern is used for game saving & loading. The
GameController(originator) supplies theIWhistGameControllers (caretakers) aGameModelobject to continue from a previously saved game. -
Strategy
Classes implementing the
WhistAiInterfacecan be swapped to create different bots. Classes extendingAbstractWhistAiexecute the strategy's logic asynchronously by default.
-
-
Structural
-
Composite
The object graph of all [
Controller]s is formed using the composite pattern.CompositeControllers, such as the app entry point (AppController), delegate work to their child controllers. Also,Screens are compositeViews. -
Facade
Screens are facades toViews. Most of the communication between [IScreenController]s and UI elements go through them (in other words screens delegate updates to the appropriateView.) -
Pools
Pools are used to recycle objects, and hence reduce the frequency of garbage collections. They are used in many places: network messages (
MultiplayerMessage), simulated game states (SimGameModel),Actions attached to actors etc.
-
-
Creational
-
Factory method
The abstract class
[Screen]provides the methodsbuildViewSync(String),buildViewAsync(String)andgetView(String, Class<T>). The subclasses ofScreendecide which views to instantiate. -
Builder
A variation of the builder pattern is used for the circular reveal animations. [
Animators] provide an [AnimatorParams] object, which can be modified to customise the animation. However, [AnimatorParams] objects are not builders per se, since they are members ofAnimators instead of handling their creation. Nevertheless, they separate the representation ofAnimators from their creation. -
Prototype
GameModels provide acopy(GameModel)member, which returns a clone ...GameModel.
-
Code metrics
In case you are interested, here are some of the metrics I obtain using the static code analysis tool SonarCube.
- Total lines of code: 22k
- Classes: 234
- SQALE Rating: A
- Technical Debt Ratio: 1.9% (Note that I am using the default SonarCube quality profile which includes in this metric a lot of minor issues (e.g. replacing tabs with white-spaces).)
- Directory tangle index: 0%
- Cycles: 0
- Dependencies to cut: 0
- Complexity: 4036
- Average complexities
- per function: 2.2
- per class: 17.2
- per file: 22.5
App statistics
The android app uses Twitter's Farbic analytic tools to track various performance metrics. The following stats are a combination of the info provided by Google's Developer console and Fabric. (Updated 1st of December 2016, approximately a year after the first public release)
- ~500 Daily active users
- 1500-2000 Games per day
- 15-20 minutes/day time in app per user
- 110 downloads per day on average (without any advertising, all organic aquisitions)
- 48% of Play store visitors are converted to downloads
- 99.4% crash-free sessions
- 4.34* rating (almost all of the negative reviews complain that the bots are impossible to win!)
Used libraries & code
As already mentioned, I am using a modified version of the StageBuilder library. The relevant code is in the assets and stage_builder packages. In addition, the Base64 class in the Cryptography.java file was obtained from here. The BaseGameUtils, GameHelper and GameHelperUtils classes where obtained from Google's samples. The LRUCache was obtained from here.