diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8e359a0145..19a9bca79d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,9 +1,13 @@ # Contributors -Display | Name | Github Profile | Homepage ----|:---:|:---:|:---: -![](https://avatars0.githubusercontent.com/u/22460123?s=100) | Jeffry Lum | [Github](https://github.com/j-lum/) | [Homepage](https://se.kasugano.moe) -![](https://avatars0.githubusercontent.com/u/1673303?s=100) | Damith C. Rajapakse | [Github](https://github.com/damithc/) | [Homepage](https://www.comp.nus.edu.sg/~damithch/) +Name | Github Profile +---|:---: +Antriksh Dhand | [Github](https://github.com/antrikshdhand/) +Ou Ningxiang | [Github](https://github.com/onx001/) +Oh Ken Wei | [Github](https://github.com/ken-ruster/) +Tong Zheng Hong | [Github](https://://github.com/TongZhengHong/) +Tricia Boo Koh | [Github](https://://github.com/TriciaBK/) # I would like to join this list. How can I help the project -For more information, please refer to our [contributor's guide](https://oss-generic.github.io/process/). + +For more information, please refer to our [contributor's guide](README.md#contributing). diff --git a/README.md b/README.md index 0d19d4ed61..a0af60b99e 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,8 @@ A summary of user commands can be found in the table below: | Pieces legend | `legend` | | History | `history` | | Step back | `stepback` | -| Abort | `abort` | +| List pieces | `captured` | +| Exit | `exit` | Please visit our [User Guide](https://ay2324s1-cs2113-t18-1.github.io/tp/UserGuide.html) for further details on each of these commands. diff --git a/docs/AboutUs.md b/docs/AboutUs.md index a6dd036950..7aa889d9b0 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ # About us -| Name | ID | Github | -|-----------------|-----------|---------------| -| Antriksh Dhand | A0278458J | antrikshdhand | -| Oh Ken Wei | A0251850J | ken-ruster | -| Ou Ningxiang | A0249445W | onx001 | -| Tong Zheng Hong | A0251819Y | TongZhengHong | -| Tricia Boo Koh | A0264665U | TriciaBK | \ No newline at end of file +| Name | ID | Github | +|--------------------------------------|-----------|---------------| +| [Antriksh Dhand](team/antrikshdhand.md) | A0278458J | antrikshdhand | +| [Oh Ken Wei](team/ken-ruster.md) | A0251850J | ken-ruster | +| [Ou Ningxiang ](team/onx001.md) | A0249445W | onx001 | +| [Tong Zheng Hong](team/tongzhenghong.md) | A0251819Y | TongZhengHong | +| [Tricia Boo Koh](team/triciabk.md) | A0264665U | TriciaBK | \ No newline at end of file diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index c9c4245a94..7c16118b83 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -40,9 +40,8 @@ Our application also uses other classes to store information about the chess gam ### ChessMaster component - - -The sequence diagram below illustrates the interactions within the ChessMaster component, when they launch the program. +The sequence diagram below illustrates the interactions within the ChessMaster component, when they launch the program. +The user has the option to terminate the game anytime during this interaction with `exit`, however, this event is not depicted in the diagram for simplicity. ![](images/ChessMasterSequence.png) @@ -50,17 +49,17 @@ How does ChessMaster component work: 1. Attempts to load previously stored game in storage 2. If previous game exists, asks the user if a new game or the previous game should be loaded. -3. If a new game is selected, the user will be prompted for the color to start. -4. Start running the new or previous game instance. +3. If a new game is selected, the user will be prompted for the color and difficulty to start. +4. Start running the new or previous game instance. ### Game component -The sequence diagram below illustrates the interactions within the Game component, taking a move of "a2 a3" as example. +The sequence diagram below illustrates the interactions within the Game component. ![](images/GameSequence.png) How does the Game component work: -1. Solicits input from user. Users can provide game commands, if not recognised, it will parsed as a `MoveCommand`. +1. Solicits input from user. Users can provide game commands, if not recognised, it will parse as a `MoveCommand`. 2. The returned command will be executed. 3. If user entered a game command (not MoveCommand), the next user input will be solicited. 4. If a MoveCommand is identified, the user's input will be parsed as a Move object with checks to ensure it is a valid move on the chessboard. @@ -70,6 +69,7 @@ How does the Game component work: ### User input handling Below is a class diagram representing the Command and Parser classes. + ![](images/ParserCommandDiagram.png) In order to handle user input into the program during the game, the `Parser` class was implemented. @@ -118,8 +118,10 @@ How the minimax algorithm works: 5. The `getBestMove` method returns the best move for the CPU to make. ### Move types -The Move class and its subclasses are responsible for handling the different types of moves in chess. The Move class is an abstract class that is extended by the following subclasses: StandardMove, PawnOpening, CastleMove, EnPassantMove and PromotionMove. The Move class is also extended by the following classes: CastleSide, Direction, MoveDirection, MoveType and PromotionPiece. -Below is a class diagram representing the Move class. +The Move class and its subclasses are responsible for handling the different types of moves in chess. The Move class is a parent class of several move types: CastleMove, EnPassantMove and PromoteMove. The MoveFactory class is responsible for generating the correct move type. + +Below is a class diagram representing the parent Move class. + ![](images/MoveClass.png) #### Standard moves @@ -132,6 +134,9 @@ How standard moves are generated: 4. Any chess piece that "blocks" the path of the chess piece is checked for colour. If the chess piece is of the same colour, the chess piece cannot move past it. If the chess piece is of the opposite colour, the chess piece can capture it. For example, a rook can capture an enemy chess piece in the up, down, left and right directions. (Note that pawn moves are an exception to this rule. Pawn pieces can only capture enemy chess pieces in the forward diagonal directions.) 5. Special cases such as castling, en passant and promotion are also encoded as `directions` and executed separately with different move functions. +Below is a sequence diagram representing the process of generating and executing moves. + +![](images/MoveSequence.png) #### Pawn Opening A pawn opening is a special type of move that involves moving a pawn from its starting position to another tile two spaces ahead. This move is only valid if the pawn is in its starting position and the tile it is moving to as well as the tile in between is empty. @@ -146,27 +151,36 @@ En passant is a special type of move that involves capturing a pawn that has jus Promotion is a special type of move that involves promoting a pawn to another piece. This move is only valid if the pawn is in the correct position, which is at the other end of the board from which it starts. This information is implemented in the Pawn class and executed with the PromotionMove class. - ### Storage Component -**API:** - Below is a class diagram representing the Storage class. The Storage component is responsible for handling the storage and retrieval of chess game state. + ![](images/StorageClass.png) +Below is a sequence diagram of the storage class. +It includes only the more essential methods (createChessmasterFile, saveBoard, loadBoard, resetBoard) required for the main storing and loading of information, to prevent the diagram from being too complex. + ![](images/StorageSequence.png) -* Creates the necessary parent directories for the file and the file itself if they don't exist -* Saves the current state of the ChessBoard to the file. It includes the player's color information and current player's turn as the first line and the state of the chessboard in an 8x8 format. -* Allows resetting the game by clearing the contents of the file. -* Loads the state of the chessboard from the file by constructing a 2D array of ChessTile objects. +Here is a brief overview of how the storage handles the data storage: +1. For the storing of information, it creates the necessary parent directories for the file and the file itself if they don't exist +2. In the text file, the following contents are stored in order, each taking up a line: + 1. Player's color information + 2. Game difficulty + 3. Color of the next player (the color to be played upon the loading of game) + 4. Human player's past moves in the form of comma-separated string + 5. CPU's past moves in the form of comma-separated string + 6. State of the chessboard in an 8x8 format, taking up 8 lines. +3. For the loading of information the above saved information are loaded (Player's color, game difficulty, next turn's color, player's moves, CPU's moves and board state) +4. To ensure data is not corrupted, the executeSavedMoves method parses the saved player's moves and CPU's moves to check against the saved board state +5. Other features include resetting the game by clearing the contents of the file. ## Product scope ### Target user profile - 1. Novice players trying to practice chess and play chess offline without a chess set. 2. Time-poor users looking for a very simple, clean, gimmick-free chess application. + ### Value proposition - Chess novices can use ChessMaster CLI to learn the game's rules and practice their skills. - Busy students can open the application up in their terminal easily for a quick game of chess. @@ -187,8 +201,9 @@ The Storage component is responsible for handling the storage and retrieval of c | v1.0 | player | save and get back to a game | leave when I am busy and resume a game when I am free | | v2.0 | new player | see available moves for a piece | learn the rules of chess and valid moves | | v2.0 | new player | refresh the rules of chess anytime | recap and learn the rules of chess | +| v2.0 | player | see past move history | recap through the gameplay | +| v2.0 | player | see captured pieces | gauge the state of the game | -{more to be added} ## Non-functional requirements diff --git a/docs/UserGuide.md b/docs/UserGuide.md index cb4cdbf46d..80f48508c7 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -6,7 +6,7 @@ ChessMasterCLI is a command-line interface (CLI) chess game designed to make lea - [Quick Start](#quick-start) - [Starting a game](#starting-a-game) -- [Gameplay]() +- [Gameplay](#gameplay) - [Features](#features) - Gameplay features - [Make a move](#make-a-move) @@ -16,10 +16,11 @@ ChessMasterCLI is a command-line interface (CLI) chess game designed to make lea - [Show chess rules: `rules`](#show-chess-rules-rules) - [Show commands: `help`](#show-commands-help) - [Legend: `legend`](#view-pieces-representation-legend) - - [Restart game: `restart`]() - - [View history of game moves: `history`](#view-history-of-game-moves-history) + - [Restart game: `restart`](#start-a-new-game-of-chess-restart) + - [View history of game moves: `history`](#view-history-of-all-game-moves-history) - [Step back in history: `stepback`](#step-back-in-history-stepback) - - [Aborting game: `abort`](#aborting-game-abort) + - [List pieces in play: `captured`](#list-pieces-in-play-captured) + - [Exit game: `exit`](#exit-the-game-exit) - [Command Summary](#command-summary) ## Quick Start @@ -60,21 +61,22 @@ _________________________________________________________________ Upon launching the application, ChessMaster checks if there is a previous game in progress. If a previous game exists, you'll be presented with the option to continue from where you left off. Please enter `y` for **yes** and `n` for **no**. ``` -You have an ongoing previous chess game. Continue game? [y/n] +You have an ongoing previous chess game. Continue game? [y/n/exit] ``` However, if you prefer to **start a fresh game** or if **no previous game is found**, ChessMaster will prompt you to select your preferred color—whether it's "White" or "Black." Please enter `b` for **black** and `w` for **white**. ``` -Choose your starting color to start new game! [b/w] +Choose your starting color to start new game! [b/w/exit] ``` -Next, you can choose the difficulty of the AI you will be pitched against! The current supported difficulty levels range from 1 to 4. Please enter the number corresponding to your preferred difficulty level. +Next, you can choose the difficulty of the AI you will be pitched against! The current supported difficulty levels range from 1 to 3. Please enter the number corresponding to your preferred difficulty level. ``` -Choose your difficulty level! [1/2/3/4] +Choose your difficulty level! [1/2/3/exit] ``` -Now, you'll be off to a rewarding journey of enhancing your chess skills and enjoying the timeless game of strategy and tactics with ChessMaster! +Inputs that are not compliant with the expect input (indicated in `[]`) will be rejected and users will be prompted to re-input. +Else, you'll be off to a rewarding journey of enhancing your chess skills and enjoying the timeless game of strategy and tactics with ChessMaster! ## Gameplay @@ -92,9 +94,9 @@ Additionally, every time a move is executed, ChessMaster provides a comprehensiv - +

-d2 d4
+move d2 d4
 _________________________________________________________________
 
 You moved Pawn from d2 to d4
@@ -125,7 +127,7 @@ _________________________________________________________________
 
 
 

-
+ChessMaster is thinking of a move...
 _________________________________________________________________
 
 ChessMaster moved Pawn from e7 to e5
