Santorini is a course project of the course Principles of Software Construction: Objects, Design, and Concurrency. It's a famous board game and I created a digital version of it.
The whole project can be split into three parts: a backend that handles all the game logic, a frontend consisting of a GUI that handles all the user's input. The backend is implemented with Java while the frontend is implemented with TypeScript and React.
The main objective of this program is to demonstrate a comprehensive design and development process including object-oriented analysis, object-oriented design, and implementation.
In a nutshell, the rules are as follows: The game is played on a 5 by 5 grid, where each grid can contain towers consisting of blocks and domes. Two players have two workers each on any field of the grid. Throughout the game, the workers move around and build towers. The first worker to make it to the top of a level-3 tower wins. Players take turns. In each turn, they select one of their workers, move this worker to an adjacent unoccupied field, and afterward add a block or dome to an unoccupied adjacent field of their new position. Players also can choose god cards to power up their workers by adding additional rules.
System Sequence Diagram
Communicate with the frontend
The communication with the front end is realized via Nanohttpd, a tiny web server in Java. Every time we receive an URL from the front end indicating the location of the grid that the player tries to modify, we put all the current game states into a JSON file and fetch it to the front end.
Then, the questions are: What state does the game need to store? Where is it stored?
1. The game needs to store the following states
* `board`: the current board that the current `Game` instance holds.
* `player`: the current player that the current `Game` instance holds.
* `worker`: the current worker that the current `Game` instance holds.
* `phase`: the current phase that the current `Game` is at.
* `history`: all the previous `Game` instances up to the current `Game` instance.
2. They are stored inside the `Game` class as fields. Additionally, a `GameState` class is created, used to wrap these states into a string, used to communication with the front end.
1. I chose the decorator pattern to solve the extensibility issue because I feel that god cards are basically "decorating" some methods of the plain `Game` class.
* For example, Minotaur only decorates the `move()` and `isValidMove()` methods while other methods are basically the same.
* Meanwhile, the decorator pattern allows behavior to be added to an individual object dynamically without affecting the behavior of other objects from the same class, which, from my perspective, is the best solution to deal with this issue.
* What's more, each Decorator has a base game, which is a plain `Game` object, so if there isn't much to modify a certain method, I can just use that method in the base game, which kind of resembles the template method pattern.
2. Initially, I thought of using the strategy pattern by making interfaces like `MoveStrategy`, `BuildStrategy`, `GetWinnerStrategy`, `IsMoveValidStrategy`, and `IsBuildStragety` and writing something like `MinotaurMove` to implement these interfaces.
* However, I find it really hard to write elegant codes to determine which strategy to use during the run time while keeping the `Worker` and `Player`class decoupled from the game logic.
* Also, I think it's kind of ugly and hard to manage when I have so many java files in the folder because for each God card I have to write several more classes to implement these interfaces.