A CLI application written in Python for playing tic-tac-toe.
-
Clone this repo.
-
Navigate to the cloned project directory in your command line. The game can be run from this directory using
python main.py
. To run the unit tests for this project, runpython3 -m pytest
. Instructions for installingpytest
can be found here.
The control scheme is described below. Type any of the following characters followed by the enter/return key to send input to the application.
w
- Move cursor up by one space.d
- Move cursor right by one space.s
- Move cursor down by one space.a
- Move cursor left by one space.f
- Performs a return/enter. When on the main menu, this will perform the action selected in the menu. When in a match, this will set the space currently occupied by the board cursor with the marker (X or O) associated with the current player. If the selected space is already occupied, then nothing will happen.u
- Undo the previous turn. This will decrement the displayed turn number, remove the marker set in the previous turn, move the cursor to the previously set space, and toggle the current user. This command does nothing on turn 1. This command only works when playing a match. The command does nothing on the main menu.r
- Redo an undone turn. Reverses all changes from the last undo command. Note: The redo cache will be cleared if you undo a command, then set a new space prior to redo. This command only works when playing a match. The command does nothing on the main menu.g
- Saves the progress of the current match. The match can be revisited later by selecting theLoad Game
option when in the main menu. Note, this input can only be used to save a game when in a match. The command does nothing on the main menu.
In this project, the Game
class encapsulates the primary application logic. The Game
class implements the state pattern and behaves similarly to a finite-state machine. Within the context of this pattern, the possible concrete states include the MainMenu
and Match
classes (both instances of the abstract class GameState
), while the role of context is handled by the Game
class.
Calling the play
method on an instance of Game
will initiate the gameplay loop. A single iteration of this loop is referred to as a tick. During a single tick, the Game
instance will do the following in order:
- Render in the terminal. This is done by clearing the terminal and then calling render on the game's current
GameState
instance (which is stored on thestate
attribute). - Retrieve input from the user, which is mapped to an instance of the
UserAction
enum. - Pass the
UserAction
to the current state viaGameStates
'splay_tick
method. This method returns an instance ofStateTickResult
. - Check the status of the
StateTickResult
produced in the previous step. If the status isStateStatus.COMPLETED
, then the game undergoes a state change whose inputs and next state are specified by theStateTickResult
.
The history feature (undo and redo commands) utilize the well-known memento pattern, where the Board
class (app/game_state/match/board.py
) acts as the originator, the BoardMemento
class (app/game_state/match/board_memento.py
) acts as the memento, and the MatchHistory
class (app/game_state/match/match_history.py
) acts as the caretaker.