diff --git a/README.md b/README.md index 1e9660d7ca5..1ec10432b64 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,84 @@ # java-chess -## 체스 게임 설명 +### 입력 요구사항 -- 체스 게임은 화이트 팀과 블랙 팀으로 나뉜다 -- 체스 게임이 시작되면 화이트 팀부터 한 수씩 턴이 진행된다 +- [x] 체스 게임을 시작하는 명령어를 입력받을 수 있다. +- [x] 체스 게임을 종료하는 명령어를 입력받을 수 있다. +- [x] 체스 말을 이동하는 명령어를 입력받을 수 있다. +- [x] 각 진영의 점수를 확인하는 명령어를 입력받을 수 있다. +- [x] 명령어 입력시 형식에 맞는 입력이 들어오지 않으면 예외를 발생시킨다. -## 1-2단계 요구사항 +### 출력 요구사항 -콘솔 UI에서 체스 게임을 할 수 있는 기능을 구현한다. -1단계는 체스 게임을 할 수 있는 체스판을 초기화한다. -체스판에서 말의 위치 값은 가로 위치는 왼쪽부터 a ~ h이고, 세로는 아래부터 위로 1 ~ 8로 구현한다. +- [x] 체스판의 초기 상태를 출력할 수 있다 +- [x] 매 턴마다 최신화된 체스판의 상태를 출력할 수 있다 +- [x] 체스판에서 각 진영은 대문자(검은색)와 소문자(흰색)로 출력한다. -### 입력 요구사항 +### 도메인 요구사항 + +#### 기물 + +- [x] 기물들은 화이트 또는 블랙의 팀을 가진다. +- [x] 기물들은 각각의 행마법에 따라 이동할 수 있다 + +``` +- 룩은 직선으로 이동할 수 있다 +- 비숍은 대각선으로 이동할 수 있다 +- 퀸은 직선/대각선으로 이동할 수 있다 +- 룩과 비숍, 퀸은 이동하는 경로에 기물이 존재하면 이동할 수 없다. +- 킹은 방향과 무관하게 1칸 이동할 수 있다 +- 나이트는 알파벳 L자 모양으로 앞으로 두칸 이동한 다음 왼쪽, 오른쪽 으로 한칸 움직일 수 있다 +- 폰은 초기 상태에서 한칸 또는 두칸씩 전진할 수 있다 +- 폰은 초기 상태에서 움직인 이후에는 한칸씩만 전진할 수 있다 +- 폰은 상대 기물이 한칸 대각선에 있다면, 이동할 수 있다 +- 모든 기물은 도착지에 같은 팀의 기물이 있다면 이동할 수 없다 +``` + +#### 체스 보드 초기화 -- [x] 체스 게임을 시작할지 여부를 입력받을 수 있다. -- [x] 체스 게임을 종료할지 여부를 입력받을 수 있다. -- [x] 체스 말의 이동정보를 입력받을 수 있다 +- [x] 게임 시작시 체스판을 초기화할 수 있다. +- [x] 체스판의 초기상태는 다음과 같다. ``` - move b2 b3 + RNBQKBNR + PPPPPPPP + ........ + ........ + ........ + ........ + pppppppp + rnbqkbnr ``` - - [x] 요구하는 형식의 입력이 들어오지 않으면 예외를 발생시킨다. +- [x] 체스판의 열은 왼쪽부터 a-h까지의 알파벳으로 이루어져 있다. +- [x] 체스판의 행은 밑부터 1-8까지의 숫자로 이루어져 있다. -### 출력 요구사항 +#### 체스 보드 -- [x] 체스판의 상태를 출력할 수 있다. - - [x] 체스판의 초기 상태를 출력할 수 있다 - - [x] 매 턴마다 최신화된 체스판의 상태를 출력할 수 있다 -- [x] 체스판에서 각 진영은 대문자(검은색)와 소문자(흰색)로 출력한다. +- [x] 체스보드 상태를 업데이트 할 수 있다 +- [x] 기물들의 행마법을 검증할 수 있다 +- [x] 기물들을 이동시킬 수 있다. +- [x] 기물들의 위치는 체스보드 범위를 벗어날 수 없다. +- [x] 기물들이 이동할 수 없다면 기물의 위치를 옮기지 않는다 -### 도메인 요구사항 +#### 체스 게임 + +- [x] 체스 게임은 백팀의 이동으로 시작해야 한다. +- [x] 기물의 움직임 마다 자신의 턴인지 검증할 수 있어야 한다. +- [ ] 킹이 잡힌 경우 게임을 끝낼 수 있어야 한다. + +#### 점수 계산 + +- [x] 체스 프로그램에서 현재까지 남아 있는 말에 따라 점수를 계산할 수 있어야 한다. + +``` +Queen은 9점 +Rook은 5점 +Bishop은 3점 +Knight는 2.5점 +Pawn의 기본 점수는 1점, 같은 세로줄에 같은 색의 폰이 있는 경우는 0.5점으로 계산 +King은 점수가 없음 (잡히는 경우 게임 끝) +``` + +#### 이어 하기 -- [x] 체스 보드 초기화 - - [x] 게임 시작시 체스판을 초기화할 수 있다. - - [x] 체스판의 초기상태는 다음과 같다. - ``` - RNBQKBNR - PPPPPPPP - ........ - ........ - ........ - ........ - pppppppp - rnbqkbnr - ``` - - [x] 체스판의 열은 왼쪽부터 a-h까지의 알파벳으로 이루어져 있다. - - [x] 체스판의 행은 밑부터 1-8까지의 숫자로 이루어져 있다. - - [x] 기물들은 화이트 또는 블랙의 색깔을 가진다. - - - [x] 기물 이동 - - [x] 기물들의 위치는 체스보드 범위를 벗어날 수 없다. - - [x] 기물들은 각자의 행마법을 따라 이동할 수 있다 - - [x] 룩은 직선으로 이동할 수 있다 - - [x] 비숍은 대각선으로 이동할 수 있다 - - [x] 퀸은 직선/대각선으로 이동할 수 있다 - - [x] 킹은 방향과 무관하게 1칸 이동할 수 있다 - - [x] 나이트는 알파벳 L자 모양으로 앞으로 두칸 이동한 다음 왼쪽, 오른쪽 으로 한칸 움직일 수 있다 - - [x] 폰의 행마법 - - [x] 폰은 초기 상태에서 한칸 또는 두칸씩 전진할 수 있다 - - [x] 폰은 초기 상태에서 움직인 이후에는 한칸씩만 전진할 수 있다 - - [x] 상대 기물이 한칸 대각선에 있다면, 이동할 수 있다 - - - [x] 체스 보드 - - [x] 체스보드 상태를 업데이트 할 수 있다 - - [x] 기물들이 이동할 수 있다면 기물의 위치를 옮긴다 - - [x] 기물들이 이동할 수 없다면 기물의 위치를 옮기지 않는다 - - [x] 도착지에 같은 팀의 기물이 있다면 이동할 수 없다 - - [x] 비숍/룩/퀸은 이동 경로에 다른 기물이 있다면 이동할 수 없다 - +- [ ] 애플리케이션을 재시작하더라도 이전에 하던 체스 게임을 다시 시작할 수 있어야 한다. diff --git a/build.gradle b/build.gradle index 3697236c6fb..041505beca4 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,9 @@ dependencies { testImplementation platform('org.assertj:assertj-bom:3.25.1') testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.assertj:assertj-core') + testImplementation 'org.mockito:mockito-core:3.6.28' + + runtimeOnly("com.mysql:mysql-connector-j:8.3.0") } java { diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000000..558a1d5a53f --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.9" +services: + db: + image: mysql:8.0.28 + platform: linux/x86_64 + restart: always + ports: + - "13306:3306" + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: chess + MYSQL_USER: user + MYSQL_PASSWORD: password + TZ: Asia/Seoul + volumes: + - ./db/mysql/data:/var/lib/mysql + - ./db/mysql/config:/etc/mysql/conf.d + - ./db/mysql/init:/docker-entrypoint-initdb.d diff --git a/src/main/java/chess/ChessApplication.java b/src/main/java/chess/ChessApplication.java index cbba067f7f9..16b30cb9178 100644 --- a/src/main/java/chess/ChessApplication.java +++ b/src/main/java/chess/ChessApplication.java @@ -1,6 +1,10 @@ package chess; import chess.controller.ChessGameController; +import chess.repository.ConnectionManager; +import chess.repository.PieceRepository; +import chess.repository.TurnRepository; +import chess.service.ChessGameService; import chess.view.CommandParser; import chess.view.InputView; import chess.view.OutputView; @@ -9,8 +13,11 @@ public class ChessApplication { public static void main(String[] args) { InputView inputView = new InputView(new CommandParser()); OutputView outputView = new OutputView(); + PieceRepository pieceRepository = new PieceRepository(new ConnectionManager()); + TurnRepository turnRepository = new TurnRepository(new ConnectionManager()); + ChessGameService chessGameService = new ChessGameService(pieceRepository, turnRepository); - ChessGameController controller = new ChessGameController(inputView, outputView); + ChessGameController controller = new ChessGameController(inputView, outputView, chessGameService); controller.run(); } } diff --git a/src/main/java/chess/controller/ChessGameController.java b/src/main/java/chess/controller/ChessGameController.java index 4d4c34956e9..84fd4254ab0 100644 --- a/src/main/java/chess/controller/ChessGameController.java +++ b/src/main/java/chess/controller/ChessGameController.java @@ -1,38 +1,31 @@ package chess.controller; import chess.controller.command.Command; -import chess.domain.ChessGame; -import chess.domain.board.ChessBoard; -import chess.domain.board.ChessBoardCreator; +import chess.controller.command.ExecuteResult; +import chess.service.ChessGameService; import chess.view.InputView; import chess.view.OutputView; public class ChessGameController { private final InputView inputView; private final OutputView outputView; + private final ChessGameService chessGameService; - public ChessGameController(InputView inputView, OutputView outputView) { + public ChessGameController(InputView inputView, OutputView outputView, ChessGameService chessGameService) { this.inputView = inputView; this.outputView = outputView; + this.chessGameService = chessGameService; } public void run() { outputView.printStartMessage(); - Command command = inputView.readCommand(); - if (command.isNotStartCommand()) { - return; - } + chessGameService.startChessGame(); - ChessGame chessGame = initializeChessGame(); - while (command.isNotEndCommand()) { - command.execute(chessGame, outputView); - command = inputView.readCommand(); + ExecuteResult result; + do { + Command command = inputView.readCommand(); + result = command.execute(chessGameService, outputView); } - } - - private ChessGame initializeChessGame() { - ChessBoardCreator chessBoardCreator = new ChessBoardCreator(); - ChessBoard chessBoard = chessBoardCreator.create(); - return new ChessGame(chessBoard); + while (result.isSuccess() && result.isNeedNextCommand() && chessGameService.isChessGameNotEnd()); } } diff --git a/src/main/java/chess/controller/command/Command.java b/src/main/java/chess/controller/command/Command.java index 0ed9399be92..1dc2a4aaaa6 100644 --- a/src/main/java/chess/controller/command/Command.java +++ b/src/main/java/chess/controller/command/Command.java @@ -1,12 +1,8 @@ package chess.controller.command; -import chess.domain.ChessGame; +import chess.service.ChessGameService; import chess.view.OutputView; public interface Command { - void execute(ChessGame chessGame, OutputView outputView); - - boolean isNotEndCommand(); - - boolean isNotStartCommand(); + ExecuteResult execute(ChessGameService chessGameService, OutputView outputView); } diff --git a/src/main/java/chess/controller/command/EndCommand.java b/src/main/java/chess/controller/command/EndCommand.java index 7d91d8c77af..f23eacb5330 100644 --- a/src/main/java/chess/controller/command/EndCommand.java +++ b/src/main/java/chess/controller/command/EndCommand.java @@ -1,20 +1,11 @@ package chess.controller.command; -import chess.domain.ChessGame; +import chess.service.ChessGameService; import chess.view.OutputView; public class EndCommand implements Command { @Override - public void execute(ChessGame chessGame, OutputView outputView) { - } - - @Override - public boolean isNotEndCommand() { - return false; - } - - @Override - public boolean isNotStartCommand() { - return true; + public ExecuteResult execute(ChessGameService chessGameService, OutputView outputView) { + return new ExecuteResult(true, false); } } diff --git a/src/main/java/chess/controller/command/ExecuteResult.java b/src/main/java/chess/controller/command/ExecuteResult.java new file mode 100644 index 00000000000..b9b8371d19a --- /dev/null +++ b/src/main/java/chess/controller/command/ExecuteResult.java @@ -0,0 +1,19 @@ +package chess.controller.command; + +public class ExecuteResult { + private final boolean success; + private final boolean needNextCommand; + + public ExecuteResult(boolean success, boolean needNextCommand) { + this.success = success; + this.needNextCommand = needNextCommand; + } + + public boolean isSuccess() { + return success; + } + + public boolean isNeedNextCommand() { + return needNextCommand; + } +} diff --git a/src/main/java/chess/controller/command/MoveCommand.java b/src/main/java/chess/controller/command/MoveCommand.java index 8b333094a90..b3dbae31b3f 100644 --- a/src/main/java/chess/controller/command/MoveCommand.java +++ b/src/main/java/chess/controller/command/MoveCommand.java @@ -1,7 +1,8 @@ package chess.controller.command; -import chess.domain.ChessGame; import chess.domain.position.Position; +import chess.dto.BoardDto; +import chess.service.ChessGameService; import chess.view.OutputView; public class MoveCommand implements Command { @@ -14,18 +15,9 @@ public MoveCommand(Position start, Position destination) { } @Override - public void execute(ChessGame chessGame, OutputView outputView) { - chessGame.move(start, destination); - outputView.printChessBoardMessage(chessGame.getChessBoard()); - } - - @Override - public boolean isNotEndCommand() { - return true; - } - - @Override - public boolean isNotStartCommand() { - return true; + public ExecuteResult execute(ChessGameService chessGameService, OutputView outputView) { + BoardDto boardDto = chessGameService.movePiece(start, destination); + outputView.printChessBoardMessage(boardDto); + return new ExecuteResult(true, true); } } diff --git a/src/main/java/chess/controller/command/StartCommand.java b/src/main/java/chess/controller/command/StartCommand.java index 1ad36bfc2c1..270a7610ed0 100644 --- a/src/main/java/chess/controller/command/StartCommand.java +++ b/src/main/java/chess/controller/command/StartCommand.java @@ -1,21 +1,14 @@ package chess.controller.command; -import chess.domain.ChessGame; +import chess.dto.BoardDto; +import chess.service.ChessGameService; import chess.view.OutputView; public class StartCommand implements Command { @Override - public void execute(ChessGame chessGame, OutputView outputView) { - outputView.printChessBoardMessage(chessGame.getChessBoard()); - } - - @Override - public boolean isNotEndCommand() { - return true; - } - - @Override - public boolean isNotStartCommand() { - return false; + public ExecuteResult execute(ChessGameService chessGameService, OutputView outputView) { + BoardDto boardDto = chessGameService.startChessGame(); + outputView.printChessBoardMessage(boardDto); + return new ExecuteResult(true, true); } } diff --git a/src/main/java/chess/controller/command/StatusCommand.java b/src/main/java/chess/controller/command/StatusCommand.java new file mode 100644 index 00000000000..271ac782209 --- /dev/null +++ b/src/main/java/chess/controller/command/StatusCommand.java @@ -0,0 +1,15 @@ +package chess.controller.command; + +import chess.dto.ScoreStatusDto; +import chess.service.ChessGameService; +import chess.view.OutputView; + +public class StatusCommand implements Command { + + @Override + public ExecuteResult execute(ChessGameService chessGameService, OutputView outputView) { + ScoreStatusDto scoreStatusDto = chessGameService.calculateScoreStatus(); + outputView.printStatusMessage(scoreStatusDto); + return new ExecuteResult(true, true); + } +} diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index c7b8ca7ecdf..b64a54edad8 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -1,38 +1,66 @@ package chess.domain; import chess.domain.board.ChessBoard; +import chess.domain.board.ChessBoardCreator; import chess.domain.piece.Piece; +import chess.domain.piece.Score; import chess.domain.piece.Team; import chess.domain.position.Position; public class ChessGame { private static final Team INITIAL_TURN = Team.WHITE; + private static final double PAWN_SCORE_WEIGHT = -0.5; private final ChessBoard chessBoard; private Team turn; public ChessGame(ChessBoard chessBoard) { + this(chessBoard, INITIAL_TURN); + } + + public ChessGame(ChessBoard chessBoard, Team turn) { this.chessBoard = chessBoard; - turn = INITIAL_TURN; + this.turn = turn; + } + + public static ChessGame createNewChessGame() { + ChessBoardCreator chessBoardCreator = new ChessBoardCreator(); + ChessBoard chessBoard = chessBoardCreator.create(); + return new ChessGame(chessBoard); } public void move(Position start, Position destiantion) { - validatePieceExist(start); validateTurn(chessBoard.findPieceByPosition(start)); - validateLegalMovement(start, destiantion); - chessBoard.move(start, destiantion); turn = turn.otherTeam(); } + public Score calculateTeamScore(Team team) { + Score defaultScore = chessBoard.calcualteDefaultScore(team); + int sameFilePawnCount = chessBoard.countSameFilePawn(team); + Score weight = new Score(sameFilePawnCount * PAWN_SCORE_WEIGHT); + return defaultScore.add(weight); + } + + public Team selectHigherScoreTeam() { + Score blackScore = calculateTeamScore(Team.BLACK); + Score whiteScore = calculateTeamScore(Team.WHITE); + if (whiteScore.isAbove(blackScore)) { + return Team.WHITE; + } + return Team.BLACK; + } + + public boolean isNotEnd() { + return chessBoard.isBlackKingAlive() && chessBoard.isWhiteKingAlive(); + } + public ChessBoard getChessBoard() { return chessBoard; } - private void validatePieceExist(Position start) { - if (chessBoard.positionIsEmpty(start)) { - throw new IllegalArgumentException("움직임을 시작하는 위치에 기물이 존재하지 않습니다"); - } + public Team getTurn() { + return turn; } private void validateTurn(Piece piece) { @@ -40,10 +68,4 @@ private void validateTurn(Piece piece) { throw new IllegalArgumentException(turn + "의 차례입니다"); } } - - private void validateLegalMovement(Position start, Position destination) { - if (!chessBoard.canMove(start, destination)) { - throw new IllegalArgumentException("기물의 행마법에 어긋나는 움직임입니다"); - } - } } diff --git a/src/main/java/chess/domain/board/ChessBoard.java b/src/main/java/chess/domain/board/ChessBoard.java index 8d01f1b92ce..7ec8dd4351c 100644 --- a/src/main/java/chess/domain/board/ChessBoard.java +++ b/src/main/java/chess/domain/board/ChessBoard.java @@ -1,8 +1,12 @@ package chess.domain.board; import chess.domain.piece.Piece; +import chess.domain.piece.Score; import chess.domain.piece.Team; +import chess.domain.position.File; import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -51,4 +55,56 @@ public boolean isPathHasObstacle(List path) { public boolean isNoHostilePieceAt(Position position, Team team) { return positionIsEmpty(position) || !findPieceByPosition(position).isOtherTeam(team); } + + public Score calcualteDefaultScore(Team team) { + return board.keySet().stream() + .map(board::get) + .filter(piece -> !piece.isOtherTeam(team)) + .map(Piece::score) + .reduce(new Score(0), Score::add); + } + + public int countSameFilePawn(Team team) { + return Arrays.stream(File.values()) + .mapToInt(file -> countFriendlyPawnAtFile(team, file)) + .sum(); + } + + public boolean isBlackKingAlive() { + return Arrays.stream(File.values()) + .flatMap(file -> Arrays.stream(Rank.values()) + .map(rank -> new Position(file, rank))) + .filter(position -> !this.positionIsEmpty(position)) + .map(this::findPieceByPosition) + .filter(Piece::isKing) + .anyMatch(Piece::isBlackTeam); + } + + public boolean isWhiteKingAlive() { + return Arrays.stream(File.values()) + .flatMap(file -> Arrays.stream(Rank.values()) + .map(rank -> new Position(file, rank))) + .filter(position -> !this.positionIsEmpty(position)) + .map(this::findPieceByPosition) + .filter(Piece::isKing) + .anyMatch(piece -> piece.isOtherTeam(Team.BLACK)); + } + + private int countFriendlyPawnAtFile(Team friendlyTeam, File file) { + int count = (int) Arrays.stream(Rank.values()) + .map(rank -> new Position(file, rank)) + .filter(position -> !this.positionIsEmpty(position)) + .map(this::findPieceByPosition) + .filter(Piece::isPawn) + .filter(piece -> !piece.isOtherTeam(friendlyTeam)) + .count(); + if (count > 1) { + return count; + } + return 0; + } + + public Map getBoard() { + return board; + } } diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java index 6d3eb402455..5d75da7cc13 100644 --- a/src/main/java/chess/domain/piece/Bishop.java +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -4,6 +4,8 @@ import chess.domain.position.Position; public class Bishop extends Piece { + private static final int SCORE_VALUE = 3; + public Bishop(Team team) { super(team); } @@ -17,4 +19,19 @@ boolean canNotMoveByItsOwnInPassing(Position start, Position destination) { boolean canNotMoveByBoardStatus(Position start, Position destination, ChessBoard chessBoard) { return chessBoard.isPathHasObstacle(start.calculateSlidingPath(destination)); } + + @Override + public Score score() { + return new Score(SCORE_VALUE); + } + + @Override + public boolean isPawn() { + return false; + } + + @Override + public boolean isKing() { + return false; + } } diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java index 14f7654e039..eb2467c9486 100644 --- a/src/main/java/chess/domain/piece/King.java +++ b/src/main/java/chess/domain/piece/King.java @@ -5,6 +5,7 @@ public class King extends Piece { private static final int MOVE_DISTANCE = 1; + private static final int SCORE_VALUE = 0; public King(Team team) { super(team); @@ -19,4 +20,19 @@ boolean canNotMoveByItsOwnInPassing(Position start, Position destination) { boolean canNotMoveByBoardStatus(Position start, Position destination, ChessBoard chessBoard) { return false; } + + @Override + public Score score() { + return new Score(SCORE_VALUE); + } + + @Override + public boolean isPawn() { + return false; + } + + @Override + public boolean isKing() { + return true; + } } diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java index e90549cf69b..0d4ed5bec15 100644 --- a/src/main/java/chess/domain/piece/Knight.java +++ b/src/main/java/chess/domain/piece/Knight.java @@ -5,6 +5,7 @@ public class Knight extends Piece { private static final int L_SHAPE_SQUARED_DISTANCE = 5; + private static final double SCORE_VALUE = 2.5; public Knight(Team team) { super(team); @@ -19,4 +20,19 @@ boolean canNotMoveByItsOwnInPassing(Position start, Position destination) { boolean canNotMoveByBoardStatus(Position start, Position destination, ChessBoard chessBoard) { return false; } + + @Override + public Score score() { + return new Score(SCORE_VALUE); + } + + @Override + public boolean isPawn() { + return false; + } + + @Override + public boolean isKing() { + return false; + } } diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index 562c3402e23..5b8080250be 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -7,6 +7,7 @@ public class Pawn extends Piece { private static final int DEFAULT_MOVE_DISTANCE = 1; private static final int INITIAL_MOVE_DISTANCE = 2; + private static final int SCORE_VALUE = 1; public Pawn(Team team) { super(team); @@ -16,19 +17,15 @@ public Pawn(Team team) { @Override boolean canNotMoveByItsOwnInPassing(Position start, Position destination) { int distance = start.calculateDistance(destination); - //폰의 이동 방향이 팀의 전진 방향과 다르다면 이동할 수 없다 if (!team.isTeamForwardDirectionsContains(Direction.of(start, destination))) { return true; } - //폰은 팀의 수직 전진 방향으로 한 칸 이동할 수 있다 if (distance == DEFAULT_MOVE_DISTANCE && start.isOrthogonalWith(destination)) { return false; } - //폰은 팀의 수직 전진 방향으로 두 칸 이동할 수 있다 if (distance == INITIAL_MOVE_DISTANCE && start.isOrthogonalWith(destination)) { return false; } - // 폰은 대각선으로 한 칸 이동할 수 있다 if (distance == DEFAULT_MOVE_DISTANCE && start.isDiagonalWith(destination)) { return false; } @@ -38,22 +35,33 @@ boolean canNotMoveByItsOwnInPassing(Position start, Position destination) { @Override boolean canNotMoveByBoardStatus(Position start, Position destination, ChessBoard chessBoard) { int distance = start.calculateDistance(destination); - // 폰의 이동 경로에 기물이 있다면 이동할 수 없다 if (chessBoard.isPathHasObstacle(start.calculateSlidingPath(destination))) { return true; } - // 폰이 수직으로 이동했고 도착지에 기물이 있다면 이동할 수 없다 if (!chessBoard.positionIsEmpty(destination) && start.isOrthogonalWith(destination)) { return true; } - // 폰이 두칸 수직으로 이동할 때 초기 위치에서 움직이지 않았다면 이동할 수 없다 if (distance == INITIAL_MOVE_DISTANCE) { return !team.isPositionOnTeamInitialPawnRank(start); } - // 다른 팀의 기물이 목적지에 존재하지 않을 경우 폰은 대각선으로 이동할 수 없다. if (distance == DEFAULT_MOVE_DISTANCE && start.isDiagonalWith(destination)) { return chessBoard.isNoHostilePieceAt(destination, team); } return false; } + + @Override + public Score score() { + return new Score(SCORE_VALUE); + } + + @Override + public boolean isPawn() { + return true; + } + + @Override + public boolean isKing() { + return false; + } } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 40d2cd2eea0..ad364667da3 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -10,6 +10,12 @@ protected Piece(Team team) { this.team = team; } + public abstract boolean isPawn(); + + public abstract boolean isKing(); + + public abstract Score score(); + abstract boolean canNotMoveByItsOwnInPassing(Position start, Position destination); abstract boolean canNotMoveByBoardStatus(Position start, Position destination, ChessBoard chessBoard); @@ -47,4 +53,8 @@ public boolean isBlackTeam() { public boolean isOtherTeam(Team other) { return this.team != other; } + + public Team getTeam() { + return team; + } } diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java index 1951a302098..57196df94dd 100644 --- a/src/main/java/chess/domain/piece/Queen.java +++ b/src/main/java/chess/domain/piece/Queen.java @@ -4,6 +4,9 @@ import chess.domain.position.Position; public class Queen extends Piece { + + public static final int SCORE_VALUE = 9; + public Queen(Team team) { super(team); } @@ -17,4 +20,19 @@ boolean canNotMoveByItsOwnInPassing(Position start, Position destination) { boolean canNotMoveByBoardStatus(Position start, Position destination, ChessBoard chessBoard) { return chessBoard.isPathHasObstacle(start.calculateSlidingPath(destination)); } + + @Override + public Score score() { + return new Score(SCORE_VALUE); + } + + @Override + public boolean isPawn() { + return false; + } + + @Override + public boolean isKing() { + return false; + } } diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java index 1bef5b3080b..1544ec18b9b 100644 --- a/src/main/java/chess/domain/piece/Rook.java +++ b/src/main/java/chess/domain/piece/Rook.java @@ -4,6 +4,9 @@ import chess.domain.position.Position; public class Rook extends Piece { + + public static final int SCORE_VALUE = 5; + public Rook(Team team) { super(team); } @@ -17,4 +20,19 @@ boolean canNotMoveByItsOwnInPassing(Position start, Position destination) { boolean canNotMoveByBoardStatus(Position start, Position destination, ChessBoard chessBoard) { return chessBoard.isPathHasObstacle(start.calculateSlidingPath(destination)); } + + @Override + public Score score() { + return new Score(SCORE_VALUE); + } + + @Override + public boolean isPawn() { + return false; + } + + @Override + public boolean isKing() { + return false; + } } diff --git a/src/main/java/chess/domain/piece/Score.java b/src/main/java/chess/domain/piece/Score.java new file mode 100644 index 00000000000..a497fdc5e4b --- /dev/null +++ b/src/main/java/chess/domain/piece/Score.java @@ -0,0 +1,48 @@ +package chess.domain.piece; + +import java.util.Objects; + +public class Score { + private final double value; + + public Score(double value) { + this.value = value; + } + + public double getValue() { + return value; + } + + public Score add(Score other) { + return new Score(this.value + other.value); + } + + public Score multiply(Score other) { + return new Score(this.value * other.value); + } + + public boolean isAbove(Score other) { + return this.value > other.value; + } + + public boolean isBelow(Score other) { + return this.value < other.value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Score score = (Score) o; + return Double.compare(value, score.value) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/chess/domain/position/File.java b/src/main/java/chess/domain/position/File.java index 9f009e64686..d1e78aeae64 100644 --- a/src/main/java/chess/domain/position/File.java +++ b/src/main/java/chess/domain/position/File.java @@ -41,4 +41,8 @@ public boolean isFurtherRightThan(File other) { public File move(int weight) { return from(columnNumber + weight); } + + public int getColumnNumber() { + return columnNumber; + } } diff --git a/src/main/java/chess/domain/position/Position.java b/src/main/java/chess/domain/position/Position.java index 2bcc3db3aa2..641ece2fc28 100644 --- a/src/main/java/chess/domain/position/Position.java +++ b/src/main/java/chess/domain/position/Position.java @@ -95,6 +95,14 @@ private Position moveOneSpace(Direction direction) { return new Position(movedFile, movedRank); } + public File getFile() { + return file; + } + + public Rank getRank() { + return rank; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/chess/domain/position/Rank.java b/src/main/java/chess/domain/position/Rank.java index 6012227fb57..d6730f0d00f 100644 --- a/src/main/java/chess/domain/position/Rank.java +++ b/src/main/java/chess/domain/position/Rank.java @@ -4,20 +4,22 @@ import java.util.NoSuchElementException; public enum Rank { - EIGHT(0), - SEVEN(1), - SIX(2), - FIVE(3), - FOUR(4), - THREE(5), - TWO(6), - ONE(7); + EIGHT(0, 8), + SEVEN(1, 7), + SIX(2, 6), + FIVE(3, 5), + FOUR(4, 4), + THREE(5, 3), + TWO(6, 2), + ONE(7, 1); private final int rowNumber; + private final int rankNumber; - Rank(int rowNumber) { + Rank(int rowNumber, int rankNumber) { this.rowNumber = rowNumber; + this.rankNumber = rankNumber; } public static Rank from(int rowNumber) { @@ -46,4 +48,8 @@ public boolean isBelow(Rank other) { public Rank move(int weight) { return from(rowNumber + weight); } + + public int getRankNumber() { + return rankNumber; + } } diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java new file mode 100644 index 00000000000..304ef03d1c9 --- /dev/null +++ b/src/main/java/chess/dto/BoardDto.java @@ -0,0 +1,46 @@ +package chess.dto; + +import chess.domain.board.ChessBoard; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.view.PieceMessage; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class BoardDto { + private final List boardSnapShot; + + public BoardDto(List boardSnapShot) { + this.boardSnapShot = boardSnapShot; + } + + public static BoardDto from(ChessBoard chessBoard) { + List boardSnapShot = Arrays.stream(Rank.values()) + .map(rank -> rankSnapShot(chessBoard, rank)) + .collect(Collectors.toList()); + return new BoardDto(boardSnapShot); + } + + private static RankDto rankSnapShot(ChessBoard chessBoard, Rank rank) { + List rankSnapShot = new ArrayList<>(); + for (File file : File.values()) { + Position position = new Position(file, rank); + rankSnapShot.add(squareSnapshot(chessBoard, position)); + } + return new RankDto(rankSnapShot); + } + + private static String squareSnapshot(ChessBoard chessBoard, Position position) { + if (chessBoard.positionIsEmpty(position)) { + return "."; + } + return PieceMessage.messageOf(chessBoard.findPieceByPosition(position)); + } + + public List getBoardSnapShot() { + return boardSnapShot; + } +} diff --git a/src/main/java/chess/dto/PieceDto.java b/src/main/java/chess/dto/PieceDto.java new file mode 100644 index 00000000000..59f4aca5390 --- /dev/null +++ b/src/main/java/chess/dto/PieceDto.java @@ -0,0 +1,36 @@ +package chess.dto; + +import chess.domain.piece.Piece; +import chess.domain.position.Position; +import chess.repository.mapper.ValueMapper; + +public class PieceDto { + private final String position; + private final String team; + private final String type; + + public PieceDto(String position, String team, String type) { + this.position = position; + this.team = team; + this.type = type; + } + + public static PieceDto of(Piece piece, Position position) { + return new PieceDto( + ValueMapper.mapPositionToString(position), + ValueMapper.mapTeamToString(piece.getTeam()), + ValueMapper.mapPieceTypeToString(piece)); + } + + public String getPosition() { + return position; + } + + public String getTeam() { + return team; + } + + public String getType() { + return type; + } +} diff --git a/src/main/java/chess/dto/PiecesDto.java b/src/main/java/chess/dto/PiecesDto.java new file mode 100644 index 00000000000..f4015854210 --- /dev/null +++ b/src/main/java/chess/dto/PiecesDto.java @@ -0,0 +1,27 @@ +package chess.dto; + +import chess.domain.board.ChessBoard; +import chess.domain.piece.Piece; +import chess.domain.position.Position; +import java.util.List; +import java.util.Map; + +public class PiecesDto { + private final List pieces; + + public PiecesDto(List pieces) { + this.pieces = pieces; + } + + public static PiecesDto from(ChessBoard chessBoard) { + Map board = chessBoard.getBoard(); + List pieces = board.keySet().stream() + .map(position -> PieceDto.of(board.get(position), position)) + .toList(); + return new PiecesDto(pieces); + } + + public List getPieces() { + return pieces; + } +} diff --git a/src/main/java/chess/dto/RankDto.java b/src/main/java/chess/dto/RankDto.java new file mode 100644 index 00000000000..82a549cfde7 --- /dev/null +++ b/src/main/java/chess/dto/RankDto.java @@ -0,0 +1,15 @@ +package chess.dto; + +import java.util.List; + +public class RankDto { + private final List rank; + + public RankDto(List rank) { + this.rank = rank; + } + + public List getRank() { + return rank; + } +} diff --git a/src/main/java/chess/dto/ScoreStatusDto.java b/src/main/java/chess/dto/ScoreStatusDto.java new file mode 100644 index 00000000000..03f912a3669 --- /dev/null +++ b/src/main/java/chess/dto/ScoreStatusDto.java @@ -0,0 +1,32 @@ +package chess.dto; + +import chess.domain.piece.Score; +import chess.domain.piece.Team; + +public class ScoreStatusDto { + private final double whiteTeamScore; + private final double blackTeamScore; + private final String winnerTeam; + + public ScoreStatusDto(double whiteTeamScore, double blackTeamScore, String winnerTeam) { + this.whiteTeamScore = whiteTeamScore; + this.blackTeamScore = blackTeamScore; + this.winnerTeam = winnerTeam; + } + + public static ScoreStatusDto of(Score whiteTeamScore, Score blackTeamScore, Team winnerTeam) { + return new ScoreStatusDto(whiteTeamScore.getValue(), blackTeamScore.getValue(), winnerTeam.name()); + } + + public double getWhiteTeamScore() { + return whiteTeamScore; + } + + public double getBlackTeamScore() { + return blackTeamScore; + } + + public String getWinnerTeam() { + return winnerTeam; + } +} diff --git a/src/main/java/chess/repository/ConnectionManager.java b/src/main/java/chess/repository/ConnectionManager.java new file mode 100644 index 00000000000..149f9d2a7c3 --- /dev/null +++ b/src/main/java/chess/repository/ConnectionManager.java @@ -0,0 +1,21 @@ +package chess.repository; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class ConnectionManager { + private static final String SERVER = "localhost:13306"; + private static final String DATABASE = "chess"; + private static final String OPTION = "?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; + private static final String USERNAME = "root"; + private static final String PASSWORD = "root"; + + public Connection getConnection() { + try { + return DriverManager.getConnection("jdbc:mysql://" + SERVER + "/" + DATABASE + OPTION, USERNAME, PASSWORD); + } catch (final SQLException e) { + throw new RuntimeException("DB 연결 오류"); + } + } +} diff --git a/src/main/java/chess/repository/PieceRepository.java b/src/main/java/chess/repository/PieceRepository.java new file mode 100644 index 00000000000..b598741ee02 --- /dev/null +++ b/src/main/java/chess/repository/PieceRepository.java @@ -0,0 +1,79 @@ +package chess.repository; + +import chess.dto.PieceDto; +import chess.dto.PiecesDto; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class PieceRepository { + private static final String TABLE_NAME = "pieces"; + + private final ConnectionManager connectionManager; + + public PieceRepository(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + public void savePieces(PiecesDto pieces) { + try (Connection connection = connectionManager.getConnection()) { + pieces.getPieces().forEach(piece -> savePiece(piece, connection)); + } catch (SQLException e) { + throw new RuntimeException("기물 저장 과정 중 오류 발생"); + } + } + + private void savePiece(PieceDto pieceDto, Connection connection) { + String query = String.format("INSERT INTO %s (position, team, type) VALUES (?, ?, ?)", TABLE_NAME); + + try (PreparedStatement pstmt = connection.prepareStatement(query)) { + pstmt.setString(1, pieceDto.getPosition()); + pstmt.setString(2, pieceDto.getTeam()); + pstmt.setString(3, pieceDto.getType()); + pstmt.execute(); + } catch (SQLException e) { + throw new RuntimeException("기물 저장 과정 중 오류 발생"); + } + } + + public Optional findPieces() { + String query = String.format("SELECT * FROM %s", TABLE_NAME); + + List result = new ArrayList<>(); + + try (Connection connection = connectionManager.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(query); + ResultSet resultSet = pstmt.executeQuery()) { + + while (resultSet.next()) { + String position = resultSet.getString("position"); + String team = resultSet.getString("team"); + String type = resultSet.getString("type"); + + result.add(new PieceDto(position, team, type)); + } + if (!result.isEmpty()) { + return Optional.of(new PiecesDto(result)); + } + } catch (SQLException e) { + throw new RuntimeException("기물 조회 과정 중 오류 발생"); + } + return Optional.empty(); + } + + public void deleteAll() { + String query = String.format("DELETE FROM %s", TABLE_NAME); + + try (Connection connection = connectionManager.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(query)) { + + pstmt.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException("기물 조회 과정 중 오류 발생"); + } + } +} diff --git a/src/main/java/chess/repository/TurnRepository.java b/src/main/java/chess/repository/TurnRepository.java new file mode 100644 index 00000000000..fd7b2c29ef5 --- /dev/null +++ b/src/main/java/chess/repository/TurnRepository.java @@ -0,0 +1,64 @@ +package chess.repository; + +import chess.domain.piece.Team; +import chess.repository.mapper.DomainMapper; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Optional; + +public class TurnRepository { + private static final String TABLE_NAME = "turn"; + + private final ConnectionManager connectionManager; + + public TurnRepository(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + public void saveTurn(Team turn) { + String query = String.format("INSERT INTO %s (turn) VALUES(?)", TABLE_NAME); + + try (Connection connection = connectionManager.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) { + + pstmt.setString(1, turn.name()); + pstmt.execute(); + + } catch (SQLException e) { + throw new RuntimeException("턴 저장 과정 중 오류 발생"); + } + } + + public Optional findCurrentTurn() { + String query = String.format("SELECT * FROM %s", TABLE_NAME); + + try (Connection connection = connectionManager.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(query); + ResultSet resultSet = pstmt.executeQuery()) { + + if (resultSet.next()) { + String value = resultSet.getString(2); + return Optional.of(DomainMapper.mapToTurn(value)); + } + } catch (SQLException e) { + throw new RuntimeException("턴 조회 과정 중 오류 발생"); + } + return Optional.empty(); + } + + public void deleteAll() { + String query = String.format("DELETE FROM %s", TABLE_NAME); + + try (Connection connection = connectionManager.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(query)) { + + pstmt.executeUpdate(); + + } catch (SQLException e) { + throw new RuntimeException("턴 삭세 과정 중 오류 발생"); + } + } +} diff --git a/src/main/java/chess/repository/mapper/DomainMapper.java b/src/main/java/chess/repository/mapper/DomainMapper.java new file mode 100644 index 00000000000..a34ea587da1 --- /dev/null +++ b/src/main/java/chess/repository/mapper/DomainMapper.java @@ -0,0 +1,79 @@ +package chess.repository.mapper; + +import chess.domain.board.ChessBoard; +import chess.domain.piece.Bishop; +import chess.domain.piece.King; +import chess.domain.piece.Knight; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import chess.domain.piece.Queen; +import chess.domain.piece.Rook; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.dto.PieceDto; +import chess.dto.PiecesDto; +import java.util.HashMap; +import java.util.Map; + +public class DomainMapper { + private static final int FILE_INDEX = 0; + private static final int RANK_INDEX = 1; + + public static Team mapToTurn(String value) { + if (value.equals("WHITE")) { + return Team.WHITE; + } + return Team.BLACK; + } + + public static ChessBoard mapToBoard(PiecesDto piecePlacements) { + Map positionPiece = new HashMap<>(); + + for (PieceDto pieceDto : piecePlacements.getPieces()) { + Position position = mapToPosition(pieceDto.getPosition()); + Team team = mapToTeam(pieceDto.getTeam()); + Piece piece = mapToPiece(pieceDto.getType(), team); + + positionPiece.put(position, piece); + } + + return new ChessBoard(positionPiece); + } + + private static Piece mapToPiece(String pieceTypeValue, Team team) { + if ("pawn".equals(pieceTypeValue)) { + return new Pawn(team); + } + if ("knight".equals(pieceTypeValue)) { + return new Knight(team); + } + if ("bishop".equals(pieceTypeValue)) { + return new Bishop(team); + } + if ("rook".equals(pieceTypeValue)) { + return new Rook(team); + } + if ("queen".equals(pieceTypeValue)) { + return new Queen(team); + } + if ("king".equals(pieceTypeValue)) { + return new King(team); + } + throw new IllegalArgumentException("Invalid piece type: " + pieceTypeValue); + } + + private static Position mapToPosition(String value) { + File file = File.from(value.charAt(FILE_INDEX) - 'a'); + Rank rank = Rank.from(8 - (value.charAt(RANK_INDEX) - '0')); + return new Position(file, rank); + } + + private static Team mapToTeam(String value) { + if (value.equals("WHITE")) { + return Team.WHITE; + } + return Team.BLACK; + } +} diff --git a/src/main/java/chess/repository/mapper/ValueMapper.java b/src/main/java/chess/repository/mapper/ValueMapper.java new file mode 100644 index 00000000000..2110b2ddd74 --- /dev/null +++ b/src/main/java/chess/repository/mapper/ValueMapper.java @@ -0,0 +1,51 @@ +package chess.repository.mapper; + +import chess.domain.piece.Bishop; +import chess.domain.piece.King; +import chess.domain.piece.Knight; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import chess.domain.piece.Queen; +import chess.domain.piece.Rook; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; + +public class ValueMapper { + private ValueMapper() { + } + + public static String mapPositionToString(Position position) { + File file = position.getFile(); + Rank rank = position.getRank(); + return file.name().toLowerCase() + rank.getRankNumber(); + } + + public static String mapTeamToString(Team team) { + return team.name(); + } + + public static String mapPieceTypeToString(Piece piece) { + if (piece instanceof Pawn) { + return "pawn"; + } + if (piece instanceof Rook) { + return "rook"; + } + if (piece instanceof Knight) { + return "knight"; + } + if (piece instanceof Bishop) { + return "bishop"; + } + if (piece instanceof Queen) { + return "queen"; + } + if (piece instanceof King) { + return "king"; + } + throw new IllegalArgumentException("알 수 없는 피스 타입입니다"); + } + +} diff --git a/src/main/java/chess/service/ChessGameService.java b/src/main/java/chess/service/ChessGameService.java new file mode 100644 index 00000000000..16f188563dd --- /dev/null +++ b/src/main/java/chess/service/ChessGameService.java @@ -0,0 +1,78 @@ +package chess.service; + +import chess.domain.ChessGame; +import chess.domain.board.ChessBoard; +import chess.domain.piece.Score; +import chess.domain.piece.Team; +import chess.domain.position.Position; +import chess.dto.BoardDto; +import chess.dto.PiecesDto; +import chess.dto.ScoreStatusDto; +import chess.repository.PieceRepository; +import chess.repository.TurnRepository; +import chess.repository.mapper.DomainMapper; + +public class ChessGameService { + private final PieceRepository pieceRepository; + private final TurnRepository turnRepository; + + public ChessGameService(PieceRepository pieceRepository, TurnRepository turnRepository) { + this.pieceRepository = pieceRepository; + this.turnRepository = turnRepository; + } + + public BoardDto startChessGame() { + if (isChessGameInProgress()) { + return BoardDto.from(loadChessGame().getChessBoard()); + } + return createNewChessGame(); + } + + public BoardDto movePiece(Position start, Position destination) { + ChessGame chessGame = loadChessGame(); + chessGame.move(start, destination); + saveChessGame(chessGame); + return BoardDto.from(chessGame.getChessBoard()); + } + + public ScoreStatusDto calculateScoreStatus() { + ChessGame chessGame = loadChessGame(); + Score blackScore = chessGame.calculateTeamScore(Team.BLACK); + Score whiteScore = chessGame.calculateTeamScore(Team.WHITE); + return ScoreStatusDto.of(whiteScore, blackScore, chessGame.selectHigherScoreTeam()); + } + + public ChessGame loadChessGame() { + PiecesDto pieces = pieceRepository.findPieces().get(); + Team currentTurn = turnRepository.findCurrentTurn().get(); + ChessBoard chessBoard = DomainMapper.mapToBoard(pieces); + return new ChessGame(chessBoard, currentTurn); + } + + public boolean isChessGameNotEnd() { + return loadChessGame().isNotEnd(); + } + + private BoardDto createNewChessGame() { + ChessGame newChessGame = ChessGame.createNewChessGame(); + saveChessGame(newChessGame); + return BoardDto.from(newChessGame.getChessBoard()); + } + + private void saveChessGame(ChessGame chessGame) { + deleteSavedChessGame(); + ChessBoard chessBoard = chessGame.getChessBoard(); + PiecesDto piecesDto = PiecesDto.from(chessBoard); + pieceRepository.savePieces(piecesDto); + turnRepository.saveTurn(chessGame.getTurn()); + } + + private void deleteSavedChessGame() { + pieceRepository.deleteAll(); + turnRepository.deleteAll(); + } + + private boolean isChessGameInProgress() { + return pieceRepository.findPieces().isPresent(); + } +} diff --git a/src/main/java/chess/view/CommandParser.java b/src/main/java/chess/view/CommandParser.java index e580e791965..ba856a1bb5b 100644 --- a/src/main/java/chess/view/CommandParser.java +++ b/src/main/java/chess/view/CommandParser.java @@ -4,6 +4,7 @@ import chess.controller.command.EndCommand; import chess.controller.command.MoveCommand; import chess.controller.command.StartCommand; +import chess.controller.command.StatusCommand; import chess.domain.position.File; import chess.domain.position.Position; import chess.domain.position.Rank; @@ -12,20 +13,26 @@ public class CommandParser { static final String START = "start"; static final String END = "end"; static final String MOVE = "move"; + static final String STATUS = "status"; + private static final int START_POSITION_INDEX = 1; private static final int DESTINATION_POSITION_INDEX = 2; private static final int FILE_INDEX = 0; private static final int RANK_INDEX = 1; private static final char RANK_TO_ROW_NUMBER_WEIGHT = '0'; private static final char FILE_TO_COLUMN_NUMBER_WEIGHT = 'a'; + public static final int MAX_RANK = 8; public Command parse(String input) { - if (input.startsWith(START)) { + if (input.equals(START)) { return new StartCommand(); } - if (input.startsWith(END)) { + if (input.equals(END)) { return new EndCommand(); } + if (input.equals(STATUS)) { + return new StatusCommand(); + } if (input.startsWith(MOVE)) { String[] split = input.split(" "); String start = split[START_POSITION_INDEX]; @@ -36,7 +43,7 @@ public Command parse(String input) { } private Position parsePosition(String input) { - Rank rank = Rank.from(8 - (input.charAt(RANK_INDEX) - RANK_TO_ROW_NUMBER_WEIGHT)); + Rank rank = Rank.from(MAX_RANK - (input.charAt(RANK_INDEX) - RANK_TO_ROW_NUMBER_WEIGHT)); File file = File.from(input.charAt(FILE_INDEX) - FILE_TO_COLUMN_NUMBER_WEIGHT); return new Position(file, rank); } diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 51c0d36e796..c4891783ef6 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -3,13 +3,12 @@ import static chess.view.CommandParser.END; import static chess.view.CommandParser.MOVE; import static chess.view.CommandParser.START; +import static chess.view.CommandParser.STATUS; -import chess.domain.board.ChessBoard; -import chess.domain.piece.Piece; -import chess.domain.position.File; -import chess.domain.position.Position; -import chess.domain.position.Rank; -import java.util.Arrays; +import chess.dto.BoardDto; +import chess.dto.RankDto; +import chess.dto.ScoreStatusDto; +import java.util.List; import java.util.StringJoiner; import java.util.stream.Collectors; @@ -21,8 +20,12 @@ public void printStartMessage() { System.out.println(resolveStartMessage()); } - public void printChessBoardMessage(ChessBoard chessBoard) { - System.out.println(resolveChessBoardMessage(chessBoard)); + public void printChessBoardMessage(BoardDto boardSnapshotDto) { + System.out.println(resolveBoardSnapshotMessage(boardSnapshotDto)); + } + + public void printStatusMessage(ScoreStatusDto scoreStatusDto) { + System.out.println(resolveStatusMessage(scoreStatusDto)); } private String resolveStartMessage() { @@ -30,28 +33,36 @@ private String resolveStartMessage() { .add("체스 게임을 시작합니다.") .add(String.format("> 게임 시작 : %s", START)) .add(String.format("> 게임 종료 : %s", END)) + .add(String.format("> 점수 출력 : %s", STATUS)) .add(String.format("> 게임 이동 : %s source위치 target위치 - 예. %s b2 b3", MOVE, MOVE)) .toString(); } - private String resolveChessBoardMessage(ChessBoard chessBoard) { - return Arrays.stream(Rank.values()) - .map(rank -> resolveRankMessage(chessBoard, rank)) + private String resolveBoardSnapshotMessage(BoardDto boardSnapshot) { + List boardSnapShot = boardSnapshot.getBoardSnapShot(); + return boardSnapShot.stream() + .map(this::resolveRankSnapshotMessage) .collect(Collectors.joining(LINE_SEPARATOR)); } - private String resolveRankMessage(ChessBoard chessBoard, Rank rank) { - return Arrays.stream(File.values()) - .map(file -> new Position(file, rank)) - .map(position -> resolveSquareMessage(chessBoard, position)) - .collect(Collectors.joining()); + private String resolveRankSnapshotMessage(RankDto rankDto) { + List rank = rankDto.getRank(); + return String.join("", rank); + } + + private String resolveStatusMessage(ScoreStatusDto scoreStatusDto) { + return new StringJoiner(LINE_SEPARATOR) + .add(resolveTeamStatusMessage("백", scoreStatusDto.getWhiteTeamScore())) + .add(resolveTeamStatusMessage("흑", scoreStatusDto.getBlackTeamScore())) + .add(resolveStatusWinnerMessage(scoreStatusDto.getWinnerTeam())) + .toString(); + } + + private String resolveTeamStatusMessage(String team, double score) { + return String.format("%s팀: %.1f점", team, score); } - private String resolveSquareMessage(ChessBoard chessBoard, Position position) { - if (chessBoard.positionIsEmpty(position)) { - return POSITION_EMPTY_MESSAGE; - } - Piece foundPiece = chessBoard.findPieceByPosition(position); - return PieceMessage.messageOf(foundPiece); + private String resolveStatusWinnerMessage(String winner) { + return String.format("현 시점 기물 점수 승부 %s 승리", winner); } } diff --git a/src/main/resources/sql/piece.sql b/src/main/resources/sql/piece.sql new file mode 100644 index 00000000000..3255140d923 --- /dev/null +++ b/src/main/resources/sql/piece.sql @@ -0,0 +1,9 @@ +CREATE TABLE pieces +( + id BIGINT NOT NULL AUTO_INCREMENT, + position VARCHAR(4), + team VARCHAR(8), + type VARCHAR(8), + + PRIMARY KEY (id) +); diff --git a/src/main/resources/sql/turn.sql b/src/main/resources/sql/turn.sql new file mode 100644 index 00000000000..439ce8d53b2 --- /dev/null +++ b/src/main/resources/sql/turn.sql @@ -0,0 +1,7 @@ +CREATE TABLE turn +( + id BIGINT NOT NULL AUTO_INCREMENT, + turn VARCHAR(8) NOT NULL, + + PRIMARY KEY (id) +); diff --git a/src/test/java/chess/domain/ChessGameTest.java b/src/test/java/chess/domain/ChessGameTest.java new file mode 100644 index 00000000000..1a9d45cb032 --- /dev/null +++ b/src/test/java/chess/domain/ChessGameTest.java @@ -0,0 +1,104 @@ +package chess.domain; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import chess.domain.board.ChessBoard; +import chess.domain.piece.King; +import chess.domain.piece.Knight; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import chess.domain.piece.Rook; +import chess.domain.piece.Score; +import chess.domain.piece.Team; +import chess.domain.position.Position; +import chess.fixture.PositionFixtures; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ChessGameTest { + @DisplayName("턴에 맞는 움직임을 시도하면 예외를 발생시키지 않는다.") + @Test + void should_DoesNotThrowAnyException_When_MovementTurnIsValid() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new King(Team.WHITE)); + board.put(PositionFixtures.A8, new King(Team.BLACK)); + ChessBoard chessBoard = new ChessBoard(board); + ChessGame chessGame = new ChessGame(chessBoard); + + assertThatCode(() -> chessGame.move(PositionFixtures.A1, PositionFixtures.A2)) + .doesNotThrowAnyException(); + } + + @DisplayName("턴에 맞지 않는 움직임을 시도하면 예외를 발생시킨다") + @Test + void should_ThrowException_When_IllegalTurnMove() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new King(Team.WHITE)); + board.put(PositionFixtures.A8, new King(Team.BLACK)); + ChessBoard chessBoard = new ChessBoard(board); + ChessGame chessGame = new ChessGame(chessBoard); + + assertThatThrownBy(() -> chessGame.move(PositionFixtures.A8, PositionFixtures.A7)) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("배치된 기물에 맞추어 팀 점수를 계산할 수 있다") + @Test + void should_CalculateTeamScore() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new Knight(Team.WHITE)); + board.put(PositionFixtures.A8, new Rook(Team.WHITE)); + + ChessBoard chessBoard = new ChessBoard(board); + ChessGame chessGame = new ChessGame(chessBoard); + + assertThat(chessGame.calculateTeamScore(Team.WHITE)).isEqualTo(new Score(7.5)); + } + + @DisplayName("폰은 열이 겹치지 않는다면 1점으로 계산한다") + @Test + void should_CalculatePawnScoreAsOne_When_NoSameFilePawn() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new Pawn(Team.WHITE)); + board.put(PositionFixtures.B1, new Pawn(Team.WHITE)); + + ChessBoard chessBoard = new ChessBoard(board); + ChessGame chessGame = new ChessGame(chessBoard); + + assertThat(chessGame.calculateTeamScore(Team.WHITE)).isEqualTo(new Score(2)); + } + + @DisplayName("폰은 열이 겹치지 않는다면 1점으로 계산한다") + @Test + void should_CalculatePawnScoreAsHalf_When_SameFilePawnExist() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new Pawn(Team.WHITE)); + board.put(PositionFixtures.A2, new Pawn(Team.WHITE)); + + ChessBoard chessBoard = new ChessBoard(board); + ChessGame chessGame = new ChessGame(chessBoard); + + assertThat(chessGame.calculateTeamScore(Team.WHITE)).isEqualTo(new Score(1)); + } + + @DisplayName("체스 게임 도메인은 점수가 더 높은 팀을 가려낼 수 있다") + @Test + void should_ChessGameCanSelectHigherScoreTeam() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new Pawn(Team.WHITE)); + board.put(PositionFixtures.A2, new Pawn(Team.WHITE)); + + board.put(PositionFixtures.A3, new Knight(Team.BLACK)); + board.put(PositionFixtures.A8, new Rook(Team.BLACK)); + + ChessBoard chessBoard = new ChessBoard(board); + ChessGame chessGame = new ChessGame(chessBoard); + + assertThat(chessGame.selectHigherScoreTeam()).isEqualTo(Team.BLACK); + } +} + diff --git a/src/test/java/chess/domain/board/ChessBoardTest.java b/src/test/java/chess/domain/board/ChessBoardTest.java index ed123385ad0..a51054357a7 100644 --- a/src/test/java/chess/domain/board/ChessBoardTest.java +++ b/src/test/java/chess/domain/board/ChessBoardTest.java @@ -2,12 +2,20 @@ import static chess.fixture.PositionFixtures.A1; import static chess.fixture.PositionFixtures.A2; +import static chess.fixture.PositionFixtures.A3; +import static chess.fixture.PositionFixtures.A4; +import static chess.fixture.PositionFixtures.B3; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import chess.domain.piece.Bishop; import chess.domain.piece.King; +import chess.domain.piece.Knight; +import chess.domain.piece.Pawn; import chess.domain.piece.Piece; +import chess.domain.piece.Rook; +import chess.domain.piece.Score; import chess.domain.piece.Team; import chess.domain.position.Position; import java.util.HashMap; @@ -77,4 +85,66 @@ void should_CheckThereIsHostilePiece_When_DestinationIsGiven() { () -> assertThat(chessBoard.isNoHostilePieceAt(A1, Team.BLACK)).isFalse() ); } + + @DisplayName("팀의 기본 점수를 계산할 수 있다") + @Test + void should_CalculateDefaultScore() { + Map positionPiece = new HashMap<>(); + + positionPiece.put(A1, new Bishop(Team.WHITE)); + positionPiece.put(A2, new Knight(Team.WHITE)); + positionPiece.put(A3, new Rook(Team.BLACK)); + positionPiece.put(A4, new Rook(Team.BLACK)); + + ChessBoard chessBoard = new ChessBoard(positionPiece); + + assertAll( + () -> assertThat(chessBoard.calcualteDefaultScore(Team.WHITE)).isEqualTo(new Score(5.5)), + () -> assertThat(chessBoard.calcualteDefaultScore(Team.BLACK)).isEqualTo(new Score(10)) + ); + } + + @DisplayName("세로 라인이 같은 폰이 총 몇개인지 셀 수 있다") + @Test + void should_CountSameFilePawn() { + Map positionPiece = new HashMap<>(); + + positionPiece.put(A1, new Pawn(Team.WHITE)); + positionPiece.put(A2, new Pawn(Team.WHITE)); + positionPiece.put(B3, new Pawn(Team.BLACK)); + positionPiece.put(A4, new Pawn(Team.BLACK)); + + ChessBoard chessBoard = new ChessBoard(positionPiece); + + assertAll( + () -> assertThat(chessBoard.countSameFilePawn(Team.WHITE)).isEqualTo(2), + () -> assertThat(chessBoard.countSameFilePawn(Team.BLACK)).isEqualTo(0) + ); + } + + @DisplayName("흑팀의 킹이 살아있는지 확인할 수 있다") + @Test + void should_CheckBlackKingIsAlive() { + Map positionPiece = new HashMap<>(); + + positionPiece.put(A1, new King(Team.WHITE)); + positionPiece.put(B3, new King(Team.BLACK)); + + ChessBoard chessBoard = new ChessBoard(positionPiece); + + assertThat(chessBoard.isBlackKingAlive()).isTrue(); + } + + @DisplayName("백팀의 킹이 살아있는지 확인할 수 있다") + @Test + void should_CheckWhiteKingIsAlive() { + Map positionPiece = new HashMap<>(); + + positionPiece.put(A1, new King(Team.WHITE)); + positionPiece.put(B3, new King(Team.BLACK)); + + ChessBoard chessBoard = new ChessBoard(positionPiece); + + assertThat(chessBoard.isWhiteKingAlive()).isTrue(); + } } diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java index cc80763f011..0aa8e5b55ba 100644 --- a/src/test/java/chess/domain/piece/PieceTest.java +++ b/src/test/java/chess/domain/piece/PieceTest.java @@ -3,15 +3,21 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import chess.domain.board.ChessBoard; +import chess.domain.position.Position; +import chess.fixture.PositionFixtures; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class PieceTest { + @DisplayName("기물의 팀이 흑팀인지 확인할 수 있다") @Test void should_CheckPieceIsBlackTeam() { - King blackPiece = new King(Team.BLACK); - King whitePiece = new King(Team.WHITE); + Piece blackPiece = new King(Team.BLACK); + Piece whitePiece = new King(Team.WHITE); assertAll( () -> assertThat(blackPiece.isBlackTeam()).isTrue(), @@ -22,12 +28,38 @@ void should_CheckPieceIsBlackTeam() { @DisplayName("기물간 서로 같은 팀인지 다른 팀인지 확인할 수 있다") @Test void should_ComparePieceTeam() { - King blackPiece = new King(Team.BLACK); - King whitePiece = new King(Team.WHITE); - + Piece blackPiece = new King(Team.BLACK); + Piece whitePiece = new King(Team.WHITE); assertAll( () -> assertThat(blackPiece.isOtherTeam(Team.WHITE)).isTrue(), () -> assertThat(whitePiece.isOtherTeam(Team.WHITE)).isFalse() ); } + + @DisplayName("기물은 시작위치와 같은 곳으로 이동하지 못한다") + @Test + void should_AllPieceCanNotMove_When_StartAndDestinationIsSame() { + Piece blackPiece = new King(Team.BLACK); + Piece whitePiece = new King(Team.WHITE); + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, whitePiece); + ChessBoard chessBoard = new ChessBoard(board); + boolean canMoveSamePosition = whitePiece.canMove(PositionFixtures.A1, PositionFixtures.A1, chessBoard); + + assertThat(canMoveSamePosition).isFalse(); + } + + @DisplayName("기물은 도착지에 아군이 있는 경우 이동하지 못한다") + @Test + void should_AllPieceCanNotMove_When_FriendlyPieceAtDestination() { + Piece blackPiece = new King(Team.BLACK); + Piece whitePiece = new King(Team.WHITE); + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, whitePiece); + board.put(PositionFixtures.A2, new King(Team.WHITE)); + ChessBoard chessBoard = new ChessBoard(board); + boolean canMoveFriendlyPiecePosition = whitePiece.canMove(PositionFixtures.A1, PositionFixtures.A2, chessBoard); + + assertThat(canMoveFriendlyPiecePosition).isFalse(); + } } diff --git a/src/test/java/chess/domain/piece/ScoreTest.java b/src/test/java/chess/domain/piece/ScoreTest.java new file mode 100644 index 00000000000..e43a079531c --- /dev/null +++ b/src/test/java/chess/domain/piece/ScoreTest.java @@ -0,0 +1,46 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ScoreTest { + @DisplayName("점수끼리 덧셈을 계산할 수 있다") + @Test + void should_CalculateAddition() { + Score score1 = new Score(1.1); + Score score2 = new Score(1.2); + Score added = score1.add(score2); + + assertThat(added.getValue()).isEqualTo(2.3); + } + + @DisplayName("점수끼리 곱셈을 계산할 수 있다") + @Test + void should_CalculateMultiplication() { + Score score1 = new Score(1.1); + Score score2 = new Score(1.2); + Score multiplied = score1.multiply(score2); + + assertThat(multiplied.getValue()).isEqualTo(1.32); + } + + @DisplayName("특정 점수가 더 높은지 계산할 수 있다") + @Test + void should_ScoreCouldCheckIsAbove_When_OtherScoreIsGiven() { + Score higher = new Score(1.3); + Score lower = new Score(1.2); + + assertThat(higher.isAbove(lower)).isTrue(); + } + + @DisplayName("특정 점수가 더 낮은지 계산할 수 있다") + @Test + void should_ScoreCouldCheckIsBelow_When_OtherScoreIsGiven() { + Score higher = new Score(1.3); + Score lower = new Score(1.2); + + assertThat(lower.isBelow(higher)).isTrue(); + } +} diff --git a/src/test/java/chess/domain/position/DirectionTest.java b/src/test/java/chess/domain/position/DirectionTest.java index 671a989346b..a2622f5c36f 100644 --- a/src/test/java/chess/domain/position/DirectionTest.java +++ b/src/test/java/chess/domain/position/DirectionTest.java @@ -1,19 +1,11 @@ package chess.domain.position; -import static chess.domain.position.Direction.E; -import static chess.domain.position.Direction.N; -import static chess.domain.position.Direction.NE; -import static chess.domain.position.Direction.NW; -import static chess.domain.position.Direction.S; -import static chess.domain.position.Direction.SE; -import static chess.domain.position.Direction.SW; -import static chess.domain.position.Direction.W; -import static chess.domain.position.Direction.of; + import static chess.fixture.PositionFixtures.A1; import static chess.fixture.PositionFixtures.A2; import static chess.fixture.PositionFixtures.B1; import static chess.fixture.PositionFixtures.B2; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,48 +15,48 @@ class DirectionTest { @DisplayName("방향을 계산할 수 있다 : a1 -> a2 = N") @Test void should_ReturnDirectionNorth() { - assertThat(of(A1, A2)).isEqualTo(N); + assertThat(Direction.of(A1, A2)).isEqualTo(Direction.N); } @DisplayName("방향을 계산할 수 있다 : a1 -> b1 = E") @Test void should_ReturnDirectionEast() { - assertThat(of(A1, B1)).isEqualTo(E); + assertThat(Direction.of(A1, B1)).isEqualTo(Direction.E); } @DisplayName("방향을 계산할 수 있다 : b1 -> a1 = W") @Test void should_ReturnDirectionWest() { - assertThat(of(B1, A1)).isEqualTo(W); + assertThat(Direction.of(B1, A1)).isEqualTo(Direction.W); } @DisplayName("방향을 계산할 수 있다 : a2 -> a1 = S") @Test void should_ReturnDirectionSouth() { - assertThat(of(A2, A1)).isEqualTo(S); + assertThat(Direction.of(A2, A1)).isEqualTo(Direction.S); } @DisplayName("방향을 계산할 수 있다 : a1 -> b2 = NE") @Test void should_ReturnDirectionNorthEast() { - assertThat(of(A1, B2)).isEqualTo(NE); + assertThat(Direction.of(A1, B2)).isEqualTo(Direction.NE); } @DisplayName("방향을 계산할 수 있다 : b2 -> a1 = SW") @Test void should_ReturnDirectionSouthWest() { - assertThat(of(B2, A1)).isEqualTo(SW); + assertThat(Direction.of(B2, A1)).isEqualTo(Direction.SW); } @DisplayName("방향을 계산할 수 있다 : b1 -> a2 = NW") @Test void should_ReturnDirectionNorthWest() { - assertThat(of(B1, A2)).isEqualTo(NW); + assertThat(Direction.of(B1, A2)).isEqualTo(Direction.NW); } @DisplayName("방향을 계산할 수 있다 : a2 -> b1 = SE") @Test void should_ReturnDirectionSoutWest() { - assertThat(of(A2, B1)).isEqualTo(SE); + assertThat(Direction.of(A2, B1)).isEqualTo(Direction.SE); } } diff --git a/src/test/java/chess/repository/ConnectionManagerTest.java b/src/test/java/chess/repository/ConnectionManagerTest.java new file mode 100644 index 00000000000..f1bb49107f0 --- /dev/null +++ b/src/test/java/chess/repository/ConnectionManagerTest.java @@ -0,0 +1,21 @@ +package chess.repository; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.sql.SQLException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ConnectionManagerTest { + private final ConnectionManager connectionManager = new ConnectionManager(); + + @DisplayName("DB 커넥션을 성공적으로 얻어올 수 있는지 테스트") + @Test + void should_ConnectionManagerCouldGetConnection() { + try (final var connection = connectionManager.getConnection()) { + assertThat(connection).isNotNull(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/chess/service/ChessGameServiceTest.java b/src/test/java/chess/service/ChessGameServiceTest.java new file mode 100644 index 00000000000..0dd244b6c8c --- /dev/null +++ b/src/test/java/chess/service/ChessGameServiceTest.java @@ -0,0 +1,136 @@ +package chess.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.when; + +import chess.domain.ChessGame; +import chess.domain.board.ChessBoard; +import chess.domain.piece.King; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import chess.domain.piece.Team; +import chess.domain.position.Position; +import chess.dto.BoardDto; +import chess.dto.PiecesDto; +import chess.dto.RankDto; +import chess.dto.ScoreStatusDto; +import chess.fixture.PositionFixtures; +import chess.repository.PieceRepository; +import chess.repository.TurnRepository; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class ChessGameServiceTest { + private ChessGameService chessGameService; + @Mock + private PieceRepository pieceRepository; + @Mock + private TurnRepository turnRepository; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + chessGameService = new ChessGameService(pieceRepository, turnRepository); + } + + @DisplayName("게임 시작 시 이미 진행되고 있는 게임이 존재하는 경우 이어서 진행된다") + @Test + void should_StartExistingGame_When_GameIsInProgress() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new Pawn(Team.WHITE)); + ChessBoard chessBoard = new ChessBoard(board); + + when(pieceRepository.findPieces()).thenReturn(Optional.of(PiecesDto.from(chessBoard))); + when(turnRepository.findCurrentTurn()).thenReturn(Optional.of(Team.WHITE)); + + BoardDto boardDto = chessGameService.startChessGame(); + List boardSnapShot = boardDto.getBoardSnapShot(); + List rank1 = boardSnapShot.get(7).getRank(); + assertThat(rank1.get(0)).isEqualTo("p"); + } + + @DisplayName("게임 시작 시 이미 진행되고 있는 게임이 없는 경우 새로운 게임이 진행된다") + @Test + void should_StartNewGame_When_ThereIsNoGameInProgress() { + when(pieceRepository.findPieces()).thenReturn(Optional.empty()); + when(turnRepository.findCurrentTurn()).thenReturn(Optional.empty()); + + BoardDto boardDto = chessGameService.startChessGame(); + List boardSnapShot = boardDto.getBoardSnapShot(); + List rank2 = boardSnapShot.get(6).getRank(); + assertThat(rank2).containsExactly("p", "p", "p", "p", "p", "p", "p", "p"); + } + + @DisplayName("움직임을 요청 받으면 저장되어있는 배치에서 움직임을 검증, 움직임을 수행한다") + @Test + void should_MoveBySavedPlacement_When_MoveCallArrive() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new Pawn(Team.WHITE)); + ChessBoard chessBoard = new ChessBoard(board); + + when(pieceRepository.findPieces()).thenReturn(Optional.of(PiecesDto.from(chessBoard))); + when(turnRepository.findCurrentTurn()).thenReturn(Optional.of(Team.WHITE)); + BoardDto boardDto = chessGameService.movePiece(PositionFixtures.A1, PositionFixtures.A2); + String a2 = boardDto.getBoardSnapShot().get(6).getRank().get(0); + assertThat(a2).isEqualTo("p"); + } + + @DisplayName("저장된 보드 상태를 기반으로 점수를 계산할 수 있다") + @Test + void should_CalculateScoreBySavedStatus() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new Pawn(Team.WHITE)); + ChessBoard chessBoard = new ChessBoard(board); + + when(pieceRepository.findPieces()).thenReturn(Optional.of(PiecesDto.from(chessBoard))); + when(turnRepository.findCurrentTurn()).thenReturn(Optional.of(Team.WHITE)); + + ScoreStatusDto scoreStatusDto = chessGameService.calculateScoreStatus(); + assertAll( + () -> assertThat(scoreStatusDto.getBlackTeamScore()).isEqualTo(0), + () -> assertThat(scoreStatusDto.getWhiteTeamScore()).isEqualTo(1), + () -> assertThat(scoreStatusDto.getWinnerTeam()).isEqualTo("WHITE") + ); + } + + @DisplayName("저장된 게임을 로드할 수 있다") + @Test + void should_LoadInProgressGame() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new King(Team.WHITE)); + board.put(PositionFixtures.A2, new King(Team.BLACK)); + ChessBoard chessBoard = new ChessBoard(board); + + when(pieceRepository.findPieces()).thenReturn(Optional.of(PiecesDto.from(chessBoard))); + when(turnRepository.findCurrentTurn()).thenReturn(Optional.of(Team.WHITE)); + + ChessGame chessGame = chessGameService.loadChessGame(); + assertAll( + () -> assertThat(chessGame.getTurn()).isEqualTo(Team.WHITE), + () -> assertThat(chessGame.getChessBoard().positionIsEmpty(PositionFixtures.A1)).isFalse(), + () -> assertThat(chessGame.getChessBoard().positionIsEmpty(PositionFixtures.A2)).isFalse() + ); + } + + @DisplayName("저장된 게임이 끝난 게임인지 확인할 수 있다") + @Test + void should_CheckSavedGameIsEnd() { + Map board = new HashMap<>(); + board.put(PositionFixtures.A1, new King(Team.BLACK)); + ChessBoard chessBoard = new ChessBoard(board); + + when(pieceRepository.findPieces()).thenReturn(Optional.of(PiecesDto.from(chessBoard))); + when(turnRepository.findCurrentTurn()).thenReturn(Optional.of(Team.WHITE)); + + boolean chessGameNotEnd = chessGameService.isChessGameNotEnd(); + assertThat(chessGameNotEnd).isFalse(); + } +} diff --git a/src/test/java/chess/view/CommandParserTest.java b/src/test/java/chess/view/CommandParserTest.java new file mode 100644 index 00000000000..45706eec8ef --- /dev/null +++ b/src/test/java/chess/view/CommandParserTest.java @@ -0,0 +1,40 @@ +package chess.view; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.controller.command.EndCommand; +import chess.controller.command.MoveCommand; +import chess.controller.command.StartCommand; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CommandParserTest { + private CommandParser commandParser; + + @BeforeEach + void setUp() { + commandParser = new CommandParser(); + } + + @DisplayName("시작 입력이 들어오면 시작 명령으로 파싱할 수 있다") + @Test + void should_ParseStartCommand_When_InputIsAboutStart() { + String input = "start"; + assertThat(commandParser.parse(input)).isInstanceOf(StartCommand.class); + } + + @DisplayName("종료 입력이 들어오면 종료 명령으로 파싱할 수 있다") + @Test + void should_ParseEndCommand_When_InputIsAboutEnd() { + String input = "end"; + assertThat(commandParser.parse(input)).isInstanceOf(EndCommand.class); + } + + @DisplayName("움직임 입력이 들어오면 움직임 명령으로 파싱할 수 있다") + @Test + void should_ParseMoveCommand_When_InputIsAboutMove() { + String input = "move b2 b4"; + assertThat(commandParser.parse(input)).isInstanceOf(MoveCommand.class); + } +}