Reversi-Merci Authors: Hannah Le Bouder, Clement Rugwiro, Emma Nakamoto
What is the problem?
We will implement the game Reversi (another name for the game Othello).
The Reversi game begins with the state of an 8x8 board containg 4 chips in the center. 2 of these chips are black and 2 are white, and the chips of opposite colours are placed diagonal to one another. This game is typically played with one player against another. The basic rule is that each player has to place their chip of a certain colour next to a chip on the board of the other colour. All chips that are sandwiched between a chip of the player's colour must flip to be that colour. The primary goal of a Reversi player is to finish with the majority of the chips on the board being their colour.
What is the something extra?
We will implement player vs player and player vs. computer, with the ability to adjust the AI's difficulty levels. (Plus, of course, this is a polite game that says Merci to the loser after a game is played). We additionally implemented a simple punishment-based timer.
What did we learn from doing this?
(This should be written after you have done the work.) What is the bottom-line? Is functional programming suitable for (part-of) the task? Make sure you include the evidence for your claims.
One thing about using Haskell to create a game is that because the state of the game is threaded through the whole code, you have to be very careful what state you are returning and using. When debugging, sometimes it would be difficult to tell where the state of the game would start deviating from what was expected because the state was being changed in many places. This made it easy for one value of the state to be missed or updated twice in different places. For example, part of our state included updating the current player’s character and score, and we would know these values would have to be switched around at some point. The problem was that because the state was being passed around and updated in many places, sometimes we would end up updating the values incorrectly or too many times and the value of one player would have the value of the opposite player. However, on the other hand, because you make functions that can easily be individually tested, it is also easy to have many functions that you can trust are doing what they are supposed to which helps narrow down where bugs are likely to be.
Another interesting thing we learned about Haskell is that multi-threading and synchronization are difficult to implement in this language. We initially wanted to implement a timer that would actively count-down while the player was choosing a move, and after a set amount of time would skip the player’s turn. This would also involve allowing a player to make invalid moves up until the timer completed. However, this proved to be more difficult to achieve in Haskell because of several reasons. It requires creating a timer which has knowledge of the current game and player state and that can affect the game state without interrupting game flow. While this is possible, it does require a variety of features such as locking mutable variables and thread primitives such as forkIOl. For example, in GHC, threads do not execute in any particular order, and it does not come with a system with which you can check if another thread is executing, completed, or crashed from an existing thread. Thus, you have to create your own synchronizing variable type. When initially trying to implement the timer so that it would skip the player’s turn before the player made a move, the need for these additional tools became clear when trying to determine how to interrupt the game flow and state when pausing the game and switching turns, but without losing the necessary information. Yet another undesirable aspect of using multiple threads with Haskell is that when an original thread is executing, if it stops then all other threads also stop executing. If trying to keep a timer thread that exists even when playing multiple games such as in a tournament style, then one would have to design a separate thread-manager system.
Therefore in short, while possible, the extent of additional work necessary to implement this kind of timer was not trivial.
However, a simple version of a punishment-based timer can be very easily implemented in Haskell, as it allows the “consequence” to occur after an action is made, and so doesn’t require threading while still providing some basic timer functionality.
Links to code etc