This is an implementation of the Dominion card game in Dart.
This code should be for personal use only. Please do not host a publicly available server of this. Support the official version instead!
I wrote this as a personal project to play around with, though I'm posting the source code because I imagine it could be useful in playtesting custom cards or writing bots.
The base set and Intrigue are both implemented, though Intrigue isn't thoroughly tested. I might get around to adding Seaside or Prosperity eventually, though both of those will require additional mechanics implemented in the core game engine. Note: I implemented the base set and Intrigue cards before the second edition was released, so they currently use the first edition cards.
Running this requires Dart 1.13 or higher. It is divided into four packages:
dominion_coreimplements the core game engine, including the basic treasures and victory cards (anything not in the kingdom)
dominion_setscontains a library for each implemented expansion (currently the base set and Intrigue). When one or more of these are imported, their cards will be registered in
CardRegistry(this is done reflectively, so you'll want to suppress the unused import warning).
dominion_serverruns a web server (defaults to port 7777, include a port as your only argument to override) that attempts to host the contents of
dominion_web/build/webas well as a web socket server for communication between the server and multiple web interfaces. The server runs the engine and maintains all game state.
dominion_webis largely standalone. It depends on the message format used by
dominion_server, but does not import the game engine (it actually can't at the moment, since the engine uses mirrors and mixins with superclasses). The server expects the built web interface to be in
dominion_web/build/web, so run
pub buildfrom this directory before running the server.
How to Start a Game
dominion_web. This only needs to be run each time the web interface is changed.
dominion_server. Add a port as the only argument to change from the default port of 7777.
- One player should go to
localhost:7777(or wherever the server is hosted), enter 10 kingdom cards and click the Create Game button. If you enter fewer than 10 cards, the rest of the kingdom will be chosen randomly.
- Each player or spectator should go to
localhost:7777/game.html?id=<game id>and either enter a player name or leave it blank to become a spectator.
- Once all players have joined, someone should click the Start Game button.
- Spectators can join at any time.
- Any clients connected with the same username are considered the same player. Any client can take actions for that player.
- If all clients playing as a particular player disconnect, the game is paused until someone with the same username reconnects.
- One way that works well to play is to have a spectator connect on a computer to show the kingdom cards and current player's status and each player connects on their phone.
- Ngrok is a great tool that you can use to allow other computers to connect to a locally run server.
Tips for Development
If you add any additional expansion libraries, I recommend adding a corresponding test file to the
dominion_sets/test/base_test.dartfor examples (Intrigue does not yet have all cards tested). You'll also want to make sure to import the new library in
dominion_server/lib/game.dartto make sure your cards are included.
@cardannotation on the line immediately above the class definition to make a card available to the game engine.
Card classes should be defined with a private constructor and a static
instanceattribute that should contain the only instance of that card ever created.
CardTargetabstract classes represent sources and targets for cards respectively.
CardBufferrepresents both and is the most common way the engine keeps track of where cards are. Any time a game action causes a card to be moved somewhere, it should be put in the proper buffer.
List<Card>is only used when prompting the player to select cards or card types.
The game engine and cards make heavy use of async/await. Any action that could potentially require player input is asynchronous, and should be awaited when called. This allows the cards to be written as if all user input is made synchronously, even though it's not.
The web interface is separate from the server and game engine, so you can rebuild it to make changes even while a game is running. If you plan on making frequent changes, you should run the web interface from a separate server with
pub serveand then provide the URL of the engine server with a