-
Notifications
You must be signed in to change notification settings - Fork 16
About the Code
This project is implemented in pure Java; the UI is implemented with Swing. There are no external dependencies or APIs used in this project since this is a hobby project where I wanted to build everything from the ground up. Since I develop primarily on macOS, the UI looks best on that operating system. However, I am also optimizing a bit for Windows 10. The game is designed to be used with a mouse/touchpad and a keyboard. It has yet to be tested for touchscreen usage. The game supports resolutions up to 4k, with HighDPI being supported.
The architecture of this project follows the model-view-controller pattern, with a state machine in the controller component. I wanted to build a project with a clean structure, from the high-level architecture down to the code. Subsequently, code quality and documentation are essential to me.
This project follows the model-view-controller pattern. With this very concise summary of the MVC pattern by Dhruv Gairola in mind, we can explain the control flow in this project as follows:
-
The users interact with the view: In this project, the users are the players that interact with the Carcassonne UI which consists of the
MainGUI
and other UI classes, such as thePreviewUI
and thePlacementUI
. The players place tiles and meeples on the grid. This interaction is relayed to theMainController
as requested. -
The controller asks the model to change its state: All requests to the
MainController
are executed on the model. The most important model classes are theGrid
, theTileStack
, and theRound
. TheGrid
is the primary data structure and contains all the placed tiles. TheTileStack
contains all tiles that have yet to be placed. TheRound
manages the player information and keeps track of the turns. -
The controller may also ask the view to change: Some requests, such as placing a tile, will lead to changes to the UI. This also includes resetting the UI between rounds.
-
The model notifies the view when its state has changed: This project mainly concerns setting changes such as changed names or player colors. To achieve this, a lightweight observer pattern is used. However, this is realized through the settings class, which is a central component separate from the model.
-
The view asks the model for the state: This happens when the view is notified on changes of if the
MainController
passed model elements as results of requests.
Note that the mouse and key listeners in Swing might be considered controller code. However, in this project, they reside in the view
packages, as grouping them with the correlating UI components is more convenient.
As previously mentioned, the MainController
handles the UI's requests. However, depending on the state of the game, these requests might need to be handled differently. Sometimes, they might even need to be ignored. Therefore, I model the controller's state with the state pattern. Thus, the control.state
package contains the different states:
- Idle: No round is started. This is the start-up state.
- Placing: A player is in the process of placing a tile.
- Manning: A player is placing a meeple on his placed tile.
- Game Over: The game is over (aborted or ended), and the game statistics are shown.
The MainController
tracks one of these states as the current one and delegates all requests to that controller state. Some requests that are delegated will lead to state changes. These state transitions are depicted in the following diagram:
Early on, I considered how much information I wanted to hardcode into the game. Hardcoding all the information in a static data structure seemed not that elegant to me, so I decided to code as little information as possible into the game. I supply a straightforward terrain structure for each tile and how often it is used in a default tile stack. Everything else is derived algorithmically.
Please take a look at the example in the figure above. As for every tile, it is divided into nine tile zones. A terrain type (CASTLE
, ROAD
, MONASTERY
, FIELDS
, or OTHER
) is encoded for each zone. This specific tile occurs three times in the default stack. This means the only hardcoded information for this tile is one integer and nine enumerals.
From this terrain information, we derive all other required information. This includes checking the rules and gameplay constraints. For example:
- Meeple spots: On which zones of a tile meeple can be placed depends on the terrain of the zones
- Emblems: Whether a tile has an emblem is based on how many zones on a tile have castle terrain
- Tile rotation: The terrain information of a rotated tile is calculated from the original terrain information
- Tile placement: Whether a tile can be placed depends on the terrain of the interfacing zones of the tile to place and the neighboring tiles of the grid spot
-
Multi-tile grid patterns: These grid patterns, such as castle structures, road networks, and fields, are built recursively based on the tiles on the grid and the correlating terrain information
- Inter-tile connections: Whether two zones on a tile are connected (e.g., does the road in the example above connect from the west to the east?) is crucial for grid patterns, especially for field connections, it is not trivial to derive that information from the terrain information
- Meeple placement: Whether a meeple can be placed depends on the meeple spots and if the spot is part of an occupied grid pattern
The three MVC components are reflected in the package structure: model
, view
, and control
. Two additional packages are not strictly related to one of the three components: settings
, which contains all classes associated with the in-game settings mechanism, and util
, which includes utility classes. In the following, we explain these packages in greater detail.
The model
package contains three sub-packages:
-
grid
contains the grid data structures and the types for the different multi-tile grid patterns. -
terrain
contains the types regarding the terrain representation, as discussed earlier in the tile encoding section. This includes the potential spots for meeple placement. -
tile
contains the tile representation and tile-related tiles like the tile stack and tile distribution type. Themodel
package also directly contains the three high-level model types:Round
,Player
, andMeeple
.
The view
package contains five sub-packages:
-
Main
contains everything regarding the main UI except the menu bar. The main UI depicts the grid and the meeples in a zoomable and scrollable view. -
menubar
contains the types for the menu bar and its menus -
secondary
contains the small sub UIs for rotating tiles and placing meeples, which are still essential for the gameplay -
tertiary
contains all other, non-essential, UI classes like the tile distribution UI, the post-game statistics UI, and the player settings UI -
util
contains a variety of UI utility classes
As previously mentioned, the control
package has only one subpackage, named states
, which contains the abstract superclass for the concrete states and the concrete states themselves.
The util
package contains utility classes regarding image manipulation and performance that are somewhat disconnected from the MVC package structure.
The settings
package contains the game settings, which are a central component of the system used by all other components.