diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index fd8c44d086..1d6af01e65 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -33,18 +33,18 @@ jobs:
- name: Build and check with Gradle
run: ./gradlew check
- - name: Perform IO redirection test (*NIX)
- if: runner.os == 'Linux'
- working-directory: ${{ github.workspace }}/text-ui-test
- run: ./runtest.sh
-
- - name: Perform IO redirection test (MacOS)
- if: always() && runner.os == 'macOS'
- working-directory: ${{ github.workspace }}/text-ui-test
- run: ./runtest.sh
-
- - name: Perform IO redirection test (Windows)
- if: always() && runner.os == 'Windows'
- working-directory: ${{ github.workspace }}/text-ui-test
- shell: cmd
- run: runtest.bat
\ No newline at end of file
+ # - name: Perform IO redirection test (*NIX)
+ # if: runner.os == 'Linux'
+ # working-directory: ${{ github.workspace }}/text-ui-test
+ # run: ./runtest.sh
+
+ # - name: Perform IO redirection test (MacOS)
+ # if: always() && runner.os == 'macOS'
+ # working-directory: ${{ github.workspace }}/text-ui-test
+ # run: ./runtest.sh
+
+ # - name: Perform IO redirection test (Windows)
+ # if: always() && runner.os == 'Windows'
+ # working-directory: ${{ github.workspace }}/text-ui-test
+ # shell: cmd
+ # run: runtest.bat
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 2873e189e1..7673416bb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,8 @@
/build/
src/main/resources/docs/
+.vscode
+
# MacOS custom attributes files created by Finder
.DS_Store
*.iml
@@ -15,3 +17,4 @@ bin/
/text-ui-test/ACTUAL.TXT
text-ui-test/EXPECTED-UNIX.TXT
+data/
\ No newline at end of file
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
----|:---:|:---:|:---:
- | Jeffry Lum | [Github](https://github.com/j-lum/) | [Homepage](https://se.kasugano.moe)
- | 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/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..c946985b42
--- /dev/null
+++ b/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: chessmaster.ChessMaster
+
diff --git a/README.md b/README.md
index f82e2494b7..a0af60b99e 100644
--- a/README.md
+++ b/README.md
@@ -1,64 +1,100 @@
-# Duke project template
-
-This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it.
-
-## Setting up in Intellij
-
-Prerequisites: JDK 11 (use the exact version), update Intellij to the most recent version.
-
-1. **Ensure Intellij JDK 11 is defined as an SDK**, as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk) -- this step is not needed if you have used JDK 11 in a previous Intellij project.
-1. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html).
-1. **Verify the set up**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below:
- ```
- > Task :compileJava
- > Task :processResources NO-SOURCE
- > Task :classes
-
- > Task :Duke.main()
- Hello from
- ____ _
- | _ \ _ _| | _____
- | | | | | | | |/ / _ \
- | |_| | |_| | < __/
- |____/ \__,_|_|\_\___|
-
- What is your name?
- ```
- Type some word and press enter to let the execution proceed to the end.
-
-## Build automation using Gradle
-
-* This project uses Gradle for build automation and dependency management. It includes a basic build script as well (i.e. the `build.gradle` file).
-* If you are new to Gradle, refer to the [Gradle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/gradle.html).
-
-## Testing
-
-### I/O redirection tests
-
-* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-ui-test` and run the `runtest(.bat/.sh)` script.
-
-### JUnit tests
-
-* A skeleton JUnit test (`src/test/java/seedu/duke/DukeTest.java`) is provided with this project template.
-* If you are new to JUnit, refer to the [JUnit Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/junit.html).
-
-## Checkstyle
-
-* A sample CheckStyle rule configuration is provided in this project.
-* If you are new to Checkstyle, refer to the [Checkstyle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/checkstyle.html).
-
-## CI using GitHub Actions
-
-The project uses [GitHub actions](https://github.com/features/actions) for CI. When you push a commit to this repo or PR against it, GitHub actions will run automatically to build and verify the code as updated by the commit/PR.
-
-## Documentation
-
-`/docs` folder contains a skeleton version of the project documentation.
-
-Steps for publishing documentation to the public:
-1. If you are using this project template for an individual project, go your fork on GitHub.
- If you are using this project template for a team project, go to the team fork on GitHub.
-1. Click on the `settings` tab.
-1. Scroll down to the `GitHub Pages` section.
-1. Set the `source` as `master branch /docs folder`.
-1. Optionally, use the `choose a theme` button to choose a theme for your documentation.
+
+
+
+_ChessMaster_ 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 simple and user-friendly chess simulator
+provides a platform for novice players to build their skills and understanding of the game.
+
+This project was created for the _CS2113: Software Engineering and Object-Oriented Programming_ team project and
+is programmed in 100% Java.
+
+## Getting started
+
+1. Ensure you have **Java 11 or above** installed in your system.
+
+2. Download the latest version of `ChessMaster.jar` from [here](https://github.com/AY2324S1-CS2113-T18-1/tp/releases).
+
+3. Open a terminal instance and navigate into the folder that contains the downloaded ChessMaster.jar file.
+
+```bash
+cd PATH_TO_JAR_FILE
+```
+
+4. Run the jar application with the following command:
+
+```bash
+java -jar ChessMaster.jar
+```
+
+You should be greeted by a welcome message from ChessMaster:
+```
+_________________________________________________________________
+
+Hey there, chess geek! You have stumbled upon the one and only:
+ ________ __ ___ __
+ / ____/ /_ ___ __________ / |/ /___ ______/ /____ _____
+ / / / __ \/ _ \/ ___/ ___/ / /|_/ / __ `/ ___/ __/ _ \/ ___/
+ / /___/ / / / __(__ |__ ) / / / / /_/ (__ ) /_/ __/ /
+ \____/_/ /_/\___/____/____/ /_/ /_/\__,_/____/\__/\___/_/
+
+where CHESS becomes an exciting journey of strategy and skill!
+_________________________________________________________________
+```
+
+## Usage
+
+A summary of user commands can be found in the table below:
+
+| 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` |
+| 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.
+
+## Contributing
+
+[](https://github.com/AY2324S1-CS2113-T18-1/tp/graphs/contributors)
+[](https://github.com/AY2324S1-CS2113-T18-1/tp/commits/master)
+[](https://github.com/AY2324S1-CS2113-T18-1/tp/issues)
+[](https://github.com/AY2324S1-CS2113-T18-1/tp/pulls)
+
+
+All contributions are greatly appreciated! If you have a suggestion that would make this better,
+please fork the repo and create a pull request.
+
+1. Fork the Project
+2. Create your Feature Branch (`git checkout -b feature/branch-FeatureName`)
+3. Commit your Changes (`git commit -m 'Add some FeatureName'`)
+4. Push to the Branch (`git push origin feature/AmazingFeature`)
+5. Open a Pull Request
+
+## Contact
+
+Please visit our [About Us](https://ay2324s1-cs2113-t18-1.github.io/tp/AboutUs.html) page for further contact
+information.
+
+Project Link: [https://github.com/AY2324S1-CS2113-T18-1/tp](https://github.com/AY2324S1-CS2113-T18-1/tp)
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index ea82051fab..55c58961b3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,11 +29,11 @@ test {
}
application {
- mainClass.set("seedu.duke.Duke")
+ mainClass.set("chessmaster.ChessMaster")
}
shadowJar {
- archiveBaseName.set("duke")
+ archiveBaseName.set("chessmaster")
archiveClassifier.set("")
}
@@ -43,4 +43,11 @@ checkstyle {
run{
standardInput = System.in
+ enableAssertions = true
+}
+
+compileJava.options.encoding = 'UTF-8'
+
+tasks.withType(JavaCompile) {
+ options.encoding = 'UTF-8'
}
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000000..53eb113fa6
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index c35db7c7e6..da78800ef8 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -242,6 +242,12 @@
+
+
+
+
+
+
chessMaster : prevBoard:ChessBoard
+deactivate storage
+
+alt Previous board exists
+
+ chessMaster -> ui : shouldStartNewGame()
+ activate ui
+ user -> ui : "yes/no"
+ ui --> chessMaster : :boolean
+ deactivate ui
+
+ opt startNewGame
+
+ chessMaster -> ui : chooseColor()
+ activate ui
+ user -> ui : "black/white"
+ ui --> chessMaster : playerColor:Color
+ deactivate ui
+
+ end
+
+else No previous board
+
+ chessMaster -> ui : chooseColor()
+ activate ui
+ user -> ui : "black/white"
+ ui --> chessMaster : playerColor:Color
+ deactivate ui
+
+end
+
+create game
+chessMaster -> game
+activate game
+chessMaster -> game : run()
+game --> chessMaster
+destroy game
+
+chessMaster --> user
+deactivate chessMaster
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/GameSequence.puml b/docs/diagrams/GameSequence.puml
new file mode 100644
index 0000000000..1853041112
--- /dev/null
+++ b/docs/diagrams/GameSequence.puml
@@ -0,0 +1,70 @@
+@startuml GameSequence
+
+actor User as user #BlanchedAlmond
+participant ":UI" as ui #IndianRed
+participant ":Parser" as parser #LightPink
+participant ":Game" as game #Orchid
+participant ":ChessBoard" as board #Orange
+participant ":CPU" as cpu #SkyBlue
+participant ":Storage" as storage #PaleTurquoise
+
+activate game #AliceBlue
+
+loop hasGameEnded
+
+ game -> ui : getUserInput()
+ activate ui
+ user -> ui : input
+ ui --> game : userInputString :String
+ deactivate ui
+
+ game -> parser : parseCommand(userInputString)
+ activate parser
+ parser --> game : :Command
+ deactivate parser
+
+ opt isMoveCommand
+
+ game -> parser : parseMove(input)
+ activate parser
+ parser --> game : humanMove:Move
+ deactivate parser
+
+ game -> board : executeMove(humanMove)
+ activate board
+ board --> game
+ deactivate board
+
+ game -> storage : saveBoard()
+ activate storage
+ storage --> game
+ deactivate storage
+
+ game -> cpu : getBestMove()
+ activate cpu
+ cpu --> game : cpuMove:Move
+ deactivate cpu
+
+ game -> board : executeMove(cpuMove)
+ activate board
+ board --> game
+ deactivate board
+
+ game -> storage : saveBoard()
+ activate storage
+ storage --> game
+ deactivate storage
+
+ game -> board : checkEndState()
+ activate board
+ board --> game : hasGameEnded:bool
+ deactivate board
+
+ end
+
+end
+
+game --> user
+destroy game
+
+@enduml
diff --git a/docs/diagrams/MiniMaxSequence.puml b/docs/diagrams/MiniMaxSequence.puml
new file mode 100644
index 0000000000..a686cb1606
--- /dev/null
+++ b/docs/diagrams/MiniMaxSequence.puml
@@ -0,0 +1,73 @@
+@startuml MiniMaxSequenceDiagram
+
+actor CPU as user #BlanchedAlmond
+participant ":MiniMax" as MM #IndianRed
+participant "Board :ChessBoard" as CB #LightPink
+participant ":Move" as M #Orange
+participant ":BoardScoreTuple" as BST #SkyBlue
+participant ":ChessMasterException" as E #PaleTurquoise
+
+create MM
+activate MM #AliceBlue
+user -> MM : getBestMove()
+
+
+MM -> MM : mostPoints()
+
+loop "depth < maxDepth"
+
+ MM -> CB : getBoard()
+ deactivate MM
+ activate CB
+ CB --> MM : ChessBoard
+ activate MM
+ deactivate CB
+
+ MM -> MM : Initialize variables
+ MM -> CB : getPoints()
+ deactivate MM
+ activate CB
+ CB --> MM : points
+ activate MM
+ deactivate CB
+ MM -> CB : Clone board
+ activate CB
+
+ CB --> CB : board.clone()
+ activate CB
+
+ CB -> M** : setPiece()
+ activate M
+ M -> CB : Execute move
+ deactivate M
+ CB -> CB : Calculate new score
+ CB -> BST** : Create BoardScoreTuple
+ activate BST
+ BST --> BST : BoardScoreTuple
+ deactivate BST
+ CB -> E : Handle exception (if any)
+ deactivate CB
+ CB -> MM : Continue loop
+ deactivate CB
+ loop "depth < maxDepth"
+ activate MM
+ MM -> MM : Recursively call mostPoints() while incrementing depth
+ MM --> MM : bestTuple
+ deactivate MM
+ end
+ MM -> MM : Update bestScore based on player turn
+ MM -> MM : Update bestTuple with bestScore and bestMove
+
+end
+
+MM -> BST : getBestMove()
+activate BST
+BST --> MM : bestMove
+deactivate BST
+
+MM -> user : Return bestMove
+deactivate MM
+destroy MM
+destroy BST
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/MoveClass.puml b/docs/diagrams/MoveClass.puml
new file mode 100644
index 0000000000..36525711a2
--- /dev/null
+++ b/docs/diagrams/MoveClass.puml
@@ -0,0 +1,20 @@
+@startuml
+hide circle
+skinparam classAttributeIconSize 0
+
+class Move {
+ - from: Coordinate
+ - 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/OverallArchitecture.puml b/docs/diagrams/OverallArchitecture.puml
new file mode 100644
index 0000000000..64ed9643a6
--- /dev/null
+++ b/docs/diagrams/OverallArchitecture.puml
@@ -0,0 +1,28 @@
+@startuml OverallArchitecture
+
+rectangle ChessMaster as chessMaster #LightPink
+rectangle Parser as parser #AliceBlue
+rectangle Game as game #Orchid
+rectangle UI as ui #IndianRed
+rectangle Storage as storage #SkyBlue
+rectangle ChessBoard as board #Orange
+actor User as user #BlanchedAlmond
+
+user -d-> ui
+
+chessMaster -r-> ui
+chessMaster -d-> game
+chessMaster -u-> storage
+chessMaster -u-> board
+
+game -> storage
+game -u-> board
+game -u-> ui
+
+storage .up.> board
+
+game .right.> parser
+storage .> parser
+board .> parser
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ParseCommandSequence.puml b/docs/diagrams/ParseCommandSequence.puml
new file mode 100644
index 0000000000..df01c2a319
--- /dev/null
+++ b/docs/diagrams/ParseCommandSequence.puml
@@ -0,0 +1,48 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+
+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
+create Command
+Parser -> Command ++
+Command --> Parser --: :Command
+Game <-- Parser --: :Command
+
+Game -> Command ++: execute
+alt MoveCommand
+ Command -> Parser ++: parseMove()
+ Parser --> Command --: :Move
+else ShowMoveCommand
+ create Coordinate
+ Command -> Coordinate ++: parseAlgebraicCoor()
+ Coordinate --> Command --: coord :Coordinate
+ Command -> ChessBoard ++: showAvailableCoordinates()
+ ChessBoard --
+ Command -> ChessBoard ++: getAvailableCoordinatesString()
+ ChessBoard --> Command --: :String
+ destroy Coordinate
+end
+Command -> CommandResult**
+activate CommandResult
+CommandResult --> Command--
+Game <-- Command --: :CommandResult
+
+opt canPromote
+Game -> Parser ++: parsePromote()
+Parser -> ChessPiece ++: getColour()
+ChessPiece --> Parser --: :Color
+Parser -> ChessPiece ++: getPosition()
+ChessPiece --> Parser --: :Coordinate
+Game <-- Parser --: :ChessPiece
+end
+
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ParserCommandDiagram.puml b/docs/diagrams/ParserCommandDiagram.puml
new file mode 100644
index 0000000000..afda39c262
--- /dev/null
+++ b/docs/diagrams/ParserCommandDiagram.puml
@@ -0,0 +1,49 @@
+@startuml
+'https://plantuml.com/class-diagram
+hide circle
+skinparam classAttributeIconSize 0
+package command{
+ abstract class Command {
+ +execute(game: Game)
+ +isMoveCommand(): boolean
+ }
+ class MoveCommand {
+ -userInput: String
+ -move: Move
+ +getMove(): Move
+ }
+ class ShowMovesCommand {
+ -userInput: String
+ -piece: ChessPiece
+ +getPiece(): ChessPiece
+ }
+ class XYZCommand
+ note top: Includes subclasses AbortCommand, HelpCommand, etc.
+
+ XYZCommand -|> Command
+ ShowMovesCommand --|> Command
+ MoveCommand --|> Command
+}
+package parser{
+ class Parser {
+ +{static} parsePromote(): ChessPiece
+ +{static} parseMove(): Move
+ +{static} parseChessPiece(): ChessPiece
+ +{static} parseCommand(): Command
+ +{static} parsePlayerColor(): Color
+ }
+}
+class Storage
+class Game
+class Human
+
+command.MoveCommand ..> parser.Parser
+command.ShowMovesCommand ..> parser.Parser
+command.XYZCommand ..> parser.Parser
+
+Game <... parser.Parser
+Storage <... parser.Parser
+Human <... parser.Parser
+ChessBoard <... parser.Parser
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/StorageClass.puml b/docs/diagrams/StorageClass.puml
new file mode 100644
index 0000000000..8567863b27
--- /dev/null
+++ b/docs/diagrams/StorageClass.puml
@@ -0,0 +1,59 @@
+@startuml
+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, 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 {
+ - SIZE: int
+}
+
+class ChessTile
+
+class Color
+
+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
new file mode 100644
index 0000000000..b67ba8af0d
--- /dev/null
+++ b/docs/diagrams/StorageSequence.puml
@@ -0,0 +1,132 @@
+@startuml
+
+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
+User -> Storage++ : new Storage(): String
+create File
+Storage -> File++ : new File()
+File --> Storage-- : storageFile:File
+Storage --> User--
+
+
+User -> Storage++ : createChessMasterFile()
+Storage -> File++ : exists()
+File --> Storage-- : boolean
+alt File: !exists()
+ File -> File++ : createParentDirectories()
+ File --> File--
+ File -> File++ : createNewFile()
+ File --> File--
+end
+Storage --> User--
+
+
+User -> Storage++ : saveBoard(): ChessBoard
+Storage -> Storage++ : createChessMasterFile()
+Storage --> Storage--
+create FileWriter
+Storage -> FileWriter++ : new FileWriter(): File
+FileWriter --> Storage-- : fileWriter: FileWriter
+Storage -> FileWriter++: fileWriter.write(): playerColor.name()
+FileWriter -> ChessBoard++ : getPlayerColor()
+ChessBoard --> FileWriter-- : Color
+FileWriter -> FileWriter++ : write(): Color
+FileWriter --> FileWriter--
+FileWriter --> Storage--
+FileWriter -> FileWriter++ : fileWriter.write(): System.lineSeparator()
+FileWriter --> FileWriter--
+Storage -> FileWriter++ : fileWriter.write(): human.movesToString()
+FileWriter -> Human++ : movesToString()
+Human --> FileWriter : human moves
+FileWriter -> FileWriter++ : write(): human moves
+FileWriter --> FileWriter--
+FileWriter --> Storage--
+Storage -> FileWriter++ : fileWriter.write(): cpu.movesToString()
+FileWriter -> CPU++ : movesToString()
+CPU --> FileWriter : CPU moves
+FileWriter -> FileWriter++ : write(): CPU moves
+FileWriter --> FileWriter--
+FileWriter --> Storage--
+loop for row from 0 to ChessBoard.SIZE-1
+ alt row loop
+ FileWriter -> FileWriter++ : .write(): System.lineSeparator()
+ FileWriter --> FileWriter--
+ loop for col from 0 to ChessBoard.SIZE-1
+ Storage -> ChessBoard++ : getPieceAtCoor(): Coordinate
+ create Coordinate
+ ChessBoard -> Coordinate++
+ Coordinate --> ChessBoard-- : Coordinate
+ ChessBoard --> Storage-- : piece
+ FileWriter -> FileWriter++ : write(): ChessPiece
+ FileWriter --> FileWriter--
+ end
+ end
+end
+FileWriter -> FileWriter++ : close()
+FileWriter --> FileWriter--
+Storage --> User--
+
+
+User -> Storage++ : resetBoard(): ChessBoard
+Storage -> Storage++ : createChessMasterFile()
+Storage --> Storage--
+create FileWriter
+Storage -> FileWriter++ : new FileWriter(): File
+FileWriter --> Storage-- : fileWriter: FileWriter
+Storage -> FileWriter++: .write(): String
+FileWriter --> Storage--
+Storage -> FileWriter++: .close()
+FileWriter --> Storage--
+Storage --> User--
+
+
+User -> Storage++ : loadBoard(): ChessBoard
+Storage -> Storage++ : createChessMasterFile()
+Storage --> Storage--
+create Scanner
+Storage -> Scanner++ : new Scanner(): File
+Scanner --> Storage-- : fileScanner: Scanner
+alt FileScanner: hasNext()
+ Storage -> Scanner++: nextLine()
+ Scanner --> Storage --
+end
+Storage -> Storage: Initialize rowIndex, boardTiles
+
+create ChessTile
+Storage -> ChessTile++
+ChessTile --> Storage-- : boardTiles: ChessTile[][]
+loop (rowIndex < ChessBoard.SIZE && fileScanner.hasNext())
+ Storage -> Scanner++ : nextLine()
+ Scanner --> Storage-- : chessRowLine: String
+ alt chessRowLine.length() != ChessBoard.SIZE
+ Storage -> Scanner++: close()
+ Scanner --> Storage--
+ end alt
+ loop for col from 0 to ChessBoard.SIZE-1
+ create String
+ Storage -> String++ : valueOf(): chessRowLine.charAt(col)
+ String --> Storage-- : chessPieceString: String
+ Storage -> Parser++ : chessPieceString
+ Parser --> Storage -- : initialPiece: ChessPiece
+
+ create ChessTile
+ Storage -> ChessTile++
+ ChessTile --> Storage-- : boardTiles: ChessTile[][]
+ end loop
+end
+Storage -> Scanner++: close
+Scanner --> Storage--
+Storage --> User-- : boardTiles
+
+@enduml
\ No newline at end of file
diff --git a/docs/images/ChessMasterLogo.png b/docs/images/ChessMasterLogo.png
new file mode 100644
index 0000000000..59b802d857
Binary files /dev/null and b/docs/images/ChessMasterLogo.png differ
diff --git a/docs/images/ChessMasterSequence.png b/docs/images/ChessMasterSequence.png
new file mode 100644
index 0000000000..285ee76221
Binary files /dev/null and b/docs/images/ChessMasterSequence.png differ
diff --git a/docs/images/GameSequence.png b/docs/images/GameSequence.png
new file mode 100644
index 0000000000..726c8214db
Binary files /dev/null and b/docs/images/GameSequence.png differ
diff --git a/docs/images/MiniMaxSequenceDiagram.png b/docs/images/MiniMaxSequenceDiagram.png
new file mode 100644
index 0000000000..0095327e0f
Binary files /dev/null and b/docs/images/MiniMaxSequenceDiagram.png differ
diff --git a/docs/images/MoveClass.png b/docs/images/MoveClass.png
new file mode 100644
index 0000000000..2be9cf50c0
Binary files /dev/null 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/OverallArchitecture.png b/docs/images/OverallArchitecture.png
new file mode 100644
index 0000000000..bd0f0f3681
Binary files /dev/null and b/docs/images/OverallArchitecture.png differ
diff --git a/docs/images/ParseCommandSequence.png b/docs/images/ParseCommandSequence.png
new file mode 100644
index 0000000000..cf9276b5c9
Binary files /dev/null and b/docs/images/ParseCommandSequence.png differ
diff --git a/docs/images/ParserCommandDiagram.png b/docs/images/ParserCommandDiagram.png
new file mode 100644
index 0000000000..34b88623c6
Binary files /dev/null and b/docs/images/ParserCommandDiagram.png differ
diff --git a/docs/images/StorageClass.png b/docs/images/StorageClass.png
new file mode 100644
index 0000000000..5779c82912
Binary files /dev/null and b/docs/images/StorageClass.png differ
diff --git a/docs/images/StorageSequence.png b/docs/images/StorageSequence.png
new file mode 100644
index 0000000000..4f16a75622
Binary files /dev/null and b/docs/images/StorageSequence.png differ
diff --git a/docs/team/antrikshdhand.md b/docs/team/antrikshdhand.md
new file mode 100644
index 0000000000..738d1a170e
--- /dev/null
+++ b/docs/team/antrikshdhand.md
@@ -0,0 +1,39 @@
+# 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
+
+#### `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.
+
+### General Contributions
+
+Regularly contributed to team discussions, providing insights and assistance where needed.
+
+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/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index ab75b391b8..0000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# John Doe - Project Portfolio Page
-
-## Overview
-
-
-### Summary of Contributions
diff --git a/docs/team/ken-ruster.md b/docs/team/ken-ruster.md
new file mode 100644
index 0000000000..0c74f5ec61
--- /dev/null
+++ b/docs/team/ken-ruster.md
@@ -0,0 +1,57 @@
+# Project: ChessMaster v2.1
+
+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**: 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
new file mode 100644
index 0000000000..0183e53ec5
--- /dev/null
+++ b/docs/team/onx001.md
@@ -0,0 +1,38 @@
+# Project Portfolio: ChessMaster
+
+## Overview
+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.
+
+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
+
+My code contributions can be found on the [TP Dashboard](https://github.com/AY2324S1-CS2113-T18-1/tp/commit/fa0fa79e972d3c962461120070ca65f00bfff965).
+
+
+### 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.
+
+### Documentation
+
+#### 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.
+
+### 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.
+
+### 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
new file mode 100644
index 0000000000..408b336e29
--- /dev/null
+++ b/docs/team/tongzhenghong.md
@@ -0,0 +1,20 @@
+# Project Portfolio: Chess Master
+
+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
+
+The `Game` class encapsulates the core logic for the chess game. It manages players, moves, and the chessboard. My role involved defining and implementing essential game functionalities within this class, ensuring smooth gameplay and adherence to chess rules. The `Game` class also interacts with the ChessMaster class to maintain the flow of the overall game.
+
+## Overall Game Processing and Execution
+Contributing to the overall game processing and execution, I focused on creating a stable and engaging environment for users. This encompassed implementing turn-based moves, handling player input, and ensuring proper execution of game commands. My efforts aimed to deliver an immersive and error-free gaming experience.
+
+## User Guide and Developer Guide
+I worked on the the introductory part, including the overvie, quick start and the gameplay specifications. This entailed providing players with clear instructions on gameplay, commands, and features, with a specific section dedicated to the turn-based nature of the game. Simultaneously, the developer guide offers valuable insights into the project's structure, class interactions, and guidelines for future enhancements, especially in the context of the turn-based gameplay mechanics.
diff --git a/docs/team/triciabk.md b/docs/team/triciabk.md
new file mode 100644
index 0000000000..1abe20c1f0
--- /dev/null
+++ b/docs/team/triciabk.md
@@ -0,0 +1,37 @@
+# TrciaBK's Project Portfolio Page
+
+## 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.
+
+### 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
+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 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
new file mode 100644
index 0000000000..abc8c0a477
--- /dev/null
+++ b/src/main/java/chessmaster/ChessMaster.java
@@ -0,0 +1,179 @@
+//@@author TongZhengHong
+package chessmaster;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.game.ChessBoard;
+import chessmaster.ui.TextUI;
+import chessmaster.storage.Storage;
+import chessmaster.game.Color;
+import chessmaster.game.Game;
+import chessmaster.user.CPU;
+import chessmaster.user.Human;
+
+/**
+ * Main entry-point for ChessMaster application.
+ */
+public class ChessMaster {
+
+ private static final String FILE_PATH_STRING = "data/ChessMaster.txt";
+
+ private TextUI ui;
+ private ChessBoard board;
+ private Storage storage;
+ private int difficulty;
+ private boolean exit = false;
+ private Color playerColor;
+ private Color currentTurnColor = Color.WHITE;
+ private Human human;
+ private CPU cpu;
+
+ private ChessMaster() {
+ ui = new TextUI();
+ storage = new Storage(FILE_PATH_STRING);
+ ui.printWelcomeMessage();
+
+ try {
+ playerColor = storage.loadPlayerColor();
+ difficulty = storage.loadDifficulty();
+
+ //@@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();
+ }
+ } catch (ChessMasterException e) {
+ ui.printLoadBoardError();
+ loadNewGame();
+ }
+ }
+
+ 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);
+
+ while (!input.equals("y") && !input.equals("n")) {
+ if (input.equalsIgnoreCase("exit")) {
+ exit = true;
+ break;
+ }
+ ui.promptContinuePrevGame(true);
+ input = ui.getUserInput(false);
+ }
+
+ if (input.equals("y")) {
+ ui.printContinuePrevGame(playerColor.name(), difficulty);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ //@@author TriciaBK
+ private boolean shouldRestartGame() {
+ ui.promptNewGame(false);
+ String input = ui.getUserInput(false);
+ while (!input.equals("y") && !input.equals("n")) {
+ if (input.equalsIgnoreCase("exit")) {
+ exit = true;
+ break;
+ }
+ ui.promptNewGame(true);
+ input = ui.getUserInput(false);
+ }
+ if (input.equals("y")) {
+ ui.printRestartingGameMessage();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void loadNewGame() {
+ ui.promptStartingColor(false);
+ String input = ui.getUserInput(false);
+
+ while (!input.equals("b") && !input.equals("w")) {
+ if (input.equalsIgnoreCase("exit")) {
+ exit = true;
+ return;
+ }
+ ui.promptStartingColor(true);
+ input = ui.getUserInput(false);
+ }
+
+ playerColor = input.equals("b") ? Color.BLACK : Color.WHITE;
+ Color cpuColor = playerColor.getOppositeColour();
+ board = new ChessBoard(playerColor);
+ ui.printStartNewGame(playerColor.name());
+
+ human = new Human(playerColor, board);
+ cpu = new CPU(cpuColor, board);
+
+ //@@author onx001
+ ui.promptDifficulty(false);
+ input = ui.getUserInput(false);
+ while (!input.equals("1") && !input.equals("2") && !input.equals("3")) {
+ if (input.equalsIgnoreCase("exit")) {
+ exit = true;
+ return;
+ }
+ ui.promptDifficulty(true);
+ input = ui.getUserInput(false);
+ }
+ difficulty = Integer.parseInt(input);
+ board.setDifficulty(difficulty);
+ currentTurnColor = Color.WHITE;
+
+ try {
+ storage.saveBoard(board, currentTurnColor, human, cpu);
+ } catch (ChessMasterException e) {
+ ui.printText(e.getMessage());
+ }
+ }
+
+ private void run() {
+ boolean shouldRestart = true;
+ while (shouldRestart && !exit) {
+ Game game = new Game(playerColor, currentTurnColor, board, storage, ui, difficulty, human, cpu);
+ boolean restartMidGame = game.run();
+ if (!restartMidGame) {
+ shouldRestart = false;
+ continue;
+ }
+ shouldRestart = shouldRestartGame();
+ if (shouldRestart) {
+ loadNewGame();
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ new ChessMaster().run();
+ }
+}
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/Command.java b/src/main/java/chessmaster/commands/Command.java
new file mode 100644
index 0000000000..9c5b8ff63b
--- /dev/null
+++ b/src/main/java/chessmaster/commands/Command.java
@@ -0,0 +1,13 @@
+//@@author TongZhengHong
+package chessmaster.commands;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.game.Game;
+
+public abstract class Command {
+ public abstract CommandResult execute(Game game) throws ChessMasterException;
+
+ public boolean isMoveCommand() {
+ return this instanceof MoveCommand;
+ }
+}
diff --git a/src/main/java/chessmaster/commands/CommandResult.java b/src/main/java/chessmaster/commands/CommandResult.java
new file mode 100644
index 0000000000..65e8c315d0
--- /dev/null
+++ b/src/main/java/chessmaster/commands/CommandResult.java
@@ -0,0 +1,23 @@
+//@@author TongZhengHong
+package chessmaster.commands;
+
+/**
+ * Represents the result of a command execution.
+ */
+public class CommandResult {
+
+ /**
+ * The feedback message to be shown to the user. Contains a description of the
+ * execution result
+ */
+ private final String[] message;
+
+ public CommandResult(String... message) {
+ this.message = message;
+ }
+
+ public String[] getMessageStrings() {
+ return this.message;
+ }
+
+}
diff --git a/src/main/java/chessmaster/commands/ExitCommand.java b/src/main/java/chessmaster/commands/ExitCommand.java
new file mode 100644
index 0000000000..dbd8d52b12
--- /dev/null
+++ b/src/main/java/chessmaster/commands/ExitCommand.java
@@ -0,0 +1,22 @@
+//@@author TongZhengHong
+package chessmaster.commands;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.game.Game;
+
+public class ExitCommand extends Command {
+
+ public static final String EXIT_COMMAND_STRING = "exit";
+
+ private static final String EXIT_MESSAGE = "Exiting program... Thanks for playing!";
+
+ @Override
+ public CommandResult execute(Game game) throws ChessMasterException {
+ return new CommandResult(EXIT_MESSAGE);
+ }
+
+ public static boolean isExit(Command command) {
+ return command instanceof ExitCommand;
+ }
+
+}
diff --git a/src/main/java/chessmaster/commands/HelpCommand.java b/src/main/java/chessmaster/commands/HelpCommand.java
new file mode 100644
index 0000000000..73ffedc884
--- /dev/null
+++ b/src/main/java/chessmaster/commands/HelpCommand.java
@@ -0,0 +1,34 @@
+//@@author TriciaBK
+package chessmaster.commands;
+
+import chessmaster.game.Game;
+
+public class HelpCommand extends Command {
+
+ public static final String HELP_COMMAND_STRING = "help";
+
+ public static final String[] HELP_STRINGS = {
+ "Here are the commands you can use to play:",
+ "move\t\tMove piece",
+ "\t\tFormat: move [column][row] [column][row]",
+ "\t\te.g. move a2 a3",
+ "moves\t\tShow available moves for a piece",
+ "\t\tFormat: moves [column][row]",
+ "\t\te.g. moves a2",
+ "show\t\tShow the chessboard",
+ "rules\t\tObtain a quick refresher on the rules of chess",
+ "legend\t\tView pieces representation",
+ "restart\t\tStart a new game",
+ "history\t\tView history of all game moves",
+ "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",
+ };
+
+ @Override
+ public CommandResult execute(Game game) {
+ return new CommandResult(HELP_STRINGS);
+ }
+}
diff --git a/src/main/java/chessmaster/commands/HistoryCommand.java b/src/main/java/chessmaster/commands/HistoryCommand.java
new file mode 100644
index 0000000000..607a95efc9
--- /dev/null
+++ b/src/main/java/chessmaster/commands/HistoryCommand.java
@@ -0,0 +1,120 @@
+package chessmaster.commands;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.game.Game;
+import chessmaster.game.move.CastleMove;
+import chessmaster.game.move.Move;
+import chessmaster.game.move.PromoteMove;
+import chessmaster.user.Player;
+
+import java.util.ArrayList;
+
+class PlayerMoveTuple {
+ private Player player;
+ private Move move;
+
+ public PlayerMoveTuple(Player player, Move move) {
+ this.player = player;
+ this.move = move;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public Move getMove() {
+ return move;
+ }
+}
+
+public class HistoryCommand extends Command {
+
+ public static final String HISTORY_COMMAND_STRING = "history";
+
+ public static ArrayList getAllMovesInChronologicalOrder(Game game) {
+ int totalMoves = game.getNumMoves();
+ ArrayList allMoves = new ArrayList<>();
+
+ Player currentPlayer = game.getCurrentPlayer();
+ ArrayList currentPlayerMoves = currentPlayer.getMoves();
+ Player opponent = currentPlayer.isHuman() ? game.getCPU() : game.getHuman();
+ ArrayList opponentMoves = opponent.getMoves();
+
+ int j = (int) Math.floor(totalMoves / 2);
+ for (int i = 0; i <= j; i++) {
+ // If totalMoves is odd, that means the current player is NOT the player
+ // who started playing. The first move of the game was from opponent.
+ if (totalMoves % 2 != 0) {
+ allMoves.add(new PlayerMoveTuple(opponent, opponentMoves.get(i)));
+ if (i == j) {
+ break; // to account for the odd number of moves
+ }
+ allMoves.add(new PlayerMoveTuple(currentPlayer, currentPlayerMoves.get(i)));
+ } else {
+ if (i == j) {
+ break;
+ }
+ allMoves.add(new PlayerMoveTuple(currentPlayer, currentPlayerMoves.get(i)));
+ allMoves.add(new PlayerMoveTuple(opponent, opponentMoves.get(i)));
+ }
+ }
+
+ return allMoves;
+ }
+
+ @Override
+ public CommandResult execute(Game game) throws ChessMasterException {
+ int numMoves = game.getNumMoves();
+ if (numMoves == 0) {
+ return new CommandResult("No moves have been played yet!");
+ }
+
+ ArrayList allMoves = getAllMovesInChronologicalOrder(game);
+
+ StringBuilder returnStringBuilder = new StringBuilder();
+ int moveCounter = 1;
+ for (PlayerMoveTuple tuple : allMoves) {
+ Move move = tuple.getMove();
+ Player player = tuple.getPlayer();
+ String moveString;
+ if (move.hasCapturedAPiece()) {
+ moveString = String.format(
+ "Move %d: %s moves %s from %s to %s capturing the opponent's %s!\n",
+ moveCounter,
+ player.getColour(),
+ move.getPieceMoved().getPieceName(),
+ move.getFrom(),
+ move.getTo(),
+ move.getPieceCaptured().getPieceName()
+ );
+ } else if (move instanceof CastleMove) {
+ moveString = String.format(
+ "Move %d: %s castles their king!\n",
+ moveCounter,
+ player.getColour()
+ );
+ } else if (move instanceof PromoteMove) {
+ moveString = String.format(
+ "Move %d: %s promotes their pawn to a %s!",
+ moveCounter,
+ player.getColour(),
+ ((PromoteMove) move).getNewPiece()
+ );
+ } else {
+ moveString = String.format(
+ "Move %d: %s moves %s from %s to %s\n",
+ moveCounter,
+ player.getColour(),
+ move.getPieceMoved().getPieceName(),
+ move.getFrom(),
+ move.getTo()
+ );
+ }
+ returnStringBuilder.append(moveString);
+
+ moveCounter++;
+ }
+
+ return new CommandResult(returnStringBuilder.toString());
+ }
+}
diff --git a/src/main/java/chessmaster/commands/InvalidCommand.java b/src/main/java/chessmaster/commands/InvalidCommand.java
new file mode 100644
index 0000000000..5e2f56fd1e
--- /dev/null
+++ b/src/main/java/chessmaster/commands/InvalidCommand.java
@@ -0,0 +1,15 @@
+package chessmaster.commands;
+
+import chessmaster.game.Game;
+
+public class InvalidCommand extends Command {
+
+ private static final String INVALID_COMMAND_STRING =
+ "Oops! It appears that the command you entered is not recognized. " + System.lineSeparator() +
+ "Please use 'help' to view a list of available commands.";
+
+ @Override
+ public CommandResult execute(Game game) {
+ return new CommandResult(INVALID_COMMAND_STRING);
+ }
+}
diff --git a/src/main/java/chessmaster/commands/LegendCommand.java b/src/main/java/chessmaster/commands/LegendCommand.java
new file mode 100644
index 0000000000..02b272054a
--- /dev/null
+++ b/src/main/java/chessmaster/commands/LegendCommand.java
@@ -0,0 +1,38 @@
+//@@author TriciaBK
+package chessmaster.commands;
+
+import chessmaster.game.Game;
+import chessmaster.pieces.Bishop;
+import chessmaster.pieces.King;
+import chessmaster.pieces.Knight;
+import chessmaster.pieces.Pawn;
+import chessmaster.pieces.Queen;
+import chessmaster.pieces.Rook;
+
+public class LegendCommand extends Command {
+
+ public static final String LEGEND_COMMAND_STRING = "legend";
+
+ private static final String[] LEGEND_STRINGS = {
+ "Black pieces:",
+ String.format("\"%s\" represents a black rook.", Rook.ROOK_BLACK),
+ String.format("\"%s\" represents a black knight.", Knight.KNIGHT_BLACK),
+ String.format("\"%s\" represents a black bishop.", Bishop.BISHOP_BLACK),
+ String.format("\"%s\" represents a black queen.", Queen.QUEEN_BLACK),
+ String.format("\"%s\" represents a black king.", King.KING_BLACK),
+ String.format("\"%s\" represents a black pawn.", Pawn.PAWN_BLACK),
+ " ",
+ "White pieces:",
+ String.format("\"%s\" represents a white rook.", Rook.ROOK_WHITE),
+ String.format("\"%s\" represents a white knight.", Knight.KNIGHT_WHITE),
+ String.format("\"%s\" represents a white bishop.", Bishop.BISHOP_WHITE),
+ String.format("\"%s\" represents a white queen.", Queen.QUEEN_WHITE),
+ String.format("\"%s\" represents a white king.", King.KING_WHITE),
+ String.format("\"%s\" represents a white pawn.", Pawn.PAWN_WHITE),
+ };
+
+ @Override
+ public CommandResult execute(Game game) {
+ return new CommandResult(LEGEND_STRINGS);
+ }
+}
diff --git a/src/main/java/chessmaster/commands/MoveCommand.java b/src/main/java/chessmaster/commands/MoveCommand.java
new file mode 100644
index 0000000000..2b019d8431
--- /dev/null
+++ b/src/main/java/chessmaster/commands/MoveCommand.java
@@ -0,0 +1,79 @@
+//@@author TongZhengHong
+package chessmaster.commands;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.exceptions.InvalidMoveException;
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Game;
+import chessmaster.game.move.CastleMove;
+import chessmaster.game.move.Move;
+import chessmaster.parser.Parser;
+
+public class MoveCommand extends Command {
+
+ public static final String MOVE_COMMAND_STRING = "move";
+
+ public static final String NO_MOVE_FOUND_STRING =
+ "Oops! It seems you forgot to provide the 'from' and 'to' squares!";
+ public static final String MOVE_FORMAT_STRING =
+ "Format: moves ";
+ public static final String MOVE_EXAMPLE_STRING = "Example: move e2 e4";
+
+ private static final String EMPTY_PAYLOAD_ERROR_STRING = NO_MOVE_FOUND_STRING + System.lineSeparator() +
+ MOVE_FORMAT_STRING + System.lineSeparator() + MOVE_EXAMPLE_STRING;
+ private static final String MOVE_PIECE_MESSAGE = "You moved %s from %s to %s";
+ private static final String MOVE_AND_CAPTURE_MESSAGE = "You moved %s from %s to %s and captured the opponent's %s!";
+
+ private static final String MOVE_CASTLE_STRING = "You castled your King!";
+
+ private String userInput;
+ private Move move;
+
+ public MoveCommand(String inputString) {
+ this.userInput = inputString;
+ }
+
+ /**
+ * Executes the command based on user input, which is expected to consist of two
+ * algebraic coordinate strings separated by whitespace.
+ *
+ * @return A CommandResult object containing the result of the command.
+ * @throws ChessMasterException If the user input cannot be parsed into two
+ * coordinate objects.
+ */
+ @Override
+ public CommandResult execute(Game game) throws ChessMasterException {
+ ChessBoard board = game.getBoard();
+
+ if (userInput.isBlank()) {
+ throw new InvalidMoveException(EMPTY_PAYLOAD_ERROR_STRING);
+ }
+
+ move = Parser.parseMove(userInput, board, true);
+ if (!move.isValid(board)) {
+ throw new InvalidMoveException();
+ }
+
+ String pieceString = move.getPieceMoved().getClass().getSimpleName();
+
+ String returnString;
+ if (move.hasCapturedAPiece()) {
+ returnString = String.format(
+ MOVE_AND_CAPTURE_MESSAGE,
+ pieceString, move.getFrom(), move.getTo(), move.getPieceCaptured().getPieceName()
+ );
+ } else if (move instanceof CastleMove) {
+ returnString = MOVE_CASTLE_STRING;
+ } else {
+ returnString = String.format(MOVE_PIECE_MESSAGE, pieceString, move.getFrom(), move.getTo());
+ }
+ return new CommandResult(returnString);
+ }
+
+ public Move getMove() throws InvalidMoveException {
+ if (move == null) {
+ throw new InvalidMoveException();
+ }
+ return move;
+ }
+}
diff --git a/src/main/java/chessmaster/commands/RestartCommand.java b/src/main/java/chessmaster/commands/RestartCommand.java
new file mode 100644
index 0000000000..22704a1aee
--- /dev/null
+++ b/src/main/java/chessmaster/commands/RestartCommand.java
@@ -0,0 +1,19 @@
+package chessmaster.commands;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.game.Game;
+
+public class RestartCommand extends Command {
+
+ public static final String RESTART_COMMAND_STRING = "restart";
+
+
+ @Override
+ public CommandResult execute(Game game) throws ChessMasterException {
+ return new CommandResult();
+ }
+
+ public static boolean isRestart(Command command) {
+ return command instanceof RestartCommand;
+ }
+}
diff --git a/src/main/java/chessmaster/commands/RulesCommand.java b/src/main/java/chessmaster/commands/RulesCommand.java
new file mode 100644
index 0000000000..fe8fe93cac
--- /dev/null
+++ b/src/main/java/chessmaster/commands/RulesCommand.java
@@ -0,0 +1,43 @@
+//@@author TriciaBK
+package chessmaster.commands;
+
+import chessmaster.game.Game;
+import chessmaster.pieces.Bishop;
+import chessmaster.pieces.King;
+import chessmaster.pieces.Knight;
+import chessmaster.pieces.Pawn;
+import chessmaster.pieces.Queen;
+import chessmaster.pieces.Rook;
+
+public class RulesCommand extends Command {
+
+ public static final String RULES_COMMAND_STRING = "rules";
+
+ static final String[] RULES_STRINGS = {
+ "Here are simple chess rules to get you started:",
+ "",
+ "Piece movement:",
+ String.format(" Pawn (\"%s\") move forward one square but capture diagonally.", Pawn.PAWN_WHITE),
+ String.format(" Rooks (\"%s\") move horizontally and vertically any number of squares.", Rook.ROOK_WHITE),
+ String.format(" Knights (\"%s\") move in an L-shape.", Knight.KNIGHT_WHITE),
+ String.format(" Bishops (\"%s\") move diagonally any number of squares.", Bishop.BISHOP_WHITE),
+ String.format(" Queens (\"%s\") move any number of squares in any direction.", Queen.QUEEN_WHITE),
+ String.format(" Kings (\"%s\") move one square in any direction.", King.KING_WHITE),
+ "",
+ "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.",
+ "",
+ "Objective:",
+ " Game ends when one player's king is in checkmate, under attack and can't escape capture.",
+ " The delivering player wins the game."
+ };
+
+
+ @Override
+ public CommandResult execute(Game game) {
+ return new CommandResult(RULES_STRINGS);
+ }
+}
diff --git a/src/main/java/chessmaster/commands/ShowCommand.java b/src/main/java/chessmaster/commands/ShowCommand.java
new file mode 100644
index 0000000000..6c0c391622
--- /dev/null
+++ b/src/main/java/chessmaster/commands/ShowCommand.java
@@ -0,0 +1,24 @@
+//@@author TriciaBK
+package chessmaster.commands;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Game;
+import chessmaster.ui.TextUI;
+
+public class ShowCommand extends Command {
+
+ public static final String SHOW_COMMAND_STRING = "show";
+
+ private static final String SHOW_STRING = "Here is the current board state:";
+
+
+ @Override
+ public CommandResult execute(Game game) {
+ ChessBoard board = game.getBoard();
+ TextUI ui = game.getUI();
+
+ ui.printText(SHOW_STRING);
+ ui.printChessBoard(board.getBoard());
+ return new CommandResult();
+ }
+}
diff --git a/src/main/java/chessmaster/commands/ShowMovesCommand.java b/src/main/java/chessmaster/commands/ShowMovesCommand.java
new file mode 100644
index 0000000000..92f8a93421
--- /dev/null
+++ b/src/main/java/chessmaster/commands/ShowMovesCommand.java
@@ -0,0 +1,53 @@
+//@@author ken-ruster
+package chessmaster.commands;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.exceptions.NullPieceException;
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Coordinate;
+import chessmaster.game.Game;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.ui.TextUI;
+
+public class ShowMovesCommand extends Command {
+ public static final String SHOW_MOVES_COMMAND_STRING = "moves";
+
+ public static final String NO_COORDINATE_FOUND_STRING =
+ "Oops! Looks like you forgot to specify a coordinate!";
+ public static final String SHOW_MOVES_FORMAT_STRING = "Format: moves [column][row]";
+ public static final String SHOW_MOVES_EXAMPLE_STRING = "Example: moves a2";
+
+ private String userInput;
+ private ChessPiece piece;
+
+ public ShowMovesCommand(String userInput) {
+ this.userInput = userInput;
+ }
+
+ @Override
+ public CommandResult execute(Game game) throws ChessMasterException {
+ ChessBoard board = game.getBoard();
+ TextUI ui = game.getUI();
+
+ if (userInput.isBlank()) {
+ return new CommandResult(NO_COORDINATE_FOUND_STRING,
+ SHOW_MOVES_FORMAT_STRING, SHOW_MOVES_EXAMPLE_STRING);
+ }
+
+ Coordinate coord = Coordinate.parseAlgebraicCoor(userInput);
+ piece = board.getPieceAtCoor(coord);
+ if (piece.isEmptyPiece()) {
+ throw new NullPieceException(coord);
+ }
+
+ Coordinate[] possibleCoordinates = piece.getLegalCoordinates(board);
+ ui.printChessBoardAvailableMoves(board.getBoard(), piece, possibleCoordinates);
+
+ String[] displayString = piece.getAvailableCoordinatesString(board);
+ return new CommandResult(displayString);
+ }
+
+ public ChessPiece getPiece() {
+ return this.piece;
+ }
+}
diff --git a/src/main/java/chessmaster/commands/StepbackCommand.java b/src/main/java/chessmaster/commands/StepbackCommand.java
new file mode 100644
index 0000000000..fae1c15980
--- /dev/null
+++ b/src/main/java/chessmaster/commands/StepbackCommand.java
@@ -0,0 +1,92 @@
+package chessmaster.commands;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Coordinate;
+import chessmaster.game.Game;
+import chessmaster.game.move.CastleMove;
+import chessmaster.game.move.Move;
+import chessmaster.game.move.PromoteMove;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.Pawn;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class StepbackCommand extends Command {
+
+ public static final String STEPBACK_COMMAND_STRING = "stepback";
+ private String userInput;
+
+ public StepbackCommand(String inputString) {
+ this.userInput = inputString;
+ }
+
+ private void reverseMove(Move move, ChessBoard board) {
+ // need to reverse the previousMove by moving the piece back from `to` to `from`.
+ Coordinate moveTo = move.getTo();
+ Coordinate moveFrom = move.getFrom();
+
+ ChessPiece clonedPieceToMove = board.getPieceAtCoor(moveTo);
+ clonedPieceToMove.updatePosition(moveFrom);
+ board.getTileAtCoor(moveFrom).updateTileChessPiece(clonedPieceToMove);
+
+ // Need to check if the move captured a piece. If it did, need to replace `to` tile with pieceCaptured
+ if (move.hasCapturedAPiece()) {
+ board.getTileAtCoor(moveTo).updateTileChessPiece(move.getPieceCaptured());
+ } else {
+ board.getTileAtCoor(moveTo).setTileEmpty(moveTo);
+ }
+ }
+
+ private void reversePromotion(PromoteMove move, ChessBoard board) {
+ // Put the original pawn which was promoted back on the tile
+ Coordinate endTile = move.getTo(); // move.getFrom() is the same
+ Pawn pawnPromoted = move.getPawnPromoted();
+ board.getTileAtCoor(endTile).updateTileChessPiece(pawnPromoted);
+ }
+
+ @Override
+ public CommandResult execute(Game game) throws ChessMasterException {
+ ChessBoard currentBoard = game.getBoard();
+ ChessBoard historyBoard = currentBoard.clone();
+ int totalNumMoves = game.getNumMoves();
+
+ // INPUT ERROR HANDLING
+ int numMovesToStepBack = 0;
+ try {
+ numMovesToStepBack = Integer.parseInt(this.userInput);
+ if (numMovesToStepBack > totalNumMoves) {
+ throw new ChessMasterException("You cannot step back more moves than have been played!");
+ } else if (numMovesToStepBack <= 0) {
+ throw new ChessMasterException("Number of steps has to be greater than 0.");
+ }
+ } catch (NumberFormatException e) {
+ throw new ChessMasterException("Please input an integer number of steps to step back.");
+ }
+
+ ArrayList allMoves = HistoryCommand.getAllMovesInChronologicalOrder(game);
+ Collections.reverse(allMoves);
+
+ for (int i = 0; i < numMovesToStepBack; i++) {
+ Move previousMove = allMoves.get(i).getMove();
+ reverseMove(previousMove, historyBoard);
+
+ // If castling, need to reverse the rook move as well
+ if (previousMove instanceof CastleMove) {
+ Move rookCastleMove = ((CastleMove) previousMove).getRookMove();
+ reverseMove(rookCastleMove, historyBoard);
+ } else if (previousMove instanceof PromoteMove) {
+ reversePromotion((PromoteMove) previousMove, historyBoard);
+ }
+ }
+
+ game.getUI().printChessBoard(historyBoard.getBoard());
+
+ return new CommandResult(String.format(
+ "Stepped back %d steps!\nUse `show` to see the current board.",
+ numMovesToStepBack)
+ );
+ }
+
+}
diff --git a/src/main/java/chessmaster/exceptions/ChessMasterException.java b/src/main/java/chessmaster/exceptions/ChessMasterException.java
new file mode 100644
index 0000000000..c4909ef426
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/ChessMasterException.java
@@ -0,0 +1,8 @@
+//@@author TongZhengHong
+package chessmaster.exceptions;
+
+public class ChessMasterException extends Exception {
+ public ChessMasterException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/chessmaster/exceptions/InvalidMoveException.java b/src/main/java/chessmaster/exceptions/InvalidMoveException.java
new file mode 100644
index 0000000000..4acd032363
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/InvalidMoveException.java
@@ -0,0 +1,14 @@
+//@@author TongZhengHong
+package chessmaster.exceptions;
+
+import chessmaster.ui.ExceptionMessages;
+
+public class InvalidMoveException extends ChessMasterException {
+ public InvalidMoveException() {
+ super(ExceptionMessages.MESSAGE_INVALID_MOVE_EXCEPTION);
+ }
+
+ public InvalidMoveException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/chessmaster/exceptions/LoadBoardException.java b/src/main/java/chessmaster/exceptions/LoadBoardException.java
new file mode 100644
index 0000000000..ee2004821e
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/LoadBoardException.java
@@ -0,0 +1,17 @@
+//@@author TriciaBK
+package chessmaster.exceptions;
+
+import chessmaster.ui.ExceptionMessages;
+
+public class LoadBoardException extends ChessMasterException {
+
+ public LoadBoardException() {
+ super(ExceptionMessages.MESSAGE_LOAD_BOARD_EXCEPTION);
+ }
+
+ public LoadBoardException(String message) {
+ super(message);
+ }
+
+}
+
diff --git a/src/main/java/chessmaster/exceptions/MoveOpponentPieceException.java b/src/main/java/chessmaster/exceptions/MoveOpponentPieceException.java
new file mode 100644
index 0000000000..4027dda63f
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/MoveOpponentPieceException.java
@@ -0,0 +1,14 @@
+//@@author TongZhengHong
+package chessmaster.exceptions;
+
+import chessmaster.ui.ExceptionMessages;
+
+public class MoveOpponentPieceException extends ChessMasterException {
+ public MoveOpponentPieceException() {
+ super(ExceptionMessages.MESSAGE_MOVE_OPPONENT_EXCEPTION);
+ }
+
+ public MoveOpponentPieceException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/chessmaster/exceptions/NullPieceException.java b/src/main/java/chessmaster/exceptions/NullPieceException.java
new file mode 100644
index 0000000000..8ca8100c4a
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/NullPieceException.java
@@ -0,0 +1,17 @@
+//@@author TongZhengHong
+package chessmaster.exceptions;
+
+import chessmaster.game.Coordinate;
+import chessmaster.ui.ExceptionMessages;
+
+public class NullPieceException extends ChessMasterException {
+
+ public NullPieceException(Coordinate coordinate) {
+ super(String.format(ExceptionMessages.MESSAGE_NULL_PIECE_COORDINATE_EXCEPTION,
+ coordinate.toString()));
+ }
+
+ public NullPieceException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/chessmaster/exceptions/ParseChessPieceException.java b/src/main/java/chessmaster/exceptions/ParseChessPieceException.java
new file mode 100644
index 0000000000..6f57662a05
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/ParseChessPieceException.java
@@ -0,0 +1,16 @@
+//@@author TongZhengHong
+package chessmaster.exceptions;
+
+import chessmaster.ui.ExceptionMessages;
+
+public class ParseChessPieceException extends ChessMasterException {
+
+ public ParseChessPieceException() {
+ super(ExceptionMessages.MESSAGE_PARSE_CHESS_PIECE_EXCEPTION);
+ }
+
+ public ParseChessPieceException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/chessmaster/exceptions/ParseColorException.java b/src/main/java/chessmaster/exceptions/ParseColorException.java
new file mode 100644
index 0000000000..cc3c6ab14d
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/ParseColorException.java
@@ -0,0 +1,14 @@
+//@@author TongZhengHong
+package chessmaster.exceptions;
+
+import chessmaster.ui.ExceptionMessages;
+
+public class ParseColorException extends ChessMasterException {
+ public ParseColorException() {
+ super(ExceptionMessages.MESSAGE_PARSE_COLOR_EXCEPTION);
+ }
+
+ public ParseColorException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/chessmaster/exceptions/ParseCoordinateException.java b/src/main/java/chessmaster/exceptions/ParseCoordinateException.java
new file mode 100644
index 0000000000..b21aac3c6b
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/ParseCoordinateException.java
@@ -0,0 +1,16 @@
+//@@author TongZhengHong
+package chessmaster.exceptions;
+
+import chessmaster.ui.ExceptionMessages;
+
+public class ParseCoordinateException extends ChessMasterException {
+
+ public ParseCoordinateException() {
+ super(ExceptionMessages.MESSAGE_PARSE_COORDINATE_EXCEPTION);
+ }
+
+ public ParseCoordinateException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/chessmaster/exceptions/SaveBoardException.java b/src/main/java/chessmaster/exceptions/SaveBoardException.java
new file mode 100644
index 0000000000..d16034a4ed
--- /dev/null
+++ b/src/main/java/chessmaster/exceptions/SaveBoardException.java
@@ -0,0 +1,17 @@
+//@@author TriciaBK
+package chessmaster.exceptions;
+
+import chessmaster.ui.ExceptionMessages;
+
+public class SaveBoardException extends ChessMasterException {
+
+ public SaveBoardException() {
+ super(ExceptionMessages.MESSAGE_SAVE_BOARD_EXCEPTION);
+ }
+
+ public SaveBoardException(String message) {
+ super(message);
+ }
+
+}
+
diff --git a/src/main/java/chessmaster/game/BoardScoreTuple.java b/src/main/java/chessmaster/game/BoardScoreTuple.java
new file mode 100644
index 0000000000..c5e771adad
--- /dev/null
+++ b/src/main/java/chessmaster/game/BoardScoreTuple.java
@@ -0,0 +1,39 @@
+package chessmaster.game;
+
+import chessmaster.game.move.Move;
+
+public class BoardScoreTuple implements Comparable {
+
+ private ChessBoard board;
+ private int score;
+ private Move move;
+
+ public BoardScoreTuple(ChessBoard board, int score, Move move) {
+ this.board = board;
+ this.score = score;
+ this.move = move;
+ }
+
+ public ChessBoard getBoard() {
+ return board;
+ }
+
+ public int getScore() {
+ return score;
+ }
+
+ public Move getMove() {
+ return move;
+ }
+
+ @Override
+ public int compareTo(BoardScoreTuple tuple) {
+ if (this.score > tuple.getScore()) {
+ return 1;
+ } else if (this.score < tuple.getScore()) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+}
diff --git a/src/main/java/chessmaster/game/ChessBoard.java b/src/main/java/chessmaster/game/ChessBoard.java
new file mode 100644
index 0000000000..6521819b6c
--- /dev/null
+++ b/src/main/java/chessmaster/game/ChessBoard.java
@@ -0,0 +1,593 @@
+package chessmaster.game;
+
+import java.util.ArrayList;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.exceptions.InvalidMoveException;
+import chessmaster.game.move.PromoteMove;
+import chessmaster.game.move.CastleMove;
+import chessmaster.game.move.CastleSide;
+import chessmaster.game.move.Move;
+import chessmaster.game.move.MoveFactory;
+import chessmaster.game.move.EnPassantMove;
+import chessmaster.parser.Parser;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.EmptyPiece;
+import chessmaster.pieces.King;
+import chessmaster.pieces.Pawn;
+import chessmaster.user.CPU;
+import chessmaster.user.Human;
+
+
+public class ChessBoard {
+
+ public static final int SIZE = 8;
+ 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" },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { "P", "P", "P", "P", "P", "P", "P", "P" },
+ { "R", "N", "B", "Q", "K", "B", "N", "R" },
+ };
+
+ private static final String[][] STARTING_CHESSBOARD_WHITE = {
+ { "R", "N", "B", "Q", "K", "B", "N", "R" },
+ { "P", "P", "P", "P", "P", "P", "P", "P" },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { "p", "p", "p", "p", "p", "p", "p", "p" },
+ { "r", "n", "b", "q", "k", "b", "n", "r" },
+ };
+
+ private Color playerColor;
+
+ private int difficulty = 1;
+
+ private final ChessTile[][] board = new ChessTile[SIZE][SIZE];
+
+ public ChessBoard(Color playerColor) {
+ this.playerColor = playerColor;
+ for (int row = 0; row < SIZE; row++) {
+ for (int col = 0; col < SIZE; col++) {
+ String chessPieceString = playerColor.isBlack()
+ ? STARTING_CHESSBOARD_BLACK[row][col]
+ : STARTING_CHESSBOARD_WHITE[row][col];
+ ChessPiece initialPiece = Parser.parseChessPiece(chessPieceString, row, col);
+ board[row][col] = new ChessTile(initialPiece);
+ assert (board[row][col] != null);
+ }
+ }
+ this.playerColor = playerColor;
+ }
+
+ public ChessBoard(Color playerColor, ChessTile[][] boardTiles) {
+ this.playerColor = playerColor;
+ for (int row = 0; row < SIZE; row++) {
+ for (int col = 0; col < SIZE; col++) {
+ board[row][col] = boardTiles[row][col];
+ }
+ }
+ }
+
+ /**
+ * Gets a copy of the current chessboard as a 2D array of ChessTile objects.
+ *
+ * This method creates a deep copy of the chessboard, allowing for the independent
+ * examination of the board's state without modifying the original chessboard.
+ *
+ * @return A 2D array copy of ChessTile objects representing the current state of the chessboard.
+ */
+ public ChessTile[][] getBoard() {
+ ChessTile[][] copy = new ChessTile[SIZE][SIZE];
+ for (int i = 0; i < SIZE; i++) {
+ copy[i] = board[i].clone();
+ }
+ return copy;
+ }
+
+ public Color getPlayerColor() {
+ return this.playerColor;
+ }
+
+ //@@author onx001
+ public void setDifficulty(int difficulty) {
+ this.difficulty = difficulty;
+ }
+
+ public int getDifficulty() {
+ return this.difficulty;
+ }
+
+ //@@author onx001
+ public boolean hasEnPassant() {
+ //Checks all chess pieces for en passant
+ 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.isEnPassant()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public ChessPiece getEnPassantPiece() {
+ 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.isEnPassant()) {
+ return piece;
+ }
+ }
+ }
+ return null;
+ }
+
+ public Coordinate getEnPassantCoor() {
+ //Checks all chess pieces for en passant
+ 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.isEnPassant()) {
+ if (piece.isSameColorAs(playerColor)) {
+ return coor.addOffsetToCoordinate(0, 1);
+ } else {
+ return coor.addOffsetToCoordinate(0, -1);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ 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.
+ *
+ * This method determines whether the player with the specified color is in check,
+ * meaning their king is under threat. It checks if any moves by the opposing player's pieces
+ * can reach the player's king.
+ *
+ * @param color The color for which to check if the king is in check ('WHITE' or 'BLACK').
+ * @return `true` if the player is in check; `false` if the player's king is not in immediate danger.
+ */
+ public boolean isChecked(Color color) {
+ Move[] moves = getPseudoLegalMoves(color.getOppositeColour());
+ for (Move move : moves) {
+ Coordinate to = move.getTo();
+ if (this.getPieceAtCoor(to) instanceof King) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve an array of pseudo-legal moves for pieces of the specified color.
+ *
+ * This method calculates and provides an array of pseudo-legal moves for all pieces of the given
+ * color on the chessboard. Pseudo-legal moves are those that are valid based on the piece's movement rules,
+ * but they may not account for potential checks on the king.
+ *
+ * @param color The color for which pseudo-legal moves should be generated ('WHITE' or 'BLACK').
+ * @return An array of Move objects, each representing a pseudo-legal move, containing the starting square,
+ * destination square, and the ChessPiece involved.
+ */
+
+ public Move[] getPseudoLegalMoves(Color color) {
+ //Declare arraylist of moves as allMoves
+ ArrayList allMoves = new ArrayList<>();
+
+ for (int row = 0; row < ChessBoard.SIZE; row++) {
+ for (int col = 0; col < ChessBoard.SIZE; col++) {
+ Coordinate currentCoor = new Coordinate(col, row);
+ ChessPiece piece = getPieceAtCoor(currentCoor);
+
+ if (piece.isSameColorAs(color)) {
+ Coordinate[] possibleCoordinates = piece.getPseudoLegalCoordinates(this);
+ for (Coordinate possible: possibleCoordinates) {
+ allMoves.add(MoveFactory.createMove(this, currentCoor, possible));
+ }
+ }
+ }
+ }
+ return allMoves.toArray(new Move[0]);
+ }
+
+ /**
+ * Retrieve an array of legal moves for pieces of the specified color.
+ *
+ * This method calculates and provides an array of legal moves for all pieces of the given color on the
+ * chessboard. This is done by executing each pseudo-legal move and ensuring that it does not result in
+ * the king being checked.
+ *
+ * Legal moves are those that adhere to the piece's movement rules and do not result in the
+ * player's own king being in check.
+ *
+ * @param color The color for which legal moves should be generated ('WHITE' or 'BLACK').
+ * @return An array of Move objects, each representing a legal move, containing the starting square,
+ * destination square, and the ChessPiece involved.
+ */
+ public Move[] getLegalMoves(Color color) {
+ Move[] moves = getPseudoLegalMoves(color);
+ ArrayList legalMoves = new ArrayList<>();
+
+ for (Move move : moves) {
+ ChessBoard newBoard = this.clone();
+ Coordinate from = move.getFrom();
+ ChessPiece piece = newBoard.getPieceAtCoor(from);
+ Move moveCopy = new Move(from, move.getTo(), piece);
+ try {
+ newBoard.executeMove(moveCopy);
+ } catch (InvalidMoveException e) {
+ continue;
+ }
+ if (!newBoard.isChecked(color)) {
+ legalMoves.add(move);
+ }
+ }
+ return legalMoves.toArray(new Move[0]);
+ }
+
+ //@@author TongZhengHong
+ public void setPromotionPiece(Coordinate coord, ChessPiece promotedPiece) {
+ getTileAtCoor(coord).updateTileChessPiece(promotedPiece);
+ }
+
+ /**
+ * Gets the ChessTile object located at the specified coordinate on the
+ * chessboard.
+ *
+ * @param coor The coordinate of the position to retrieve the tile for.
+ * @return The ChessTile object at the specified coordinate.
+ */
+ public ChessTile getTileAtCoor(Coordinate coor) {
+ return board[coor.getY()][coor.getX()];
+ }
+
+ /**
+ * Gets the chess piece located at the specified coordinate on the chessboard.
+ *
+ * @param coor The coordinate of the position to check.
+ * @return The ChessPiece object at the specified coordinate. If empty piece at
+ * coordinate, returns EmptyPiece object
+ */
+ public ChessPiece getPieceAtCoor(Coordinate coor) {
+ ChessTile tile = getTileAtCoor(coor);
+ return tile.getChessPiece();
+ }
+
+ /**
+ * For castling moves, executeBasicMove() only moves the King. This method will move the rook correctly
+ * based on whether the player has castled queen-side or king-side.
+ * @param move The CastleMove to execute
+ * @throws InvalidMoveException If the rook move is not valid according to game rules
+ */
+ private void executeCastlingRookMove(CastleMove move) throws InvalidMoveException {
+ Coordinate startCoor = move.getFrom();
+ CastleSide side = move.getSide();
+
+ Move rookCastleMove;
+ if (side == CastleSide.LEFT && startCoor.isOffsetWithinBoard(-4, 0)) {
+ Coordinate rookStartCoor = startCoor.addOffsetToCoordinate(-4, 0);
+ Coordinate rookDestCoor = startCoor.addOffsetToCoordinate(-1, 0);
+ rookCastleMove = MoveFactory.createMove(this, rookStartCoor, rookDestCoor);
+ move.setRookMove(rookCastleMove);
+
+ this.executeMove(rookCastleMove);
+ } else if (side == CastleSide.RIGHT && startCoor.isOffsetWithinBoard(3, 0)) {
+ Coordinate rookStartCoor = startCoor.addOffsetToCoordinate(3, 0);
+ Coordinate rookDestCoor = startCoor.addOffsetToCoordinate(1, 0);
+ rookCastleMove = MoveFactory.createMove(this, rookStartCoor, rookDestCoor);
+ move.setRookMove(rookCastleMove);
+
+ this.executeMove(rookCastleMove);
+ }
+ }
+
+ private void executeEnPassantCapture(EnPassantMove move) {
+ ChessPiece enPassantPiece = move.getPieceCaptured();
+
+ // Capture the enPassantPiece
+ this.getTileAtCoor(enPassantPiece.getPosition()).setTileEmpty(enPassantPiece.getPosition());
+ enPassantPiece.setIsCaptured();
+ }
+
+ /**
+ * Moves a piece from the start coordinate to the destination coordinate.
+ * @param move The move to execute
+ */
+ private void executeBasicMove(Move move) {
+ Coordinate startCoor = move.getFrom();
+ Coordinate destCoor = move.getTo();
+ ChessPiece pieceMoved = move.getPieceMoved();
+
+ pieceMoved.setHasMoved();
+ pieceMoved.updatePosition(destCoor);
+
+ getTileAtCoor(startCoor).setTileEmpty(startCoor);
+ getTileAtCoor(destCoor).getChessPiece().setIsCaptured();
+ getTileAtCoor(destCoor).updateTileChessPiece(pieceMoved);
+ }
+
+
+ private void clearAllEnPassants(Move move) {
+ 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.isEnPassant() && piece.getColor() != move.getPieceMoved().getColor()) {
+ piece.clearEnPassant();
+ }
+ }
+ }
+ }
+
+ /**
+ * Executes a chess move on the chessboard.
+ *
+ * @param move The Move object representing the move to be executed.
+ * @throws InvalidMoveException If the move is not valid according to the game
+ * rules.
+ */
+ public void executeMove(Move move) throws InvalidMoveException {
+ this.executeBasicMove(move);
+
+ if (move instanceof CastleMove) {
+ this.executeCastlingRookMove((CastleMove) move);
+ } else if (move instanceof EnPassantMove) {
+ this.executeEnPassantCapture((EnPassantMove) move);
+ } else if (move.isSkippingPawn()) {
+ move.getPieceMoved().setEnPassant();
+ }
+
+ this.clearAllEnPassants(move);
+ }
+
+ public void executeMoveWithCheck(Move move) throws InvalidMoveException {
+ if (move.isValidWithCheck(this)) {
+ executeMove(move);
+ } else {
+ throw new InvalidMoveException("Move causes a check");
+ }
+ }
+
+ //@@author ken-ruster
+ public boolean canPromote(Move move) {
+ ChessPiece piece = move.getPieceMoved();
+ Coordinate endCoord = move.getTo();
+
+ if (!piece.isPawn()) {
+ return false;
+ }
+
+ if (isPieceFriendly(piece)) {
+ return endCoord.getY() == TOP_ROW_INDEX;
+ }
+
+ if (isPieceOpponent(piece)) {
+ return endCoord.getY() == BOTTOM_ROW_INDEX;
+ }
+
+ return false;
+ }
+
+ //@@author onx001
+ public boolean isEndGame() throws ChessMasterException {
+ if (!isKingAlive(playerColor) || !isKingAlive(playerColor.getOppositeColour())) {
+ throw new ChessMasterException("King is dead!");
+ }
+ return isCheckmated(playerColor) || isCheckmated(playerColor.getOppositeColour());
+ }
+
+ public Color getWinningColor() {
+ if (isCheckmated(playerColor) && isCheckmated(playerColor.getOppositeColour())) {
+ return Color.DRAW;
+ } else if (isCheckmated(playerColor)) {
+ return playerColor.getOppositeColour();
+ } else if (isCheckmated(playerColor.getOppositeColour())) {
+ return playerColor;
+ } else {
+ return Color.EMPTY;
+ }
+ }
+
+
+ public int getPoints(Color color) {
+ int points = 0;
+ int enemyPoints = 0;
+ boolean isUpright;
+
+ if (this.playerColor == color) {
+ isUpright = true;
+ } else {
+ isUpright = false;
+ }
+
+ for (int row = 0; row < ChessBoard.SIZE; row++) {
+ for (int col = 0; col < ChessBoard.SIZE; col++) {
+ Coordinate coor = new Coordinate(col, row);
+ ChessPiece piece = this.getPieceAtCoor(coor);
+
+ if (piece.isSameColorAs(color)) {
+ points += piece.getPoints(isUpright);
+ } else {
+ enemyPoints += piece.getPoints(isUpright);
+ }
+ }
+ }
+
+ return points - enemyPoints;
+ }
+
+
+ public ChessBoard clone() {
+ String boardString = this.toString();
+ return toBoard(boardString);
+ }
+
+
+ /**
+ * Converts a String representing a board into a new ChessBoard.
+ *
+ * @param board String representing the board
+ * @return ChessBoard generated from input string
+ */
+ public ChessBoard toBoard(String board) {
+ ChessTile[][] boardTiles = new ChessTile[SIZE][SIZE];
+ int row = 0;
+ int col = 0;
+ for (int i = 0; i < board.length(); i++) {
+ String pieceString = board.substring(i, i + 1);
+ ChessPiece piece = Parser.parseChessPiece(pieceString, row, col);
+ assert (row < SIZE);
+ assert (col < SIZE);
+ boardTiles[row][col] = new ChessTile(piece);
+ col++;
+ if (col == SIZE) {
+ col = 0;
+ row++;
+ }
+ if (row == SIZE) {
+ break;
+ }
+ }
+ return new ChessBoard(this.playerColor, boardTiles);
+ }
+
+ //@@author ken-ruster
+ /**
+ * Takes in an array of multiple moves, and executes them in order. Also updates the move history
+ * stored in the human and CPU objects.
+ *
+ * @param moves ArrayList of moves to be executed
+ * @param human Object representing the human player
+ * @param cpu Object representing the CPU player
+ * @throws ChessMasterException
+ */
+ public void executeMoveArray(ArrayList moves, Human human, CPU cpu) throws ChessMasterException {
+ boolean isPlayersTurn = playerColor.isWhite();
+
+ for (String move : moves) {
+ String[] moveCommandArray = move.split("\\s+");
+ boolean isPromote = moveCommandArray[0].equals(PROMOTE_MOVE_STRING);
+
+ if (!isPromote) {
+ Move toExecute = Parser.parseMove(move, this, false);
+ if (!toExecute.isValid(this)) {
+ throw new InvalidMoveException(INVALID_SAVE_STRING);
+ }
+ this.executeMove(toExecute);
+
+ if (isPlayersTurn) {
+ human.addMove(toExecute);
+ } else {
+ cpu.addMove(toExecute);
+ }
+ } else {
+ Coordinate coord = Coordinate.parseAlgebraicCoor(moveCommandArray[1]);
+ ChessPiece oldPiece = this.getPieceAtCoor(coord);
+ 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);
+
+ PromoteMove promoteMove = MoveFactory.createPromoteMove(coord, (Pawn) oldPiece, newPiece);
+ if (isPlayersTurn) {
+ human.addMove(promoteMove);
+ } else {
+ cpu.addMove(promoteMove);
+ }
+ }
+
+ isPlayersTurn = !isPlayersTurn;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder boardString = new StringBuilder();
+ for (ChessTile[] row : board) {
+ for (ChessTile tile : row) {
+ boardString.append(tile.toFileString());
+ }
+ }
+ return boardString.toString();
+ }
+
+ //@@author TongZhengHong
+ public boolean isPieceFriendly(ChessPiece otherPiece) {
+ return this.playerColor == otherPiece.getColor();
+ }
+
+ public boolean isPieceOpponent(ChessPiece otherPiece) {
+ return this.playerColor != otherPiece.getColor();
+ }
+
+ public boolean isTileOccupied(Coordinate coord) {
+ ChessPiece piece = this.getPieceAtCoor(coord);
+ return piece == null || !(piece instanceof EmptyPiece);
+ }
+
+ //@@author ken_ruster
+ public boolean equals(ChessBoard otherBoard) {
+ String otherBoardString = otherBoard.toString();
+
+ return otherBoardString.equals(this.toString());
+ }
+}
diff --git a/src/main/java/chessmaster/game/ChessTile.java b/src/main/java/chessmaster/game/ChessTile.java
new file mode 100644
index 0000000000..4642a6fc3a
--- /dev/null
+++ b/src/main/java/chessmaster/game/ChessTile.java
@@ -0,0 +1,97 @@
+package chessmaster.game;
+
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.EmptyPiece;
+
+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 = ".";
+ private static final String BACKGROUND_RESET = "\u001B[0m";
+ private static final String CAPTURABLE_BACKGROUND = "\u001B[43m";
+
+ /** Nullable ChessPiece object. Null signifies that this tile is empty */
+ private ChessPiece chessPiece;
+
+ public ChessTile(Coordinate coor) {
+ chessPiece = new EmptyPiece(coor.getX(), coor.getY());
+ }
+
+ public ChessTile(ChessPiece piece) {
+ chessPiece = piece;
+ }
+
+ public ChessPiece getChessPiece() {
+ return chessPiece;
+ }
+
+ public boolean isEmpty() {
+ return chessPiece.isEmptyPiece();
+ }
+
+ public void setTileEmpty(Coordinate coor) {
+ chessPiece = new EmptyPiece(coor.getX(),coor.getY());
+ }
+
+ /**
+ * Updates the ChessTile with a new ChessPiece, considering piece interactions.
+ * 1. Replace the new piece on an EMPTY tile.
+ * 2. Cannot capture a friendly piece; no change is made UNLESS it is for promotion.
+ * 3. If new piece captures the opponent piece, mark the opponent piece as captured and replace it.
+ *
+ * @param newPiece The new ChessPiece to place on the tile.
+ */
+ public void updateTileChessPiece(ChessPiece newPiece) {
+ if (chessPiece.isEmptyPiece()) {
+ // Move newPiece to empty tile
+ chessPiece = newPiece;
+ return;
+ }
+
+ if (newPiece.isFriendly(chessPiece)) {
+ // Only update if friendly pawn piece is promoting
+ if (chessPiece.isPawn() && newPiece.isPromotionPiece()) {
+ chessPiece = newPiece;
+ }
+ return; // Cannot capture friendly piece
+ }
+
+ if (newPiece.isOpponent(chessPiece)) {
+ // Mark opponent piece as captured
+ chessPiece.setIsCaptured();
+ chessPiece = newPiece;
+ }
+ }
+
+ @Override
+ public String toString() {
+ String tileContent = isEmpty() ? EMPTY_TILE_STRING : chessPiece.toString();
+ return String.format(" %s ", tileContent);
+ }
+
+ public String toStringSelected() {
+ String tileContent = isEmpty() ? EMPTY_TILE_STRING : chessPiece.toString();
+ return CAPTURABLE_BACKGROUND + String.format("{%s}", tileContent) + BACKGROUND_RESET;
+ }
+
+ public String toStringPrevMove() {
+ String tileContent = isEmpty() ? EMPTY_TILE_STRING : chessPiece.toString();
+ return CAPTURABLE_BACKGROUND + String.format("(%s)", tileContent) + BACKGROUND_RESET;
+ }
+
+ //@@author ken-ruster
+ public String toStringAvailableDest() {
+ String tileContent = isEmpty() ? EMPTY_TILE_MOVES_STRING : chessPiece.toString();
+ String addBrackets = String.format("[%s]", tileContent);
+
+ if (!isEmpty()) {
+ return CAPTURABLE_BACKGROUND + addBrackets + BACKGROUND_RESET;
+ }
+ return addBrackets;
+ }
+
+ //@@author onx001
+ public String toFileString() {
+ return chessPiece.toString();
+ }
+}
diff --git a/src/main/java/chessmaster/game/Color.java b/src/main/java/chessmaster/game/Color.java
new file mode 100644
index 0000000000..a9242dcf12
--- /dev/null
+++ b/src/main/java/chessmaster/game/Color.java
@@ -0,0 +1,58 @@
+//@@author TongZhengHong
+package chessmaster.game;
+
+public enum Color {
+ WHITE, BLACK, EMPTY, DRAW;
+
+ /**
+ * Get the opposite color given the player's color.
+ * Used to identify the color for CPU player.
+ *
+ * @return The opposite color.
+ */
+ public Color getOppositeColour() {
+ if (this == Color.WHITE) {
+ return Color.BLACK;
+ } else if (this == Color.BLACK) {
+ return Color.WHITE;
+ } else {
+ return Color.EMPTY;
+ }
+ }
+
+ /**
+ * Checks if a given color is white.
+ *
+ * @return true if the color is white; false otherwise.
+ */
+ public boolean isWhite() {
+ return this == Color.WHITE;
+ }
+
+ /**
+ * Checks if a given color is black.
+ *
+ * @return true if the color is black; false otherwise.
+ */
+ public boolean isBlack() {
+ return this == Color.BLACK;
+ }
+
+ /**
+ * Checks if a given color is empty.
+ *
+ * @return true if the color is empty; false otherwise.
+ */
+ public boolean isEmpty() {
+ return this == Color.EMPTY;
+ }
+
+ /**
+ * Checks if a given color is draw.
+ *
+ * @return true if the color is draw; false otherwise.
+ */
+ public boolean isDraw() {
+ return this == Color.DRAW;
+ }
+}
diff --git a/src/main/java/chessmaster/game/Coordinate.java b/src/main/java/chessmaster/game/Coordinate.java
new file mode 100644
index 0000000000..267823fec7
--- /dev/null
+++ b/src/main/java/chessmaster/game/Coordinate.java
@@ -0,0 +1,126 @@
+//@@author TongZhengHong
+package chessmaster.game;
+
+import chessmaster.exceptions.ParseCoordinateException;
+
+public class Coordinate {
+
+ private static final String BOARD_COLUMNS = "abcdefgh";
+
+ private int x;
+ private int y;
+
+ public Coordinate(int x, int y) {
+ this.x = x;
+ this.y = y;
+ assert !isCoorOutofBoard(x, y) : "Coordinates (x,y) should NOT be out of chessboard (8x8 size)!";
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public static boolean isCoorOutofBoard(int x, int y) {
+ return (x < 0 || x >= ChessBoard.SIZE) || (y < 0 || y >= ChessBoard.SIZE);
+ }
+
+ /**
+ * Checks if applying a given offset from the current position stays within the
+ * bounds of the chessboard.
+ *
+ * @param offsetX The horizontal offset to apply.
+ * @param offsetY The vertical offset to apply.
+ * @return true if the resulting position is within the board boundaries;
+ * otherwise, false.
+ */
+ public boolean isOffsetWithinBoard(int offsetX, int offsetY) {
+ int newX = x + offsetX;
+ int newY = y + offsetY;
+
+ return (newX >= 0 && newX < ChessBoard.SIZE) &&
+ (newY >= 0 && newY < ChessBoard.SIZE);
+ }
+
+ /**
+ * Adds the given offsets to the current coordinate and returns the new
+ * coordinate.
+ *
+ * @param offsetX The horizontal offset to apply.
+ * @param offsetY The vertical offset to apply.
+ * @return A new coordinate after applying the offsets, or the current
+ * coordinate
+ * if the new position is out of the board boundaries.
+ */
+ public Coordinate addOffsetToCoordinate(int offsetX, int offsetY) {
+ int newX = x + offsetX;
+ int newY = y + offsetY;
+
+ if (isCoorOutofBoard(newX, newY)) {
+ return new Coordinate(x, y);
+ }
+
+ return new Coordinate(newX, newY);
+ }
+
+ /**
+ * Parses an algebraic chess coordinate notation (e.g., "a1") and returns a
+ * Coordinate object.
+ *
+ * @param notation The algebraic coordinate notation to parse.
+ * @return A Coordinate object representing the parsed chess coordinate.
+ * @throws ParseCoordinateException If the input notation is invalid or out of
+ * bounds.
+ */
+ public static Coordinate parseAlgebraicCoor(String notation) throws ParseCoordinateException {
+ notation = notation.toLowerCase();
+ if (notation.length() != 2) {
+ throw new ParseCoordinateException();
+ }
+
+ String colString = Character.toString(notation.charAt(0));
+ boolean isColValid = BOARD_COLUMNS.contains(colString);
+
+ try {
+ String rowString = String.valueOf(notation.charAt(1));
+ int rowInt = Integer.parseInt(String.valueOf(rowString));
+
+ if (rowInt < 1 || rowInt > ChessBoard.SIZE || !isColValid) {
+ throw new ParseCoordinateException();
+ }
+
+ int indexX = BOARD_COLUMNS.indexOf(colString);
+ int indexY = (rowInt - 8) * -1;
+
+ return new Coordinate(indexX, indexY);
+
+ } catch (NumberFormatException e) {
+ throw new ParseCoordinateException();
+ }
+ }
+
+ public int[] calculateOffsetFrom(Coordinate otherCoordinate) {
+ int offsetX = this.x - otherCoordinate.getX();
+ int offsetY = this.y - otherCoordinate.getY();
+ return new int[]{ offsetX, offsetY };
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s%d", BOARD_COLUMNS.charAt(x), (ChessBoard.SIZE - y));
+ }
+
+ //@@author onx001
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Coordinate) {
+ Coordinate other = (Coordinate) obj;
+ return x == other.x && y == other.y;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/chessmaster/game/Game.java b/src/main/java/chessmaster/game/Game.java
new file mode 100644
index 0000000000..37c96fdc7f
--- /dev/null
+++ b/src/main/java/chessmaster/game/Game.java
@@ -0,0 +1,222 @@
+//@@author TongZhengHong
+package chessmaster.game;
+
+import chessmaster.commands.ExitCommand;
+import chessmaster.commands.Command;
+import chessmaster.commands.CommandResult;
+import chessmaster.commands.HelpCommand;
+import chessmaster.commands.MoveCommand;
+import chessmaster.commands.RestartCommand;
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.game.move.Move;
+import chessmaster.parser.Parser;
+import chessmaster.storage.Storage;
+import chessmaster.ui.TextUI;
+import chessmaster.user.CPU;
+import chessmaster.user.Human;
+import chessmaster.user.Player;
+
+public class Game {
+
+ private static final String[] START_HELP_STRINGS = new String[HelpCommand.HELP_STRINGS.length + 1];
+
+ private CPU cpu;
+ private Human human;
+ private Player currentPlayer;
+ private int numMoves;
+
+ private TextUI ui;
+ private ChessBoard board;
+ private Storage storage;
+ private int difficulty;
+
+ private Command command;
+ private boolean hasEnded;
+
+ public Game(Color playerColour, Color currentTurnColor, ChessBoard board,
+ Storage storage, TextUI ui, int difficulty, Human human, CPU cpu) {
+
+ this.ui = ui;
+ this.board = board;
+ this.storage = storage;
+ this.difficulty = difficulty;
+
+ this.human = human;
+ Color cpuColor = playerColour.getOppositeColour();
+ this.cpu = cpu;
+
+ this.numMoves = human.getMovesLength() + cpu.getMovesLength();
+
+ // Choose which player goes first
+ currentPlayer = currentTurnColor == playerColour ? human : cpu;
+
+ // Make the START_HELP_STRINGS more robust with just one source-of-truth in HelpCommand.HELP_STRINGS
+ START_HELP_STRINGS[0] = "Thank you for choosing ChessMaster!";
+ System.arraycopy(HelpCommand.HELP_STRINGS, 0, START_HELP_STRINGS, 1, HelpCommand.HELP_STRINGS.length);
+
+ assert playerColour != Color.EMPTY : "Human player color should not be EMPTY!";
+ assert cpuColor != Color.EMPTY : "CPU player color should not be EMPTY!";
+ assert currentPlayer != null : "A player should always exist in a game!";
+ assert (1 <= difficulty) && (difficulty <= 3) : "Difficulty should be between 1 and 3!";
+ }
+
+
+ /**
+ * Manages the main gameplay of ChessMaster
+ * This code segment orchestrates the primary gameplay loop of Chess Master, which encompasses player turns,
+ * move handling, and the management of the game state.
+ * It starts by displaying the initial game setup, including the chessboard, and then enters a loop where players
+ * take turns making moves.
+ * If a player enters a valid move, the chessboard is updated, and the game progresses.
+ * If an exception is encountered during this process, an error message is displayed to the user.
+ * The loop continues until the game ends or specific commands are issued to abort, restart, or exit the game.
+ *
+ * @return true if the game has ended, either by checkmate, stalemate,
+ * or if users wants to reset the game. Returns false if the game is aborted.
+ */
+ public boolean run() {
+ ui.printText(START_HELP_STRINGS);
+ ui.printChessBoard(board.getBoard());
+
+ while (!hasEnded && !ExitCommand.isExit(command) && !RestartCommand.isRestart(command)) {
+ try {
+ assert currentPlayer.isCPU() || currentPlayer.isHuman() :
+ "Player should only either be human or CPU!";
+
+ if (currentPlayer.isHuman()) {
+ command = getAndExecuteUserCommand();
+ if (!command.isMoveCommand()) {
+ continue; // Get next command
+ }
+ Move playedMove = handleHumanMove();
+ ui.printChessBoardWithMove(board.getBoard(), playedMove);
+
+ } else if (currentPlayer.isCPU()) {
+ Move playedMove = handleCPUMove();
+ ui.printChessBoardWithMove(board.getBoard(), playedMove);
+ }
+
+ currentPlayer = togglePlayerTurn();
+ storage.saveBoard(board, currentPlayer.getColour(), human, cpu);
+ hasEnded = checkEndState(); // Resets board if end
+
+ } catch (ChessMasterException e) {
+ ui.printErrorMessage(e);
+ }
+ }
+ return hasEnded || RestartCommand.isRestart(command);
+ }
+
+ /**
+ * Represents main part of gameplay loop for the human user. Responsible for
+ * taking in the user's input and discerning the user's intention based on the input.
+ * Also executes and handles all commands except `MoveCommand`.
+ *
+ * @return Command corresponding to the user's input
+ * @throws ChessMasterException
+ */
+ private Command getAndExecuteUserCommand() throws ChessMasterException {
+ String userInputString = ui.getUserInput(true);
+ this.command = Parser.parseCommand(userInputString);
+
+ CommandResult result = command.execute(this);
+ ui.printCommandResult(result);
+ return this.command;
+ }
+
+ /**
+ * Handles the user input in the case that a move was made by the player.
+ * Extracts the move from the current command, and reflects the move on the chessboard.
+ * When the move is done, it also handles any possible promotions which can be made.
+ * Returns the move which has been made to be printed in the run() function.
+ *
+ * @return Move which has been executed
+ * @throws ChessMasterException
+ */
+ private Move handleHumanMove() throws ChessMasterException {
+ Move humanMove = ((MoveCommand) this.command).getMove();
+ board.executeMoveWithCheck(humanMove);
+ human.addMove(humanMove);
+ numMoves++;
+
+ // Handle human promotion
+ if (!board.isEndGame()) {
+ if (board.canPromote(humanMove)) {
+ human.handlePromote(board, ui, humanMove);
+ }
+ }
+
+ return humanMove;
+ }
+
+ /**
+ * Obtains and executes the CPU's move in response to the current board state.
+ * Also prints a message informing the player of the CPU thinking as it may take some time
+ * to compute the most optimal move in higher difficulty levels.
+ *
+ * @return The move that the CPU made
+ * @throws ChessMasterException
+ */
+ private Move handleCPUMove() throws ChessMasterException {
+ ui.printCPUThinkingMessage();
+
+ Move cpuMove = cpu.getBestMove(board, difficulty);
+ ui.printCPUMove(cpuMove);
+ board.executeMoveWithCheck(cpuMove);
+
+ cpu.addMove(cpuMove);
+ numMoves++;
+
+ return cpuMove;
+ }
+
+ /**
+ * Checks whether the current state of the board qualifies as the game having ended.
+ * If the game has ended, prints a message signalling the end of the game, and resets the saved board.
+ *
+ * @return Whether the game has ended
+ * @throws ChessMasterException
+ */
+ private boolean checkEndState() throws ChessMasterException {
+ boolean end = board.isEndGame();
+ if (end) {
+ Color winningColor = board.getWinningColor();
+ if (winningColor == Color.DRAW) {
+ ui.printDrawMessage();
+ } else {
+ Player winnerPlayer = human.getColour() == winningColor ? human : cpu;
+ ui.printEndMessage(winnerPlayer);
+ }
+ storage.resetBoard();
+ }
+ return end;
+ }
+
+ private Player togglePlayerTurn() {
+ return currentPlayer.isHuman() ? cpu : human;
+ }
+
+ public ChessBoard getBoard() {
+ return this.board;
+ }
+
+ public TextUI getUI() {
+ return this.ui;
+ }
+
+ public CPU getCPU() {
+ return this.cpu;
+ }
+
+ public Human getHuman() {
+ return this.human;
+ }
+
+ public int getNumMoves() {
+ return this.numMoves;
+ }
+
+ public Player getCurrentPlayer() {
+ return this.currentPlayer;
+ }
+}
diff --git a/src/main/java/chessmaster/game/MiniMax.java b/src/main/java/chessmaster/game/MiniMax.java
new file mode 100644
index 0000000000..c1bd9e762e
--- /dev/null
+++ b/src/main/java/chessmaster/game/MiniMax.java
@@ -0,0 +1,130 @@
+//@@author onx001
+package chessmaster.game;
+import chessmaster.game.move.Move;
+import chessmaster.pieces.ChessPiece;
+
+import chessmaster.exceptions.ChessMasterException;
+
+public class MiniMax {
+ protected int depth;
+ protected int maxDepth;
+ protected int score;
+ protected Move bestMove;
+ protected ChessBoard board;
+ protected Color color;
+ protected Color opponentColor;
+ protected BoardScoreTuple tuple;
+
+ //declares all variables needed for the minimax algorithm
+ public MiniMax(ChessBoard board, Color color, int maxDepth, int score) {
+ this.board = board;
+ this.color = color;
+ this.opponentColor = color.getOppositeColour();
+ this.maxDepth = maxDepth;
+ this.score = score;
+ this.tuple = new BoardScoreTuple(board, score, null);
+ }
+
+ /**
+ * returns the best move tuple for the current player
+ * @author onx001
+ * @param tuple the BoardScoreTuple to be weighed
+ * @param color the color of the current player
+ * @param depth the current depth of the minimax algorithm
+ * @param score the score of the current board
+ * @param isMax whether the current player is the CPU or the player
+ * @param maxDepth the maximum depth of the minimax algorithm
+ * @return the best move tuple for the current player
+ */
+ public static BoardScoreTuple mostPoints(BoardScoreTuple tuple, Color color, int depth,
+ int score, boolean isMax, int maxDepth) {
+
+ //gets all the moves for the current player
+ ChessBoard board = tuple.getBoard();
+ Color playerColor = isMax ? color : color.getOppositeColour();
+ Move[] moves = board.getLegalMoves(playerColor);
+ if (moves.length == 0) {
+ return tuple;
+ }
+ BoardScoreTuple[] boards = new BoardScoreTuple[moves.length];
+ int bestScore = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
+ BoardScoreTuple bestTuple = null;
+
+ //if the depth is the max depth, return the score of the board as base case
+ if (depth == maxDepth) {
+ int newscore = board.getPoints(color);
+ return new BoardScoreTuple(board,newscore, null);
+ }
+
+ //for each move, clone the board and execute the move as a possibility
+ boards = getBoards(moves, board, color);
+
+ //go through boards and find one with best points
+ for (int i = 0; i < boards.length; i++) {
+ BoardScoreTuple iterTuple = boards[i];
+ assert iterTuple != null : "iterTuple is null";
+ assert iterTuple.getMove() != null : "iterTuple move is null";
+
+ //recursively call mostPoints to find the best move for current board
+ BoardScoreTuple tuple1 = mostPoints(iterTuple, color, depth + 1, score, !isMax, maxDepth);
+
+ //Sets new score to current child score
+ int newScore = tuple1.getScore();
+ bestScore = updateScore(isMax, bestScore, newScore);
+
+ //set current tuple based on best child score
+ bestTuple = bestScore == newScore ? iterTuple : bestTuple;
+ }
+
+ return bestTuple;
+ }
+
+ /**
+ * Kicks off minimax algorithm and returns the best move for the current player
+ */
+ public Move getBestMove() {
+ BoardScoreTuple bestTuple = mostPoints(tuple, color, 0, score, true, maxDepth);
+ Move bestMove = bestTuple.getMove();
+ return bestMove;
+ }
+
+
+
+ //Helper function to get all the boards for the current player
+ private static BoardScoreTuple[] getBoards(Move[] moves, ChessBoard board, Color color) {
+ BoardScoreTuple[] boards = new BoardScoreTuple[moves.length];
+ for (int i = 0; i < moves.length; i++) {
+ ChessBoard newBoard = board.clone();
+ Move move = moves[i];
+ Coordinate from = move.getFrom();
+ ChessPiece piece = newBoard.getPieceAtCoor(from);
+ move.setPieceMoved(piece);
+ try {
+ newBoard.executeMove(move);
+ int newScore = newBoard.getPoints(color);
+ boards[i] = new BoardScoreTuple(newBoard, newScore, move);
+ } catch (ChessMasterException e) {
+ continue;
+ }
+ }
+ return boards;
+ }
+
+ //Helper function to update the best score
+ private static int updateScore(boolean isMax, int bestScore, int newScore) {
+ if (isMax) {
+ //maximises child score if CPU turn
+ if (newScore > bestScore) {
+ bestScore = newScore;
+ }
+ } else {
+ //minimises child score if player turn
+ if (newScore < bestScore) {
+ bestScore = newScore;
+ }
+ }
+ return bestScore;
+ }
+
+
+}
diff --git a/src/main/java/chessmaster/game/move/CastleMove.java b/src/main/java/chessmaster/game/move/CastleMove.java
new file mode 100644
index 0000000000..8225f445bd
--- /dev/null
+++ b/src/main/java/chessmaster/game/move/CastleMove.java
@@ -0,0 +1,58 @@
+package chessmaster.game.move;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Coordinate;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.King;
+
+import java.util.Arrays;
+
+public class CastleMove extends Move {
+
+ protected CastleSide side;
+ protected Move rookMove;
+
+ public CastleMove(Coordinate from, Coordinate to, ChessPiece pieceMoved, CastleSide side) {
+ super(from, to, pieceMoved);
+ this.side = side;
+
+ }
+
+ public static boolean checkIfLeftCastling(ChessPiece pieceMoved, Coordinate from, Coordinate to) {
+ if (!(pieceMoved instanceof King)) {
+ return false;
+ }
+
+ int[] offset = to.calculateOffsetFrom(from);
+ return Arrays.equals(offset, ChessPiece.CASTLE_LEFT);
+ }
+
+ public static boolean checkIfRightCastling(ChessPiece pieceMoved, Coordinate from, Coordinate to) {
+ if (!(pieceMoved instanceof King)) {
+ return false;
+ }
+
+ int[] offset = to.calculateOffsetFrom(from);
+ return Arrays.equals(offset, ChessPiece.CASTLE_RIGHT);
+ }
+
+ @Override
+ protected boolean isTryingToCastleUnderCheck(ChessBoard board) {
+ if (board.isChecked(this.getPieceMoved().getColor())) {
+ return true;
+ }
+ return false;
+ }
+
+ public CastleSide getSide() {
+ return side;
+ }
+
+ public Move getRookMove() {
+ return rookMove;
+ }
+
+ public void setRookMove(Move rookMove) {
+ this.rookMove = rookMove;
+ }
+}
diff --git a/src/main/java/chessmaster/game/move/CastleSide.java b/src/main/java/chessmaster/game/move/CastleSide.java
new file mode 100644
index 0000000000..932a9917f3
--- /dev/null
+++ b/src/main/java/chessmaster/game/move/CastleSide.java
@@ -0,0 +1,5 @@
+package chessmaster.game.move;
+
+public enum CastleSide {
+ RIGHT, LEFT
+}
diff --git a/src/main/java/chessmaster/game/move/EnPassantMove.java b/src/main/java/chessmaster/game/move/EnPassantMove.java
new file mode 100644
index 0000000000..e03e8198d3
--- /dev/null
+++ b/src/main/java/chessmaster/game/move/EnPassantMove.java
@@ -0,0 +1,13 @@
+package chessmaster.game.move;
+
+import chessmaster.game.Coordinate;
+import chessmaster.pieces.ChessPiece;
+
+public class EnPassantMove extends Move {
+
+ public EnPassantMove(Coordinate from, Coordinate to, ChessPiece pieceMoved, ChessPiece enPassantPiece) {
+ // enPassantPiece is the pieceCaptured
+ super(from, to, pieceMoved, enPassantPiece);
+ }
+
+}
diff --git a/src/main/java/chessmaster/game/move/Move.java b/src/main/java/chessmaster/game/move/Move.java
new file mode 100644
index 0000000000..2328f41588
--- /dev/null
+++ b/src/main/java/chessmaster/game/move/Move.java
@@ -0,0 +1,145 @@
+// @author TongZhengHong
+package chessmaster.game.move;
+
+import java.util.Arrays;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Coordinate;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.Pawn;
+
+public class Move {
+ protected Coordinate from;
+ protected Coordinate to;
+ protected ChessPiece pieceMoved; // if castling then pieceMoved instanceof King
+ protected ChessPiece pieceCaptured;
+ protected boolean hasCapturedAPiece;
+
+ public Move(Coordinate from, Coordinate to, ChessPiece pieceMoved) {
+ this.from = from;
+ this.to = to;
+ this.pieceMoved = pieceMoved;
+ this.pieceCaptured = null;
+ this.hasCapturedAPiece = false;
+
+ assert from != null && to != null : "Coordinates in Move should not be null!";
+ assert pieceMoved != null && !pieceMoved.isEmptyPiece() : "Chess piece in Move should not be null or empty!";
+ }
+
+ public Move(Coordinate from, Coordinate to, ChessPiece pieceMoved, ChessPiece pieceCaptured) {
+ this(from, to, pieceMoved);
+ this.pieceCaptured = pieceCaptured;
+ this.hasCapturedAPiece = true;
+ }
+
+ public Coordinate getFrom() {
+ return from;
+ }
+
+ public Coordinate getTo() {
+ return to;
+ }
+
+ public ChessPiece getPieceMoved() {
+ return pieceMoved;
+ }
+
+ public boolean hasCapturedAPiece() {
+ return this.hasCapturedAPiece;
+ }
+
+ public ChessPiece getPieceCaptured() {
+ return this.pieceCaptured;
+ }
+
+ public void setFrom(Coordinate from) {
+ this.from = from;
+ }
+
+ public void setTo(Coordinate to) {
+ this.to = to;
+ }
+
+ public void setPieceMoved(ChessPiece pieceMoved) {
+ this.pieceMoved = pieceMoved;
+ }
+
+
+ //@@author onx001
+ /**
+ * Checks if the move is valid by checking if the to coordinate is in the
+ * possibleCoordinates 2d array
+ * @param board
+ * @return
+ */
+ public boolean isValid(ChessBoard board) {
+ Coordinate[] coordinates = pieceMoved.getPseudoLegalCoordinates(board);
+ for (Coordinate coor : coordinates) {
+ if (coor.equals(to)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected boolean isTryingToCastleUnderCheck(ChessBoard board) {
+ return false; // Only CastleMove objects can castle!
+ }
+
+ public boolean isValidWithCheck(ChessBoard board) {
+ // Check if the current move is valid on the board
+ if (!this.isValid(board)) {
+ return false;
+ }
+
+ // Cannot castle while in check
+ if (isTryingToCastleUnderCheck(board)) {
+ return false;
+ }
+
+ // Attempt to make the move on a cloned board
+ ChessBoard boardCopy = board.clone();
+ ChessPiece pieceCopy = boardCopy.getPieceAtCoor(from);
+ Move moveCopy = new Move(from, to, pieceCopy);
+ try {
+ boardCopy.executeMove(moveCopy);
+ } catch (ChessMasterException e) {
+ return false;
+ }
+ boolean stillInCheckAfterMove = boardCopy.isChecked(this.getPieceMoved().getColor());
+
+ return !stillInCheckAfterMove;
+ }
+
+ public boolean isSkippingPawn() {
+ if (!(pieceMoved instanceof Pawn)) {
+ return false;
+ }
+
+ int[] offset = to.calculateOffsetFrom(from);
+ return Arrays.equals(offset, ChessPiece.UP_UP) || Arrays.equals(offset, ChessPiece.DOWN_DOWN);
+ }
+
+ @Override
+ public String toString() {
+ return "Move [from=" + from + ", to=" + to + ", piece=" + pieceMoved + "]";
+ }
+
+ public String toFileString() {
+ return from + " " + to;
+ }
+
+ // @author TongZhengHong
+ @Override
+ public boolean equals(Object obj) {
+ if (obj != null && obj instanceof Move) {
+ final Move other = (Move) obj;
+ boolean sameFrom = from.equals(other.getFrom());
+ boolean sameTo = to.equals(other.getTo());
+ boolean samePiece = pieceMoved.getPieceName().equals(other.getPieceMoved().getPieceName());
+ return sameFrom && sameTo && samePiece;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/chessmaster/game/move/MoveFactory.java b/src/main/java/chessmaster/game/move/MoveFactory.java
new file mode 100644
index 0000000000..22046237ef
--- /dev/null
+++ b/src/main/java/chessmaster/game/move/MoveFactory.java
@@ -0,0 +1,45 @@
+package chessmaster.game.move;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Coordinate;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.Pawn;
+
+public class MoveFactory {
+
+ public static Move createMove(ChessBoard board, Coordinate from, Coordinate to) {
+ ChessPiece pieceMoved = board.getPieceAtCoor(from);
+
+ // Check if the move is a castling move
+ if (CastleMove.checkIfLeftCastling(pieceMoved, from, to)) {
+ return new CastleMove(from, to, pieceMoved, CastleSide.LEFT);
+ } else if (CastleMove.checkIfRightCastling(pieceMoved, from, to)) {
+ return new CastleMove(from, to, pieceMoved, CastleSide.RIGHT);
+ }
+
+ // Check if move is an en passant move
+ //@@author onx001
+ if (pieceMoved instanceof Pawn && board.hasEnPassant()) {
+ Coordinate enPassantCoor = board.getEnPassantCoor();
+ if (to.equals(enPassantCoor)) {
+ ChessPiece enPassantPiece = board.getEnPassantPiece();
+ return new EnPassantMove(from, to, pieceMoved, enPassantPiece);
+ }
+ }
+
+ // Check if the move is a capturing move
+ Move move;
+ if (board.isTileOccupied(to)) {
+ ChessPiece pieceCaptured = board.getPieceAtCoor(to);
+ move = new Move(from, to, pieceMoved, pieceCaptured);
+ } else {
+ move = new Move(from, to, pieceMoved);
+ }
+ return move;
+ }
+
+ public static PromoteMove createPromoteMove(Coordinate coord, Pawn pawnPromoted, ChessPiece newPiece) {
+ return new PromoteMove(coord, pawnPromoted, newPiece);
+ }
+
+}
diff --git a/src/main/java/chessmaster/game/move/PromoteMove.java b/src/main/java/chessmaster/game/move/PromoteMove.java
new file mode 100644
index 0000000000..973151b899
--- /dev/null
+++ b/src/main/java/chessmaster/game/move/PromoteMove.java
@@ -0,0 +1,44 @@
+package chessmaster.game.move;
+
+import chessmaster.game.Coordinate;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.Pawn;
+
+/**
+ * Note: a promotion is technically done in two Move objects:
+ * Move 1. Move the pawn from 7th to 8th row or 2nd to 1st row
+ * Move 2. Promote the pawn
+ * This class represents Move #2.
+ */
+public class PromoteMove extends Move {
+ public static final String PROMOTE_MOVE_STRING = "p %s %s";
+
+ protected ChessPiece newPiece;
+ protected Pawn pawnPromoted;
+
+ public PromoteMove(Coordinate coord, Pawn pawnPromoted, ChessPiece newPiece) {
+ super(coord, coord, newPiece);
+ this.pawnPromoted = pawnPromoted;
+ this.newPiece = newPiece;
+ }
+
+ /**
+ * Converts the move to a format to be saved in the .txt file.
+ * Only called after the piece has been promoted to.
+ *
+ * @return String representing the move in the format of the .txt file
+ */
+ @Override
+ public String toFileString() {
+ String out = String.format(PROMOTE_MOVE_STRING, this.getTo(), this.getPieceMoved().toString());
+ return out;
+ }
+
+ public ChessPiece getNewPiece() {
+ return newPiece;
+ }
+
+ public Pawn getPawnPromoted() {
+ return pawnPromoted;
+ }
+}
diff --git a/src/main/java/chessmaster/parser/Parser.java b/src/main/java/chessmaster/parser/Parser.java
new file mode 100644
index 0000000000..e5a5bcf093
--- /dev/null
+++ b/src/main/java/chessmaster/parser/Parser.java
@@ -0,0 +1,221 @@
+package chessmaster.parser;
+
+import chessmaster.commands.Command;
+import chessmaster.commands.ExitCommand;
+import chessmaster.commands.HelpCommand;
+import chessmaster.commands.HistoryCommand;
+import chessmaster.commands.InvalidCommand;
+import chessmaster.commands.LegendCommand;
+import chessmaster.commands.MoveCommand;
+import chessmaster.commands.RestartCommand;
+import chessmaster.commands.RulesCommand;
+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;
+import chessmaster.exceptions.ParseCoordinateException;
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+import chessmaster.game.move.Move;
+import chessmaster.game.move.MoveFactory;
+import chessmaster.pieces.Bishop;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.EmptyPiece;
+import chessmaster.pieces.King;
+import chessmaster.pieces.Knight;
+import chessmaster.pieces.Pawn;
+import chessmaster.pieces.Queen;
+import chessmaster.pieces.Rook;
+
+public class Parser {
+
+ //@@author ken-ruster
+ /**
+ * Parses a string telling which chess piece the user wants to promote his piece
+ * to, and promotes the relevant piece
+ *
+ * @param promoteFrom Chess piece to be promoted.
+ * @param promoteTo String representing the type of piece to be promoted to.
+ * @return Promoted chess piece.
+ */
+ public static ChessPiece parsePromote(ChessPiece promoteFrom, String promoteTo) {
+ Color color = promoteFrom.getColor();
+ Coordinate position = promoteFrom.getPosition();
+
+ switch (promoteTo) {
+ case Bishop.BISHOP_WHITE:
+ return new Bishop(position.getY(), position.getX(), color);
+ case Queen.QUEEN_WHITE:
+ return new Queen(position.getY(), position.getX(), color);
+ case Knight.KNIGHT_WHITE:
+ return new Knight(position.getY(), position.getX(), color);
+ case Rook.ROOK_WHITE:
+ return new Rook(position.getY(), position.getX(), color);
+ default:
+ return promoteFrom;
+ }
+ }
+
+ //@@author TongZhengHong
+ /**
+ * Parses a chess move from user input and creates a Move object. Used to read
+ * user inputs during the chess game.
+ *
+ * @param in The user input string with 2 algebraic coordinate notations
+ * (e.g., "e2 e4").
+ * @param board The ChessBoard where the move is taking place.
+ * @return Move object containing information about the move to be made.
+ *
+ * @throws ParseCoordinateException If the string entered is not in the
+ * algebraic coordinate notation.
+ * @throws NullPieceException
+ * @throws MoveOpponentPieceException
+ */
+ public static Move parseMove(String in, ChessBoard board, boolean isPlayerTurn) throws ParseCoordinateException,
+ NullPieceException, MoveOpponentPieceException {
+
+ String[] parseArray = in.split("\\s+", 2);
+ if (parseArray.length < 2) {
+ throw new ParseCoordinateException();
+ }
+
+ Coordinate from = Coordinate.parseAlgebraicCoor(parseArray[0]);
+ Coordinate to = Coordinate.parseAlgebraicCoor(parseArray[1]);
+ ChessPiece pieceMoved = board.getPieceAtCoor(from);
+
+ if (pieceMoved.isEmptyPiece()) {
+ throw new NullPieceException(from);
+ } else if (isPlayerTurn && board.isPieceOpponent(pieceMoved)) {
+ throw new MoveOpponentPieceException();
+ }
+
+ return MoveFactory.createMove(board, from, to);
+ }
+
+ /**
+ * Parses an input string and creates a ChessPiece object at the specified row
+ * and column. Used for loading ChessPiece(s) from storage file or loading
+ * starting ChessBoard. Returns null for recognised input string to signify that
+ * piece is empty (for ChessTile)
+ *
+ * @param pieceString The string representation of the chess piece, e.g., "bB"
+ * for black bishop.
+ * @param row The row where the piece is located.
+ * @param col The column where the piece is located.
+ * @return A ChessPiece object representing the parsed chess piece, or null if
+ * the pieceString is not recognized.
+ */
+ public static ChessPiece parseChessPiece(String pieceString, int row, int col) {
+ switch (pieceString) {
+ case Bishop.BISHOP_BLACK:
+ return new Bishop(row, col, Color.BLACK);
+ case Bishop.BISHOP_WHITE:
+ return new Bishop(row, col, Color.WHITE);
+ case King.KING_BLACK:
+ return new King(row, col, Color.BLACK);
+ case King.KING_WHITE:
+ return new King(row, col, Color.WHITE);
+ case Queen.QUEEN_BLACK:
+ return new Queen(row, col, Color.BLACK);
+ case Queen.QUEEN_WHITE:
+ return new Queen(row, col, Color.WHITE);
+ case Knight.KNIGHT_BLACK:
+ return new Knight(row, col, Color.BLACK);
+ case Knight.KNIGHT_WHITE:
+ return new Knight(row, col, Color.WHITE);
+ case Pawn.PAWN_BLACK:
+ return new Pawn(row, col, Color.BLACK);
+ case Pawn.PAWN_WHITE:
+ return new Pawn(row, col, Color.WHITE);
+ case Rook.ROOK_BLACK:
+ return new Rook(row, col, Color.BLACK);
+ case Rook.ROOK_WHITE:
+ return new Rook(row, col, Color.WHITE);
+ default:
+ return new EmptyPiece(row, col);
+ }
+ }
+
+ //@@author
+
+ /**
+ * Parses the user's input to return the appropriate Command.
+ * Returns an InvalidCommand if the input does not match any of the expected patterns.
+ * Used to discern the program's appropriate response to the user's input.
+ *
+ * @param in User's input, stored as a String
+ * @return A command corresponding to user input, or InvalidCommand if the input is not recognised
+ */
+
+ public static Command parseCommand(String in) {
+ String[] splitInputStrings = in.split("\\s+", 2);
+ String commandString = splitInputStrings[0];
+ String payload = splitInputStrings.length > 1 ? splitInputStrings[1] : ""; // Remaining input text
+
+ switch (commandString) {
+ case MoveCommand.MOVE_COMMAND_STRING:
+ return new MoveCommand(payload);
+ case ShowMovesCommand.SHOW_MOVES_COMMAND_STRING:
+ return new ShowMovesCommand(payload);
+ case ShowCommand.SHOW_COMMAND_STRING:
+ return new ShowCommand();
+ case RulesCommand.RULES_COMMAND_STRING:
+ return new RulesCommand();
+ case HelpCommand.HELP_COMMAND_STRING:
+ return new HelpCommand();
+ case LegendCommand.LEGEND_COMMAND_STRING:
+ return new LegendCommand();
+ case ExitCommand.EXIT_COMMAND_STRING:
+ return new ExitCommand();
+ case RestartCommand.RESTART_COMMAND_STRING:
+ return new RestartCommand();
+ case HistoryCommand.HISTORY_COMMAND_STRING:
+ return new HistoryCommand();
+ case StepbackCommand.STEPBACK_COMMAND_STRING:
+ return new StepbackCommand(payload);
+ case CapturedCommand.CAPTURED_COMMAND_STRING:
+ return new CapturedCommand();
+ default:
+ return new InvalidCommand();
+ }
+ }
+
+ //@@author TongZhengHong
+ /**
+ * Parses a player's color from a provided string and returns the corresponding Color enumeration.
+ *
+ * This method takes an input color string and converts it into the appropriate Color enumeration value,
+ * which can be either 'WHITE' or 'BLACK'.
+ *
+ * It ensures that the provided color is valid and not 'EMPTY' since a player color can only be black or white.
+ * If the input does not match any valid color, a ParseColorException is thrown.
+ *
+ * @param inputColorString A string representing the player's color ('WHITE' or 'BLACK').
+ * @return The Color enumeration corresponding to the parsed color.
+ * @throws ParseColorException If the input color is not valid or if it is 'EMPTY'.
+ */
+ public static Color parsePlayerColor(String inputColorString) throws ParseColorException {
+ try {
+ Color color = Color.valueOf(inputColorString);
+ if (color.isEmpty()) {
+ throw new ParseColorException();
+ }
+ return color;
+ } catch (IllegalArgumentException e) {
+ throw new ParseColorException();
+ }
+ }
+
+ //@@author onx001
+ public static int parseDifficulty(String inputDifficultyString) throws NumberFormatException {
+ try {
+ return Integer.parseInt(inputDifficultyString);
+ } catch (NumberFormatException e) {
+ throw new NumberFormatException();
+ }
+ }
+}
diff --git a/src/main/java/chessmaster/pieces/Bishop.java b/src/main/java/chessmaster/pieces/Bishop.java
new file mode 100644
index 0000000000..3b74ee9e52
--- /dev/null
+++ b/src/main/java/chessmaster/pieces/Bishop.java
@@ -0,0 +1,78 @@
+package chessmaster.pieces;
+
+import java.util.ArrayList;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+
+public class Bishop extends ChessPiece {
+ public static final String BISHOP_WHITE = "b"; // ♗
+ public static final String BISHOP_BLACK = "B"; // ♝
+
+ public static final int[][] DIRECTIONS = {
+ UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT,
+ };
+
+ protected static int points = 30;
+ protected static int[][] boardWeight =
+ {{-2,-1,-1,-1,-1,-1,-1,-2},
+ {-1,0,0,0,0,0,0,-1},
+ {-1,0,1,1,1,1,0,-1},
+ {-1,0,1,1,1,1,0,-1},
+ {-1,0,1,1,1,1,0,-1},
+ {-1,0,1,1,1,1,0,-1},
+ {-1,0,0,0,0,0,0,-1},
+ {-2,-1,-1,-1,-1,-1,-1,-2}};
+
+ public Bishop(int row, int col, Color color) {
+ super(row, col, color);
+ this.setPoints(points);
+ this.setBoardWeight(boardWeight);
+ assert color != Color.EMPTY : "Bishop piece should have either black or white color";
+ }
+
+ /**
+ * Returns available coordinates in multiple diagonal directions from the current position.
+ * @param board the current board
+ * @return available coordinates in a 2D array. The first index is of the direction and the second
+ * is of the coordinates in that direction.
+ */
+ @Override
+ public Coordinate[] getPseudoLegalCoordinates(ChessBoard board) {
+ Coordinate[][] result = new Coordinate[DIRECTIONS.length][0];
+
+ for (int dir = 0; dir < DIRECTIONS.length; dir++) {
+ int offsetX = DIRECTIONS[dir][0];
+ int offsetY = DIRECTIONS[dir][1];
+
+ int multiplier = 1;
+ boolean isBlocked = false;
+ ArrayList possibleCoordInDirection = new ArrayList<>();
+
+ while (!isBlocked && multiplier < ChessBoard.SIZE && position.isOffsetWithinBoard(offsetX, offsetY)) {
+ Coordinate possibleCoord = position.addOffsetToCoordinate(offsetX, offsetY);
+ ChessPiece destPiece = board.getPieceAtCoor(possibleCoord);
+
+ isBlocked = !destPiece.isEmptyPiece();
+ if (destPiece.isEmptyPiece() || isOpponent(destPiece)) {
+ possibleCoordInDirection.add(possibleCoord);
+ }
+
+ multiplier++;
+ offsetX = DIRECTIONS[dir][0] * multiplier;
+ offsetY = DIRECTIONS[dir][1] * multiplier;
+ }
+
+ // Convert arraylist to array
+ result[dir] = possibleCoordInDirection.toArray(new Coordinate[0]);
+ }
+
+ return flattenArray(result);
+ }
+
+ @Override
+ public String toString() {
+ return color == Color.BLACK ? BISHOP_BLACK : BISHOP_WHITE;
+ }
+}
diff --git a/src/main/java/chessmaster/pieces/ChessPiece.java b/src/main/java/chessmaster/pieces/ChessPiece.java
new file mode 100644
index 0000000000..d5edc960f0
--- /dev/null
+++ b/src/main/java/chessmaster/pieces/ChessPiece.java
@@ -0,0 +1,320 @@
+package chessmaster.pieces;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+import chessmaster.game.move.Move;
+
+import java.util.ArrayList;
+
+public abstract class ChessPiece {
+
+ public static final int[] CASTLE_LEFT = {-2, 0};
+ public static final int[] CASTLE_RIGHT = {2, 0};
+ public static final int[] UP_UP = {0, -2};
+ public static final int[] DOWN_DOWN = {0, 2};
+
+ protected static final int[] UP_UP_LEFT = {1, -2};
+ protected static final int[] UP_UP_RIGHT = {-1, -2};
+ protected static final int[] DOWN_DOWN_LEFT = {1, 2};
+ protected static final int[] DOWN_DOWN_RIGHT = {-1, 2};
+ protected static final int[] LEFT_UP_LEFT = {2, -1};
+ protected static final int[] LEFT_DOWN_LEFT = {2, 1};
+ protected static final int[] RIGHT_UP_RIGHT = {-2, -1};
+ protected static final int[] RIGHT_DOWN_RIGHT = {-2, 1};
+
+ protected static final int[] UP = {0, -1};
+ protected static final int[] DOWN = {0, 1};
+ protected static final int[] LEFT = {1, 0};
+ protected static final int[] RIGHT = {-1, 0};
+
+ protected static final int[] UP_LEFT = {1, -1};
+ protected static final int[] UP_RIGHT = {-1, -1};
+ protected static final int[] DOWN_LEFT = {1, 1};
+ protected static final int[] DOWN_RIGHT = {-1, 1};
+
+ protected static final String NO_AVAILABLE_MOVES_STRING =
+ "There aren't any moves available for %s at %s!";
+ protected static final String AVAILABLE_MOVES_STRING =
+ "Available coordinates for %s at %s: ";
+
+ protected Color color;
+ protected Coordinate position;
+ protected boolean hasMoved = false;
+ protected boolean isCaptured = false;
+ protected boolean isEnPassant = false;
+ protected int points = 0;
+
+
+ //initialise empty boardweights of 0 for parent class to be used for the AI
+ private int[][] boardWeight =
+ {{0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0}};
+
+ public ChessPiece(int row, int col, Color color) {
+ this.position = new Coordinate(col, row);
+ this.color = color;
+ }
+
+ //@@author ken-ruster
+ public boolean isSameAs(ChessPiece other) {
+ return (this.toString().equals(other.toString()));
+ }
+ /**
+ * Gets an array of pseudo-legal coordinates for potential moves.
+ *
+ * This abstract method is meant to be implemented by subclasses to provide an array of pseudo-legal
+ * coordinates representing potential moves for a specific chess piece. The coordinates are based on
+ * the piece's movement rules and the current state of the chessboard.
+ *
+ * @param board The current state of the chessboard.
+ * @return An array of Coordinate objects, each representing a pseudo-legal move or destination for the chess piece.
+ */
+ public abstract Coordinate[] getPseudoLegalCoordinates(ChessBoard board);
+
+ /**
+ * Flatten a 2D array of coordinates into a 1D array.
+ *
+ * This method takes a 2D array of coordinates, representing various directions,
+ * and flattens it into a 1D array for easy access and processing.
+ *
+ * @param coordInDirections A 2D array of coordinates to be flattened.
+ * @return A 1D array of coordinates, combining all coordinates from the input directions.
+ */
+ public Coordinate[] flattenArray(Coordinate[][] coordInDirections) {
+ ArrayList flattenedCoordinates = new ArrayList<>();
+ for (Coordinate[] direction : coordInDirections) {
+ for (Coordinate coordinate : direction) {
+ flattenedCoordinates.add(coordinate);
+ }
+ }
+ return flattenedCoordinates.toArray(new Coordinate[0]);
+ }
+
+ //@@author onx001
+ /**
+ * Gets an array of legal coordinates representing potential moves that adhere to game rules.
+ *
+ * This method calculates and provides an array of legal coordinates, which represent potential
+ * moves for the chess piece, based on its movement rules and the current state of the chessboard.
+ * These coordinates are filtered to ensure they adhere to the game rules, including preventing
+ * moves that result in the player's own king being in check.
+ *
+ * @param board The current state of the chessboard.
+ * @return An array of Coordinate objects, each representing a legal move or destination for the chess piece.
+ */
+ public Coordinate[] getLegalCoordinates(ChessBoard board) {
+ Coordinate[] pseudoLegalCoordinates = getPseudoLegalCoordinates(board);
+ Move[] allLegalMoves = board.getLegalMoves(this.color);
+
+ ArrayList result = new ArrayList<>();
+ for (Move legalMove : allLegalMoves) {
+ for (Coordinate destCoor : pseudoLegalCoordinates) {
+ Move pseudoLegalMove = new Move(this.position, destCoor, this);
+ if (pseudoLegalMove.equals(legalMove)) {
+ result.add(destCoor);
+ }
+ }
+ }
+
+ return result.toArray(new Coordinate[0]);
+ }
+ //@@author
+
+ public boolean isWhiteKing() {
+ return this instanceof King && this.isWhite();
+ }
+
+ public boolean isBlackKing() {
+ return this instanceof King && this.isBlack();
+ }
+
+ public void clearEnPassant() {
+ this.isEnPassant = false;
+ }
+
+ public boolean isEnPassant() {
+ return this.isEnPassant;
+ }
+
+ public void setEnPassant() {
+ this.isEnPassant = true;
+ }
+
+ //@@author ken-ruster
+ /**
+ * Convert all the possible moves of the piece into a String.
+ * Returns a message saying that there are no moves available if there are no legal moves the piece can make.
+ *
+ * @param board Board the piece is located on
+ * @return A String containing the coordinates the piece is able to move to
+ */
+ public String[] getAvailableCoordinatesString(ChessBoard board) {
+ StringBuilder out = new StringBuilder();
+ Coordinate[] legalCoordinates = getLegalCoordinates(board);
+
+ if (legalCoordinates.length == 0) {
+ return new String[] {
+ String.format(NO_AVAILABLE_MOVES_STRING, getPieceName(), this.position)
+ };
+ }
+
+ for (Coordinate possibleCoord : legalCoordinates) {
+ out.append(possibleCoord + " ");
+ }
+
+ return new String[] {
+ String.format(AVAILABLE_MOVES_STRING, getPieceName(), this.position),
+ out.toString()
+ };
+ }
+ //@@author onx001
+
+ public Coordinate getPosition() {
+ return this.position;
+ }
+
+ public void updatePosition(Coordinate newCoordinate) {
+ this.position = newCoordinate;
+ }
+
+ public void setHasMoved() {
+ this.hasMoved = true;
+ }
+
+ public boolean getHasMoved() {
+ return this.hasMoved;
+ }
+
+ @Override
+ public String toString() {
+ return "ChessPiece [color=" + color + ", position=" + position + "]";
+ }
+
+ public Color getColor() {
+ return color;
+ }
+
+ public boolean getIsCaptured() {
+ return this.isCaptured;
+ }
+
+ public void setIsCaptured() {
+ this.isCaptured = true;
+ }
+
+ /**
+ * Returns the points of the ChessPiece object.
+ * The points are calculated based on the ChessPiece's position
+ * @param isUpright Whether the chess board is aligned to the player it is processed for.
+ * @return The points of the ChessPiece object.
+ */
+ public int getPoints(boolean isUpright) {
+ int boardPoints;
+ if (isUpright) {
+ //finds board weight points of a friendly piece
+ boardPoints = boardWeight[position.getX()][position.getY()];
+ } else {
+ //finds board weight points of an opponent piece
+ boardPoints = boardWeight[7 - position.getX()][position.getY()];
+ }
+ //adds the board weight points to the piece's points
+ int points = this.points + boardPoints;
+ return points;
+ }
+ //@@author
+
+ /**
+ * Checks if the ChessPiece object has the same color as a given color.
+ *
+ * @param color The color to compare with the ChessPiece's color.
+ * @return true if the ChessPiece has the same color as the provided color; false otherwise.
+ */
+ public boolean isSameColorAs(Color color) {
+ if (isEmptyPiece()) {
+ return false;
+ }
+ return this.color == color;
+ }
+
+ /**
+ * Checks if the ChessPiece object is WHTIE.
+ *
+ * @return true if the ChessPiece is white; false otherwise.
+ */
+ public boolean isWhite() {
+ if (isEmptyPiece()) {
+ return false;
+ }
+ return this.color == Color.WHITE;
+ }
+
+ /**
+ * Checks if the ChessPiece object is BLACK.
+ *
+ * @return true if the ChessPiece is white; false otherwise.
+ */
+ public boolean isBlack() {
+ if (isEmptyPiece()) {
+ return false;
+ }
+ return this.color == Color.BLACK;
+ }
+
+ /**
+ * Checks if the provided ChessPiece object is friendly (has the same color) as the current ChessPiece.
+ *
+ * @param chessPiece The ChessPiece to compare with.
+ * @return true if the provided ChessPiece is friendly; false otherwise.
+ */
+ public boolean isFriendly(ChessPiece chessPiece) {
+ if (isEmptyPiece()) {
+ return false;
+ }
+ return chessPiece.color == this.color;
+ }
+
+ /**
+ * Checks if the provided ChessPiece is an opponent (has a different color) compared to the current ChessPiece.
+ *
+ * @param chessPiece The ChessPiece to compare with.
+ * @return true if the provided ChessPiece is an opponent; false otherwise.
+ */
+ public boolean isOpponent(ChessPiece chessPiece) {
+ if (isEmptyPiece()) {
+ return false;
+ }
+ return chessPiece.color != this.color;
+ }
+
+ public boolean isEmptyPiece() {
+ return this instanceof EmptyPiece;
+ }
+
+ public boolean isPawn() {
+ return this instanceof Pawn;
+ }
+
+ public boolean isPromotionPiece() {
+ return this instanceof Queen || this instanceof Rook
+ || this instanceof Bishop || this instanceof Knight;
+ }
+
+ public String getPieceName() {
+ return this.getClass().getSimpleName();
+ }
+
+ protected void setPoints(int points) {
+ this.points = points;
+ }
+
+ protected void setBoardWeight(int[][] boardWeight) {
+ this.boardWeight = boardWeight;
+ }
+}
diff --git a/src/main/java/chessmaster/pieces/EmptyPiece.java b/src/main/java/chessmaster/pieces/EmptyPiece.java
new file mode 100644
index 0000000000..95920fa9fc
--- /dev/null
+++ b/src/main/java/chessmaster/pieces/EmptyPiece.java
@@ -0,0 +1,31 @@
+package chessmaster.pieces;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+
+public class EmptyPiece extends ChessPiece {
+
+ public static final String EMPTY_PIECE = ".";
+
+ public EmptyPiece(int row, int col) {
+ super(row, col, Color.EMPTY);
+ }
+
+ @Override
+ public Coordinate[] getPseudoLegalCoordinates(ChessBoard board) {
+ return new Coordinate[0];
+ }
+
+ // An empty piece will never be moved
+ @Override
+ public boolean getHasMoved() {
+ return false;
+ }
+
+ //returns the string representation of the empty piece for cloning
+ @Override
+ public String toString() {
+ return EMPTY_PIECE;
+ }
+}
diff --git a/src/main/java/chessmaster/pieces/King.java b/src/main/java/chessmaster/pieces/King.java
new file mode 100644
index 0000000000..f421aaaddc
--- /dev/null
+++ b/src/main/java/chessmaster/pieces/King.java
@@ -0,0 +1,93 @@
+package chessmaster.pieces;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+
+public class King extends ChessPiece {
+ public static final String KING_WHITE = "k"; // ♔
+ public static final String KING_BLACK = "K"; // ♚
+ public static final int POINTS = 1000;
+ public static final int[][] BOARDWEIGHT =
+ {{-3,-4,-4,-5,-5,-4,-4,-3},
+ {-3,-4,-4,-5,-5,-4,-4,-3},
+ {-3,-4,-4,-5,-5,-4,-4,-3},
+ {-3,-4,-4,-5,-5,-4,-4,-3},
+ {-2,-3,-3,-4,-4,-3,-3,-2},
+ {-1,-2,-2,-2,-2,-2,-2,-1},
+ {2,2,0,0,0,0,2,2},
+ {2,3,1,0,0,1,3,2}};
+
+
+ public static final int[][] DIRECTIONS = {
+ UP, DOWN, LEFT, RIGHT, UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT, CASTLE_LEFT, CASTLE_RIGHT
+ };
+
+ protected static int points = 1000;
+
+ public King(int row, int col, Color color) {
+ super(row, col, color);
+ this.setPoints(points);
+ this.setBoardWeight(BOARDWEIGHT);
+ assert color != Color.EMPTY : "King piece should have either black or white color";
+ }
+
+ @Override
+ public Coordinate[] getPseudoLegalCoordinates(ChessBoard board) {
+ Coordinate[][] result = new Coordinate[DIRECTIONS.length][0];
+
+ for (int dir = 0; dir < DIRECTIONS.length; dir++) {
+ int offsetX = DIRECTIONS[dir][0];
+ int offsetY = DIRECTIONS[dir][1];
+
+ if (!position.isOffsetWithinBoard(offsetX, offsetY)) {
+ continue; // Possible coordinate out of board
+ }
+
+ Coordinate newCoor = position.addOffsetToCoordinate(offsetX, offsetY);
+ ChessPiece destPiece = board.getPieceAtCoor(newCoor);
+
+ if (DIRECTIONS[dir] == CASTLE_LEFT) {
+ Coordinate pos1 = position.addOffsetToCoordinate(-1, 0);
+ Coordinate pos2 = position.addOffsetToCoordinate(-2, 0);
+ Coordinate pos3 = position.addOffsetToCoordinate(-3, 0);
+ Coordinate rookPos = position.addOffsetToCoordinate(-4, 0);
+
+ boolean hasRookMoved = board.getPieceAtCoor(rookPos).hasMoved;
+ boolean isSidesEmpty = board.getPieceAtCoor(pos1).isEmptyPiece()
+ && board.getPieceAtCoor(pos2).isEmptyPiece()
+ && board.getPieceAtCoor(pos3).isEmptyPiece();
+
+ if (isSidesEmpty && !hasRookMoved && !hasMoved) {
+ result[dir] = new Coordinate[]{ newCoor };
+ }
+
+ } else if (DIRECTIONS[dir] == CASTLE_RIGHT) {
+ Coordinate pos1 = position.addOffsetToCoordinate(+1, 0);
+ Coordinate pos2 = position.addOffsetToCoordinate(+2, 0);
+ Coordinate rookPos = position.addOffsetToCoordinate(+3, 0);
+
+ boolean hasRookMoved = board.getPieceAtCoor(rookPos).hasMoved;
+ boolean isSidesEmpty = board.getPieceAtCoor(pos1).isEmptyPiece()
+ && board.getPieceAtCoor(pos2).isEmptyPiece();
+
+ if (isSidesEmpty && !hasRookMoved && !hasMoved) {
+ result[dir] = new Coordinate[]{ newCoor };
+ }
+
+ } else { // Normal or capture move
+ if (destPiece.isEmptyPiece() || isOpponent(destPiece)) {
+ result[dir] = new Coordinate[] { position.addOffsetToCoordinate(offsetX, offsetY) };
+ }
+ }
+ }
+
+ return flattenArray(result);
+ }
+
+ @Override
+ public String toString() {
+ return color == Color.BLACK ? KING_BLACK : KING_WHITE;
+ }
+
+}
diff --git a/src/main/java/chessmaster/pieces/Knight.java b/src/main/java/chessmaster/pieces/Knight.java
new file mode 100644
index 0000000000..ae1457660c
--- /dev/null
+++ b/src/main/java/chessmaster/pieces/Knight.java
@@ -0,0 +1,62 @@
+package chessmaster.pieces;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+
+public class Knight extends ChessPiece {
+ public static final String KNIGHT_WHITE = "n"; // ♘
+ public static final String KNIGHT_BLACK = "N"; // ♞
+
+ public static final int[][] DIRECTIONS = {
+ UP_UP_LEFT, UP_UP_RIGHT, DOWN_DOWN_LEFT, DOWN_DOWN_RIGHT,
+ LEFT_UP_LEFT, LEFT_DOWN_LEFT, RIGHT_UP_RIGHT, RIGHT_DOWN_RIGHT,
+ };
+
+ protected static int points = 30;
+ protected static int[][] boardWeight =
+ {{-5,-4,-3,-3,-3,-3,-4,-5},
+ {-4,-2,0,0,0,0,-2,-4},
+ {-3,0,1,2,2,1,0,-3},
+ {-3,1,2,3,3,2,1,-3},
+ {-3,0,2,3,3,2,0,-3},
+ {-3,1,1,2,2,1,1,-3},
+ {-4,-2,0,1,1,0,-2,-4},
+ {-5,-4,-3,-3,-3,-3,-4,-5}};
+
+ public Knight(int row, int col, Color color) {
+ super(row, col, color);
+ this.setPoints(points);
+ this.setBoardWeight(boardWeight);
+ assert color != Color.EMPTY : "Knight piece should have either black or white color";
+ }
+
+ @Override
+ public String toString() {
+ return color == Color.BLACK ? KNIGHT_BLACK : KNIGHT_WHITE;
+ }
+
+ @Override
+ public Coordinate[] getPseudoLegalCoordinates(ChessBoard board) {
+ Coordinate[][] result = new Coordinate[DIRECTIONS.length][0];
+
+ for (int dir = 0; dir < DIRECTIONS.length; dir++) {
+ int offsetX = DIRECTIONS[dir][0];
+ int offsetY = DIRECTIONS[dir][1];
+
+ if (!position.isOffsetWithinBoard(offsetX, offsetY)) {
+ continue; // Possible coordinate out of board
+ }
+
+ Coordinate newCoor = position.addOffsetToCoordinate(offsetX, offsetY);
+ ChessPiece destPiece = board.getPieceAtCoor(newCoor);
+
+ if (destPiece.isEmptyPiece() || isOpponent(destPiece)) {
+ result[dir] = new Coordinate[]{ newCoor };
+ }
+ }
+
+ return flattenArray(result);
+ }
+
+}
diff --git a/src/main/java/chessmaster/pieces/Pawn.java b/src/main/java/chessmaster/pieces/Pawn.java
new file mode 100644
index 0000000000..c6e88e0a22
--- /dev/null
+++ b/src/main/java/chessmaster/pieces/Pawn.java
@@ -0,0 +1,102 @@
+package chessmaster.pieces;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+
+public class Pawn extends ChessPiece {
+ public static final String PAWN_WHITE = "p"; // ♙
+ public static final String PAWN_BLACK = "P"; // ♟
+
+ public static final int[][] DIRECTIONS_UP = {
+ UP_LEFT, UP_RIGHT, UP, UP_UP,
+ };
+ public static final int[][] DIRECTIONS_DOWN = {
+ DOWN_LEFT, DOWN_RIGHT, DOWN, DOWN_DOWN,
+ };
+
+ protected static int points = 10;
+ protected static int[][] boardWeight =
+ {{0,0,0,0,0,0,0,0},
+ {5,5,5,5,5,5,5,5},
+ {1,1,2,3,3,2,1,1},
+ {1,1,1,4,4,1,1,1},
+ {0,0,0,3,3,0,0,0},
+ {1,-1,-1,0,0,-1,-1,1},
+ {1,1,1,-2,-2,1,1,1},
+ {0,0,0,0,0,0,0,0}};
+ protected boolean enPassed = false;
+
+ public Pawn(int row, int col, Color color) {
+ super(row, col, color);
+ this.setPoints(points);
+ this.setBoardWeight(boardWeight);
+ assert color != Color.EMPTY : "Pawn piece should have either black or white color";
+ }
+
+ @Override
+ public Coordinate[] getPseudoLegalCoordinates(ChessBoard board) {
+ Coordinate[][] result = new Coordinate[DIRECTIONS_UP.length][0];
+ int[][] directions = board.isPieceFriendly(this) ? DIRECTIONS_UP : DIRECTIONS_DOWN;
+
+ boolean canEnPassant = false;
+ Coordinate enPassantCoor = null;
+
+ for (int dir = 0; dir < DIRECTIONS_UP.length; dir++) {
+ int offsetX = directions[dir][0];
+ int offsetY = directions[dir][1];
+
+ if (!position.isOffsetWithinBoard(offsetX, offsetY)) {
+ continue; // Possible coordinate out of board
+ }
+
+ Coordinate newCoor = position.addOffsetToCoordinate(offsetX, offsetY);
+ ChessPiece destPiece = board.getPieceAtCoor(newCoor);
+ boolean isThisPlayerEat = directions[dir] == UP_LEFT || directions[dir] == UP_RIGHT;
+ boolean isOpponentEat = directions[dir] == DOWN_LEFT || directions[dir] == DOWN_RIGHT;
+
+ if (isThisPlayerEat || isOpponentEat) {
+
+ if (board.hasEnPassant()) {
+ enPassantCoor = board.getEnPassantCoor();
+ ChessPiece enPassantPiece = board.getEnPassantPiece();
+ canEnPassant = newCoor.equals(enPassantCoor) && isOpponent(enPassantPiece);
+ }
+
+
+
+ // Diagonal move: Destination tile has opponent piece
+ if ( (!destPiece.isEmptyPiece() && isOpponent(destPiece)) || canEnPassant) {
+ result[dir] = new Coordinate[]{ newCoor };
+ }
+
+
+
+ } else if (directions[dir] == UP || directions[dir] == DOWN) {
+ // Normal move: when destination tile is empty
+ if (destPiece.isEmptyPiece()) {
+ result[dir] = new Coordinate[]{ newCoor };
+ }
+
+ } else if (directions[dir] == UP_UP || directions[dir] == DOWN_DOWN) {
+ // Double move: first move AND when destination empty AND no blocking piece
+ Coordinate blockPos = board.isPieceFriendly(this)
+ ? position.addOffsetToCoordinate(UP[0], UP[1])
+ : position.addOffsetToCoordinate(DOWN[0], DOWN[1]);
+ ChessPiece blockPiece = board.getPieceAtCoor(blockPos);
+
+ if (!hasMoved && blockPiece.isEmptyPiece() && destPiece.isEmptyPiece()) {
+ result[dir] = new Coordinate[]{ newCoor };
+ }
+
+ }
+ }
+
+ return flattenArray(result);
+ }
+
+ @Override
+ public String toString() {
+ return color == Color.BLACK ? PAWN_BLACK : PAWN_WHITE;
+ }
+}
diff --git a/src/main/java/chessmaster/pieces/Queen.java b/src/main/java/chessmaster/pieces/Queen.java
new file mode 100644
index 0000000000..ac6513e066
--- /dev/null
+++ b/src/main/java/chessmaster/pieces/Queen.java
@@ -0,0 +1,73 @@
+package chessmaster.pieces;
+
+import java.util.ArrayList;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+
+public class Queen extends ChessPiece {
+ public static final String QUEEN_WHITE = "q"; // ♕
+ public static final String QUEEN_BLACK = "Q"; // ♛
+
+ public static final int[][] DIRECTIONS = {
+ UP, DOWN, LEFT, RIGHT, UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT,
+ };
+
+ protected static int points = 90;
+ protected static int[][] boardWeight =
+ {{-2,-1,-1,-1,-1,-1,-1,-2},
+ {-1,0,0,0,0,0,0,-1},
+ {-1,0,1,1,1,1,0,-1},
+ {-1,0,1,1,1,1,0,-1},
+ {-1,0,1,1,1,1,0,-1},
+ {-1,0,1,1,1,1,0,-1},
+ {-1,0,0,0,0,0,0,-1},
+ {-2,-1,-1,-1,-1,-1,-1,-2}};
+
+ public Queen(int row, int col, Color color) {
+ super(row, col, color);
+ this.setPoints(points);
+ this.setBoardWeight(boardWeight);
+ assert color != Color.EMPTY : "Queen piece should have either black or white color";
+ }
+
+ @Override
+ public Coordinate[] getPseudoLegalCoordinates(ChessBoard board) {
+ Coordinate[][] result = new Coordinate[DIRECTIONS.length][0];
+
+ for (int dir = 0; dir < DIRECTIONS.length; dir++) {
+ int offsetX = DIRECTIONS[dir][0];
+ int offsetY = DIRECTIONS[dir][1];
+
+ int multiplier = 1;
+ boolean isBlocked = false;
+ ArrayList possibleCoordInDirection = new ArrayList<>();
+
+ while (!isBlocked && multiplier < ChessBoard.SIZE && position.isOffsetWithinBoard(offsetX, offsetY)) {
+
+ Coordinate possibleCoord = position.addOffsetToCoordinate(offsetX, offsetY);
+ ChessPiece destPiece = board.getPieceAtCoor(possibleCoord);
+
+ isBlocked = !destPiece.isEmptyPiece();
+ if (destPiece.isEmptyPiece() || isOpponent(destPiece)) {
+ possibleCoordInDirection.add(possibleCoord);
+ }
+
+ multiplier++;
+ offsetX = DIRECTIONS[dir][0] * multiplier;
+ offsetY = DIRECTIONS[dir][1] * multiplier;
+ }
+
+ // Convert arraylist to array
+ result[dir] = possibleCoordInDirection.toArray(new Coordinate[0]);
+ }
+
+ return flattenArray(result);
+ }
+
+ @Override
+ public String toString() {
+ return color == Color.BLACK ? QUEEN_BLACK : QUEEN_WHITE;
+ }
+}
diff --git a/src/main/java/chessmaster/pieces/Rook.java b/src/main/java/chessmaster/pieces/Rook.java
new file mode 100644
index 0000000000..8f41b79b72
--- /dev/null
+++ b/src/main/java/chessmaster/pieces/Rook.java
@@ -0,0 +1,73 @@
+package chessmaster.pieces;
+
+import java.util.ArrayList;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+
+public class Rook extends ChessPiece {
+ public static final String ROOK_WHITE = "r"; // ♖
+ public static final String ROOK_BLACK = "R"; // ♜
+
+ public static final int[][] DIRECTIONS = {
+ UP, DOWN, LEFT, RIGHT,
+ };
+
+ protected static int points = 50;
+ protected static int[][] boardWeight =
+ {{0,0,0,0,0,0,0,0},
+ {1,2,2,2,2,2,2,1},
+ {-1,0,0,0,0,0,0,-1},
+ {-1,0,0,0,0,0,0,-1},
+ {-1,0,0,0,0,0,0,-1},
+ {-1,0,0,0,0,0,0,-1},
+ {-1,0,0,0,0,0,0,-1},
+ {0,0,0,1,1,0,0,0}};
+
+ public Rook(int row, int col, Color color) {
+ super(row, col, color);
+ this.setPoints(points);
+ this.setBoardWeight(boardWeight);
+ assert color != Color.EMPTY : "Rook piece should have either black or white color";
+ }
+
+ @Override
+ public Coordinate[] getPseudoLegalCoordinates(ChessBoard board) {
+ Coordinate[][] result = new Coordinate[DIRECTIONS.length][0];
+
+ for (int dir = 0; dir < DIRECTIONS.length; dir++) {
+ int offsetX = DIRECTIONS[dir][0];
+ int offsetY = DIRECTIONS[dir][1];
+
+ int multiplier = 1;
+ boolean isBlocked = false;
+ ArrayList possibleCoordInDirection = new ArrayList<>();
+
+ while (!isBlocked && multiplier < ChessBoard.SIZE && position.isOffsetWithinBoard(offsetX, offsetY)) {
+
+ Coordinate possibleCoord = position.addOffsetToCoordinate(offsetX, offsetY);
+ ChessPiece destPiece = board.getPieceAtCoor(possibleCoord);
+
+ isBlocked = !destPiece.isEmptyPiece();
+ if (destPiece.isEmptyPiece() || isOpponent(destPiece)) {
+ possibleCoordInDirection.add(possibleCoord);
+ }
+
+ multiplier++;
+ offsetX = DIRECTIONS[dir][0] * multiplier;
+ offsetY = DIRECTIONS[dir][1] * multiplier;
+ }
+
+ // Convert arraylist to array
+ result[dir] = possibleCoordInDirection.toArray(new Coordinate[0]);
+ }
+
+ return flattenArray(result);
+ }
+
+ @Override
+ public String toString() {
+ return color == Color.BLACK ? ROOK_BLACK : ROOK_WHITE;
+ }
+}
diff --git a/src/main/java/chessmaster/storage/Storage.java b/src/main/java/chessmaster/storage/Storage.java
new file mode 100644
index 0000000000..ccee9f50d9
--- /dev/null
+++ b/src/main/java/chessmaster/storage/Storage.java
@@ -0,0 +1,542 @@
+package chessmaster.storage;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.exceptions.LoadBoardException;
+import chessmaster.exceptions.SaveBoardException;
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.parser.Parser;
+import chessmaster.user.CPU;
+import chessmaster.user.Human;
+import chessmaster.user.Player;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Scanner;
+
+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 PATH_EMPTY_STRING = "File path cannot be empty or null";
+ //@@author TriciaBK
+ private String filePathString;
+ private File storageFile;
+ // private Coordinate lastMove;
+
+ public Storage(String filePath) {
+ filePathString = filePath;
+ storageFile = new File(filePath);
+ assert !filePathString.isEmpty() && filePath != null : PATH_EMPTY_STRING;
+ }
+
+ //@@author TongZhengHong
+ /**
+ * Creates a ChessMaster program file to store game state, including necessary
+ * parent directories.
+ *
+ * @throws ChessMasterException If there is an error creating the file or parent
+ * directories.
+ */
+ private void createChessMasterFile() throws ChessMasterException {
+ // Create the necessary parent directories for new file
+ if (!storageFile.exists()) {
+ storageFile.getParentFile().mkdirs();
+ }
+
+ // Create file if it does not exist
+ try {
+ storageFile.createNewFile();
+ } catch (IOException e) {
+ throw new ChessMasterException("Fatal: Error creating file: " + filePathString);
+ }
+ }
+
+ //@@author TriciaBK
+ /**
+ * Saves the state of the ChessBoard to a file. Writes the player's color to the
+ * first line
+ * and subsequently chess pieces in a 8 x 8 format.
+ *
+ * @param board The ChessBoard to save.
+ * @throws ChessMasterException If there is an error saving the board to a file.
+ */
+ public void saveBoard(ChessBoard board, Color currentColor, Human human, CPU cpu) throws ChessMasterException {
+ createChessMasterFile();
+
+ try {
+ FileWriter fileWriter = new FileWriter(storageFile);
+ fileWriter.write(board.getPlayerColor().name());
+ fileWriter.write(System.lineSeparator());
+
+ fileWriter.write(String.valueOf(board.getDifficulty()));
+ fileWriter.write(System.lineSeparator());
+
+ fileWriter.write(currentColor.name());
+ fileWriter.write(System.lineSeparator());
+
+ //@@author ken_ruster
+ // Save human moves
+ fileWriter.write(human.movesToString());
+ fileWriter.write(System.lineSeparator());
+
+ // Save cpu moves
+ fileWriter.write(cpu.movesToString());
+ fileWriter.write(System.lineSeparator());
+
+ //@@author TriciaBK
+ /*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());
+ }
+ fileWriter.write(System.lineSeparator());
+ }
+
+ for (int row = 0; row < ChessBoard.SIZE; row++) {
+ for (int col = 0; col < ChessBoard.SIZE; col++) {
+ ChessPiece piece = board.getPieceAtCoor(new Coordinate(col, row));
+ String hasMovedString = piece.getHasMoved() ? "1" : "0";
+ fileWriter.write(hasMovedString);
+ }
+ fileWriter.write(System.lineSeparator());
+ }*/
+
+ fileWriter.close();
+ } catch (IOException e) {
+ throw new SaveBoardException();
+ }
+ }
+
+ //@@author TongZhengHong
+ public void resetBoard() throws ChessMasterException {
+ createChessMasterFile();
+ try {
+ FileWriter fileWriter = new FileWriter(storageFile);
+ fileWriter.write("");
+ fileWriter.close();
+
+ } catch (IOException e) {
+ throw new SaveBoardException();
+ }
+ }
+
+ //@@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 {
+ createChessMasterFile();
+
+ blackPieceNum = 0;
+ whitePieceNum = 0;
+ blackKingPresent = false;
+ whiteKingPresent = false;
+
+ try {
+ fileScanner = new Scanner(storageFile);
+ } catch (FileNotFoundException e) {
+ throw new LoadBoardException("Invalid file path: " + filePathString);
+ }
+
+ // Skip first five lines
+ for (int i = 0; i < 5; i++) {
+ if (fileScanner.hasNext()) {
+ fileScanner.nextLine();
+ }
+ }
+
+ int rowIndex = 0;
+ ChessTile[][] boardTiles = new ChessTile[ChessBoard.SIZE][ChessBoard.SIZE];
+ while (rowIndex < ChessBoard.SIZE && fileScanner.hasNext()) {
+ String chessRowLine = fileScanner.nextLine().trim();
+ if (chessRowLine.length() != ChessBoard.SIZE) {
+ fileScanner.close();
+ throw new LoadBoardException();
+ }
+
+ for (int col = 0; col < ChessBoard.SIZE; col++) {
+ String chessPieceString = String.valueOf(chessRowLine.charAt(col));
+ ChessPiece initialPiece = Parser.parseChessPiece(chessPieceString, rowIndex, col);
+ //@@author onx001
+ if (!this.isPieceValid(initialPiece)) {
+ fileScanner.close();
+ throw new LoadBoardException();
+ }
+ //@@author TriciaBK
+ boardTiles[rowIndex][col] = new ChessTile(initialPiece);
+ }
+ rowIndex++;
+ }
+
+ boolean hasBothKings = blackKingPresent && whiteKingPresent;
+ if (!hasBothKings) {
+ fileScanner.close();
+ throw new LoadBoardException();
+ }
+
+ rowIndex = 0;
+ while (rowIndex < ChessBoard.SIZE && fileScanner.hasNext()) {
+ String chessRowLine = fileScanner.nextLine().trim();
+ if (chessRowLine.length() != ChessBoard.SIZE) {
+ fileScanner.close();
+ throw new LoadBoardException();
+ }
+
+ for (int col = 0; col < ChessBoard.SIZE; col++) {
+ boolean hasMoved = Character.getNumericValue(chessRowLine.charAt(col)) > 0;
+ if (hasMoved) {
+ boardTiles[rowIndex][col].getChessPiece().setHasMoved();
+ }
+ }
+
+ rowIndex++;
+ }
+
+ 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 human Object representing information about the human player
+ * @param cpu Object representing information about the computer-controlled player
+ * @throws ChessMasterException
+ */
+ 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
+ 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);
+ }
+
+ 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 {
+ throw new LoadBoardException();
+ }
+ */
+
+ //Execute move string Array
+ board.executeMoveArray(moveStringList, human, cpu);
+
+ //@@author onx001
+ // get the destination coordinate of the last move
+ /*try {
+ String lastMoveString = moveStringList.get(moveStringList.size() - 1);
+ String[] lastMoveArray = lastMoveString.split("\\s+");
+ if (lastMoveArray.length < 2) {
+ throw new LoadBoardException();
+ }
+ lastMove = Coordinate.parseAlgebraicCoor(lastMoveArray[1]);
+
+ if (otherBoard.getPieceAtCoor(lastMove).isPawn()) {
+ otherBoard.getPieceAtCoor(lastMove).setEnPassant();
+ board.getPieceAtCoor(lastMove).setEnPassant();
+ }
+ } 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)) {
+ throw new LoadBoardException(LOAD_BOARD_MISMATCH_STRING);
+ }*/
+
+ return board;
+ }
+
+
+ //@@author onx001
+ /*private boolean isPieceValid(ChessPiece initialPiece) {
+ if (initialPiece.isBlackKing()) {
+ if (blackKingPresent) {
+ return false;
+ } else {
+ blackKingPresent = true;
+ blackPieceNum++;
+ }
+ } else if (initialPiece.isWhiteKing()) {
+ if (whiteKingPresent) {
+ return false;
+ } else {
+ whiteKingPresent = true;
+ whitePieceNum++;
+ }
+ } else if (initialPiece.isBlack()) {
+ if (blackPieceNum >= ChessBoard.MAX_PIECES) {
+ return false;
+ } else {
+ blackPieceNum++;
+ }
+ } else if (initialPiece.isWhite()) {
+ if (whitePieceNum >= ChessBoard.MAX_PIECES) {
+ return false;
+ } else {
+ whitePieceNum++;
+ }
+ }
+
+ return true;
+ }*/
+
+ //@@author TongZhengHong
+ /**
+ * Loads the player's color from a file.
+ * Expects the player color information on the first line of text file.
+ *
+ * @return The player's color as a Color enumeration.
+ * @throws ChessMasterException If there is an error loading the player's color
+ * from the file.
+ */
+ public Color loadPlayerColor() throws ChessMasterException {
+ createChessMasterFile();
+
+ Scanner fileScanner;
+ try {
+ fileScanner = new Scanner(storageFile);
+ } catch (FileNotFoundException e) {
+ throw new LoadBoardException("Invalid file path: " + filePathString);
+ }
+
+ if (fileScanner.hasNext()) {
+ String colorLine = fileScanner.nextLine().trim();
+ Color playerColor = Parser.parsePlayerColor(colorLine.toUpperCase());
+
+ fileScanner.close();
+ return playerColor;
+ }
+
+ fileScanner.close();
+ throw new LoadBoardException();
+ }
+
+ //@@author onx001
+ /**
+ * Loads the difficulty from a file.
+ * Expects the difficulty information on the second line of text file.
+ * @return The difficulty as an integer.
+ */
+ public int loadDifficulty() throws ChessMasterException {
+ createChessMasterFile();
+
+ Scanner fileScanner;
+ try {
+ fileScanner = new Scanner(storageFile);
+ } catch (FileNotFoundException e) {
+ throw new LoadBoardException("Invalid file path: " + filePathString);
+ }
+
+ // Skip player color first line
+ if (fileScanner.hasNext()) {
+ fileScanner.nextLine();
+ }
+
+ if (fileScanner.hasNext()) {
+ try {
+ String difficultyLine = fileScanner.nextLine().trim();
+ int difficulty = Parser.parseDifficulty(difficultyLine);
+
+ fileScanner.close();
+ if (difficulty < 1 || difficulty > 3) {
+ throw new LoadBoardException();
+ }
+ return difficulty;
+ } catch (NumberFormatException e) {
+ throw new LoadBoardException();
+ }
+ }
+
+ fileScanner.close();
+ throw new LoadBoardException();
+ }
+
+
+ //@@author TriciaBK
+ /**
+ * Loads the current turn player's
+ * @return The difficulty as an integer.
+ */
+ public Color loadCurrentColor() throws ChessMasterException {
+ createChessMasterFile();
+
+ Scanner fileScanner;
+ try {
+ fileScanner = new Scanner(storageFile);
+ } catch (FileNotFoundException e) {
+ throw new LoadBoardException("Invalid file path: " + filePathString);
+ }
+
+ if (fileScanner.hasNext()) {
+ fileScanner.nextLine();
+ }
+
+ if (fileScanner.hasNext()) {
+ fileScanner.nextLine();
+ }
+
+ if (fileScanner.hasNext()) {
+ String currentColorString = fileScanner.nextLine().trim();
+ Color color = Parser.parsePlayerColor(currentColorString.toUpperCase());
+ fileScanner.close();
+ return color;
+ }
+
+ fileScanner.close();
+ 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.
+ *
+ * @return ArrayList containing the move history of the human player
+ * @throws ChessMasterException
+ */
+ public ArrayList loadHumanMoves() throws ChessMasterException {
+ createChessMasterFile();
+
+ Scanner fileScanner;
+ try {
+ fileScanner = new Scanner(storageFile);
+ } catch (FileNotFoundException e) {
+ throw new LoadBoardException("Invalid file path: " + filePathString);
+ }
+
+ // Skip first 3 lines
+ for (int i = 0; i < 3; i ++) {
+ if (fileScanner.hasNext()) {
+ fileScanner.nextLine();
+ }
+ }
+
+ ArrayList out = new ArrayList();
+ if (fileScanner.hasNext()) {
+ 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.trim()));
+ }
+
+ fileScanner.close();
+ return out;
+ }
+
+ /**
+ * Loads the history of moves made by the CPU player in the saved game.
+ * Parses the moves into Move objects, and returns a null array if no moves were found.
+ *
+ * @return ArrayList containing the move history of the CPU player
+ * @throws ChessMasterException
+ */
+ public ArrayList loadCPUMoves() throws ChessMasterException {
+ createChessMasterFile();
+
+ Scanner fileScanner;
+ try {
+ fileScanner = new Scanner(storageFile);
+ } catch (FileNotFoundException e) {
+ throw new LoadBoardException("Invalid file path: " + filePathString);
+ }
+
+ // Skip first 4 lines
+ for (int i = 0; i < 4; i ++) {
+ if (fileScanner.hasNext()) {
+ fileScanner.nextLine();
+ }
+ }
+
+ ArrayList out = new ArrayList();
+ if (fileScanner.hasNext()) {
+ 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.trim()));
+ }
+
+ fileScanner.close();
+ return out;
+ }
+
+}
diff --git a/src/main/java/chessmaster/ui/ExceptionMessages.java b/src/main/java/chessmaster/ui/ExceptionMessages.java
new file mode 100644
index 0000000000..28c281ce0b
--- /dev/null
+++ b/src/main/java/chessmaster/ui/ExceptionMessages.java
@@ -0,0 +1,15 @@
+package chessmaster.ui;
+
+public class ExceptionMessages {
+ public static final String MESSAGE_PARSE_CHESS_PIECE_EXCEPTION = "Unable to parse chess piece!";
+ public static final String MESSAGE_PARSE_COORDINATE_EXCEPTION = "Unable to parse coordinate!";
+ public static final String MESSAGE_SAVE_BOARD_EXCEPTION = "Unable to save board!";
+ public static final String MESSAGE_LOAD_BOARD_EXCEPTION = "Unable to load board!";
+ public static final String MESSAGE_INVALID_MOVE_EXCEPTION = "Oops, that move isn't valid!";
+ public static final String MESSAGE_NULL_PIECE_EXCEPTION = "No chess piece found at coordinate!";
+ public static final String MESSAGE_NULL_PIECE_COORDINATE_EXCEPTION =
+ "No chess piece found at coordinate %s!";
+ public static final String MESSAGE_MOVE_OPPONENT_EXCEPTION = "You have chosen an opponent piece! "
+ + "Please choose your pieces!";
+ public static final String MESSAGE_PARSE_COLOR_EXCEPTION = "Unable to parse color!";
+}
diff --git a/src/main/java/chessmaster/ui/TextUI.java b/src/main/java/chessmaster/ui/TextUI.java
new file mode 100644
index 0000000000..927756b3d4
--- /dev/null
+++ b/src/main/java/chessmaster/ui/TextUI.java
@@ -0,0 +1,316 @@
+package chessmaster.ui;
+
+import java.util.Arrays;
+import java.util.Scanner;
+
+import chessmaster.commands.CommandResult;
+import chessmaster.game.ChessBoard;
+import chessmaster.game.ChessTile;
+import chessmaster.game.Coordinate;
+import chessmaster.game.move.Move;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.user.Player;
+
+public final class TextUI {
+
+ private static final String CHESS_BOARD_TAB = " ".repeat(4);
+ private static final String CHESS_BOARD_PADDING = CHESS_BOARD_TAB.repeat(3);
+ public static final String CHESS_BOARD_DIVIDER = CHESS_BOARD_PADDING + CHESS_BOARD_TAB +
+ "_".repeat(4 * ChessBoard.SIZE + 1);
+
+ private static final String COLUMN_HEADER = "abcdefgh";
+
+ /** A platform independent line separator. */
+ private static final String LS = System.lineSeparator();
+ private static final String DIVIDER = "_".repeat(65) + LS;
+ /**
+ * Format of a comment input line. Comment lines are silently consumed when
+ * reading user input.
+ */
+ private static final String COMMENT_LINE_FORMAT_REGEX = "#.*";
+
+ private static final Scanner scanner = new Scanner(System.in);
+
+ /**
+ * Prompts for the command and reads the text entered by the user.
+ * Ignores empty, pure whitespace, and comment lines.
+ *
+ * @return user input string in LOWER case
+ */
+ public String getUserInput(boolean shouldIgnoreEmpty) {
+
+ String fullInputLine = "";
+
+ if (scanner.hasNextLine()) {
+ fullInputLine = scanner.nextLine().trim();
+ }
+
+ if (shouldIgnoreEmpty) {
+ // silently consume all ignored lines
+ while (shouldIgnore(fullInputLine)) {
+ if (scanner.hasNextLine()) {
+ fullInputLine = scanner.nextLine().trim();
+ }
+ }
+ }
+
+ return fullInputLine.toLowerCase();
+ }
+
+ /**
+ * Returns true if the user input line should be ignored.
+ * Input should be ignored if it is parsed as a comment, is only whitespace, or
+ * is empty.
+ *
+ * @param rawInputLine full raw user input line.
+ * @return true if the entire user input line should be ignored.
+ */
+ private boolean shouldIgnore(String rawInputLine) {
+ boolean isCommentLine = rawInputLine.trim().matches(COMMENT_LINE_FORMAT_REGEX);
+ return rawInputLine.trim().isEmpty() || isCommentLine;
+ }
+
+ /**
+ * Prints one or more lines of text, surrounded by a divider, to the user
+ * console.
+ *
+ * @param texts The lines of text to be printed.
+ */
+ public void printText(String... texts) {
+ System.out.println(DIVIDER);
+
+ for (String text : texts) {
+ System.out.println(text);
+ }
+
+ System.out.println(DIVIDER);
+ }
+
+ //@@author TongZhengHong
+ public void printChessBoard(ChessTile[][] tiles) {
+ printChessBoardHeader();
+ printChessBoardDivider();
+
+ for (int i = 0; i < tiles.length; i++) {
+ ChessTile[] row = tiles[i];
+ StringBuilder rowString = new StringBuilder();
+
+ for (ChessTile tile : row) {
+ rowString.append(ChessTile.TILE_DIVIDER);
+ rowString.append(tile.toString());
+ }
+
+ int rowNum = 8 - i;
+ printChessBoardRow(rowNum, rowString.toString());
+ }
+ printChessBoardHeader();
+ System.out.println("");
+ }
+
+ public void printChessBoardWithMove(ChessTile[][] tiles, Move move) {
+ printChessBoardHeader();
+ printChessBoardDivider();
+
+ for (int i = 0; i < tiles.length; i++) {
+ ChessTile[] row = tiles[i];
+ StringBuilder rowString = new StringBuilder();
+
+ for (int j = 0; j < tiles.length; j++) {
+ rowString.append(ChessTile.TILE_DIVIDER);
+
+ ChessTile tile = row[j];
+ Coordinate coord = new Coordinate(j, i);
+ boolean isPrevMove = move.getFrom().equals(coord) ||
+ move.getTo().equals(coord);
+
+ String pieceString = isPrevMove ? tile.toStringPrevMove() : tile.toString();
+ rowString.append(pieceString);
+ }
+
+ int rowNum = 8 - i;
+ printChessBoardRow(rowNum, rowString.toString());
+ }
+ printChessBoardHeader();
+ System.out.println("");
+ }
+
+ //@@author ken-ruster
+ /**
+ * Prints the chessboard along with highlighted moves for a specific chess piece.
+ *
+ * This method displays the chessboard, emphasizing available destination squares for a
+ * selected piece and marking the selected piece itself.
+ *
+ * @param tiles The 2D array of ChessTile objects representing the chessboard.
+ * @param piece The chess piece for which moves are highlighted.
+ * @param coordinates An array of coordinates representing available destination squares.
+ */
+ public void printChessBoardAvailableMoves(ChessTile[][] tiles, ChessPiece piece,
+ Coordinate[] coordinates) {
+
+ printChessBoardHeader();
+ printChessBoardDivider();
+
+ for (int i = 0; i < tiles.length; i++) {
+ ChessTile[] row = tiles[i];
+ StringBuilder rowString = new StringBuilder();
+
+ for (int j = 0; j < tiles.length; j++) {
+ rowString.append(ChessTile.TILE_DIVIDER);
+
+ ChessTile tile = row[j];
+ Coordinate coord = new Coordinate(j, i);
+
+ String pieceString;
+ if (Arrays.asList(coordinates).contains(coord)) {
+ pieceString = tile.toStringAvailableDest();
+ } else if (piece.getPosition().equals(coord)) {
+ pieceString = tile.toStringSelected();
+ } else {
+ pieceString = tile.toString();
+ }
+ rowString.append(pieceString);
+ }
+
+ int rowNum = 8 - i;
+ printChessBoardRow(rowNum, rowString.toString());
+ }
+ printChessBoardHeader();
+ System.out.println("");
+ }
+ //@@author
+
+ public void printWelcomeMessage() {
+ printText(UiMessages.WELCOME_MESSAGE);
+ }
+
+ public void printLoadBoardError() {
+ printText(UiMessages.LOAD_BOARD_ERROR_MESSAGE);
+ }
+
+ public void promptContinuePrevGame(boolean error) {
+ if (error) {
+ System.out.print(UiMessages.CONTINUE_PREV_GAME_ERROR_MESSAGE);
+ } else {
+ System.out.print(UiMessages.EXIST_PREV_GAME_MESSAGE);
+ }
+ }
+
+ public void promptDifficulty(boolean error) {
+ if (error) {
+ System.out.print(UiMessages.CHOOSE_DIFFICULTY_ERROR_MESSAGE);
+ } else {
+ System.out.print(UiMessages.CHOOSE_DIFFICULTY_MESSAGE);
+ }
+ }
+
+ public void promptStartingColor(boolean error) {
+ if (error) {
+ System.out.print(UiMessages.CHOOSE_PLAYER_COLOR_ERROR_MESSAGE);
+ } else {
+ System.out.print(UiMessages.CHOOSE_PLAYER_COLOR_MESSAGE);
+ }
+ }
+
+ public void printStartNewGame(String colorString) {
+ String displayText = String.format(UiMessages.START_NEW_GAME_MESSAGE, colorString);
+ printText(displayText);
+ }
+
+ public void printContinuePrevGame(String colorString, int difficulty) {
+ String displayText = String.format(UiMessages.CONTINUE_PREV_GAME_MESSAGE, colorString, difficulty);
+ printText(displayText);
+ }
+
+ public void printPromotePrompt(Coordinate coord) {
+ String message = String.format(UiMessages.PROMPT_PROMOTE_MESSAGE, coord.toString());
+ System.out.print(message);
+ }
+
+ public void printPromoteInvalidMessage() {
+ System.out.print(UiMessages.PROMPT_PROMOTE_INVALID_MESSAGE);
+ }
+
+ public void printCPUThinkingMessage() {
+ System.out.println(UiMessages.CHESSMASTER_THINKING_MESSAGE);
+ }
+
+ public void printCPUMove(Move cpuMove) {
+ String pieceString = cpuMove.getPieceMoved().getClass().getSimpleName();
+ String returnString;
+ if (cpuMove.hasCapturedAPiece()) {
+ returnString = String.format(
+ UiMessages.CPU_MOVE_AND_CAPTURE_MESSAGE,
+ pieceString, cpuMove.getFrom(), cpuMove.getTo(), cpuMove.getPieceCaptured().getPieceName()
+ );
+ } else {
+ returnString = String.format(UiMessages.CPU_MOVE_MESSAGE, pieceString, cpuMove.getFrom(), cpuMove.getTo());
+ }
+
+ printText(returnString);
+ }
+
+ private void printChessBoardDivider() {
+ System.out.println(CHESS_BOARD_DIVIDER);
+ }
+
+ private void printChessBoardHeader() {
+ System.out.print(CHESS_BOARD_PADDING + CHESS_BOARD_TAB);
+ for (int i = 0; i < COLUMN_HEADER.length(); i++) {
+ char col = COLUMN_HEADER.charAt(i);
+ System.out.printf(" (%s)", col);
+ }
+ System.out.println("");
+ }
+
+ private void printChessBoardRow(int rowNum, String chessBoardRow) {
+ System.out.print(CHESS_BOARD_PADDING);
+ System.out.print(String.format("(%d) ", rowNum));
+ System.out.print(chessBoardRow);
+ System.out.print(ChessTile.TILE_DIVIDER);
+ System.out.print(String.format(" (%d)", rowNum));
+ System.out.print(System.lineSeparator() + CHESS_BOARD_DIVIDER);
+ System.out.println("");
+ }
+
+ public void printCommandResult(CommandResult result) {
+ String[] resultStrings = result.getMessageStrings();
+ if (resultStrings != null && resultStrings.length > 0) {
+ printText(resultStrings);
+ }
+ }
+
+ public void printErrorMessage(Exception e) {
+ printText(e.getMessage());
+ }
+
+ public void printEndMessage(Player winner) {
+ String winningColorString = winner.getColour().name();
+ if (winner.isHuman()) {
+ printText(String.format(UiMessages.HUMAN_WIN_MESSAGE, winningColorString));
+ } else if (winner.isCPU()) { // Human lost
+ String playerColorString = winner.getColour().getOppositeColour().name();
+ printText(String.format(UiMessages.CPU_WIN_MESSAGE, playerColorString));
+ }
+ }
+
+ public void printRestartingGameMessage() {
+ System.out.println(UiMessages.RESTARTING_GAME_MESSAGE);
+ }
+
+ //@@author onx001
+ public void printDrawMessage() {
+ System.out.println(UiMessages.DRAW_MESSAGE);
+ }
+
+ //@@author TriciaBK
+ public void promptNewGame(boolean error) {
+ if (error) {
+ System.out.println(UiMessages.RESTART_GAME_ERROR_MESSAGE);
+ } else {
+ System.out.println(UiMessages.RESTART_GAME_MESSAGE);
+ }
+ }
+
+
+}
diff --git a/src/main/java/chessmaster/ui/UiMessages.java b/src/main/java/chessmaster/ui/UiMessages.java
new file mode 100644
index 0000000000..dc4d553bfa
--- /dev/null
+++ b/src/main/java/chessmaster/ui/UiMessages.java
@@ -0,0 +1,52 @@
+package chessmaster.ui;
+
+public class UiMessages {
+
+ public static final String[] WELCOME_MESSAGE = {
+ "Hey there, chess geek! You have stumbled upon the one and only: ",
+ " ________ __ ___ __ ",
+ " / ____/ /_ ___ __________ / |/ /___ ______/ /____ _____",
+ " / / / __ \\/ _ \\/ ___/ ___/ / /|_/ / __ `/ ___/ __/ _ \\/ ___/",
+ " / /___/ / / / __(__ |__ ) / / / / /_/ (__ ) /_/ __/ / ",
+ " \\____/_/ /_/\\___/____/____/ /_/ /_/\\__,_/____/\\__/\\___/_/ ", "",
+ "where CHESS becomes an exciting journey of strategy and skill!" };
+
+ public static final String EXIST_PREV_GAME_MESSAGE =
+ "You have an ongoing previous chess game. Continue game? [y/n/exit] ";
+ public static final String CONTINUE_PREV_GAME_ERROR_MESSAGE =
+ "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/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";
+
+ public static final String LOAD_BOARD_ERROR_MESSAGE = "No valid previous game found. Starting new chess game...";
+ public static final String CPU_MOVE_MESSAGE = "ChessMaster moved %s from %s to %s";
+ public static final String CPU_MOVE_AND_CAPTURE_MESSAGE = "ChessMaster moved %s from %s to %s " +
+ "and captured your %s!";
+
+ public static final String PROMPT_PROMOTE_MESSAGE =
+ "Promote the pawn at %s! Choose piece to promote to [b/q/r/n]: ";
+ public static final String PROMPT_PROMOTE_INVALID_MESSAGE =
+ "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/exit]: ";
+ public static final String CHOOSE_DIFFICULTY_ERROR_MESSAGE =
+ "Invalid input! Please enter either '1', '2' or '3': ";
+
+ public static final String CHESSMASTER_THINKING_MESSAGE = "ChessMaster is thinking of a move...";
+
+ public static final String HUMAN_WIN_MESSAGE = "Congratulations! You have won as %s! :)";
+ public static final String CPU_WIN_MESSAGE = "Oh no! You have lost as %s. Please try harder next time :(";
+ public static final String DRAW_MESSAGE = "It's a draw!";
+
+ public static final String RESTART_GAME_MESSAGE =
+ "Do you want to restart game? [y/n] ";
+ public static final String RESTART_GAME_ERROR_MESSAGE =
+ "Invalid input! Please enter either 'y' for yes or 'n' for no: ";
+ public static final String RESTARTING_GAME_MESSAGE = "Great! Restarting a new game!";
+}
diff --git a/src/main/java/chessmaster/user/CPU.java b/src/main/java/chessmaster/user/CPU.java
new file mode 100644
index 0000000000..e2083e9b68
--- /dev/null
+++ b/src/main/java/chessmaster/user/CPU.java
@@ -0,0 +1,80 @@
+package chessmaster.user;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+import chessmaster.game.MiniMax;
+import chessmaster.game.move.Move;
+import chessmaster.game.move.MoveFactory;
+import chessmaster.pieces.ChessPiece;
+
+import java.util.Random;
+
+public class CPU extends Player {
+
+ private static final int MAX_LOOP_ITERATIONS = 16;
+
+ private static final int RANDOM_SEED = 100;
+ private final Random rand = new Random(RANDOM_SEED);
+
+ public CPU(Color colour, ChessBoard board) {
+ super(colour, board);
+ }
+
+ /**
+ * The main function behind the CPU's logic, this function randomly selects an active (non-captured) piece
+ * from the CPU's pieces which has possible valid moves.
+ * @param board The board to extract the random move from.
+ * @return A random move
+ */
+ public Move getRandomMove(ChessBoard board) {
+ // 1. Get a random piece that
+ // - isn't captured
+ // - has possible legal moves
+ ChessPiece randomPiece = getRandomPiece();
+
+ // Need a cap on the number of pieces it checks to prevent an infinite loop when no moves are possible
+ // on the CPUs side.
+ int iter = 0;
+ while (iter < MAX_LOOP_ITERATIONS
+ && (randomPiece.getIsCaptured()
+ || randomPiece.getLegalCoordinates(board).length == 0)) {
+ randomPiece = getRandomPiece();
+ iter++;
+ }
+
+ return getRandomMoveFromPiece(randomPiece, board);
+ }
+
+ //@@author onx001
+
+ public Move getBestMove(ChessBoard board, int maxDepth) {
+ MiniMax miniMax = new MiniMax(board, this.colour, maxDepth, 0);
+ Move bestMove = miniMax.getBestMove();
+ Coordinate from = bestMove.getFrom();
+ ChessPiece piece = board.getPieceAtCoor(from);
+ bestMove.setPieceMoved(piece);
+ return bestMove;
+ }
+
+ //@@author
+
+ private ChessPiece getRandomPiece() {
+ return this.pieces.get(rand.nextInt(pieces.size()));
+ }
+
+ /**
+ * This function returns any random valid move that a given piece can make on a given board.
+ * @param piece The piece to extract a random move from.
+ * @param board The board the piece is currently on.
+ * @return A random move the given piece can make on the given board.
+ */
+ private Move getRandomMoveFromPiece(ChessPiece piece, ChessBoard board) {
+ Coordinate[] allPossibleMoves = piece.getLegalCoordinates(board);
+ int randIndex = rand.nextInt(allPossibleMoves.length);
+ Coordinate randomDestination = allPossibleMoves[randIndex];
+
+ return MoveFactory.createMove(board, piece.getPosition(), randomDestination);
+ }
+
+}
diff --git a/src/main/java/chessmaster/user/Human.java b/src/main/java/chessmaster/user/Human.java
new file mode 100644
index 0000000000..0c78cb3c46
--- /dev/null
+++ b/src/main/java/chessmaster/user/Human.java
@@ -0,0 +1,61 @@
+package chessmaster.user;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+import chessmaster.game.move.Move;
+import chessmaster.game.move.MoveFactory;
+import chessmaster.game.move.PromoteMove;
+import chessmaster.parser.Parser;
+import chessmaster.pieces.ChessPiece;
+import chessmaster.pieces.Pawn;
+import chessmaster.ui.TextUI;
+
+public class Human extends Player {
+
+ public Human(Color colour, ChessBoard board) {
+ super(colour, board);
+ }
+
+ //@@author ken-ruster
+ /**
+ * Prompts the user to enter a type of piece to promote a pawn to. If the
+ * promotion is not successful,
+ * the user is prompted again. If successful, the pawn is replaced with the new
+ * piece.
+ *
+ * @param board Chessboard that the game is being played on.
+ * @param move The piece being promoted.
+ */
+ public void handlePromote(ChessBoard board, TextUI ui, Move move) {
+ ChessPiece pawnPiece = move.getPieceMoved();
+ if (!pawnPiece.isPawn()) {
+ return;
+ }
+ Pawn pawnPromoted = (Pawn) pawnPiece;
+
+ ui.printChessBoard(board.getBoard());
+ Coordinate coord = pawnPiece.getPosition();
+ boolean promoteFailure = true;
+
+ ui.printPromotePrompt(coord);
+ String in = ui.getUserInput(false);
+ do {
+ ChessPiece promotedPiece = Parser.parsePromote(pawnPiece, in);
+ promoteFailure = promotedPiece.isPawn();
+
+ if (promoteFailure) {
+ ui.printPromoteInvalidMessage();
+ in = ui.getUserInput(false);
+ } else {
+ promotedPiece.setHasMoved();
+ this.pieces.add(promotedPiece);
+ this.pieces.remove(pawnPiece);
+ board.setPromotionPiece(coord, promotedPiece);
+ PromoteMove promoteMove = MoveFactory.createPromoteMove(coord, pawnPromoted, promotedPiece);
+ this.addMove(promoteMove);
+ }
+ } while (promoteFailure);
+ }
+
+}
diff --git a/src/main/java/chessmaster/user/Player.java b/src/main/java/chessmaster/user/Player.java
new file mode 100644
index 0000000000..3e8689b0db
--- /dev/null
+++ b/src/main/java/chessmaster/user/Player.java
@@ -0,0 +1,117 @@
+package chessmaster.user;
+
+import chessmaster.game.ChessBoard;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+import chessmaster.game.move.Move;
+import chessmaster.pieces.ChessPiece;
+
+import java.util.ArrayList;
+
+public abstract class Player {
+
+ public static final String MOVE_DELIMITER = ",";
+
+ protected ArrayList moves;
+ protected ArrayList pieces;
+ protected Color colour;
+
+ /**
+ * A player is a dependency of the Game class. This class stores all move
+ * history, all current pieces, and colour of each player.
+ * It also contains functions to request input from the user for the next
+ * move and to execute that move.
+ *
+ * @param colour The ChessPiece.Colour desired for this player.
+ */
+ public Player(Color colour, ChessBoard board) {
+ this.moves = new ArrayList();
+ this.pieces = new ArrayList();
+ this.colour = colour;
+ initialisePieces(board);
+ }
+
+ /**
+ * Adds a given move into the Player's move history.
+ *
+ * @param move The given move to be added to history.
+ */
+ public void addMove(Move move) {
+ this.moves.add(move);
+ }
+
+ public Color getColour() {
+ return this.colour;
+ }
+
+ /**
+ * Adds all the player's pieces to their ChessPiece array
+ * when Player is initialised.
+ *
+ * @param board The new ChessBoard containing all 32 chess pieces.
+ */
+ private void initialisePieces(ChessBoard board) {
+ for (int row = 0; row < ChessBoard.SIZE; row++) {
+ for (int col = 0; col < ChessBoard.SIZE; col++) {
+ ChessPiece piece = board.getPieceAtCoor(new Coordinate(col, row));
+ if (piece.isSameColorAs(this.colour)) {
+ this.pieces.add(piece);
+ }
+ }
+ }
+ }
+
+ /**
+ * Prints out all the player's pieces including whether it has been captured or
+ * not.
+ * Used for debugging purposes only.
+ */
+ public void printAllPieces() {
+ for (ChessPiece p : pieces) {
+ System.out.println("Piece: " + p);
+ System.out.println("Colour: " + p.getColor().toString());
+ System.out.println("Is captured: " + p.getIsCaptured());
+ }
+ }
+
+ /**
+ * Converts the information in the moves array into a String, to be used in saving the game.
+ *
+ * @return String containing information about the player's past moves
+ */
+ public String movesToString() {
+ String out = new String();
+
+ for (int i = 0; i < moves.size(); i = i + 1) {
+ Move move = moves.get(i);
+
+ if (i < moves.size() - 1) {
+ out = out + move.toFileString() + MOVE_DELIMITER;
+ } else {
+ out = out + move.toFileString();
+ }
+ }
+
+ return out;
+ }
+
+ public boolean isHuman() {
+ return this instanceof Human;
+ }
+
+ public boolean isCPU() {
+ return this instanceof CPU;
+ }
+
+ public ArrayList getMoves() {
+ return this.moves;
+ }
+
+ public ArrayList getPieces() {
+ return this.pieces;
+ }
+
+ public int getMovesLength() {
+ return this.moves.size();
+ }
+}
diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java
deleted file mode 100644
index 5c74e68d59..0000000000
--- a/src/main/java/seedu/duke/Duke.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.duke;
-
-import java.util.Scanner;
-
-public class Duke {
- /**
- * Main entry-point for the java.duke.Duke application.
- */
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- System.out.println("What is your name?");
-
- Scanner in = new Scanner(System.in);
- System.out.println("Hello " + in.nextLine());
- }
-}
diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/chessmaster/ChessMasterTest.java
similarity index 56%
rename from src/test/java/seedu/duke/DukeTest.java
rename to src/test/java/chessmaster/ChessMasterTest.java
index 2dda5fd651..d07e81b5b5 100644
--- a/src/test/java/seedu/duke/DukeTest.java
+++ b/src/test/java/chessmaster/ChessMasterTest.java
@@ -1,12 +1,14 @@
-package seedu.duke;
+package chessmaster;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import org.junit.jupiter.api.Test;
-class DukeTest {
+class ChessMasterTest {
@Test
public void sampleTest() {
assertTrue(true);
+ assertFalse(false);
}
}
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
new file mode 100644
index 0000000000..73cb59ce77
--- /dev/null
+++ b/src/test/java/chessmaster/game/ChessBoardTest.java
@@ -0,0 +1,73 @@
+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() {
+ TextUI ui = new TextUI();
+ ChessBoard board = new ChessBoard(Color.WHITE);
+
+ ui.printChessBoard(board.getBoard());
+ 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/game/ColorTest.java b/src/test/java/chessmaster/game/ColorTest.java
new file mode 100644
index 0000000000..25da39b3d4
--- /dev/null
+++ b/src/test/java/chessmaster/game/ColorTest.java
@@ -0,0 +1,30 @@
+package chessmaster.game;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class ColorTest {
+
+ @Test
+ public void testGetOppositeColour() {
+ assertEquals(Color.BLACK, Color.WHITE.getOppositeColour());
+ assertEquals(Color.WHITE, Color.BLACK.getOppositeColour());
+ assertEquals(Color.EMPTY, Color.EMPTY.getOppositeColour());
+ }
+
+ @Test
+ public void testIsWhite() {
+ assertTrue(Color.WHITE.isWhite());
+ assertFalse(Color.BLACK.isWhite());
+ assertFalse(Color.EMPTY.isWhite());
+ }
+
+ @Test
+ public void testIsBlack() {
+ assertTrue(Color.BLACK.isBlack());
+ assertFalse(Color.WHITE.isBlack());
+ assertFalse(Color.EMPTY.isBlack());
+ }
+}
diff --git a/src/test/java/chessmaster/game/CoordinateTest.java b/src/test/java/chessmaster/game/CoordinateTest.java
new file mode 100644
index 0000000000..20fb2644a0
--- /dev/null
+++ b/src/test/java/chessmaster/game/CoordinateTest.java
@@ -0,0 +1,79 @@
+package chessmaster.game;
+
+import chessmaster.exceptions.ParseCoordinateException;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class CoordinateTest {
+
+ @Test
+ public void testIsOffsetWithinBoard() {
+ Coordinate coordinate = new Coordinate(3, 4);
+
+ assertTrue(coordinate.isOffsetWithinBoard(-1, 0));
+ assertTrue(coordinate.isOffsetWithinBoard(0, -1));
+ assertTrue(coordinate.isOffsetWithinBoard(1, 1));
+
+ assertFalse(coordinate.isOffsetWithinBoard(8, 0));
+ assertFalse(coordinate.isOffsetWithinBoard(0, 8));
+ assertFalse(coordinate.isOffsetWithinBoard(-4, 0));
+ }
+
+ @Test
+ public void testAddOffsetToCoordinate() {
+ Coordinate coordinate = new Coordinate(2, 3);
+
+ Coordinate newCoord1 = coordinate.addOffsetToCoordinate(1, 2);
+ Coordinate newCoord2 = coordinate.addOffsetToCoordinate(-2, -3);
+ Coordinate newCoord3 = coordinate.addOffsetToCoordinate(0, 0);
+
+ assertEquals(new Coordinate(3, 5), newCoord1);
+ assertEquals(new Coordinate(0, 0), newCoord2);
+ assertEquals(coordinate, newCoord3); // Should be the same coordinate
+ }
+
+ @Test
+ public void testParseAlgebraicCoor() throws ParseCoordinateException {
+ Coordinate coordinate = Coordinate.parseAlgebraicCoor("d5");
+
+ assertEquals(new Coordinate(3, 3), coordinate);
+
+ // Test invalid notations
+ assertThrows(ParseCoordinateException.class, () -> Coordinate.parseAlgebraicCoor("x3"));
+ assertThrows(ParseCoordinateException.class, () -> Coordinate.parseAlgebraicCoor("h0"));
+ assertThrows(ParseCoordinateException.class, () -> Coordinate.parseAlgebraicCoor("a9"));
+ }
+
+ @Test
+ public void testCalculateOffsetFrom() {
+ Coordinate coordinate1 = new Coordinate(2, 3);
+ Coordinate coordinate2 = new Coordinate(4, 1);
+
+ int[] offset = coordinate1.calculateOffsetFrom(coordinate2);
+
+ assertEquals(-2, offset[0]);
+ assertEquals(2, offset[1]);
+ }
+
+ @Test
+ public void testToString() {
+ Coordinate coordinate = new Coordinate(1, 6);
+
+ assertEquals("b2", coordinate.toString());
+ }
+
+ @Test
+ public void testEquals() {
+ Coordinate coordinate1 = new Coordinate(2, 3);
+ Coordinate coordinate2 = new Coordinate(2, 3);
+ Coordinate coordinate3 = new Coordinate(4, 1);
+
+ assertEquals(coordinate1, coordinate2);
+ assertNotEquals(coordinate1, coordinate3);
+ }
+}
diff --git a/src/test/java/chessmaster/game/MiniMaxTest.java b/src/test/java/chessmaster/game/MiniMaxTest.java
new file mode 100644
index 0000000000..bf2d3714ed
--- /dev/null
+++ b/src/test/java/chessmaster/game/MiniMaxTest.java
@@ -0,0 +1,18 @@
+package chessmaster.game;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import chessmaster.game.move.Move;
+import org.junit.jupiter.api.Test;
+
+public class MiniMaxTest {
+ @Test
+ public void testMiniMax() {
+ ChessBoard board = new ChessBoard(Color.BLACK);
+ MiniMax miniMax = new MiniMax(board, Color.BLACK, 3, 0);
+ Move move = miniMax.getBestMove();
+ assertEquals(move.getFrom(), new Coordinate(6, 7));
+
+ }
+ //ChessBoard board, Color color, int maxDepth, int score
+}
diff --git a/src/test/java/chessmaster/parser/ParseMoveTest.java b/src/test/java/chessmaster/parser/ParseMoveTest.java
new file mode 100644
index 0000000000..0c5fc49cea
--- /dev/null
+++ b/src/test/java/chessmaster/parser/ParseMoveTest.java
@@ -0,0 +1,110 @@
+package chessmaster.parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import chessmaster.exceptions.ChessMasterException;
+import chessmaster.exceptions.MoveOpponentPieceException;
+import chessmaster.exceptions.NullPieceException;
+import chessmaster.exceptions.ParseCoordinateException;
+import chessmaster.game.ChessBoard;
+import chessmaster.game.ChessTile;
+import chessmaster.game.Color;
+import chessmaster.game.Coordinate;
+import chessmaster.game.move.Move;
+import chessmaster.pieces.ChessPiece;
+
+public class ParseMoveTest {
+ private static final String[][] STARTING_CHESSBOARD_BLACK = {
+ { "r", "n", "b", "q", "k", "b", "n", "r" },
+ { "p", "p", "p", "p", "p", "p", "p", "p" },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { ".", ".", ".", ".", ".", ".", ".", "." },
+ { "P", "P", "P", "P", "P", "P", "P", "P" },
+ { "R", "N", "B", "Q", "K", "B", "N", "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 = STARTING_CHESSBOARD_BLACK[row][col];
+ ChessPiece initialPiece = Parser.parseChessPiece(chessPieceString, row, col);
+ chessTiles[row][col] = new ChessTile(initialPiece);
+ assert (chessTiles[row][col] != null);
+ }
+ }
+ return new ChessBoard(Color.BLACK, chessTiles);
+ }
+
+ @Test
+ public void testParseMove_inputValidMove() throws ChessMasterException {
+ String inputString = "h2 h4";
+ ChessBoard emptyBoard = loadChessBoard();
+ Move move = Parser.parseMove(inputString, emptyBoard, true);
+
+ Coordinate from = new Coordinate(7, 6); // h2
+ Coordinate to = new Coordinate(7, 4); // h4
+ Move expectedMove = new Move(from, to, emptyBoard.getPieceAtCoor(from));
+
+ assertEquals(move, expectedMove);
+ }
+
+ @Test
+ public void testParseMove_inputCannotParseStart_expectParseException() throws ChessMasterException {
+ String inputString = "h2dasd h4";
+ ChessBoard emptyBoard = loadChessBoard();
+ assertThrows(ParseCoordinateException.class, () -> {
+ Parser.parseMove(inputString, emptyBoard, true);
+ });
+ }
+
+ @Test
+ public void testParseMove_inputCannotParseDest_expectParseException() throws ChessMasterException {
+ String inputString = "h2 h4dafsd";
+ ChessBoard emptyBoard = loadChessBoard();
+ assertThrows(ParseCoordinateException.class, () -> {
+ Parser.parseMove(inputString, emptyBoard, true);
+ });
+ }
+
+ @Test
+ public void testParseMove_inputMoreThan2Inputs_expectParseException() throws ChessMasterException {
+ String inputString = "h2 h4 a3";
+ ChessBoard emptyBoard = loadChessBoard();
+ assertThrows(ParseCoordinateException.class, () -> {
+ Parser.parseMove(inputString, emptyBoard, true);
+ });
+ }
+
+ @Test
+ public void testParseMove_inputOneInput_expectParseException() throws ChessMasterException {
+ String inputString = "h2";
+ ChessBoard emptyBoard = loadChessBoard();
+ assertThrows(ParseCoordinateException.class, () -> {
+ Parser.parseMove(inputString, emptyBoard, true);
+ });
+ }
+
+ @Test
+ public void testParseMove_inputEmptyPiece_expectNullPieceException() throws ChessMasterException {
+ String inputString = "d4 d5";
+ ChessBoard emptyBoard = loadChessBoard();
+ assertThrows(NullPieceException.class, () -> {
+ Parser.parseMove(inputString, emptyBoard, true);
+ });
+ }
+
+ @Test
+ public void testParseMove_inputNotFriendly_expectMoveOpponentException() throws ChessMasterException {
+ String inputString = "a7 a6";
+ ChessBoard emptyBoard = loadChessBoard();
+ assertThrows(MoveOpponentPieceException.class, () -> {
+ Parser.parseMove(inputString, emptyBoard, true);
+ });
+ }
+}
diff --git a/src/test/java/chessmaster/parser/ParsePlayerColorTest.java b/src/test/java/chessmaster/parser/ParsePlayerColorTest.java
new file mode 100644
index 0000000000..66ab30098e
--- /dev/null
+++ b/src/test/java/chessmaster/parser/ParsePlayerColorTest.java
@@ -0,0 +1,54 @@
+package chessmaster.parser;
+
+import chessmaster.exceptions.ParseColorException;
+import chessmaster.game.Color;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ParsePlayerColorTest {
+
+ @Test
+ public void testParsePlayerColor_inputValidColor() throws ParseColorException {
+ String inputWhiteString = "WHITE";
+ Color whiteColor = Parser.parsePlayerColor(inputWhiteString);
+ assertEquals(Color.WHITE, whiteColor);
+
+ String inputBlackString = "BLACK";
+ Color blackColor = Parser.parsePlayerColor(inputBlackString);
+ assertEquals(Color.BLACK, blackColor);
+ }
+
+ @Test
+ public void testParsePlayerColor_inputEmpty_expectParseException() throws ParseColorException {
+ String inputEmptyString = "EMPTY";
+ assertThrows(ParseColorException.class, () -> {
+ Parser.parsePlayerColor(inputEmptyString);
+ });
+ }
+
+ @Test
+ public void testParsePlayerColor_inputInvalid_expectParseException() {
+ String inputColorString = "dfljasdka";
+ assertThrows(ParseColorException.class, () -> {
+ Parser.parsePlayerColor(inputColorString);
+ });
+ }
+
+ @Test
+ public void testParsePlayerColor_inputLowerCase_expectParseException() {
+ String inputColorString = "white";
+ assertThrows(ParseColorException.class, () -> {
+ Parser.parsePlayerColor(inputColorString);
+ });
+ }
+
+ @Test
+ public void testParsePlayerColor_inputEmptyString_expectParseException() {
+ String inputColorString = "";
+ assertThrows(ParseColorException.class, () -> {
+ Parser.parsePlayerColor(inputColorString);
+ });
+ }
+}
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/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/historyCommand_noMoves.txt b/src/test/resources/historyCommand_noMoves.txt
new file mode 100644
index 0000000000..87ee1ae1ac
--- /dev/null
+++ b/src/test/resources/historyCommand_noMoves.txt
@@ -0,0 +1,49 @@
+_________________________________________________________________
+
+Thank you for choosing ChessMaster! Here are the commands that you can use:
+Move piece - Input coordinate of piece, followed by coordinate to move to
+ Format: [column][row] [column][row]
+ E.g. a2 a3
+Show board - Shows the current state of the chess board
+ Format: show
+Show available moves - Lists all the available moves for a piece at a coordinate
+ Format: moves [column][row]
+ E.g. moves a2
+Abort game - Exit programme
+ Format: abort
+Obtain rules - Obtain a quick refresher on the rules of chess
+ Format: rules
+Obtain help - Show a list of commands and what they do
+ Format: help
+_________________________________________________________________
+
+ (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)
+
+_________________________________________________________________
+
+No moves have been played yet!
+_________________________________________________________________
+
+_________________________________________________________________
+
+Exiting program... Thanks for playing!
+_________________________________________________________________
+
diff --git a/src/test/resources/historyCommand_twoMovesWhiteStarts.txt b/src/test/resources/historyCommand_twoMovesWhiteStarts.txt
new file mode 100644
index 0000000000..5d21453514
--- /dev/null
+++ b/src/test/resources/historyCommand_twoMovesWhiteStarts.txt
@@ -0,0 +1,102 @@
+_________________________________________________________________
+
+Thank you for choosing ChessMaster! Here are the commands that you can use:
+Move piece - Input coordinate of piece, followed by coordinate to move to
+ Format: [column][row] [column][row]
+ E.g. a2 a3
+Show board - Shows the current state of the chess board
+ Format: show
+Show available moves - Lists all the available moves for a piece at a coordinate
+ Format: moves [column][row]
+ E.g. moves a2
+Abort game - Exit programme
+ Format: abort
+Obtain rules - Obtain a quick refresher on the rules of chess
+ Format: rules
+Obtain help - Show a list of commands and what they do
+ Format: help
+_________________________________________________________________
+
+ (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)
+
+_________________________________________________________________
+
+You moved Pawn from a2 to a3
+_________________________________________________________________
+
+ (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) |\u001B[43m(p)\u001B[0m| | | | | | | | (3)
+ _________________________________
+ (2) |\u001B[43m( )\u001B[0m| 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)
+
+ChessMaster is thinking of a move...
+_________________________________________________________________
+
+ChessMaster moved Knight from g8 to f6
+_________________________________________________________________
+
+ (a) (b) (c) (d) (e) (f) (g) (h)
+ _________________________________
+ (8) | R | N | B | Q | K | B |\u001B[43m( )\u001B[0m| R | (8)
+ _________________________________
+ (7) | P | P | P | P | P | P | P | P | (7)
+ _________________________________
+ (6) | | | | | |\u001B[43m(N)\u001B[0m| | | (6)
+ _________________________________
+ (5) | | | | | | | | | (5)
+ _________________________________
+ (4) | | | | | | | | | (4)
+ _________________________________
+ (3) | p | | | | | | | | (3)
+ _________________________________
+ (2) | | 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)
+
+_________________________________________________________________
+
+Move 1: WHITE moves Pawn from a2 to a3
+Move 2: BLACK moves Knight from g8 to f6
+
+_________________________________________________________________
+
+_________________________________________________________________
+
+Exiting program... Thanks for playing!
+_________________________________________________________________
+
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
diff --git a/text-ui-test/.gradle/file-system.probe b/text-ui-test/.gradle/file-system.probe
new file mode 100644
index 0000000000..f0ecac35dc
Binary files /dev/null and b/text-ui-test/.gradle/file-system.probe differ
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 892cb6cae7..8c6962e19e 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,9 +1,116 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
-What is your name?
-Hello James Gosling
+_________________________________________________________________
+
+Hey there, chess geek! You have stumbled upon the one and only:
+ ________ __ ___ __
+ / ____/ /_ ___ __________ / |/ /___ ______/ /____ _____
+ / / / __ \/ _ \/ ___/ ___/ / /|_/ / __ `/ ___/ __/ _ \/ ___/
+ / /___/ / / / __(__ |__ ) / / / / /_/ (__ ) /_/ __/ /
+ \____/_/ /_/\___/____/____/ /_/ /_/\__,_/____/\__/\___/_/
+
+where CHESS becomes an exciting journey of strategy and skill!
+_________________________________________________________________
+
+_________________________________________________________________
+
+No valid previous game found. Starting new chess game...
+_________________________________________________________________
+
+Choose your starting color to start new game! [b/w] Invalid input! Please enter either 'b' for Black or 'w' for White: _________________________________________________________________
+
+Great! Starting new game as WHITE
+_________________________________________________________________
+
+Choose difficulty level [1/2/3/4]: _________________________________________________________________
+
+Thank you for choosing ChessMaster! Here are the commands that you can use:
+Move piece - Input coordinate of piece, followed by coordinate to move to
+ Format: [column][row] [column][row]
+ E.g. a2 a3
+Show board - Shows the current state of the chess board
+ Format: show
+Show available moves - Lists all the available moves for a piece at a coordinate
+ Format: moves [column][row]
+ E.g. moves a2
+Abort game - Exit programme
+ Format: abort
+Obtain rules - Obtain a quick refresher on the rules of chess
+ Format: rules
+Obtain help - Show a list of commands and what they do
+ Format: help
+_________________________________________________________________
+
+ (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)
+
+_________________________________________________________________
+
+You moved Pawn from d2 to d4
+_________________________________________________________________
+
+ (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) | | | |[43m(p)[0m| | | | | (4)
+ _________________________________
+ (3) | | | | | | | | | (3)
+ _________________________________
+ (2) | p | p | p |[43m( )[0m| p | p | p | p | (2)
+ _________________________________
+ (1) | r | n | b | q | k | b | n | r | (1)
+ _________________________________
+ (a) (b) (c) (d) (e) (f) (g) (h)
+
+_________________________________________________________________
+
+ChessMaster moved Pawn from e7 to e5
+_________________________________________________________________
+
+ (a) (b) (c) (d) (e) (f) (g) (h)
+ _________________________________
+ (8) | R | N | B | Q | K | B | N | R | (8)
+ _________________________________
+ (7) | P | P | P | P |[43m( )[0m| P | P | P | (7)
+ _________________________________
+ (6) | | | | | | | | | (6)
+ _________________________________
+ (5) | | | | |[43m(P)[0m| | | | (5)
+ _________________________________
+ (4) | | | | p | | | | | (4)
+ _________________________________
+ (3) | | | | | | | | | (3)
+ _________________________________
+ (2) | 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)
+
+_________________________________________________________________
+
+Exiting program... Thanks for playing!
+_________________________________________________________________
+
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index f6ec2e9f95..68a018ad96 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -1 +1,6 @@
-James Gosling
\ No newline at end of file
+n
+w
+4
+d2 d4
+abort
+