370 lines
13 KiB
Java
370 lines
13 KiB
Java
package chess.view.consolerender;
|
|
|
|
import chess.controller.Command;
|
|
import chess.controller.CommandExecutor;
|
|
import chess.controller.CommandSender;
|
|
import chess.controller.commands.*;
|
|
import chess.controller.commands.PromoteCommand.PromoteType;
|
|
import chess.controller.event.GameAdapter;
|
|
import chess.model.Color;
|
|
import chess.model.Coordinate;
|
|
import chess.model.Move;
|
|
import chess.model.Piece;
|
|
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Scanner;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
|
|
/**
|
|
* Console renderer.
|
|
*/
|
|
|
|
public class Console extends GameAdapter implements CommandSender {
|
|
private final Scanner scanner = new Scanner(System.in);
|
|
private final CommandExecutor commandExecutor;
|
|
private final ConsolePieceName consolePieceName = new ConsolePieceName();
|
|
private boolean captureInput;
|
|
private final ExecutorService executor;
|
|
|
|
public Console(CommandExecutor commandExecutor, boolean captureInput) {
|
|
this.commandExecutor = commandExecutor;
|
|
this.executor = Executors.newSingleThreadExecutor();
|
|
this.captureInput = captureInput;
|
|
}
|
|
|
|
public Console(CommandExecutor commandExecutor) {
|
|
this(commandExecutor, true);
|
|
}
|
|
|
|
/**
|
|
* Translate a string containing chess coordinates (such as "a1" or "d6") to coordinates.
|
|
* @param coordinates the string to translate
|
|
* @return the coordinates of the cell
|
|
* @throws Exception if the string is not valid
|
|
*/
|
|
public Coordinate stringToCoordinate(String coordinates) throws Exception {
|
|
char xPos = coordinates.charAt(0);
|
|
char yPos = coordinates.charAt(1);
|
|
int x;
|
|
if (xPos >= 'A' && xPos <= 'Z') {
|
|
x = xPos - 'A';
|
|
} else if (xPos >= 'a' && xPos <= 'z') {
|
|
x = xPos - 'a';
|
|
} else {
|
|
throw new Exception("Invalid input");
|
|
}
|
|
if (!(yPos >= '1' && yPos <= '9')) {
|
|
throw new Exception("Invalid input");
|
|
}
|
|
int y = Coordinate.VALUE_MAX - 1 - (yPos - '1');
|
|
return new Coordinate(x, y);
|
|
}
|
|
|
|
/**
|
|
* Open a dialog so the user can, during their turn, move a piece, show move previews, or surrender.
|
|
*/
|
|
@Override
|
|
public void onPlayerTurn(Color color, boolean undone) {
|
|
if (!captureInput)
|
|
return;
|
|
System.out.println(Colors.RED + "Player turn: " + color + Colors.RESET);
|
|
this.executor.submit(() -> {
|
|
boolean endTurn;
|
|
String line = "0";
|
|
do {
|
|
if (!line.isEmpty()) {
|
|
System.out.println("""
|
|
Pick your choice:
|
|
1 - Move
|
|
2 - Show potential moves
|
|
3 - Surrender
|
|
""");
|
|
System.out.flush();
|
|
}
|
|
line = scanner.nextLine();
|
|
endTurn = switch (line) {
|
|
case "1" -> playerPickedMove(color);
|
|
case "2" -> playerPickedShowMoves(color);
|
|
case "3" -> playerPickedSurrender(color);
|
|
default -> false;
|
|
};
|
|
} while (!endTurn);
|
|
});
|
|
}
|
|
|
|
private boolean playerPickedSurrender(Color color) {
|
|
sendSurrender(color);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Ask the user to pick a move
|
|
* @param color the color of the player
|
|
* @return true if there has been a move, false otherwise
|
|
*/
|
|
public boolean playerPickedMove(Color color) {
|
|
try {
|
|
System.out.println("Piece to move, or \"castling\" for a castling");
|
|
String answer = scanner.nextLine();
|
|
if (answer.equalsIgnoreCase("castling")) {
|
|
return onAskedCastling();
|
|
}
|
|
Coordinate start = stringToCoordinate(answer);
|
|
System.out.println("New position: ");
|
|
Coordinate end = stringToCoordinate(scanner.nextLine());
|
|
Command.CommandResult result = sendMove(new Move(start, end));
|
|
|
|
return switch (Objects.requireNonNull(result)) {
|
|
case Command.CommandResult.Moved, Command.CommandResult.ActionNeeded -> true;
|
|
default -> false;
|
|
};
|
|
|
|
} catch (Exception e) {
|
|
System.out.println(e.getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ask the user to pick a piece, and show its potential moves
|
|
* @param color
|
|
* @return
|
|
*/
|
|
private boolean playerPickedShowMoves(Color color) {
|
|
try {
|
|
System.out.println("Piece to examine: ");
|
|
Coordinate piece = stringToCoordinate(scanner.nextLine());
|
|
List<Coordinate> allowedMoves = getPieceAllowedMoves(piece);
|
|
if (allowedMoves.isEmpty()) {
|
|
System.out.println("No moves allowed for this piece.");
|
|
return false;
|
|
}
|
|
displayMoves(piece, allowedMoves);
|
|
return false;
|
|
} catch (Exception e) {
|
|
System.out.println(e.getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onWin(Color color) {
|
|
System.out.println(Colors.RED + "Victory of player " + color + Colors.RESET);
|
|
}
|
|
|
|
@Override
|
|
public void onKingInCheck() {
|
|
System.out.println(Colors.RED + "Check!" + Colors.RESET);
|
|
}
|
|
|
|
@Override
|
|
public void onKingInMat() {
|
|
System.out.println(Colors.RED + "Checkmate!" + Colors.RESET);
|
|
}
|
|
|
|
@Override
|
|
public void onPatSituation() {
|
|
System.out.println("Pat! It's a draw!");
|
|
}
|
|
|
|
@Override
|
|
public void onSurrender(Color color) {
|
|
System.out.println("The " + color + " player has surrendered!");
|
|
}
|
|
|
|
@Override
|
|
public void onGameStart() {
|
|
System.out.println("Game start!");
|
|
onBoardUpdate();
|
|
}
|
|
|
|
@Override
|
|
public void onGameEnd() {
|
|
System.out.println("Thank you for playing!");
|
|
this.commandExecutor.close();
|
|
this.executor.shutdown();
|
|
}
|
|
|
|
/**
|
|
* Open the dialog to promote a pawn.
|
|
* @param pieceCoords the coordinates of the pawn to promote
|
|
*/
|
|
@Override
|
|
public void onPromotePawn(Coordinate pieceCoords) {
|
|
System.out.println("The pawn on the " + pieceCoords + " coordinates needs to be promoted.");
|
|
System.out.println("Enter 'B' to promote it into a Bishop, 'N' for a Knight, 'Q' for a Queen, 'R' for a Rook.");
|
|
System.out.flush();
|
|
this.executor.submit(() -> {
|
|
PromoteType newPiece;
|
|
boolean valid = false;
|
|
do {
|
|
try {
|
|
String promotion = scanner.next();
|
|
newPiece = switch (promotion) {
|
|
case "B", "b", "Bishop", "bishop" -> PromoteType.Bishop;
|
|
case "N", "n", "Knight", "knight" -> PromoteType.Knight;
|
|
case "Q", "q", "Queen", "queen" -> PromoteType.Queen;
|
|
case "R", "r", "Rook", "rook" -> PromoteType.Rook;
|
|
default -> throw new Exception();
|
|
};
|
|
valid = true;
|
|
sendPawnPromotion(newPiece);
|
|
} catch (Exception e) {
|
|
System.out.println("Invalid input!");
|
|
}
|
|
} while (!valid);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update and print the board in the console.
|
|
*/
|
|
@Override
|
|
public void onBoardUpdate() {
|
|
if (!this.captureInput)
|
|
return;
|
|
StringBuilder string = new StringBuilder();
|
|
string.append(" a b c d e f g h \n");
|
|
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
string.append(8 - i).append(" ");
|
|
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
Piece p = getPieceAt(new Coordinate(j, i));
|
|
if ((i + j) % 2 == 0) {
|
|
string.append(Colors.LIGHT_GRAY_BACKGROUND);
|
|
} else {
|
|
string.append(Colors.DARK_GRAY_BACKGROUND);
|
|
}
|
|
if (p == null) {
|
|
string.append(" " + Colors.RESET);
|
|
} else {
|
|
string.append(" ").append(consolePieceName.getString(p)).append(" ").append(Colors.RESET);
|
|
}
|
|
}
|
|
string.append("\n");
|
|
}
|
|
System.out.println(string);
|
|
System.out.flush();
|
|
}
|
|
|
|
/**
|
|
* Display the possible moves of a piece.
|
|
* @param piece
|
|
* @param moves
|
|
*/
|
|
public void displayMoves(Coordinate piece, List<Coordinate> moves) {
|
|
StringBuilder string = new StringBuilder();
|
|
string.append(" a b c d e f g h \n");
|
|
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
string.append(8 - i).append(" ");
|
|
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
Coordinate currentCell = new Coordinate(j, i);
|
|
Piece p = getPieceAt(new Coordinate(j, i));
|
|
if (moves.contains(currentCell)) {
|
|
string.append(Colors.YELLOW_BACKGROUND);
|
|
} else {
|
|
if ((i + j) % 2 == 0) {
|
|
string.append(Colors.LIGHT_GRAY_BACKGROUND);
|
|
} else {
|
|
string.append(Colors.DARK_GRAY_BACKGROUND);
|
|
}
|
|
}
|
|
if (p == null) {
|
|
string.append(" " + Colors.RESET);
|
|
} else {
|
|
if (currentCell.equals(piece)) {
|
|
string.append(Colors.RED_BACKGROUND).append(" ").append(consolePieceName.getString(p))
|
|
.append(" ").append(Colors.RESET);
|
|
} else {
|
|
string.append(" ").append(consolePieceName.getString(p)).append(" ").append(Colors.RESET);
|
|
}
|
|
}
|
|
}
|
|
string.append("\n");
|
|
}
|
|
System.out.println(string);
|
|
}
|
|
|
|
@Override
|
|
public void onMoveNotAllowed(Move move) {
|
|
System.out.println("Move not allowed.");
|
|
}
|
|
|
|
@Override
|
|
public void onDraw() {
|
|
System.out.println("Repeated positions!");
|
|
}
|
|
|
|
/**
|
|
* Open different dialogs according to which castling is allowed.
|
|
* @return true if a castling was played, false otherwise
|
|
*/
|
|
private boolean onAskedCastling() {
|
|
return switch (getAllowedCastlings()) {
|
|
case Small -> onSmallCastling();
|
|
case Big -> onBigCastling();
|
|
case Both -> onBothCastling();
|
|
default -> {
|
|
System.out.println("No castling allowed.");
|
|
yield false;
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Ask the user to confirm a small castling.
|
|
* @return true if the castling was played, false otherwise
|
|
*/
|
|
private boolean onSmallCastling() {
|
|
System.out.println("Small castling allowed. Confirm with \"y\":");
|
|
String answer = scanner.nextLine();
|
|
if (!(answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("yes"))) {
|
|
return false;
|
|
} else {
|
|
return (commandExecutor.executeCommand(new CastlingCommand(false)) != Command.CommandResult.Moved);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ask the user to confirm a big castling.
|
|
* @return true if the castling was played, false otherwise
|
|
*/
|
|
private boolean onBigCastling() {
|
|
System.out.println("Big castling allowed. Confirm with \"y\":");
|
|
String answer = scanner.nextLine();
|
|
if (!(answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("yes"))) {
|
|
return false;
|
|
} else {
|
|
return (commandExecutor.executeCommand(new CastlingCommand(true)) != Command.CommandResult.Moved);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ask the user to pick a castling when both are allowed.
|
|
* @return true if a castling was played, false otherwise
|
|
*/
|
|
private boolean onBothCastling() {
|
|
System.out.println("Both castlings allowed. Pick \"s\" to play a castling, \"b\" to play a big castling.");
|
|
String answer = scanner.nextLine();
|
|
return switch (answer) {
|
|
case "s", "S", "small", "Small", "castling", "normal", "Normal" ->
|
|
(commandExecutor.executeCommand(new CastlingCommand(false)) != Command.CommandResult.Moved);
|
|
case "b", "B", "big", "Big", "big castling", "Big castling" ->
|
|
(commandExecutor.executeCommand(new CastlingCommand(true)) != Command.CommandResult.Moved);
|
|
default -> false;
|
|
};
|
|
}
|
|
|
|
public void setCaptureInput(boolean captureInput) {
|
|
this.captureInput = captureInput;
|
|
}
|
|
|
|
@Override
|
|
public CommandExecutor getCommandExecutor() {
|
|
return this.commandExecutor;
|
|
}
|
|
|
|
|
|
}
|