@@ -158,13 +160,11 @@ _________________________________________________________________
 
 ## Ending the game
 
-The game will automatically end and a victor be declared when one side has been checkmated, or if their king has been captured.
-
-Checkmate is the point in the game where the attacking player has the opponent's king in a situation where it can't escape being captured on the next move.
+The game will automatically end and a victor be declared when one side has been checkmated, or if both players are stuck in a stalemate situation where neither player can avoid moving into a checked position.
 
-The king is "trapped" and has no safe squares to move to, and no other pieces can help.
+Checkmate is the point in the game where the attacking player has the opponent's king in a situation where it can't escape being captured on the next move. The king is "trapped" and has no safe squares to move to, and no other pieces can help.
 
-Otherwise, you may use the `abort` command if you wish to end the game midway. 
+Otherwise, you may use the `exit` command if you wish to end the game midway and quit the program or `restart` command to start a new game. 
 
 ## Features 
 
@@ -178,7 +178,7 @@ Examples:
 - `move a2 a4`
 - `move b3 g6`
 
-ChessMaster also automatically checks if the move was valid and legal before it is executed. If the move is valid, an output as shown above in the [Gameplay](#gameplay) section will be output. Else, the following error
+ChessMaster automatically checks if the move is valid and legal before it is executed. If the move is valid, an output as shown above in the [Gameplay](#gameplay) section will be output. Else, the following error
 message will be shown:
 
 `Oops, that move isn't valid!`
@@ -229,6 +229,33 @@ Format: `show`
 
 Expected Output:
 
+```
+_________________________________________________________________
+
+Here is the current board state:
+_________________________________________________________________
+
+                 (a) (b) (c) (d) (e) (f) (g) (h)
+                _________________________________
+            (8) | R | N | B | Q | K | B | N | R | (8)
+                _________________________________
+            (7) | P | P | P | P | P | P | P | P | (7)
+                _________________________________
+            (6) |   |   |   |   |   |   |   |   | (6)
+                _________________________________
+            (5) |   |   |   |   |   |   |   |   | (5)
+                _________________________________
+            (4) |   |   |   |   |   |   |   |   | (4)
+                _________________________________
+            (3) |   |   |   |   |   |   |   |   | (3)
+                _________________________________
+            (2) | p | p | p | p | p | p | p | p | (2)
+                _________________________________
+            (1) | r | n | b | q | k | b | n | r | (1)
+                _________________________________
+                 (a) (b) (c) (d) (e) (f) (g) (h)
+                 
+```
 
 ### Show chess rules: `rules`
 
@@ -250,8 +277,9 @@ Piece movement:
    Queens ("q") move any number of squares in any direction.
    Kings ("k") move one square in any direction.
 
-Special Rules: (Refer to specific move methods in the User Guide)
-   Castling - King and rook move simultaneously to safeguard the king. (e.g. e1 g1)
+Special Rules:
+[Refer to specific move methods in the User Guide]
+   Castling - King and rook move simultaneously to safeguard the king.
    En Passant - Pawn capturing when moving two squares from starting position.
    Pawn Promotion - Promote a pawn to another piece (except king) upon reaching the back rank.
 
@@ -261,25 +289,29 @@ Objective:
 _________________________________________________________________
 ```
 
-Further details of special rules
-1. Castling
+#### 1. Castling
 
-   For the move to be valid:
-   - It's the king's first move. 
-   - It's the rook's first move. 
-   - There are no pieces between the king and the rook. 
-   - The king is not in check and the king will not be in check after castling
-   
-   To perform castling on either sides:
-    - Move your king two squares to the right/left (towards the rook).
-    - The rook will jump over the king and land on the square next to the king.
+For the move to be valid:
+- It's the king's first move. 
+- It's the rook's first move. 
+- There are no pieces between the king and the rook. 
+- The king is not in check and the king will not be in check after castling
 
-   Example:  `moves e1`
+To perform castling on either sides:
+- Move your king two squares to the right/left (towards the rook).
+- The rook will jump over the king and land on the square next to the king.
 
-   Expected Output:
+Examples and expected output:
 
-   ```
-                 (a) (b) (c) (d) (e) (f) (g) (h)
+
+
+    
+    
+
+
+
+
+
+
Castling availability: moves e1Execute castle move: moves e1 c1
+ +
                 (a) (b) (c) (d) (e) (f) (g) (h)
                 _________________________________
             (8) | R |   |   |   | K |   |   | R | (8)
                 _________________________________
@@ -297,27 +329,23 @@ Further details of special rules
                 _________________________________
             (1) | r |   |[.]|[.]|{k}| b | n | r | (1)
                 _________________________________
-                 (a) (b) (c) (d) (e) (f) (g) (h)
-
-   _________________________________________________________________
+                (a) (b) (c) (d) (e) (f) (g) (h)
 
-   Available coordinates for King at e1:
-   e2 d1 d2 c1
-   _________________________________________________________________
-   
-   ```
+_________________________________________________________________
 
-   Example:  `move e1 c1`
+Available coordinates for King at e1:
+e2 d1 d2 c1
+_________________________________________________________________
- Expected Output: +
- ``` - _________________________________________________________________ +
_________________________________________________________________
 
-   You moved King from e1 to c1
-   _________________________________________________________________
+You moved King from e1 to c1
+_________________________________________________________________
 
-                 (a) (b) (c) (d) (e) (f) (g) (h)
+                (a) (b) (c) (d) (e) (f) (g) (h)
                 _________________________________
             (8) | R |   |   |   | K |   |   | R | (8)
                 _________________________________
@@ -335,78 +363,82 @@ Further details of special rules
                 _________________________________
             (1) |   |   |(k)| r |( )| b | n | r | (1)
                 _________________________________
-                 (a) (b) (c) (d) (e) (f) (g) (h)
-   ```
+                (a) (b) (c) (d) (e) (f) (g) (h)
-2. En Passant: - - This move only happens when your opponent moves their pawn two squares forward from its starting position and lands next to your pawn. - To capture en passant, you must do it on your very next move. - - Move your pawn diagonally forward to the square that your opponent's pawn would have occupied if it had moved only one square forward. +
+#### 2. En Passant: + +This move only happens when your opponent moves their pawn two squares forward from its starting position and lands next to your pawn. +To capture en passant, you must do it on your very next move. +- Move your pawn diagonally forward to the square that your opponent's pawn would have occupied if it had moved only one square forward. - Sample previous board: - ``` - _________________________________________________________________ + + + + + + + + + +
Sample board before en passantExecuting en passant move: move d5 e6
- ChessMaster moved Pawn from e7 to e5 - _________________________________________________________________ +
_________________________________________________________________
 
-                 (a) (b) (c) (d) (e) (f) (g) (h)
-                _________________________________
-            (8) | R |   | B | Q | K | B |   | R | (8)
-                _________________________________
-            (7) | P | P | P | P |( )| P | P | P | (7)
-                _________________________________
-            (6) |   |   | N |   |   |   |   |   | (6)
-                _________________________________
-            (5) |   |   |   | p |(P)|   |   |   | (5)
-                _________________________________
-            (4) |   |   |   |   |   |   |   |   | (4)
-                _________________________________
-            (3) |   |   |   |   |   |   |   |   | (3)
-                _________________________________
-            (2) | p | p |   |   | p | p | p | p | (2)
-                _________________________________
-            (1) | r | n | b | q | k | b | n | r | (1)
-                _________________________________
-                 (a) (b) (c) (d) (e) (f) (g) (h)
-   ```
-
-   Example: `moves d5`
+ChessMaster moved Pawn from e7 to e5
+_________________________________________________________________
 
-   Expected output:
+             (a) (b) (c) (d) (e) (f) (g) (h)
+            _________________________________
+        (8) | R |   | B | Q | K | B |   | R | (8)
+            _________________________________
+        (7) | P | P | P | P |( )| P | P | P | (7)
+            _________________________________
+        (6) |   |   | N |   |   |   |   |   | (6)
+            _________________________________
+        (5) |   |   |   | p |(P)|   |   |   | (5)
+            _________________________________
+        (4) |   |   |   |   |   |   |   |   | (4)
+            _________________________________
+        (3) |   |   |   |   |   |   |   |   | (3)
+            _________________________________
+        (2) | p | p |   |   | p | p | p | p | (2)
+            _________________________________
+        (1) | r | n | b | q | k | b | n | r | (1)
+            _________________________________
+             (a) (b) (c) (d) (e) (f) (g) (h)
- ``` - (a) (b) (c) (d) (e) (f) (g) (h) - _________________________________ - (8) | R | | B | Q | K | B | | R | (8) - _________________________________ - (7) | P | P | P | P | | P | P | P | (7) - _________________________________ - (6) | | |[N]|[.]|[.]| | | | (6) - _________________________________ - (5) | | | |{p}| P | | | | (5) - _________________________________ - (4) | | | | | | | | | (4) - _________________________________ - (3) | | | | | | | | | (3) - _________________________________ - (2) | p | p | | | p | p | p | p | (2) - _________________________________ - (1) | r | n | b | q | k | b | n | r | (1) - _________________________________ - (a) (b) (c) (d) (e) (f) (g) (h) - - _________________________________________________________________ +
- Available coordinates for Pawn at d5: - e6 c6 d6 - _________________________________________________________________ +
_________________________________________________________________
 
-   ```
+You moved Pawn from d5 to e6
+_________________________________________________________________
 
+             (a) (b) (c) (d) (e) (f) (g) (h)
+            _________________________________
+        (8) | R |   | B | Q | K | B |   | R | (8)
+            _________________________________
+        (7) | P | P | P | P |   | P | P | P | (7)
+            _________________________________
+        (6) |   |   | N |   |(p)|   |   |   | (6)
+            _________________________________
+        (5) |   |   |   |( )|   |   |   |   | (5)
+            _________________________________
+        (4) |   |   |   |   |   |   |   |   | (4)
+            _________________________________
+        (3) |   |   |   |   |   |   |   |   | (3)
+            _________________________________
+        (2) | p | p |   |   | p | p | p | p | (2)
+            _________________________________
+        (1) | r | n | b | q | k | b | n | r | (1)
+            _________________________________
+             (a) (b) (c) (d) (e) (f) (g) (h)
+
### Show commands: `help` @@ -415,29 +447,31 @@ Show a list of commands and what they do Format: `help` Expected Output: + ``` _________________________________________________________________ -Seems like you need some help! -Here are the following commands to play: -Move piece - Input coordinate of piece, followed by coordinate to move to - Format: [column][row] [column][row] - E.g. a2 a3 -Show available moves - Lists all the available moves for a piece - Format: moves [column][row] - E.g. moves a2 -Show board - Shows the current state of the chess board - Format: show -Obtain rules - Obtain a quick refresher on the rules of chess - Format: rules -View pieces representation - Display a legend that explains the - piece representations - Format: legend -Abort game - Exit programme - Format: abort +Here are the commands you can use to play: +move Move piece + Format: move [column][row] [column][row] + e.g. move a2 a3 +moves Show available moves for a piece + Format: moves [column][row] + e.g. moves a2 +show Show the chessboard +rules Obtain a quick refresher on the rules of chess +legend View pieces representation +restart Start a new game +history View history of all game moves +stepback View the board as it was a certain number of moves ago + Format: stepback [number of moves to step back] + e.g. stepback 4 +captured See which Player and CPU pieces have been captured so far +exit Exit game _________________________________________________________________ ``` + ### View pieces representation: `legend` Display a legend that explains the piece representations @@ -445,6 +479,7 @@ Display a legend that explains the piece representations Format: `legend` Expected Output: + ``` _________________________________________________________________ @@ -468,34 +503,117 @@ _________________________________________________________________ ### Start a new game of chess: `restart` -Enables the player to restart the game +Enables the player to restart the game. +Selecting `y` will restart the game, while `n` will cause player to exit program. Format: `restart` Expected output: + ``` Do you want to restart game? [y/n] ``` - ### View history of all game moves: `history` Displays a list of all previous moves in the current game Format: `history` +Sample output: +``` +_________________________________________________________________ + +Move 1: WHITE moves Knight from g8 to f6 +Move 2: BLACK moves Pawn from a2 to a4 +Move 3: WHITE moves Knight from b8 to c6 + +_________________________________________________________________ +``` + ### Step back in history: `stepback` -Displays the board state as it was a certain number of moves ago. Note that this command does not reverse any moves -in the current game. +Displays the board state as it was a certain number of moves ago. **Note that this command does not reverse any moves +in the current game.** Format: `stepback [number of moves to step back]` -### Saving and loading games +Example: `stepback 2` -Every time a turn ends, ChessMaster will save the current state of the game. When ChessMaster is restarted, the -user will be prompted to choose whether to load the saved game or begin a new one. ChessMaster only supports a single -saved game, and starting a new one will delete the existing save file. +Sample output: + +``` + (a) (b) (c) (d) (e) (f) (g) (h) + _________________________________ + (8) | r | n | b | q | k | b | | r | (8) + _________________________________ + (7) | p | p | p | p | p | p | p | p | (7) + _________________________________ + (6) | | | | | | n | | | (6) + _________________________________ + (5) | | | | | | | | | (5) + _________________________________ + (4) | | | | | | | | | (4) + _________________________________ + (3) | | | | | | | | | (3) + _________________________________ + (2) | P | P | P | P | P | P | P | P | (2) + _________________________________ + (1) | R | N | B | Q | K | B | N | R | (1) + _________________________________ + (a) (b) (c) (d) (e) (f) (g) (h) + +_________________________________________________________________ + +Stepped back 2 steps! +Use `show` to see the current board. +_________________________________________________________________ +``` + +### List pieces in play: `captured` + +Lists each player's pieces grouped by whether they have been captured or not. + +Format: `captured` + +Sample output: + +``` +_________________________________________________________________ + +Player's pieces +---------------------------------------- +In play: +- Bishop x2 +- King +- Knight +- Pawn x7 +- Rook x2 + +Captured: +- Queen +- Knight +- Pawn + + +CPU's pieces +---------------------------------------- + +In play: +- Bishop x2 +- King +- Queen +- Knight +- Pawn x7 +- Rook x2 + +Captured: +- Knight +- Pawn + +_________________________________________________________________ + +``` ### Exit the game: `exit` @@ -504,6 +622,7 @@ Exit the ChessMaster application. Format: `exit` Expected Output: + ``` _________________________________________________________________ @@ -511,38 +630,57 @@ Exiting program... Thanks for playing! _________________________________________________________________ ``` +### Saving and loading games + +Every time a turn ends, ChessMaster will save the current state of the game. When ChessMaster is restarted, the +user will be prompted to choose whether to load the saved game or begin a new one. ChessMaster only supports a single +saved game, and starting a new one will delete the existing save file. + +### Editing the storage file +Tasks data are saved automatically in a text (.txt) file `/data/ChessMaster.txt`. Users are welcome to update data directly by editing this data file to achieve a desired game state. + +The format of the saved game in the storage file is as follows: + +```java +[CURRENT_PLAYER_COLOR] // Either BLACK or WHITE +[GAME_DIFFICULTY] // Integer from 1 to 3 +[HUMAN_MOVE_HISTORY] // delimited by ',' +[CPU_MOVE_HISTORY] // delimited by ',' +``` + ## FAQ -**Q**: How do I transfer my data to another computer? +> **Q**: How do I transfer my data to another computer? **A**: You can navigate to your root folder, and find the file `data/ChessMaster.txt`. Transfer the file to your other computer, and find the `data` folder in the ChessMaster install folder in the other computer. Copy the file into the folder. Start ChessMaster, and type `y` when prompted to load a saved game. -**Q**: How do I play with another person? +> **Q**: How do I play with another person? **A**: Currently, ChessMaster does not support multiplayer. -**Q**: Can we play timed games? +> **Q**: Can we play timed games? -**A**: ChessMaster does not come with an internal timer. However, you are able to use your own chess timer or stopwatch -to simulate timed games. +**A**: ChessMaster does not come with an internal timer. However, you are able to use your own chess timer or stopwatch to simulate timed games. -**Q**: Can I draw by repeating moves? +> **Q**: Can I draw by repeating moves? -**A**: ChessMaster does not currently support draws by repetition. However, you can use the `abort` command to end the game. Alternatively, you can maneuver the game to a draw by stalemate, where neither party can move without being in check. +**A**: ChessMaster does not currently support draws by repetition. However, you can use the `exit` command to end the game. Alternatively, you can maneuver the game to a draw by stalemate, where neither party can move without being in check or there are only kings left on the board. ## Command Summary -| Action | Format | -|---------------|------------------------------------| -| Move | `move [column][row] [column][row]` | -| Show moves | `moves [column][row]` | -| Show board | `show` | -| Rules | `rules` | -| Help | `help` | -| Pieces legend | `legend` | -| History | `history` | -| Step back | `stepback` | -| Exit | `exit` | +| Action | Format | +|----------------------|------------------------------------| +| Move | `move [column][row] [column][row]` | +| Show moves | `moves [column][row]` | +| Show board | `show` | +| Rules | `rules` | +| Help | `help` | +| Pieces legend | `legend` | +| Restart game | `restart` | +| History | `history` | +| Step back | `stepback [number of steps]` | +| List captured pieces | `captured` | +| Exit | `exit` | diff --git a/docs/diagrams/MoveClass.puml b/docs/diagrams/MoveClass.puml index b0c63bd218..36525711a2 100644 --- a/docs/diagrams/MoveClass.puml +++ b/docs/diagrams/MoveClass.puml @@ -7,9 +7,14 @@ class Move { - to: Coordinate - pieceMoved: ChessPiece - pieceCaptured: ChessPiece + + + getFrom(): Coordinate + + getTo(): Coordinate + getPieceMoved(): ChessPiece + hasCapturedAPiece(): boolean + getPieceCaptured(): ChessPiece + isValidWithCheck(board: ChessBoard): boolean + + isSkippingPawn(): boolean + + toString(): String } @enduml diff --git a/docs/diagrams/MoveSequence.puml b/docs/diagrams/MoveSequence.puml new file mode 100644 index 0000000000..c296663ffc --- /dev/null +++ b/docs/diagrams/MoveSequence.puml @@ -0,0 +1,54 @@ +@startuml MoveSequence + +actor User as user #BlanchedAlmond +participant ":Game" as g #LightGreen +participant ":UI" as ui #LightBlue +participant ":MoveCommand" as mc #LightCoral +participant ":MoveFactory" as mf #IndianRed +participant ":ChessBoard" as cb #LightPink + +user --> ui : move coordinates :String +activate ui +ui --> g : move coordinates :String +deactivate ui +activate g +create mc +g --> mc : inputString :String +activate mc +g -> mc : execute() +mc --> g : move :Move +deactivate mc +destroy mc +g -> cb : move :Move +deactivate g +activate cb +cb -> cb : isValidMove() +activate cb +cb --> cb : :boolean +deactivate cb +alt Move is valid + create mf + cb -> mf : createMove() + deactivate cb + activate mf + mf --> cb : move :Move + deactivate mf + destroy mf + activate cb + cb -> cb : executeMove() + cb --> g : board :ChessBoard + activate g + g -> ui : printChessBoardWithMove() + deactivate g + activate ui + ui --> user + deactivate ui +else Move is invalid + cb -> ui : :InvalidMoveException + deactivate cb + activate ui + ui --> user + deactivate ui +end + +@enduml \ No newline at end of file diff --git a/docs/diagrams/ParseCommandSequence.puml b/docs/diagrams/ParseCommandSequence.puml index 80080ff983..df01c2a319 100644 --- a/docs/diagrams/ParseCommandSequence.puml +++ b/docs/diagrams/ParseCommandSequence.puml @@ -1,13 +1,13 @@ @startuml 'https://plantuml.com/sequence-diagram -participant ":Game" as Game -participant ":Parser" as Parser -participant ":Command" as Command -participant ":CommandResult" as CommandResult -participant "coord :Coordinate" as Coordinate -participant "board :ChessBoard" as ChessBoard -participant "promoteFrom :ChessPiece" as ChessPiece +participant ":Game" as Game #Pink +participant ":Parser" as Parser #SkyBlue +participant ":Command" as Command #IndianRed +participant ":CommandResult" as CommandResult #LimeGreen +participant "coord :Coordinate" as Coordinate #LightPink +participant "board :ChessBoard" as ChessBoard #Turquoise +participant "promoteFrom :ChessPiece" as ChessPiece #Orchid Game -> Parser: parseCommand() activate Parser diff --git a/docs/diagrams/ParserCommandDiagram.puml b/docs/diagrams/ParserCommandDiagram.puml index 573b9fde3d..afda39c262 100644 --- a/docs/diagrams/ParserCommandDiagram.puml +++ b/docs/diagrams/ParserCommandDiagram.puml @@ -4,8 +4,8 @@ hide circle skinparam classAttributeIconSize 0 package command{ abstract class Command { - +execute() - +isMoveCommand() + +execute(game: Game) + +isMoveCommand(): boolean } class MoveCommand { -userInput: String diff --git a/docs/diagrams/StorageClass.puml b/docs/diagrams/StorageClass.puml index a3153ecba6..8567863b27 100644 --- a/docs/diagrams/StorageClass.puml +++ b/docs/diagrams/StorageClass.puml @@ -3,14 +3,29 @@ hide circle skinparam classAttributeIconSize 0 class Storage { - filePathString: String + - storageFile: File + - blackPieceNum: int + - whitePieceNum: int + - blackKingPresent: boolean + - whiteKingPresent: boolean + - fileScanner: Scanner + - lastMove: Coordinate + Storage(filePath: String) - + saveBoard(board: ChessBoard, playerColor: Color): void + + saveBoard(board: ChessBoard, currentColor: Color, human: Human, cpu: CPU): void + resetBoard(): void + loadBoard(): ChessTile[][] + + executeSavedMoves(playerColor: Color, otherBoard: ChessBoard, human: Human, cpu: CPU): void + loadPlayerColor(): Color + + loadDifficulty(): int + + loadCurrentColor(): Color + + getFilePath(): String + + loadHumanMoves(): ArrayList + + loadCPUMoves(): ArrayList + - isPieceValid(initialPiece: ChessPiece): Boolean - createChessMasterFile(): void + } class ChessBoard { @@ -21,8 +36,24 @@ class ChessTile class Color -Storage --|> File +class Coordinate + +class Parser + +class ChessPiece + +class Human + +class CPU + Storage --|> ChessBoard Storage --|> ChessTile Storage --|> Color +Storage --|> Coordinate +Storage --|> Parser +Storage --|> ChessPiece +Storage --|> Human +Storage --|> CPU + @enduml + diff --git a/docs/diagrams/StorageSequence.puml b/docs/diagrams/StorageSequence.puml index 3536c57601..b67ba8af0d 100644 --- a/docs/diagrams/StorageSequence.puml +++ b/docs/diagrams/StorageSequence.puml @@ -1,15 +1,15 @@ @startuml -actor User -participant ":Storage" as Storage -participant ":File" as File -participant ":FileWriter" as FileWriter -participant ":Scanner" as Scanner -participant ":ChessTile" as ChessTile -participant ":String" as String -participant "human:Human" as Human -participant "cpu:CPU" as CPU -participant "board:ChessBoard" as ChessBoard +actor User #BlanchedAlmond +participant ":Storage" as Storage #DeepSkyBlue +participant ":File" as File #Orange +participant ":FileWriter" as FileWriter #PaleTurquoise +participant ":Scanner" as Scanner #Pink +participant ":ChessTile" as ChessTile #Orchid +participant ":String" as String #IndianRed +participant "human:Human" as Human #Lime +participant "cpu:CPU" as CPU #LightGreen +participant "board:ChessBoard" as ChessBoard #LightPink create Storage diff --git a/docs/images/ChessMasterSequence.png b/docs/images/ChessMasterSequence.png index e157d4e6c5..285ee76221 100644 Binary files a/docs/images/ChessMasterSequence.png and b/docs/images/ChessMasterSequence.png differ diff --git a/docs/images/GameSequence.png b/docs/images/GameSequence.png index d4fa0de77c..726c8214db 100644 Binary files a/docs/images/GameSequence.png and b/docs/images/GameSequence.png differ diff --git a/docs/images/MiniMaxSequenceDiagram.png b/docs/images/MiniMaxSequenceDiagram.png index b5d1ee5e92..0095327e0f 100644 Binary files a/docs/images/MiniMaxSequenceDiagram.png and b/docs/images/MiniMaxSequenceDiagram.png differ diff --git a/docs/images/MoveClass.png b/docs/images/MoveClass.png index 0b1a26069b..2be9cf50c0 100644 Binary files a/docs/images/MoveClass.png and b/docs/images/MoveClass.png differ diff --git a/docs/images/MoveSequence.png b/docs/images/MoveSequence.png new file mode 100644 index 0000000000..9d2fc1522c Binary files /dev/null and b/docs/images/MoveSequence.png differ diff --git a/docs/images/ParseCommandSequence.png b/docs/images/ParseCommandSequence.png index 756872fbfb..cf9276b5c9 100644 Binary files a/docs/images/ParseCommandSequence.png and b/docs/images/ParseCommandSequence.png differ diff --git a/docs/images/ParserCommandDiagram.png b/docs/images/ParserCommandDiagram.png index d2548b46ea..34b88623c6 100644 Binary files a/docs/images/ParserCommandDiagram.png and b/docs/images/ParserCommandDiagram.png differ diff --git a/docs/images/StorageClass.png b/docs/images/StorageClass.png index 7e0558e1a0..5779c82912 100644 Binary files a/docs/images/StorageClass.png and b/docs/images/StorageClass.png differ diff --git a/docs/images/StorageSequence.png b/docs/images/StorageSequence.png index 4edee72b56..4f16a75622 100644 Binary files a/docs/images/StorageSequence.png and b/docs/images/StorageSequence.png differ diff --git a/docs/team/antrikshdhand.md b/docs/team/antrikshdhand.md index 4c49307f4b..738d1a170e 100644 --- a/docs/team/antrikshdhand.md +++ b/docs/team/antrikshdhand.md @@ -1,17 +1,39 @@ -# Project Portfolio Page [DRAFT] +# Project Portfolio Page + +By Antriksh Dhand (@antrikshdhand) ## Overview +I primarily focused on enhancing the gameplay experience and ensuring the robustness of the codebase. I implemented new key commands, refactored existing code for an improved object-oriented design, and explored end-to-end testing strategies. + ## Contributions ### Code contributed +Check out my contributions on the [TP Dashboard](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=antrikshdhhttps://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=antrikshdhand&breakdown=truehttps://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=antrikshdhand&breakdown=truehttps://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=antrikshdhand&breakdown=trueand&breakdown=true). + ### Enhancements implemented -### Contributions to the UG +#### `captured`, `history`, and `stepback` Commands + +I implemented the `captured`, `history`, and `stepback` commands, allowing the player access to commands they would generally expect to see in a modern chess game. The `stepback` command posed a unique challenge, requiring a deep dive into move reversal mechanisms. This effort involved significant code refactoring to promote cleaner, more object-oriented structures. + +#### Object-Oriented Design Refactoring + +To enhance code maintainability and extensibility, I introduced sub-classes such as `CastleMove` and `EnPassantMove`. Additionally, the implementation of the `MoveFactory` class, adopting the Factory method pattern, has streamlined the creation of Move objects. This in turn fed into making the reversal of moves much more cleaner to implement and more robust overall. I also encouraged and administered the use of SLAP in our codebase. + +### Contributions to the User Guide (UG) + +While not introducing new major sections, I diligently maintained the UG by documenting my new commands and ensuring that information stays relevant and up to date. + +### Contributions to the Developer Guide (DG) + +#### End-to-End Testing Section + +I made substantial progress in establishing end-to-end testing for the CLI. Despite not achieving full completion, I laid the groundwork and documented the process in the DG, providing a valuable resource for future testing efforts. You can see a stub of this code in the DG. -### Contributions to the DG +### General Contributions -### Contributions to team-based tasks +Regularly contributed to team discussions, providing insights and assistance where needed. -### Review/mentoring contributions \ No newline at end of file +Actively participated in code reviews and offered constructive feedback to enhance overall code quality. \ No newline at end of file diff --git a/docs/team/ken-ruster.md b/docs/team/ken-ruster.md index 73bb5d5b37..0c74f5ec61 100644 --- a/docs/team/ken-ruster.md +++ b/docs/team/ken-ruster.md @@ -4,30 +4,54 @@ ChessMaster is a desktop application targeted at chess beginners for them to be able to learn chess on the go. Users interact with it using the CLI, and its simplicity means that even low-end machines should be able to run it. +### Code contributions: [RepoSense Link](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=ken-ruster&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) + Given below are my contributions to the project: -- **New Feature**: Checking for the validity of the save file. - - Justification: To make sure that the move history and current board state match. Prevents the user from making - illegal moves via changing the save file. - - Highlights: The implementation required a revision to the structure used to save the file. - It required implementing a `PromoteMove` class as well to store possible promotions. -- **New feature**: Displaying valid moves for a piece - - Justification: New players may not be able to tell how certain pieces move. - This feature assists them in learning how the game works in an accessible manner. - - Highlights: Doing this posed a challenge on how best to display the different possibilities for moves - , eg capturable pieces, current position of the piece and empty tiles it could move to. - Some difficulty arose while learning to go formatting for text highlighting which works - for both Windows and Mac. -- **New feature**: Handling promotion - - Justification: Promotion is an essential mechanic of Chess. - - Highlights: Handling promotion required extensive modification of several classes relating to game function. -- **Project Management**: - - Managed bug testing and checkStyle adherence for releases `1.0` and `2.0` -- **Enhancements to existing features**: - - Added additional tests for existing features -- **Documentation**: - - Developer Guide: - - Added documentation for the `Parser` and `Command` subclasses - - User Guide: - - Added documentation for the Features section - - Updated the Command Summary - - Added the FAQ section +### **New Feature**: Move class +- A class representing a move to be made. +- Includes the coordinates from and to of the move, as well as the piece involved to check for validity later. +### **New Feature**: Checking for the validity of the save file. +- Justification: To make sure that the move history and current board state match. Prevents the user from making + illegal moves via changing the save file. +- Highlights: The implementation required a revision to the structure of the saved file. + It required implementing a `PromoteMove` class as well to store possible promotions. +- How it works: + - Move history is stored in the .txt file + - When the game is loaded, the file reads the moves of the human and CPU, and merges them to form a single array. + - Each of the moves in the array are parsed and executed in the exact order they were stored in. + - The board it was executed on then is passed into the Game to be run after being checked against the saved board state. + - By doing this, we are able to ensure the History, Stepback and Captured commands are able to work normally after a saved game is loaded. +### **New feature**: Displaying valid moves for a piece +- Justification: New players may not be able to tell how certain pieces move. + This feature assists them in learning how the game works in an accessible manner. +- Highlights: Doing this posed a challenge on how best to display the different possibilities for moves + , eg capturable pieces, current position of the piece and empty tiles it could move to. + Some difficulty arose while learning to go formatting for text highlighting which works + for both Windows and Mac. +### **New feature**: Handling promotion +- Justification: Promotion is an essential mechanic of Chess. +- Highlights: Handling promotion required extensive modification of several classes relating to game function. +- How it works: + - Game checks for pieces able to promoted at the end of each move + - User is prompted to promote a piece, and the input is parsed to return a new piece + - The pawn to be promoted is replaced with the new piece +### **Project Management**: +- Set up GitHub organisation and repo +- Managed repo branch security settings +- Maintained issue tracker +- Managed bug testing and checkStyle adherence for releases `1.0` and `2.0` +### **Enhancements to existing features**: +- Added additional tests for existing features, including: + - `parsePromote` + - `ShowMovesCommand` + - `executeSavedMoves`, `loadHumanMoves()` and `loadCPUMoves()` in `Storage` + - `executeMoveArray` in `ChessBoard` +### **Documentation**: +- Developer Guide: + - Added documentation for the `Parser` and `Command` subclasses + - Added sequence diagram for move parsing + - Added class diagram describing `Parser` and `Command` +- User Guide: + - Added documentation for the Features section + - Updated the Command Summary + - Added and wrote the FAQ section diff --git a/docs/team/onx001.md b/docs/team/onx001.md index 9b108bbdeb..0183e53ec5 100644 --- a/docs/team/onx001.md +++ b/docs/team/onx001.md @@ -1,43 +1,38 @@ -This is a draft for the PPP. - -In here goes my contributions and features. - -Let me know if there is more to add. - -Here are some additional lines. - -Features: - -Minimax -Castling -En-Passant -Move validity -Check -Checkmate - # Project Portfolio: ChessMaster ## Overview -I implemented the main game logic, including the `Minimax` class and algorithm which serves as the backbone for the +I implemented the main game logic, including the `Minimax` class and algorithm which serves as the backbone for the CPU, as well as move logic for the individual chesspieces. These classes dictate the precise functioning and internal logic of the game. My responsibilities extend to detailing documentation for these gameplay features, and for ironing out bugs and issues that might arise from the progressive integration of these features into the game. ## Contributions ### Minimax Class and Algorithm +The Minimax class and algorithm is the backbone of the CPU. It is responsible for determining the best move for the CPU to make, and is the core of the CPU's decision-making process. The Minimax algorithm is a recursive algorithm that searches through the game tree, and is used to determine the best move for the CPU to make. The Minimax class is responsible for the implementation of the algorithm, and is the class that is called by the CPU to determine the best move to make. I implemented the algorithm with SLAP and extracted helper functions to improve the readability of the code. + +#### Difficulty +The difficulty of the game is determined by the depth of the Minimax algorithm. The higher the depth, the more moves the CPU will look ahead, and the more difficult the game will be. The depth of the Minimax algorithm can be set by the user in the savefile and when starting a new game. The maximum depth is set to 3, as the CPU will take a long time to make a move if the depth is set too high. -#### Loading and Difficulty +The difficulty is also saved in the savefile, and is loaded when the user loads the savefile. I implemented this feature by adding a new field in the savefile, and by adding a new parameter in the `Game` class to set the difficulty of the game. This was added in the style of the existing parameters in the `Storage` class. ### Move Validity +The move validity of the chesspieces is determined by the `isValidMove()` method in the `ChessPiece` class. This method is called by the `Game` class to determine if a move is valid. The `isValidMove()` method is implemented in the child classes of the `ChessPiece` class, and is responsible for determining if a move is valid for the chesspiece. + +I implemented this alongside various non-standard chess moves, such as castling and en passant in a manner that minimised code coupling and enabled other developers to have a more bug-free experience. + +### Reposense -#### Castling +My code contributions can be found on the [TP Dashboard](https://github.com/AY2324S1-CS2113-T18-1/tp/commit/fa0fa79e972d3c962461120070ca65f00bfff965). -#### En Passant -#### Checking and Checkmate +### Project Management +I solved bugs and issues that arose from the integration of the Minimax algorithm and the move validity of the chesspieces. I also managed the bug testing and checkstyle adherence, and delivered key features in a timely manner which allowed for other developers to have a longer runway for implementing their features. -### Chess Pieces +### Documentation -#### ChessPiece Parent Class and Child Classes +#### Developer's Guide +I documented the `Minimax` class and algorithm with a sequence diagram, as well as the `Move` class and the various types of moves available. I also added a sequence diagram and a class diagram of the `Move` class. -#### Scores +### User's Guide +I added documentation in the user guide for castling and en passant, as well as the difficulty of the game. In addition, I polished and edited the user guide to make it more comprehensive about the various features ChessMaster has to offer. -#### Board Weights \ No newline at end of file +### Team-based tasks +I added several issues and reviewed several PRs, and helped to manage the project board. An example can be found [in this pull request](https://github.com/AY2324S1-CS2113-T18-1/tp/pull/182) and [in this issue](https://github.com/AY2324S1-CS2113-T18-1/tp/issues/185). I also submitted various bugs for other teams such as [here](https://github.com/AY2324S1-CS2113-F11-1/tp/issues/58). \ No newline at end of file diff --git a/docs/team/tongzhenghong.md b/docs/team/tongzhenghong.md index b8774480a6..408b336e29 100644 --- a/docs/team/tongzhenghong.md +++ b/docs/team/tongzhenghong.md @@ -5,6 +5,8 @@ By: Tong Zheng Hong ## Overview As a key contributor to the Chess Master project, I played a central role in developing the foundational classes for the game, including the `Game` and `ChessMaster` classes. These classes serve as the backbone of the chess-playing application, managing overall game processing and execution. My responsibilities extended to crafting a comprehensive user guide and developer guide to ensure a seamless experience for both end-users and fellow developers. +My code contribution can be found in this [TP Dashboard](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=tongzhenghong&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) + ## Chess Master Class The ChessMaster class acts as the orchestrator, handling the primary game loop, player turns, move execution, and game state management. I meticulously designed and implemented this class to provide a robust and enjoyable chess-playing experience. The ChessMaster class integrates seamlessly with other components, such as the chessboard, players, and user interface. `Game` Class diff --git a/docs/team/triciabk.md b/docs/team/triciabk.md index dd928e464d..1abe20c1f0 100644 --- a/docs/team/triciabk.md +++ b/docs/team/triciabk.md @@ -2,27 +2,36 @@ ## Project: ChessMaster -###### Chessmaster is a ChessMasterCLI is a command-line interface (CLI) chess game designed to make learning and training accessible for beginners while offering an engaging experience for all skill levels. This sleek and user-friendly chess simulator provides a platform for novice players to build their skills and understanding of the game. +ChessMaster is a ChessMasterCLI is a command-line interface (CLI) chess game designed to make learning and training accessible for beginners while offering an engaging experience for all skill levels. +This sleek and user-friendly chess simulator provides a platform for novice players to build their skills and understanding of the game. ### Given below are my contributions to the project: +###### My code contribution can be found in this [TP Dashboard](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=triciabk&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) #### Storage Class -- stores the current state of board -- stores the last current player before exit of game -- loads the state of board +- Stores the current state of board +- Stores the last current player before exit of game +- Loads the state of board +I managed the `storage` class of the board and the data that are required to be stored and returned when loading a new game. #### Restart function - Restart allows users to restart the game any time they want, without having to exit the program and entering again. - Allow for restart of new game after a game is completed #### Other Trivial Commands -- +- `help` command +- `show` board command +- `rules` command + +### Project Management +- Managed Release v1.0 on GitHub ### Documentation: #### User Guide: -Added documentation for the features storage and command features (Exit, Help, Restart, Rules, Show) +- Added documentation for the features releating to storage and command features (Exit, Help, Restart, Rules, Show) +- Implemented command summary and expected outputs of commands. #### Developer Guide: - Added implementation details of the storage feature. - Contributed to product scope and target users. - +- Updated user stories. \ No newline at end of file diff --git a/src/main/java/chessmaster/ChessMaster.java b/src/main/java/chessmaster/ChessMaster.java index 4fe2c39f4c..abc8c0a477 100644 --- a/src/main/java/chessmaster/ChessMaster.java +++ b/src/main/java/chessmaster/ChessMaster.java @@ -5,13 +5,11 @@ import chessmaster.game.ChessBoard; import chessmaster.ui.TextUI; import chessmaster.storage.Storage; -import chessmaster.game.ChessTile; import chessmaster.game.Color; import chessmaster.game.Game; import chessmaster.user.CPU; import chessmaster.user.Human; - /** * Main entry-point for ChessMaster application. */ @@ -37,16 +35,19 @@ private ChessMaster() { try { playerColor = storage.loadPlayerColor(); difficulty = storage.loadDifficulty(); - currentTurnColor = storage.loadCurrentColor(); - ChessTile[][] existingBoardState = storage.loadBoard(); - board = new ChessBoard(playerColor, existingBoardState); + //@@author ken_ruster + // ChessTile[][] existingBoardState = storage.loadBoard(); + // ChessBoard existingBoard = new ChessBoard(playerColor, existingBoardState); + board = new ChessBoard(playerColor); human = new Human(playerColor, board); cpu = new CPU(playerColor.getOppositeColour(), board); - + storage.executeSavedMoves(playerColor, board, human, cpu); board.setDifficulty(difficulty); + currentTurnColor = getCurrentTurnColor(human, cpu, playerColor); + if (shouldStartNewGame() && !exit) { loadNewGame(); } @@ -56,6 +57,22 @@ private ChessMaster() { } } + private Color getCurrentTurnColor(Human human, CPU cpu, Color playerColor) throws ChessMasterException { + int noHumanMoves = human.getMovesLength(); + int noCPUMoves = cpu.getMovesLength(); + + if (noHumanMoves == noCPUMoves && playerColor.isWhite() + || noHumanMoves < noCPUMoves && playerColor.isBlack()) { + return playerColor; + } else if (noHumanMoves > noCPUMoves && playerColor.isWhite() + || noHumanMoves == noCPUMoves && playerColor.isBlack()) { + return playerColor.getOppositeColour(); + } + + throw new ChessMasterException( + "Moves in save file are invalid! Please start a new game or correct the error"); + } + private boolean shouldStartNewGame() { ui.promptContinuePrevGame(false); String input = ui.getUserInput(false); diff --git a/src/main/java/chessmaster/commands/CapturedCommand.java b/src/main/java/chessmaster/commands/CapturedCommand.java new file mode 100644 index 0000000000..751e575840 --- /dev/null +++ b/src/main/java/chessmaster/commands/CapturedCommand.java @@ -0,0 +1,103 @@ +package chessmaster.commands; + +import chessmaster.exceptions.ChessMasterException; +import chessmaster.game.Game; +import chessmaster.pieces.ChessPiece; +import chessmaster.user.CPU; +import chessmaster.user.Human; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CapturedCommand extends Command { + + public static final String CAPTURED_COMMAND_STRING = "captured"; + + private String getCapturedDisplayString( + ArrayList humanInPlay, ArrayList humanCaptured, + ArrayList cpuInPlay, ArrayList cpuCaptured) { + + StringBuilder sb = new StringBuilder(); + sb.append("Player's pieces\n"); + sb.append("-".repeat(40)).append(System.lineSeparator()); + + // In play: Human + sb.append("In play:\n"); + appendPiecesWithCount(sb, humanInPlay); + + // Captured: Human + sb.append("\nCaptured:\n"); // Add a separator between "In play" and "Captured" + appendPiecesWithCount(sb, humanCaptured); + + // Space between Human and CPU pieces + sb.append(System.lineSeparator()); + sb.append(System.lineSeparator()); + + sb.append("CPU's pieces\n"); + sb.append("-".repeat(40)).append(System.lineSeparator()); + + // In play: CPU + sb.append("\nIn play:\n"); // Add a separator between "Captured" and "In play" + appendPiecesWithCount(sb, cpuInPlay); + + // Captured: CPU + sb.append("\nCaptured:\n"); // Add a separator between "In play" and "Captured" + appendPiecesWithCount(sb, cpuCaptured); + + return sb.toString(); + } + + private void appendPiecesWithCount(StringBuilder sb, List pieces) { + Map pieceCountMap = new HashMap<>(); + for (ChessPiece piece : pieces) { + String pieceName = piece.getPieceName(); + pieceCountMap.put(pieceName, pieceCountMap.getOrDefault(pieceName, 0) + 1); + } + + // This relies on the fact that the "In play" pieces should never be empty + if (pieceCountMap.isEmpty()) { + sb.append("No pieces have been captured yet!\n"); + } + + for (Map.Entry entry : pieceCountMap.entrySet()) { + sb.append("- ").append(entry.getKey()); + if (entry.getValue() > 1) { + sb.append(" x").append(entry.getValue()); + } + sb.append(System.lineSeparator()); + } + } + + public CommandResult execute(Game game) throws ChessMasterException { + Human human = game.getHuman(); + ArrayList humanInPlay = new ArrayList<>(); + ArrayList humanCaptured = new ArrayList<>(); + for (ChessPiece p : human.getPieces()) { + if (p.getIsCaptured()) { + humanCaptured.add(p); + } else { + humanInPlay.add(p); + } + } + + CPU cpu = game.getCPU(); + ArrayList cpuInPlay = new ArrayList<>(); + ArrayList cpuCaptured = new ArrayList<>(); + for (ChessPiece p : cpu.getPieces()) { + if (p.getIsCaptured()) { + cpuCaptured.add(p); + } else { + cpuInPlay.add(p); + } + } + + String displayString = this.getCapturedDisplayString( + humanInPlay, humanCaptured, + cpuInPlay, cpuCaptured + ); + + return new CommandResult(displayString); + } +} diff --git a/src/main/java/chessmaster/commands/HelpCommand.java b/src/main/java/chessmaster/commands/HelpCommand.java index 06e0ef8874..73ffedc884 100644 --- a/src/main/java/chessmaster/commands/HelpCommand.java +++ b/src/main/java/chessmaster/commands/HelpCommand.java @@ -23,6 +23,7 @@ public class HelpCommand extends Command { "stepback\tView the board as it was a certain number of moves ago", "\t\tFormat: stepback [number of moves to step back]", "\t\te.g. stepback 4", + "captured\tSee which Player and CPU pieces have been captured so far", "exit\t\tExit game", }; diff --git a/src/main/java/chessmaster/game/ChessBoard.java b/src/main/java/chessmaster/game/ChessBoard.java index 1a83ac818f..6521819b6c 100644 --- a/src/main/java/chessmaster/game/ChessBoard.java +++ b/src/main/java/chessmaster/game/ChessBoard.java @@ -25,8 +25,14 @@ public class ChessBoard { public static final int TOP_ROW_INDEX = 0; public static final int BOTTOM_ROW_INDEX = 7; public static final int MAX_PIECES = 16; + public static final String PROMOTE_MOVE_STRING = "p"; + public static final String INVALID_SAVE_STRING = + "Invalid move found in save file! Please start a new game or correct the invalid move!"; + public static final String INVALID_PROMOTE_STRING = + "Invalid promotion found in save file! Please start a new game or correct the invalid move!"; + private static final String[][] STARTING_CHESSBOARD_BLACK = { { "r", "n", "b", "q", "k", "b", "n", "r" }, { "p", "p", "p", "p", "p", "p", "p", "p" }, @@ -51,7 +57,7 @@ public class ChessBoard { private Color playerColor; - private int difficulty = 4; + private int difficulty = 1; private final ChessTile[][] board = new ChessTile[SIZE][SIZE]; @@ -156,9 +162,38 @@ public Coordinate getEnPassantCoor() { public boolean isCheckmated(Color color) { Move[] moves = getLegalMoves(color); + ChessPiece[] playerPieces = getAllPieces(color); + ChessPiece[] opponentPieces = getAllPieces(color.getOppositeColour()); + if (playerPieces.length == 1 && opponentPieces.length == 1) { + return true; + } return moves.length == 0; } + public ChessPiece[] getAllPieces(Color color) { + ArrayList pieces = new ArrayList(); + for (int row = 0; row < ChessBoard.SIZE; row++) { + for (int col = 0; col < ChessBoard.SIZE; col++) { + Coordinate coor = new Coordinate(col, row); + ChessPiece piece = getPieceAtCoor(coor); + if (piece.isSameColorAs(color)) { + pieces.add(piece); + } + } + } + return pieces.toArray(new ChessPiece[0]); + } + + public boolean isKingAlive(Color color) { + ChessPiece[] pieces = getAllPieces(color); + for (ChessPiece piece : pieces) { + if (piece instanceof King) { + return true; + } + } + return false; + } + /** * Check if the player of the specified color is in check. * @@ -391,7 +426,10 @@ public boolean canPromote(Move move) { } //@@author onx001 - public boolean isEndGame() { + public boolean isEndGame() throws ChessMasterException { + if (!isKingAlive(playerColor) || !isKingAlive(playerColor.getOppositeColour())) { + throw new ChessMasterException("King is dead!"); + } return isCheckmated(playerColor) || isCheckmated(playerColor.getOppositeColour()); } @@ -489,7 +527,9 @@ public void executeMoveArray(ArrayList moves, Human human, CPU cpu) thro if (!isPromote) { Move toExecute = Parser.parseMove(move, this, false); - assert toExecute.isValid(this) : "Move in file is not valid!"; + if (!toExecute.isValid(this)) { + throw new InvalidMoveException(INVALID_SAVE_STRING); + } this.executeMove(toExecute); if (isPlayersTurn) { @@ -500,9 +540,10 @@ public void executeMoveArray(ArrayList moves, Human human, CPU cpu) thro } else { Coordinate coord = Coordinate.parseAlgebraicCoor(moveCommandArray[1]); ChessPiece oldPiece = this.getPieceAtCoor(coord); - assert this.canPromote(new Move(coord, coord, oldPiece)) - : "Move in file tries to make an invalid promotion!"; assert oldPiece instanceof Pawn; + if (!this.canPromote(new Move(coord, coord, oldPiece))) { + throw new InvalidMoveException(INVALID_PROMOTE_STRING); + } ChessPiece newPiece = Parser.parsePromote(oldPiece, moveCommandArray[2]); this.setPromotionPiece(coord, newPiece); diff --git a/src/main/java/chessmaster/game/ChessTile.java b/src/main/java/chessmaster/game/ChessTile.java index d068dec45e..4642a6fc3a 100644 --- a/src/main/java/chessmaster/game/ChessTile.java +++ b/src/main/java/chessmaster/game/ChessTile.java @@ -3,7 +3,7 @@ import chessmaster.pieces.ChessPiece; import chessmaster.pieces.EmptyPiece; -public class ChessTile { +public class ChessTile implements Cloneable { public static final String TILE_DIVIDER = "|"; private static final String EMPTY_TILE_STRING = " "; private static final String EMPTY_TILE_MOVES_STRING = "."; diff --git a/src/main/java/chessmaster/game/Game.java b/src/main/java/chessmaster/game/Game.java index fb946269a6..37c96fdc7f 100644 --- a/src/main/java/chessmaster/game/Game.java +++ b/src/main/java/chessmaster/game/Game.java @@ -32,7 +32,6 @@ public class Game { private Command command; private boolean hasEnded; - private boolean exit = false; public Game(Color playerColour, Color currentTurnColor, ChessBoard board, Storage storage, TextUI ui, int difficulty, Human human, CPU cpu) { @@ -46,7 +45,7 @@ public Game(Color playerColour, Color currentTurnColor, ChessBoard board, Color cpuColor = playerColour.getOppositeColour(); this.cpu = cpu; - this.numMoves = 0; + this.numMoves = human.getMovesLength() + cpu.getMovesLength(); // Choose which player goes first currentPlayer = currentTurnColor == playerColour ? human : cpu; diff --git a/src/main/java/chessmaster/parser/Parser.java b/src/main/java/chessmaster/parser/Parser.java index eacc81d9c5..e5a5bcf093 100644 --- a/src/main/java/chessmaster/parser/Parser.java +++ b/src/main/java/chessmaster/parser/Parser.java @@ -12,6 +12,7 @@ import chessmaster.commands.ShowCommand; import chessmaster.commands.ShowMovesCommand; import chessmaster.commands.StepbackCommand; +import chessmaster.commands.CapturedCommand; import chessmaster.exceptions.MoveOpponentPieceException; import chessmaster.exceptions.NullPieceException; import chessmaster.exceptions.ParseColorException; @@ -176,6 +177,8 @@ public static Command parseCommand(String in) { return new HistoryCommand(); case StepbackCommand.STEPBACK_COMMAND_STRING: return new StepbackCommand(payload); + case CapturedCommand.CAPTURED_COMMAND_STRING: + return new CapturedCommand(); default: return new InvalidCommand(); } diff --git a/src/main/java/chessmaster/storage/Storage.java b/src/main/java/chessmaster/storage/Storage.java index 39956afa31..ccee9f50d9 100644 --- a/src/main/java/chessmaster/storage/Storage.java +++ b/src/main/java/chessmaster/storage/Storage.java @@ -4,14 +4,11 @@ import chessmaster.exceptions.LoadBoardException; import chessmaster.exceptions.SaveBoardException; import chessmaster.game.ChessBoard; -import chessmaster.game.ChessTile; import chessmaster.game.Color; -import chessmaster.game.Coordinate; import chessmaster.parser.Parser; -import chessmaster.pieces.ChessPiece; import chessmaster.user.CPU; import chessmaster.user.Human; - +import chessmaster.user.Player; import java.io.File; import java.io.FileNotFoundException; @@ -23,22 +20,18 @@ public class Storage { //@@author ken_ruster - private static final String LOAD_BOARD_MISMATCH_STRING = - "Board state does not match state dictated by move history!"; + // private static final String LOAD_BOARD_MISMATCH_STRING = + // "Board state does not match state dictated by move history!"; + private static final String PATH_EMPTY_STRING = "File path cannot be empty or null"; //@@author TriciaBK private String filePathString; private File storageFile; - private int blackPieceNum; - private int whitePieceNum; - private boolean blackKingPresent; - private boolean whiteKingPresent; - private Scanner fileScanner; - private Coordinate lastMove; + // private Coordinate lastMove; public Storage(String filePath) { filePathString = filePath; storageFile = new File(filePath); - assert !filePathString.isEmpty() && filePath != null : "File path cannot be empty or null"; + assert !filePathString.isEmpty() && filePath != null : PATH_EMPTY_STRING; } //@@author TongZhengHong @@ -96,7 +89,7 @@ public void saveBoard(ChessBoard board, Color currentColor, Human human, CPU cpu fileWriter.write(System.lineSeparator()); //@@author TriciaBK - for (int row = 0; row < ChessBoard.SIZE; row++) { + /*for (int row = 0; row < ChessBoard.SIZE; row++) { for (int col = 0; col < ChessBoard.SIZE; col++) { ChessPiece piece = board.getPieceAtCoor(new Coordinate(col, row)); fileWriter.write(piece.toString()); @@ -111,7 +104,7 @@ public void saveBoard(ChessBoard board, Color currentColor, Human human, CPU cpu fileWriter.write(hasMovedString); } fileWriter.write(System.lineSeparator()); - } + }*/ fileWriter.close(); } catch (IOException e) { @@ -133,16 +126,16 @@ public void resetBoard() throws ChessMasterException { } //@@author TriciaBK - /** - * Loads the state of the chessboard from a file. - * Ignores the first line player color information as it can be retrieved with - * loadPlayerColor() method - * - * @return A 2D array of ChessTile objects representing the loaded chessboard. - * @throws ChessMasterException If there is an error loading the board from the - * file. - */ - public ChessTile[][] loadBoard() throws ChessMasterException { + // /** + // * Loads the state of the chessboard from a file. + // * Ignores the first line player color information as it can be retrieved with + // * loadPlayerColor() method + // * + // * @return A 2D array of ChessTile objects representing the loaded chessboard. + // * @throws ChessMasterException If there is an error loading the board from the + // * file. + // */ + /* public ChessTile[][] loadBoard() throws ChessMasterException { createChessMasterFile(); blackPieceNum = 0; @@ -166,7 +159,7 @@ public ChessTile[][] loadBoard() throws ChessMasterException { int rowIndex = 0; ChessTile[][] boardTiles = new ChessTile[ChessBoard.SIZE][ChessBoard.SIZE]; while (rowIndex < ChessBoard.SIZE && fileScanner.hasNext()) { - String chessRowLine = fileScanner.nextLine(); + String chessRowLine = fileScanner.nextLine().trim(); if (chessRowLine.length() != ChessBoard.SIZE) { fileScanner.close(); throw new LoadBoardException(); @@ -194,7 +187,7 @@ public ChessTile[][] loadBoard() throws ChessMasterException { rowIndex = 0; while (rowIndex < ChessBoard.SIZE && fileScanner.hasNext()) { - String chessRowLine = fileScanner.nextLine(); + String chessRowLine = fileScanner.nextLine().trim(); if (chessRowLine.length() != ChessBoard.SIZE) { fileScanner.close(); throw new LoadBoardException(); @@ -212,28 +205,65 @@ public ChessTile[][] loadBoard() throws ChessMasterException { fileScanner.close(); return boardTiles; - } + } */ + //@@author ken-ruster /** * Executes moves saved in the txt file so that it can be checked against the save board state. * Also stores the saved move history of both the human and CPU. * * @param playerColor The color which the player is playing as - * @param otherBoard A temporary board to be compared with the board saved in the .txt file * @param human Object representing information about the human player * @param cpu Object representing information about the computer-controlled player * @throws ChessMasterException */ - public void executeSavedMoves(Color playerColor, - ChessBoard otherBoard, + public ChessBoard executeSavedMoves(Color playerColor, + ChessBoard board, Human human, CPU cpu) throws ChessMasterException { ArrayList moveStringList = new ArrayList(); ArrayList humanMoves = loadHumanMoves(); ArrayList cpuMoves = loadCPUMoves(); + if (cpuMoves.size() > humanMoves.size() && playerColor.isWhite() + || cpuMoves.size() < humanMoves.size() && playerColor.isBlack()) { + throw new ChessMasterException( + "Moves in save file are invalid! Please start a new game or correct the error"); + } + // Merge move string arrays into a singular array - if (playerColor.isWhite()) { + boolean isPlayerTurn = playerColor.isWhite(); + int noOfMoves = humanMoves.size() + cpuMoves.size(); + + for (int i = 0; i < noOfMoves; i ++) { + int turnIndex = i / 2; + if (isPlayerTurn && humanMoves.size() >= turnIndex + 1) { + moveStringList.add(humanMoves.get(turnIndex)); + } else if (cpuMoves.size() >= turnIndex + 1) { + moveStringList.add(cpuMoves.get(turnIndex)); + } + + isPlayerTurn = !isPlayerTurn; + } + + /* + if (playerColor.isWhite() && humanMoves.size() == cpuMoves.size()) { + for (String move : humanMoves) { + moveStringList.add(move); + } + + for (int i = 0; i < cpuMoves.size(); i ++) { + moveStringList.add(2 * i + 1, cpuMoves.get(i)); + } + } else if (playerColor.isBlack() && humanMoves.size() == cpuMoves.size()) { + for (String move : cpuMoves) { + moveStringList.add(move); + } + + for (int i = 0; i < humanMoves.size(); i ++) { + moveStringList.add(2 * i + 1, humanMoves.get(i)); + } + } else if (playerColor.isWhite() && humanMoves.size() > cpuMoves.size()) { for (String move : humanMoves) { moveStringList.add(move); } @@ -241,7 +271,7 @@ public void executeSavedMoves(Color playerColor, for (int i = 0; i < cpuMoves.size(); i ++) { moveStringList.add(2 * i + 1, cpuMoves.get(i)); } - } else if (playerColor.isBlack()) { + } else if (playerColor.isBlack() && humanMoves.size() > cpuMoves.size()) { for (String move : cpuMoves) { moveStringList.add(move); } @@ -252,17 +282,19 @@ public void executeSavedMoves(Color playerColor, } else { throw new LoadBoardException(); } - - ChessBoard board = new ChessBoard(playerColor); + */ //Execute move string Array board.executeMoveArray(moveStringList, human, cpu); //@@author onx001 // get the destination coordinate of the last move - try { + /*try { String lastMoveString = moveStringList.get(moveStringList.size() - 1); - String[] lastMoveArray = lastMoveString.split(" "); + String[] lastMoveArray = lastMoveString.split("\\s+"); + if (lastMoveArray.length < 2) { + throw new LoadBoardException(); + } lastMove = Coordinate.parseAlgebraicCoor(lastMoveArray[1]); if (otherBoard.getPieceAtCoor(lastMove).isPawn()) { @@ -271,20 +303,23 @@ public void executeSavedMoves(Color playerColor, } } catch (Exception e) { assert moveStringList.size() == 0 : "Last move should be empty"; - } + throw new LoadBoardException(); + }*/ //@@author TriciaBK // Check obtained board with loaded board state - if (!board.equals(otherBoard)) { + /*if (!board.equals(otherBoard)) { throw new LoadBoardException(LOAD_BOARD_MISMATCH_STRING); - } + }*/ + + return board; } //@@author onx001 - private boolean isPieceValid (ChessPiece initialPiece) { + /*private boolean isPieceValid(ChessPiece initialPiece) { if (initialPiece.isBlackKing()) { if (blackKingPresent) { return false; @@ -314,7 +349,7 @@ private boolean isPieceValid (ChessPiece initialPiece) { } return true; - } + }*/ //@@author TongZhengHong /** @@ -336,8 +371,8 @@ public Color loadPlayerColor() throws ChessMasterException { } if (fileScanner.hasNext()) { - String colorLine = fileScanner.nextLine(); - Color playerColor = Parser.parsePlayerColor(colorLine); + String colorLine = fileScanner.nextLine().trim(); + Color playerColor = Parser.parsePlayerColor(colorLine.toUpperCase()); fileScanner.close(); return playerColor; @@ -370,7 +405,7 @@ public int loadDifficulty() throws ChessMasterException { if (fileScanner.hasNext()) { try { - String difficultyLine = fileScanner.nextLine(); + String difficultyLine = fileScanner.nextLine().trim(); int difficulty = Parser.parseDifficulty(difficultyLine); fileScanner.close(); @@ -387,6 +422,7 @@ public int loadDifficulty() throws ChessMasterException { throw new LoadBoardException(); } + //@@author TriciaBK /** * Loads the current turn player's @@ -411,8 +447,8 @@ public Color loadCurrentColor() throws ChessMasterException { } if (fileScanner.hasNext()) { - String currentColorString = fileScanner.nextLine(); - Color color = Parser.parsePlayerColor(currentColorString); + String currentColorString = fileScanner.nextLine().trim(); + Color color = Parser.parsePlayerColor(currentColorString.toUpperCase()); fileScanner.close(); return color; } @@ -421,10 +457,12 @@ public Color loadCurrentColor() throws ChessMasterException { throw new LoadBoardException(); } + public String getFilePath() { return this.filePathString; } + //@@author ken-ruster /** * Loads the history of moves made by the human player in the saved game. * Parses the moves into Move objects, and returns a null array if no moves were found. @@ -451,11 +489,12 @@ public ArrayList loadHumanMoves() throws ChessMasterException { ArrayList out = new ArrayList(); if (fileScanner.hasNext()) { - String[] movesArray = fileScanner.nextLine().split(", "); + String movesString = fileScanner.nextLine().trim(); + String[] movesArray = movesString.split(Player.MOVE_DELIMITER); Arrays.stream(movesArray) .sequential() .filter(x -> !x.equals("")) - .forEach(x -> out.add(x)); + .forEach(x -> out.add(x.trim())); } fileScanner.close(); @@ -488,11 +527,12 @@ public ArrayList loadCPUMoves() throws ChessMasterException { ArrayList out = new ArrayList(); if (fileScanner.hasNext()) { - String[] movesArray = fileScanner.nextLine().split(", "); + String movesString = fileScanner.nextLine().trim(); + String[] movesArray = movesString.split(Player.MOVE_DELIMITER); Arrays.stream(movesArray) .sequential() .filter(x -> !x.equals("")) - .forEach(x -> out.add(x)); + .forEach(x -> out.add(x.trim())); } fileScanner.close(); diff --git a/src/main/java/chessmaster/ui/UiMessages.java b/src/main/java/chessmaster/ui/UiMessages.java index 23c2876584..dc4d553bfa 100644 --- a/src/main/java/chessmaster/ui/UiMessages.java +++ b/src/main/java/chessmaster/ui/UiMessages.java @@ -17,7 +17,8 @@ public class UiMessages { "Invalid input! Please enter either 'y' for yes or 'n' for no: "; public static final String CONTINUE_PREV_GAME_MESSAGE = "Great! Continuing previous game as %s at difficulty %d"; - public static final String CHOOSE_PLAYER_COLOR_MESSAGE = "Choose your starting color to start new game! [b/w] "; + public static final String CHOOSE_PLAYER_COLOR_MESSAGE = + "Choose your starting color to start new game! [b/w/exit] "; public static final String CHOOSE_PLAYER_COLOR_ERROR_MESSAGE = "Invalid input! Please enter either 'b' for Black or 'w' for White: "; public static final String START_NEW_GAME_MESSAGE = "Great! Starting new game as %s"; @@ -33,7 +34,7 @@ public class UiMessages { "Invalid piece! Enter b(Bishop), q(Queen), r(Rook) or n(Knight): "; public static final String CHOOSE_DIFFICULTY_MESSAGE = - "Choose difficulty level [1/2/3]: "; + "Choose difficulty level [1/2/3/exit]: "; public static final String CHOOSE_DIFFICULTY_ERROR_MESSAGE = "Invalid input! Please enter either '1', '2' or '3': "; diff --git a/src/main/java/chessmaster/user/Player.java b/src/main/java/chessmaster/user/Player.java index 6f61b60c60..3e8689b0db 100644 --- a/src/main/java/chessmaster/user/Player.java +++ b/src/main/java/chessmaster/user/Player.java @@ -10,7 +10,7 @@ public abstract class Player { - private static final String MOVE_DELIMITER = ", "; + public static final String MOVE_DELIMITER = ","; protected ArrayList moves; protected ArrayList pieces; @@ -110,4 +110,8 @@ public ArrayList getMoves() { public ArrayList getPieces() { return this.pieces; } + + public int getMovesLength() { + return this.moves.size(); + } } diff --git a/src/test/java/chessmaster/commands/ShowMovesCommandTest.java b/src/test/java/chessmaster/commands/ShowMovesCommandTest.java new file mode 100644 index 0000000000..6d7f8205a1 --- /dev/null +++ b/src/test/java/chessmaster/commands/ShowMovesCommandTest.java @@ -0,0 +1,66 @@ +package chessmaster.commands; + +import chessmaster.exceptions.ChessMasterException; +import chessmaster.exceptions.NullPieceException; +import chessmaster.game.ChessBoard; +import chessmaster.game.Color; +import chessmaster.game.Game; +import chessmaster.storage.Storage; +import chessmaster.ui.TextUI; +import chessmaster.user.CPU; +import chessmaster.user.Human; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +public class ShowMovesCommandTest { + private static final String FILE_PATH_STRING = "data/ChessMaster.txt"; + private static final String[] AVAILABLE_COORDINATES_STRING = {"Available coordinates for Pawn at e2: ", "e3 e4 " }; + private static final String[] NO_AVAILABLE_STRING = {"There aren't any moves available for King at e1!"}; + private static final String[] NO_COORDINATE_FOUND_STRING = + {"Oops! Looks like you forgot to specify a coordinate!" + , "Format: moves [column][row]" + , "Example: moves a2"}; + private static final String AVAILABLE_COORDINATES_INPUT = "e2"; + private static final String NO_AVAILABLE_INPUT = "e1"; + private static final String NO_PIECE_INPUT = "e4"; + + public Game loadGame() { + ChessBoard board = new ChessBoard(Color.WHITE); + Human human = new Human(Color.WHITE, board); + CPU cpu = new CPU(Color.WHITE, board); + Storage storage = new Storage(FILE_PATH_STRING); + TextUI ui = new TextUI(); + Game game = new Game(Color.WHITE, Color.WHITE, board, storage, ui, 1, human, cpu); + return game; + } + + @Test + public void testExecute_inputValid() throws ChessMasterException { + Game game = loadGame(); + + ShowMovesCommand commandHasPiece = new ShowMovesCommand(AVAILABLE_COORDINATES_INPUT); + ShowMovesCommand commandNoPiece = new ShowMovesCommand(NO_PIECE_INPUT); + ShowMovesCommand commandNoMoves = new ShowMovesCommand(NO_AVAILABLE_INPUT); + + String[] hasPiece = commandHasPiece.execute(game).getMessageStrings(); + assertArrayEquals(hasPiece, AVAILABLE_COORDINATES_STRING); + + String[] noMoves = commandNoMoves.execute(game).getMessageStrings(); + assertArrayEquals(noMoves, NO_AVAILABLE_STRING); + + assertThrows(NullPieceException.class, + () -> commandNoPiece.execute(game).getMessageStrings()); + } + + @Test + public void testExecute_inputEmpty() throws ChessMasterException { + Game game = loadGame(); + + ShowMovesCommand commandNoInput = new ShowMovesCommand(""); + + String[] noInput = commandNoInput.execute(game).getMessageStrings(); + assertArrayEquals(noInput, NO_COORDINATE_FOUND_STRING); + } +} diff --git a/src/test/java/chessmaster/game/ChessBoardTest.java b/src/test/java/chessmaster/game/ChessBoardTest.java index c1fb8d839b..73cb59ce77 100644 --- a/src/test/java/chessmaster/game/ChessBoardTest.java +++ b/src/test/java/chessmaster/game/ChessBoardTest.java @@ -1,12 +1,48 @@ package chessmaster.game; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import chessmaster.exceptions.ChessMasterException; +import chessmaster.parser.Parser; +import chessmaster.pieces.ChessPiece; +import chessmaster.user.CPU; +import chessmaster.user.Human; import org.junit.jupiter.api.Test; import chessmaster.ui.TextUI; +import java.util.ArrayList; + public class ChessBoardTest { + private static final String[] MOVE_STRING_ARRAY = + {"d2 d4", "g8 f6", "g1 f3", "b8 c6", "c1 f4", "d7 d5", + "c2 c3", "f6 e4", "b1 d2", "e4 d2", "d1 d2", "f7 f5"}; + private static final String[][] MOVED_CHESSBOARD = { + { "R", ".", "B", "Q", "K", "B", ".", "R" }, + { "P", "P", "P", ".", "P", ".", "P", "P" }, + { ".", ".", "N", ".", ".", ".", ".", "." }, + { ".", ".", ".", "P", ".", "P", ".", "." }, + { ".", ".", ".", "p", ".", "b", ".", "." }, + { ".", ".", "p", ".", ".", "n", ".", "." }, + { "p", "p", ".", "q", "p", "p", "p", "p" }, + { "r", ".", ".", ".", "k", "b", ".", "r" }, + }; + + private ChessBoard loadChessBoard() { + ChessTile[][] chessTiles = new ChessTile[ChessBoard.SIZE][ChessBoard.SIZE]; + for (int row = 0; row < ChessBoard.SIZE; row++) { + for (int col = 0; col < ChessBoard.SIZE; col++) { + String chessPieceString = MOVED_CHESSBOARD[row][col]; + ChessPiece initialPiece = Parser.parseChessPiece(chessPieceString, row, col); + chessTiles[row][col] = new ChessTile(initialPiece); + assert (chessTiles[row][col] != null); + } + } + return new ChessBoard(Color.WHITE, chessTiles); + } + // @@author onx001 @Test public void pointTest() { @@ -17,4 +53,21 @@ public void pointTest() { int points = board.getPoints(Color.WHITE); assertEquals(0, points); } + //@@author ken_ruster + @Test + public void executeMoveArrayTest() throws ChessMasterException { + ChessBoard board = new ChessBoard(Color.WHITE); + Human human = new Human(Color.WHITE, board); + CPU cpu = new CPU(Color.BLACK, board); + ArrayList moveList = new ArrayList(); + for (String moveString: MOVE_STRING_ARRAY) { + moveList.add(moveString); + } + + board.executeMoveArray(moveList, human, cpu); + + ChessBoard otherBoard = loadChessBoard(); + + assertTrue(board.equals(otherBoard)); + } } diff --git a/src/test/java/chessmaster/parser/ParsePromoteTest.java b/src/test/java/chessmaster/parser/ParsePromoteTest.java new file mode 100644 index 0000000000..8efe31b105 --- /dev/null +++ b/src/test/java/chessmaster/parser/ParsePromoteTest.java @@ -0,0 +1,62 @@ +//@@author ken-ruster +package chessmaster.parser; + +import chessmaster.game.Color; +import chessmaster.pieces.Bishop; +import chessmaster.pieces.ChessPiece; +import chessmaster.pieces.Knight; +import chessmaster.pieces.Pawn; +import chessmaster.pieces.Queen; +import chessmaster.pieces.Rook; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ParsePromoteTest { + + @Test + public void testPromote_inputValidPiece() { + ChessPiece promotedQueenWhite = Parser.parsePromote(new Pawn(0, 0, Color.WHITE), "q"); + assertEquals(promotedQueenWhite.toString(), Queen.QUEEN_WHITE); + + ChessPiece promotedQueenBlack = Parser.parsePromote(new Pawn(0, 0, Color.BLACK), "q"); + assertEquals(promotedQueenBlack.toString(), Queen.QUEEN_BLACK); + + ChessPiece promotedRookWhite = Parser.parsePromote(new Pawn(0, 0, Color.WHITE), "r"); + assertEquals(promotedRookWhite.toString(), Rook.ROOK_WHITE); + + ChessPiece promotedRookBlack = Parser.parsePromote(new Pawn(0, 0, Color.BLACK), "r"); + assertEquals(promotedRookBlack.toString(), Rook.ROOK_BLACK); + + ChessPiece promotedKnightWhite = Parser.parsePromote(new Pawn(0, 0, Color.WHITE), "n"); + assertEquals(promotedKnightWhite.toString(), Knight.KNIGHT_WHITE); + + ChessPiece promotedKnightBlack = Parser.parsePromote(new Pawn(0, 0, Color.BLACK), "n"); + assertEquals(promotedKnightBlack.toString(), Knight.KNIGHT_BLACK); + + ChessPiece promotedBishopWhite = Parser.parsePromote(new Pawn(0, 0, Color.WHITE), "b"); + assertEquals(promotedBishopWhite.toString(), Bishop.BISHOP_WHITE); + + ChessPiece promotedBishopBlack = Parser.parsePromote(new Pawn(0, 0, Color.BLACK), "b"); + assertEquals(promotedBishopBlack.toString(), Bishop.BISHOP_BLACK); + } + + @Test + public void testPromote_inputEmpty_expectReturnPawn() { + ChessPiece promotedEmptyWhite = Parser.parsePromote(new Pawn(0, 0, Color.WHITE), ""); + assertEquals(promotedEmptyWhite.toString(), Pawn.PAWN_WHITE); + + ChessPiece promotedEmptyBlack = Parser.parsePromote(new Pawn(0, 0, Color.BLACK), ""); + assertEquals(promotedEmptyBlack.toString(), Pawn.PAWN_BLACK); + } + + @Test + public void testPromote_inputInvalid_expectReturnPawn() { + ChessPiece promotedEmptyWhite = Parser.parsePromote(new Pawn(0, 0, Color.WHITE), "isdjncv"); + assertEquals(promotedEmptyWhite.toString(), Pawn.PAWN_WHITE); + + ChessPiece promotedEmptyBlack = Parser.parsePromote(new Pawn(0, 0, Color.BLACK), "jasdnc"); + assertEquals(promotedEmptyBlack.toString(), Pawn.PAWN_BLACK); + } + +} diff --git a/src/test/java/chessmaster/parser/ParserTest.java b/src/test/java/chessmaster/parser/ParserTest.java deleted file mode 100644 index c51fb36852..0000000000 --- a/src/test/java/chessmaster/parser/ParserTest.java +++ /dev/null @@ -1,20 +0,0 @@ -//@@author ken-ruster -package chessmaster.parser; - -import chessmaster.game.Color; -import chessmaster.pieces.ChessPiece; -import chessmaster.pieces.Pawn; -import chessmaster.pieces.Queen; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class ParserTest { - - @Test - public void promoteTest() { - ChessPiece promoted = Parser.parsePromote(new Pawn(0, 0, Color.WHITE), "q"); - assertEquals(promoted.toString(), Queen.QUEEN_WHITE); - } - -} diff --git a/src/test/java/chessmaster/storage/ExecuteSavedMovesTest.java b/src/test/java/chessmaster/storage/ExecuteSavedMovesTest.java new file mode 100644 index 0000000000..e8ee13046b --- /dev/null +++ b/src/test/java/chessmaster/storage/ExecuteSavedMovesTest.java @@ -0,0 +1,76 @@ +package chessmaster.storage; + +import chessmaster.exceptions.ChessMasterException; +import chessmaster.exceptions.InvalidMoveException; +import chessmaster.game.ChessBoard; +import chessmaster.game.Color; +import chessmaster.user.CPU; +import chessmaster.user.Human; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ExecuteSavedMovesTest { + private static final String FILE_PATH_STRING = + "src/test/resources/storageTest.txt"; + private static final String INVALID_FILE_PATH_STRING = + "src/test/resources/storageTest_invalidMove.txt"; + // private static final String[][] MOVED_CHESSBOARD = { + // { "R", ".", "B", "Q", "K", "B", ".", "R" }, + // { "P", "P", "P", ".", "P", ".", "P", "P" }, + // { ".", ".", "N", ".", ".", ".", ".", "." }, + // { ".", ".", ".", "P", ".", "P", ".", "." }, + // { ".", ".", ".", "p", ".", "b", ".", "." }, + // { ".", ".", "p", ".", ".", "n", ".", "." }, + // { "p", "p", ".", "q", "p", "p", "p", "p" }, + // { "r", ".", ".", ".", "k", "b", ".", "r" }, + // }; + + // private ChessBoard loadChessBoard() { + // ChessTile[][] chessTiles = new ChessTile[ChessBoard.SIZE][ChessBoard.SIZE]; + // for (int row = 0; row < ChessBoard.SIZE; row++) { + // for (int col = 0; col < ChessBoard.SIZE; col++) { + // String chessPieceString = MOVED_CHESSBOARD[row][col]; + // ChessPiece initialPiece = Parser.parseChessPiece(chessPieceString, row, col); + // chessTiles[row][col] = new ChessTile(initialPiece); + // assert (chessTiles[row][col] != null); + // } + // } + // return new ChessBoard(Color.WHITE, chessTiles); + // } + + @Test + public void testExecuteSavedMoves_validMoves() throws ChessMasterException { + Storage storage = new Storage(FILE_PATH_STRING); + // ChessBoard otherBoard = loadChessBoard(); + ChessBoard board = new ChessBoard(Color.WHITE); + Human human = new Human(Color.WHITE, board); + CPU cpu = new CPU(Color.BLACK, board); + + storage.executeSavedMoves(Color.WHITE, board, human, cpu); + } + + @Test + public void testExecuteSavedMoves_invalidMoves_expectInvalidMoveException() throws ChessMasterException { + Storage storage = new Storage(INVALID_FILE_PATH_STRING); + // ChessBoard otherBoard = loadChessBoard(); + ChessBoard board = new ChessBoard(Color.WHITE); + Human human = new Human(Color.WHITE, board); + CPU cpu = new CPU(Color.BLACK, board); + + assertThrows(InvalidMoveException.class, + () -> storage.executeSavedMoves(Color.WHITE, board, human, cpu)); + } + + // @Test + // public void testExecuteSavedMoves_boardMismatch_expectLoadBoardException() throws ChessMasterException { + // Storage storage = new Storage(FILE_PATH_STRING); + // ChessBoard otherBoard = new ChessBoard(Color.WHITE); + // ChessBoard board = new ChessBoard(Color.WHITE); + // Human human = new Human(Color.WHITE, board); + // CPU cpu = new CPU(Color.BLACK, board); + + // assertThrows(LoadBoardException.class, + // () -> storage.executeSavedMoves(Color.WHITE, board, human, cpu)); + // } +} diff --git a/src/test/java/chessmaster/storage/LoadCpuMovesTest.java b/src/test/java/chessmaster/storage/LoadCpuMovesTest.java new file mode 100644 index 0000000000..e3f8c411d2 --- /dev/null +++ b/src/test/java/chessmaster/storage/LoadCpuMovesTest.java @@ -0,0 +1,21 @@ +package chessmaster.storage; + +import chessmaster.exceptions.ChessMasterException; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LoadCpuMovesTest { + private static final String[] EXPECTED_LIST = {"g8 f6", "b8 c6", "d7 d5", "f6 e4", "e4 d2", "f7 f5"}; + private static final String FILE_PATH_STRING = "src/test/resources/storageTest.txt"; + Storage storage = new Storage(FILE_PATH_STRING); + + @Test + public void testLoadCPUMoves_validString() throws ChessMasterException { + ArrayList compareList = storage.loadCPUMoves(); + assertTrue(compareList.equals(Arrays.asList(EXPECTED_LIST))); + } +} diff --git a/src/test/java/chessmaster/storage/LoadHumanMovesTest.java b/src/test/java/chessmaster/storage/LoadHumanMovesTest.java new file mode 100644 index 0000000000..f8a6451ee7 --- /dev/null +++ b/src/test/java/chessmaster/storage/LoadHumanMovesTest.java @@ -0,0 +1,21 @@ +package chessmaster.storage; + +import chessmaster.exceptions.ChessMasterException; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LoadHumanMovesTest { + private static final String[] EXPECTED_LIST = {"d2 d4", "g1 f3", "c1 f4", "c2 c3", "b1 d2", "d1 d2"}; + private static final String FILE_PATH_STRING = "src/test/resources/storageTest.txt"; + Storage storage = new Storage(FILE_PATH_STRING); + + @Test + public void testLoadHumanMoves_validString() throws ChessMasterException { + ArrayList compareList = storage.loadHumanMoves(); + assertTrue(compareList.equals(Arrays.asList(EXPECTED_LIST))); + } +} diff --git a/src/test/resources/storageTest.txt b/src/test/resources/storageTest.txt new file mode 100644 index 0000000000..7563d563df --- /dev/null +++ b/src/test/resources/storageTest.txt @@ -0,0 +1,21 @@ +WHITE +2 +WHITE +d2 d4, g1 f3, c1 f4, c2 c3, b1 d2, d1 d2 +g8 f6, b8 c6, d7 d5, f6 e4, e4 d2, f7 f5 +R.BQKB.R +PPP.P.PP +..N..... +...P.P.. +...p.b.. +..p..n.. +pp.qpppp +r...kb.r +00000000 +00000000 +00100000 +00010100 +00010100 +00100100 +00010000 +00000000 \ No newline at end of file diff --git a/src/test/resources/storageTest_invalidMove.txt b/src/test/resources/storageTest_invalidMove.txt new file mode 100644 index 0000000000..91085efa65 --- /dev/null +++ b/src/test/resources/storageTest_invalidMove.txt @@ -0,0 +1,21 @@ +WHITE +2 +WHITE +d2 d5, g1 f3, c1 f4, c2 c3, b1 d2, d1 d2 +g8 f6, b8 c6, d7 d5, f6 e4, e4 d2, f7 f5 +R.BQKB.R +PPP.P.PP +..N..... +...P.P.. +...p.b.. +..p..n.. +pp.qpppp +r...kb.r +00000000 +00000000 +00100000 +00010100 +00010100 +00100100 +00010000 +00000000 \ No newline at end of file