From 977d9e4758855f9902142414ece9c2359f71906a Mon Sep 17 00:00:00 2001 From: hyxrxn <89867757+hyxrxn@users.noreply.github.com> Date: Tue, 26 Mar 2024 01:24:41 +0900 Subject: [PATCH 01/36] =?UTF-8?q?[=EC=B2=B4=EC=8A=A4=20-=201,=202=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EC=95=84=ED=86=A0(=EC=9D=B4=ED=98=9C=EB=A6=B0)=20?= =?UTF-8?q?=EB=AF=B8=EC=85=98=20=EC=A0=9C=EC=B6=9C=ED=95=A9=EB=8B=88?= =?UTF-8?q?=EB=8B=A4.=20(#706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 기능 요구 사항 정리 * feat(King): 검정 팀 여부 확인 Co-authored-by: leegwichan * docs: '팀 결정 여부 확인' 문서에 반영 Co-authored-by: leegwichan * feat(Position): 각 위치 구분 - 가로 위치와 세로 위치 저장 - 가로 위치와 세로 위치가 같으면, 같은 객체로 판단 Co-authored-by: leegwichan * feat(Board): 해당 위치의 말 확인 Co-authored-by: leegwichan * feat(BoardFactory): 체스 말 위치 초기화 Co-authored-by: leegwichan * refactor(domain): 패키지 분리 Co-authored-by: leegwichan * feat(ChessGame): 초기 체스 보드 출력 - 대문자는 검정말, 소문자는 흰말로 출력 Co-authored-by: leegwichan * refactor(OutputView): 메서드 분리 Co-authored-by: leegwichan * refactor(ChessGame): 메서드 분리 Co-authored-by: leegwichan * style(OutputView): 불필요한 공백 제거 Co-authored-by: leegwichan * feat(InputView): 커멘드 입력 Co-authored-by: leegwichan * feat(ChessGame): 종료 커멘드 반영 Co-authored-by: leegwichan * style(Team): 개행 추가 Co-authored-by: leegwichan * feat(Row): 남북 이동 Co-authored-by: leegwichan * feat(Column): 동서 이동 Co-authored-by: leegwichan * feat(Position): 다음 위치 확인 Co-authored-by: leegwichan * refactor(Rank, File): 클래스명 변경 Co-authored-by: leegwichan * feat(MovementRule): 연속적인 움직임 - 이동 가능 여부 판단 - 해당 경로 파악 Co-authored-by: leegwichan * feat(KnightMovement): 나이트 움직임 - 이동 가능 여부 판단 - 해당 경로 파악 Co-authored-by: leegwichan * feat(KingMovement): 킹 움직임 - 이동 가능 여부 판단 - 해당 경로 파악 Co-authored-by: leegwichan * feat(Piece): 각 기물별 가능한 경로 파악 Co-authored-by: leegwichan * feat(Board): 시작 위치의 말을 도착 위치로 이동 Co-authored-by: leegwichan * feat(ChessGame): 이동 커멘드 반영 Co-authored-by: leegwichan * feat(PawnDefaultMovement): 폰 기본 움직임 - 이동 가능 여부 판단 - 해당 경로 파악 Co-authored-by: leegwichan * fix(Rook): 남쪽으로 이동할 수 있도록 수정 Co-authored-by: leegwichan * feat(PawnFirstMovement): 폰 처음 움직임 - 이동 가능 여부 판단 - 해당 경로 파악 Co-authored-by: leegwichan * style: 컨벤션에 맞추어 공백 추가 Co-authored-by: leegwichan * style(KingTest): 불필요한 구문 제거 Co-authored-by: leegwichan * feat(Pawn): 가능한 경로 파악 Co-authored-by: leegwichan * refactor(Piece): stream을 이용하여 가독성 개선 Co-authored-by: leegwichan * refactor(ContinuousMovementRule): 패키지 분리 Co-authored-by: leegwichan * refactor(PieceTest): 팀 확인 테스트 이동 Co-authored-by: leegwichan * style(Piece): 불필요한 구문 제거 Co-authored-by: leegwichan * test(Piece): 가능한 경로 파악 Co-authored-by: leegwichan * feat(Piece): 에러 메시지 추가 Co-authored-by: leegwichan * test(Position): 파일과 랭크 차이 계산 Co-authored-by: leegwichan * refactor(ChessGame): 메서드 분리 Co-authored-by: leegwichan * feat(ChessApplication): 사용자에게 에러메시지 출력 후 종료 Co-authored-by: leegwichan * feat(DiscreteMovementRule): 공통 로직 추상 클래스로 분리 Co-authored-by: leegwichan * style(ContinuousMovementRule): 불필요한 개행 제거 Co-authored-by: leegwichan * feat(DiscreteMovementRule): 에러 메시지 추가 Co-authored-by: leegwichan * feat(PawnFirstMovement): 잘못된 파라미터 입력 시 에러 발생 Co-authored-by: leegwichan * refactor: 가독성을 위해 상수 추출 Co-authored-by: leegwichan * refactor(GameCommand): 불필요한 메서드 삭제 Co-authored-by: leegwichan * feat(BoardFactory): 객체 생성을 막기 위해 생성자 추가 Co-authored-by: leegwichan * refactor(NorthWestMovementTest): 불필요한 접근제어자 제거 Co-authored-by: leegwichan * style: 불필요한 개행 삭제 Co-authored-by: leegwichan * refactor: 디미터 규칙 준수 Co-authored-by: leegwichan * style: 가독성을 위해 개행 추가 Co-authored-by: leegwichan * refactor(Position): 불필요한 메서드 삭제 Co-authored-by: leegwichan * docs: 개선 필요 사항 분리 Co-authored-by: leegwichan * style(Position): 가독성을 위해 this 추가 Co-authored-by: leegwichan * refactor(BoardFactory): 메서드명 변경 * refactor(PieceDto): 클래스로 변경 - 주 생성자를 private으로 변경 * feat(BoardDto): 체스 보드 출력을 위한 Dto 생성 - OutputView에서 null 사용 제거 - ChessGame의 역할 분리 * feat(Board): 팀 규칙 추가 - 흰 말부터 차례로 이동 - 같은 팀 말이 있는 위치로 이동 불가 * refactor(MovementRule): 상속되지 않을 클래스에 final 추가 * refactor(Position): 파라미터 변수명 변경 * docs: 기능 요구 사항 추가 * feat(MovementRule): 적 여부 파라미터에 추가 - 폰 이동 시 적 여부 판단해서 이동 가능 여부 반환 - 폰 대각선 이동 방법 추가 * feat(MovementRule): 도착 위치는 이동 경로에 포함되지 않도록 변경 - 이동 경로란 출발 위치에서 도착 위치까지 갈 때 거쳐가는 칸들 - 출발 위치에서 시작해 이동 경로를 지나 도착 위치에 도달 - 출발 위치에서 도착 위치 사이에 거쳐가는 칸이 없는 경우 빈 리스트 반환 * feat(ChessGame): 잘못된 입력 시 오류 메시지 출력 후 재입력 * style(ChessGame): 컨벤션에 맞춰 공백 추가 * refactor(File): 오타 수정 * refactor(Board): 메서드 간략화 --------- Co-authored-by: leegwichan --- .gitignore | 4 + README.md | 38 +++- src/main/java/.gitkeep | 0 src/main/java/chess/ChessApplication.java | 15 ++ src/main/java/chess/ChessGame.java | 84 +++++++++ src/main/java/chess/domain/Board.java | 76 ++++++++ src/main/java/chess/domain/BoardFactory.java | 73 ++++++++ .../chess/domain/movement/MovementRule.java | 11 ++ .../continuous/ContinuousMovementRule.java | 42 +++++ .../movement/continuous/EastMovement.java | 16 ++ .../continuous/NorthEastMovement.java | 17 ++ .../movement/continuous/NorthMovement.java | 16 ++ .../continuous/NorthWestMovement.java | 17 ++ .../continuous/SouthEastMovement.java | 17 ++ .../movement/continuous/SouthMovement.java | 16 ++ .../continuous/SouthWestMovement.java | 17 ++ .../movement/continuous/WestMovement.java | 16 ++ .../discrete/DiscreteMovementRule.java | 27 +++ .../movement/discrete/KingMovement.java | 12 ++ .../movement/discrete/KnightMovement.java | 13 ++ .../pawn/BlackPawnDefaultMovement.java | 23 +++ .../pawn/BlackPawnDiagonalMovement.java | 20 +++ .../movement/pawn/BlackPawnFirstMovement.java | 25 +++ .../pawn/WhitePawnDefaultMovement.java | 23 +++ .../pawn/WhitePawnDiagonalMovement.java | 20 +++ .../movement/pawn/WhitePawnFirstMovement.java | 25 +++ src/main/java/chess/domain/piece/Bishop.java | 18 ++ src/main/java/chess/domain/piece/King.java | 14 ++ src/main/java/chess/domain/piece/Knight.java | 14 ++ src/main/java/chess/domain/piece/Pawn.java | 29 +++ src/main/java/chess/domain/piece/Piece.java | 32 ++++ src/main/java/chess/domain/piece/Queen.java | 23 +++ src/main/java/chess/domain/piece/Rook.java | 18 ++ src/main/java/chess/domain/piece/Team.java | 17 ++ src/main/java/chess/domain/position/File.java | 53 ++++++ .../java/chess/domain/position/Position.java | 66 +++++++ src/main/java/chess/domain/position/Rank.java | 53 ++++++ src/main/java/chess/dto/BoardDto.java | 41 +++++ src/main/java/chess/dto/PieceDto.java | 29 +++ src/main/java/chess/dto/PieceType.java | 33 ++++ src/main/java/chess/view/GameCommand.java | 23 +++ src/main/java/chess/view/InputView.java | 62 +++++++ src/main/java/chess/view/OutputView.java | 73 ++++++++ src/test/java/.gitkeep | 0 .../java/chess/domain/BoardFactoryTest.java | 89 +++++++++ src/test/java/chess/domain/BoardTest.java | 106 +++++++++++ .../movement/continuous/EastMovementTest.java | 45 +++++ .../continuous/NorthEastMovementTest.java | 45 +++++ .../continuous/NorthMovementTest.java | 45 +++++ .../continuous/NorthWestMovementTest.java | 45 +++++ .../continuous/SouthEastMovementTest.java | 45 +++++ .../continuous/SouthMovementTest.java | 45 +++++ .../continuous/SouthWestMovementTest.java | 45 +++++ .../movement/continuous/WestMovementTest.java | 45 +++++ .../movement/discrete/KingMovementTest.java | 49 +++++ .../movement/discrete/KnightMovementTest.java | 49 +++++ .../pawn/BlackPawnDefaultMovementTest.java | 59 ++++++ .../pawn/BlackPawnDiagonalMovementTest.java | 61 +++++++ .../pawn/BlackPawnFirstMovementTest.java | 72 ++++++++ .../pawn/WhitePawnDefaultMovementTest.java | 59 ++++++ .../pawn/WhitePawnDiagonalMovementTest.java | 61 +++++++ .../pawn/WhitePawnFirstMovementTest.java | 72 ++++++++ .../java/chess/domain/piece/BishopTest.java | 55 ++++++ .../java/chess/domain/piece/KingTest.java | 41 +++++ .../java/chess/domain/piece/KnightTest.java | 41 +++++ .../java/chess/domain/piece/PawnTest.java | 170 ++++++++++++++++++ .../java/chess/domain/piece/PieceTest.java | 27 +++ .../java/chess/domain/piece/QueenTest.java | 55 ++++++ .../java/chess/domain/piece/RookTest.java | 55 ++++++ .../java/chess/domain/position/FileTest.java | 49 +++++ .../chess/domain/position/PositionTest.java | 36 ++++ .../java/chess/domain/position/RankTest.java | 49 +++++ 72 files changed, 2873 insertions(+), 3 deletions(-) delete mode 100644 src/main/java/.gitkeep create mode 100644 src/main/java/chess/ChessApplication.java create mode 100644 src/main/java/chess/ChessGame.java create mode 100644 src/main/java/chess/domain/Board.java create mode 100644 src/main/java/chess/domain/BoardFactory.java create mode 100644 src/main/java/chess/domain/movement/MovementRule.java create mode 100644 src/main/java/chess/domain/movement/continuous/ContinuousMovementRule.java create mode 100644 src/main/java/chess/domain/movement/continuous/EastMovement.java create mode 100644 src/main/java/chess/domain/movement/continuous/NorthEastMovement.java create mode 100644 src/main/java/chess/domain/movement/continuous/NorthMovement.java create mode 100644 src/main/java/chess/domain/movement/continuous/NorthWestMovement.java create mode 100644 src/main/java/chess/domain/movement/continuous/SouthEastMovement.java create mode 100644 src/main/java/chess/domain/movement/continuous/SouthMovement.java create mode 100644 src/main/java/chess/domain/movement/continuous/SouthWestMovement.java create mode 100644 src/main/java/chess/domain/movement/continuous/WestMovement.java create mode 100644 src/main/java/chess/domain/movement/discrete/DiscreteMovementRule.java create mode 100644 src/main/java/chess/domain/movement/discrete/KingMovement.java create mode 100644 src/main/java/chess/domain/movement/discrete/KnightMovement.java create mode 100644 src/main/java/chess/domain/movement/pawn/BlackPawnDefaultMovement.java create mode 100644 src/main/java/chess/domain/movement/pawn/BlackPawnDiagonalMovement.java create mode 100644 src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java create mode 100644 src/main/java/chess/domain/movement/pawn/WhitePawnDefaultMovement.java create mode 100644 src/main/java/chess/domain/movement/pawn/WhitePawnDiagonalMovement.java create mode 100644 src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java create mode 100644 src/main/java/chess/domain/piece/Bishop.java create mode 100644 src/main/java/chess/domain/piece/King.java create mode 100644 src/main/java/chess/domain/piece/Knight.java create mode 100644 src/main/java/chess/domain/piece/Pawn.java create mode 100644 src/main/java/chess/domain/piece/Piece.java create mode 100644 src/main/java/chess/domain/piece/Queen.java create mode 100644 src/main/java/chess/domain/piece/Rook.java create mode 100644 src/main/java/chess/domain/piece/Team.java create mode 100644 src/main/java/chess/domain/position/File.java create mode 100644 src/main/java/chess/domain/position/Position.java create mode 100644 src/main/java/chess/domain/position/Rank.java create mode 100644 src/main/java/chess/dto/BoardDto.java create mode 100644 src/main/java/chess/dto/PieceDto.java create mode 100644 src/main/java/chess/dto/PieceType.java create mode 100644 src/main/java/chess/view/GameCommand.java create mode 100644 src/main/java/chess/view/InputView.java create mode 100644 src/main/java/chess/view/OutputView.java delete mode 100644 src/test/java/.gitkeep create mode 100644 src/test/java/chess/domain/BoardFactoryTest.java create mode 100644 src/test/java/chess/domain/BoardTest.java create mode 100644 src/test/java/chess/domain/movement/continuous/EastMovementTest.java create mode 100644 src/test/java/chess/domain/movement/continuous/NorthEastMovementTest.java create mode 100644 src/test/java/chess/domain/movement/continuous/NorthMovementTest.java create mode 100644 src/test/java/chess/domain/movement/continuous/NorthWestMovementTest.java create mode 100644 src/test/java/chess/domain/movement/continuous/SouthEastMovementTest.java create mode 100644 src/test/java/chess/domain/movement/continuous/SouthMovementTest.java create mode 100644 src/test/java/chess/domain/movement/continuous/SouthWestMovementTest.java create mode 100644 src/test/java/chess/domain/movement/continuous/WestMovementTest.java create mode 100644 src/test/java/chess/domain/movement/discrete/KingMovementTest.java create mode 100644 src/test/java/chess/domain/movement/discrete/KnightMovementTest.java create mode 100644 src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java create mode 100644 src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java create mode 100644 src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java create mode 100644 src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java create mode 100644 src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java create mode 100644 src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java create mode 100644 src/test/java/chess/domain/piece/BishopTest.java create mode 100644 src/test/java/chess/domain/piece/KingTest.java create mode 100644 src/test/java/chess/domain/piece/KnightTest.java create mode 100644 src/test/java/chess/domain/piece/PawnTest.java create mode 100644 src/test/java/chess/domain/piece/PieceTest.java create mode 100644 src/test/java/chess/domain/piece/QueenTest.java create mode 100644 src/test/java/chess/domain/piece/RookTest.java create mode 100644 src/test/java/chess/domain/position/FileTest.java create mode 100644 src/test/java/chess/domain/position/PositionTest.java create mode 100644 src/test/java/chess/domain/position/RankTest.java diff --git a/.gitignore b/.gitignore index 6c018781387..5e0f049c593 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ out/ ### VS Code ### .vscode/ + +db/ + +docker-compose.yml diff --git a/README.md b/README.md index 8102f91c870..149d2490724 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,39 @@ # java-chess -체스 미션 저장소 +## 페어와 지킬 컨벤션 +1. 클래스 정의 다음 줄은 공백으로 한다. +2. test code에 사용하는 메서드는 `static import`한다. +3. `this`는 같은 클래스의 객체가 파라미터로 넘어왔을 때, 파라미터 변수 명이 필드의 변수 명과 겹칠 때 사용한다. -## 우아한테크코스 코드리뷰 +## 기능 요구 사항 -- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) +### 체스 말 +- [x] 무슨 팀인지 알려준다. + +### 움직임 +- [x] 이동 가능한지 판단한다. +- [x] 해당 경로를 구한다. + +### 팀 +- [x] 흰색, 검은색을 구분한다. +- [x] 다음 팀을 알려준다. + +### 체스 보드 +- [x] 체스 말 위치 초기화를 한다. +- [x] 해당 위치에 어떤 말이 있는지 알려준다. +- [x] 시작 위치의 말을 도착 위치로 옮긴다. + - [x] 시작 위치에 말이 없을 경우 예외 + - [x] 말의 이동 범위를 넘어갈 경우 예외 + - [x] 이동 경로에 다른 말이 있을 경우 예외 +- [x] 마지막 위치에 적 말이 있을 경우 잡는다. + - [x] 폰의 경우 대각선이 아닌 앞으로 이동할 때에는 잡지 못한다. +- [x] 흰색부터 번갈아가며 플레이한다. + +### 위치 +- [x] 가로 위치(왼쪽부터 a~h)를 저장한다. +- [x] 세로 위치(아래부터 1~8)를 저장한다. +- [x] 서로 같은 위치인지 판단한다. +- [x] 다음 동, 서, 남, 북쪽 위치를 알려준다. + +### 출력 +- [x] 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다. diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/main/java/chess/ChessApplication.java b/src/main/java/chess/ChessApplication.java new file mode 100644 index 00000000000..0fed82c4e21 --- /dev/null +++ b/src/main/java/chess/ChessApplication.java @@ -0,0 +1,15 @@ +package chess; + +import chess.view.InputView; +import chess.view.OutputView; + +public class ChessApplication { + + public static void main(String[] args) { + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + ChessGame chessGame = new ChessGame(inputView, outputView); + + chessGame.tryStart(); + } +} diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java new file mode 100644 index 00000000000..4cd196ea31e --- /dev/null +++ b/src/main/java/chess/ChessGame.java @@ -0,0 +1,84 @@ +package chess; + +import chess.domain.Board; +import chess.domain.BoardFactory; +import chess.domain.position.Position; +import chess.dto.BoardDto; +import chess.view.GameCommand; +import chess.view.InputView; +import chess.view.OutputView; + +public class ChessGame { + + private final InputView inputView; + private final OutputView outputView; + + public ChessGame(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void tryStart() { + try { + outputView.printStartGame(); + start(); + } catch (IllegalArgumentException exception) { + outputView.printExceptionMessage(exception); + tryStart(); + } + } + + private void start() { + GameCommand command = inputView.readCommand(); + if (command == GameCommand.START) { + Board board = BoardFactory.createInitialBoard(); + showBoard(board); + play(board); + return; + } + if (command == GameCommand.MOVE) { + throw new IllegalArgumentException("아직 게임을 시작하지 않았습니다."); + } + } + + private void play(Board board) { + boolean gameEnd = false; + while (!gameEnd) { + gameEnd = tryProcessTurn(board); + } + } + + private boolean tryProcessTurn(Board board) { + try { + return processTurn(board); + } catch (IllegalArgumentException exception) { + outputView.printExceptionMessage(exception); + tryProcessTurn(board); + } + return false; + } + + private boolean processTurn(Board board) { + GameCommand command = inputView.readCommand(); + if (command == GameCommand.START) { + throw new IllegalArgumentException("이미 게임을 시작했습니다."); + } + if (command == GameCommand.END) { + return true; + } + executeMove(board); + return false; + } + + private void executeMove(Board board) { + Position start = inputView.readPosition(); + Position end = inputView.readPosition(); + board.tryMove(start, end); + showBoard(board); + } + + private void showBoard(Board board) { + BoardDto boardDto = BoardDto.of(board); + outputView.printBoard(boardDto); + } +} diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java new file mode 100644 index 00000000000..5f242b412ef --- /dev/null +++ b/src/main/java/chess/domain/Board.java @@ -0,0 +1,76 @@ +package chess.domain; + +import chess.domain.piece.Piece; +import chess.domain.piece.Team; +import chess.domain.position.Position; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class Board { + + private final Map board; + private Team turn; + + public Board(Map board) { + this.board = new HashMap<>(board); + this.turn = Team.WHITE; + } + + public Optional find(Position position) { + Piece piece = board.get(position); + return Optional.ofNullable(piece); + } + + public void tryMove(Position start, Position end) { + Piece movingPiece = findMovingPiece(start); + validateTeamRule(movingPiece, end); + + boolean hasEnemy = hasEnemy(end); + List path = movingPiece.findPath(start, end, hasEnemy); + validatePath(path); + + move(start, end, movingPiece); + } + + private void validatePath(List path) { + if (isBlocked(path)) { + throw new IllegalArgumentException("다른 말이 있어 이동 불가능합니다."); + } + } + + private boolean hasEnemy(Position end) { + return find(end).isPresent(); + } + + private Piece findMovingPiece(Position start) { + return find(start) + .orElseThrow(() -> new IllegalArgumentException("해당 위치에 말이 없습니다.")); + } + + private void validateTeamRule(Piece movingPiece, Position end) { + if (!movingPiece.isSameTeam(turn)) { + throw new IllegalArgumentException("상대 팀의 차례입니다."); + } + if (isSameTeamAtDestination(end)) { + throw new IllegalArgumentException("같은 팀의 말을 잡을 수 없습니다."); + } + } + + private boolean isSameTeamAtDestination(Position end) { + return find(end).map(piece -> piece.isSameTeam(turn)) + .orElse(false); + } + + private boolean isBlocked(List path) { + return path.stream() + .anyMatch(board::containsKey); + } + + private void move(Position start, Position end, Piece movingPiece) { + board.remove(start); + board.put(end, movingPiece); + turn = turn.next(); + } +} diff --git a/src/main/java/chess/domain/BoardFactory.java b/src/main/java/chess/domain/BoardFactory.java new file mode 100644 index 00000000000..a5686459154 --- /dev/null +++ b/src/main/java/chess/domain/BoardFactory.java @@ -0,0 +1,73 @@ +package chess.domain; + +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 java.util.HashMap; +import java.util.Map; + +public class BoardFactory { + + private BoardFactory() { + } + + public static Board createInitialBoard() { + Map map = new HashMap<>(); + initializePawn(map); + initializeKnight(map); + initializeBishop(map); + initializeRook(map); + initializeQueen(map); + initializeKing(map); + + return new Board(map); + } + + private static void initializePawn(Map map) { + for (File file : File.values()) { + map.put(new Position(file, Rank.TWO), new Pawn(Team.WHITE)); + } + for (File file : File.values()) { + map.put(new Position(file, Rank.SEVEN), new Pawn(Team.BLACK)); + } + } + + private static void initializeKnight(Map map) { + map.put(new Position(File.B, Rank.ONE), new Knight(Team.WHITE)); + map.put(new Position(File.G, Rank.ONE), new Knight(Team.WHITE)); + map.put(new Position(File.B, Rank.EIGHT), new Knight(Team.BLACK)); + map.put(new Position(File.G, Rank.EIGHT), new Knight(Team.BLACK)); + } + + private static void initializeBishop(Map map) { + map.put(new Position(File.C, Rank.ONE), new Bishop(Team.WHITE)); + map.put(new Position(File.F, Rank.ONE), new Bishop(Team.WHITE)); + map.put(new Position(File.C, Rank.EIGHT), new Bishop(Team.BLACK)); + map.put(new Position(File.F, Rank.EIGHT), new Bishop(Team.BLACK)); + } + + private static void initializeRook(Map map) { + map.put(new Position(File.A, Rank.ONE), new Rook(Team.WHITE)); + map.put(new Position(File.H, Rank.ONE), new Rook(Team.WHITE)); + map.put(new Position(File.A, Rank.EIGHT), new Rook(Team.BLACK)); + map.put(new Position(File.H, Rank.EIGHT), new Rook(Team.BLACK)); + } + + private static void initializeQueen(Map map) { + map.put(new Position(File.D, Rank.ONE), new Queen(Team.WHITE)); + map.put(new Position(File.D, Rank.EIGHT), new Queen(Team.BLACK)); + } + + private static void initializeKing(Map map) { + map.put(new Position(File.E, Rank.ONE), new King(Team.WHITE)); + map.put(new Position(File.E, Rank.EIGHT), new King(Team.BLACK)); + } +} diff --git a/src/main/java/chess/domain/movement/MovementRule.java b/src/main/java/chess/domain/movement/MovementRule.java new file mode 100644 index 00000000000..9c16ec8f3a5 --- /dev/null +++ b/src/main/java/chess/domain/movement/MovementRule.java @@ -0,0 +1,11 @@ +package chess.domain.movement; + +import chess.domain.position.Position; +import java.util.List; + +public interface MovementRule { + + boolean isMovable(Position start, Position end, boolean hasEnemy); + + List findPath(Position start, Position end, boolean hasEnemy); +} diff --git a/src/main/java/chess/domain/movement/continuous/ContinuousMovementRule.java b/src/main/java/chess/domain/movement/continuous/ContinuousMovementRule.java new file mode 100644 index 00000000000..d8483efcf74 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/ContinuousMovementRule.java @@ -0,0 +1,42 @@ +package chess.domain.movement.continuous; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; +import java.util.stream.Stream; + +public abstract class ContinuousMovementRule implements MovementRule { + + @Override + public final boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return isMovable(rankDifference, fileDifference); + } + + @Override + public final List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + int amount = calculate(start, end); + + return Stream.iterate(next(start), this::next) + .limit(amount) + .toList(); + } + + private int calculate(Position start, Position end) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + int rankDifferenceSize = Math.abs(rankDifference); + int fileDifferenceSize = Math.abs(fileDifference); + + return Math.max(rankDifferenceSize, fileDifferenceSize) - 1; + } + + protected abstract boolean isMovable(int rankDifference, int fileDifference); + + protected abstract Position next(Position position); +} diff --git a/src/main/java/chess/domain/movement/continuous/EastMovement.java b/src/main/java/chess/domain/movement/continuous/EastMovement.java new file mode 100644 index 00000000000..fdad60b825c --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/EastMovement.java @@ -0,0 +1,16 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class EastMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference == 0 && fileDifference > 0; + } + + @Override + protected Position next(Position position) { + return position.moveToEast(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/NorthEastMovement.java b/src/main/java/chess/domain/movement/continuous/NorthEastMovement.java new file mode 100644 index 00000000000..058c133e3c3 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/NorthEastMovement.java @@ -0,0 +1,17 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class NorthEastMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference > 0 && rankDifference == fileDifference; + } + + @Override + protected Position next(Position position) { + return position.moveToNorth() + .moveToEast(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/NorthMovement.java b/src/main/java/chess/domain/movement/continuous/NorthMovement.java new file mode 100644 index 00000000000..c6135f7772e --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/NorthMovement.java @@ -0,0 +1,16 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class NorthMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return fileDifference == 0 && rankDifference > 0; + } + + @Override + protected Position next(Position position) { + return position.moveToNorth(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/NorthWestMovement.java b/src/main/java/chess/domain/movement/continuous/NorthWestMovement.java new file mode 100644 index 00000000000..9d2dd2b63e1 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/NorthWestMovement.java @@ -0,0 +1,17 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class NorthWestMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference > 0 && rankDifference == -fileDifference; + } + + @Override + protected Position next(Position position) { + return position.moveToNorth() + .moveToWest(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/SouthEastMovement.java b/src/main/java/chess/domain/movement/continuous/SouthEastMovement.java new file mode 100644 index 00000000000..d0f0da32dda --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/SouthEastMovement.java @@ -0,0 +1,17 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class SouthEastMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference < 0 && rankDifference == -fileDifference; + } + + @Override + protected Position next(Position position) { + return position.moveToSouth() + .moveToEast(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/SouthMovement.java b/src/main/java/chess/domain/movement/continuous/SouthMovement.java new file mode 100644 index 00000000000..bcd766531a6 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/SouthMovement.java @@ -0,0 +1,16 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class SouthMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return fileDifference == 0 && rankDifference < 0; + } + + @Override + protected Position next(Position position) { + return position.moveToSouth(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/SouthWestMovement.java b/src/main/java/chess/domain/movement/continuous/SouthWestMovement.java new file mode 100644 index 00000000000..d4a6bcf65f4 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/SouthWestMovement.java @@ -0,0 +1,17 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class SouthWestMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference < 0 && rankDifference == fileDifference; + } + + @Override + protected Position next(Position position) { + return position.moveToSouth() + .moveToWest(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/WestMovement.java b/src/main/java/chess/domain/movement/continuous/WestMovement.java new file mode 100644 index 00000000000..f9e4a9e1c8a --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/WestMovement.java @@ -0,0 +1,16 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class WestMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference == 0 && fileDifference < 0; + } + + @Override + protected Position next(Position position) { + return position.moveToWest(); + } +} diff --git a/src/main/java/chess/domain/movement/discrete/DiscreteMovementRule.java b/src/main/java/chess/domain/movement/discrete/DiscreteMovementRule.java new file mode 100644 index 00000000000..ced0ac5cb29 --- /dev/null +++ b/src/main/java/chess/domain/movement/discrete/DiscreteMovementRule.java @@ -0,0 +1,27 @@ +package chess.domain.movement.discrete; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public abstract class DiscreteMovementRule implements MovementRule { + + @Override + public final boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return isMovable(rankDifference, fileDifference); + } + + @Override + public final List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + return List.of(); + } + + protected abstract boolean isMovable(int rankDifference, int fileDifference); +} diff --git a/src/main/java/chess/domain/movement/discrete/KingMovement.java b/src/main/java/chess/domain/movement/discrete/KingMovement.java new file mode 100644 index 00000000000..44351e744ad --- /dev/null +++ b/src/main/java/chess/domain/movement/discrete/KingMovement.java @@ -0,0 +1,12 @@ +package chess.domain.movement.discrete; + +public final class KingMovement extends DiscreteMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + int rankDifferenceSize = Math.abs(rankDifference); + int fileDifferenceSize = Math.abs(fileDifference); + + return rankDifferenceSize <= 1 && fileDifferenceSize <= 1; + } +} diff --git a/src/main/java/chess/domain/movement/discrete/KnightMovement.java b/src/main/java/chess/domain/movement/discrete/KnightMovement.java new file mode 100644 index 00000000000..71a7aab77c2 --- /dev/null +++ b/src/main/java/chess/domain/movement/discrete/KnightMovement.java @@ -0,0 +1,13 @@ +package chess.domain.movement.discrete; + +public final class KnightMovement extends DiscreteMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + int rankDifferenceSize = Math.abs(rankDifference); + int fileDifferenceSize = Math.abs(fileDifference); + + return (rankDifferenceSize == 2 && fileDifferenceSize == 1) + || (fileDifferenceSize == 2 && rankDifferenceSize == 1); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/BlackPawnDefaultMovement.java b/src/main/java/chess/domain/movement/pawn/BlackPawnDefaultMovement.java new file mode 100644 index 00000000000..325584a818b --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnDefaultMovement.java @@ -0,0 +1,23 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public final class BlackPawnDefaultMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return !hasEnemy && rankDifference == -1 && fileDifference == 0; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + return List.of(); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/BlackPawnDiagonalMovement.java b/src/main/java/chess/domain/movement/pawn/BlackPawnDiagonalMovement.java new file mode 100644 index 00000000000..364431ce834 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnDiagonalMovement.java @@ -0,0 +1,20 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public class BlackPawnDiagonalMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + int fileDifferenceSize = Math.abs(fileDifference); + + return hasEnemy && rankDifference == -1 && fileDifferenceSize == 1; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + return List.of(); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java b/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java new file mode 100644 index 00000000000..d11949b8420 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java @@ -0,0 +1,25 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.List; + +public final class BlackPawnFirstMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return !hasEnemy && start.isSameRank(Rank.SEVEN) && rankDifference == -2 && fileDifference == 0; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + Position middle = start.moveToSouth(); + return List.of(middle); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/WhitePawnDefaultMovement.java b/src/main/java/chess/domain/movement/pawn/WhitePawnDefaultMovement.java new file mode 100644 index 00000000000..e222c3ba045 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnDefaultMovement.java @@ -0,0 +1,23 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public final class WhitePawnDefaultMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return !hasEnemy && rankDifference == 1 && fileDifference == 0; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + return List.of(); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/WhitePawnDiagonalMovement.java b/src/main/java/chess/domain/movement/pawn/WhitePawnDiagonalMovement.java new file mode 100644 index 00000000000..77a44292944 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnDiagonalMovement.java @@ -0,0 +1,20 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public class WhitePawnDiagonalMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + int fileDifferenceSize = Math.abs(fileDifference); + + return hasEnemy && rankDifference == 1 && fileDifferenceSize == 1; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + return List.of(); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java b/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java new file mode 100644 index 00000000000..67c5f685fdd --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java @@ -0,0 +1,25 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.List; + +public final class WhitePawnFirstMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return !hasEnemy && start.isSameRank(Rank.TWO) && rankDifference == 2 && fileDifference == 0; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + Position middle = start.moveToNorth(); + return List.of(middle); + } +} diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java new file mode 100644 index 00000000000..ddef1bee55a --- /dev/null +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -0,0 +1,18 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.continuous.NorthEastMovement; +import chess.domain.movement.continuous.NorthWestMovement; +import chess.domain.movement.continuous.SouthEastMovement; +import chess.domain.movement.continuous.SouthWestMovement; +import java.util.List; + +public final class Bishop extends Piece { + + private static final List MOVEMENT_RULES = List.of( + new NorthEastMovement(), new SouthEastMovement(), new NorthWestMovement(), new SouthWestMovement()); + + public Bishop(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java new file mode 100644 index 00000000000..512dd282846 --- /dev/null +++ b/src/main/java/chess/domain/piece/King.java @@ -0,0 +1,14 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.discrete.KingMovement; +import java.util.List; + +public final class King extends Piece { + + private static final List MOVEMENT_RULES = List.of(new KingMovement()); + + public King(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java new file mode 100644 index 00000000000..b9532ba6169 --- /dev/null +++ b/src/main/java/chess/domain/piece/Knight.java @@ -0,0 +1,14 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.discrete.KnightMovement; +import java.util.List; + +public final class Knight extends Piece { + + private static final List MOVEMENT_RULES = List.of(new KnightMovement()); + + public Knight(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java new file mode 100644 index 00000000000..8262fe13c60 --- /dev/null +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -0,0 +1,29 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.pawn.BlackPawnDefaultMovement; +import chess.domain.movement.pawn.BlackPawnDiagonalMovement; +import chess.domain.movement.pawn.BlackPawnFirstMovement; +import chess.domain.movement.pawn.WhitePawnDefaultMovement; +import chess.domain.movement.pawn.WhitePawnDiagonalMovement; +import chess.domain.movement.pawn.WhitePawnFirstMovement; +import java.util.List; + +public final class Pawn extends Piece { + + private static final List BLACK_MOVEMENT_RULES = List.of( + new BlackPawnFirstMovement(), new BlackPawnDefaultMovement(), new BlackPawnDiagonalMovement()); + private static final List WHITE_MOVEMENT_RULES = List.of( + new WhitePawnFirstMovement(), new WhitePawnDefaultMovement(), new WhitePawnDiagonalMovement()); + + public Pawn(Team team) { + super(team, findMovementRule(team)); + } + + private static List findMovementRule(Team team) { + if (team.isBlack()) { + return BLACK_MOVEMENT_RULES; + } + return WHITE_MOVEMENT_RULES; + } +} diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java new file mode 100644 index 00000000000..601de0104f5 --- /dev/null +++ b/src/main/java/chess/domain/piece/Piece.java @@ -0,0 +1,32 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public abstract class Piece { + + private final Team team; + private final List movementRules; + + protected Piece(Team team, List movementRules) { + this.team = team; + this.movementRules = movementRules; + } + + public final boolean isBlackTeam() { + return team.isBlack(); + } + + public final List findPath(Position start, Position end, boolean hasEnemy) { + return movementRules.stream() + .filter(movementRule -> movementRule.isMovable(start, end, hasEnemy)) + .findAny() + .map(movementRule -> movementRule.findPath(start, end, hasEnemy)) + .orElseThrow(() -> new IllegalArgumentException("불가능한 경로입니다.")); + } + + public boolean isSameTeam(Team team) { + return this.team == team; + } +} diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java new file mode 100644 index 00000000000..f9c9c19e378 --- /dev/null +++ b/src/main/java/chess/domain/piece/Queen.java @@ -0,0 +1,23 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.continuous.EastMovement; +import chess.domain.movement.continuous.NorthEastMovement; +import chess.domain.movement.continuous.NorthMovement; +import chess.domain.movement.continuous.NorthWestMovement; +import chess.domain.movement.continuous.SouthEastMovement; +import chess.domain.movement.continuous.SouthMovement; +import chess.domain.movement.continuous.SouthWestMovement; +import chess.domain.movement.continuous.WestMovement; +import java.util.List; + +public final class Queen extends Piece { + + private static final List MOVEMENT_RULES = List.of( + new EastMovement(), new WestMovement(), new SouthMovement(), new NorthMovement(), + new NorthEastMovement(), new SouthEastMovement(), new NorthWestMovement(), new SouthWestMovement()); + + public Queen(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java new file mode 100644 index 00000000000..c7ffb684c51 --- /dev/null +++ b/src/main/java/chess/domain/piece/Rook.java @@ -0,0 +1,18 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.continuous.EastMovement; +import chess.domain.movement.continuous.NorthMovement; +import chess.domain.movement.continuous.SouthMovement; +import chess.domain.movement.continuous.WestMovement; +import java.util.List; + +public final class Rook extends Piece { + + private static final List MOVEMENT_RULES = List.of( + new EastMovement(), new WestMovement(), new SouthMovement(), new NorthMovement()); + + public Rook(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/Team.java b/src/main/java/chess/domain/piece/Team.java new file mode 100644 index 00000000000..4891d246ae9 --- /dev/null +++ b/src/main/java/chess/domain/piece/Team.java @@ -0,0 +1,17 @@ +package chess.domain.piece; + +public enum Team { + + BLACK, WHITE; + + public boolean isBlack() { + return this == BLACK; + } + + public Team next() { + if (this == BLACK) { + return WHITE; + } + return BLACK; + } +} diff --git a/src/main/java/chess/domain/position/File.java b/src/main/java/chess/domain/position/File.java new file mode 100644 index 00000000000..027dc6a959b --- /dev/null +++ b/src/main/java/chess/domain/position/File.java @@ -0,0 +1,53 @@ +package chess.domain.position; + +import java.util.Arrays; + +public enum File { + + A(1), + B(2), + C(3), + D(4), + E(5), + F(6), + G(7), + H(8); + + private static final int MIN_WEST_TO_EAST = 1; + private static final int MAX_WEST_TO_EAST = 8; + private static final int TO_EAST = 1; + private static final int TO_WEST = -1; + + private final int westToEast; + + File(int westToEast) { + this.westToEast = westToEast; + } + + public File toEast() { + if (westToEast >= MAX_WEST_TO_EAST) { + throw new IllegalStateException("동쪽으로 이동할 수 없습니다."); + } + + return find(westToEast + TO_EAST); + } + + public File toWest() { + if (westToEast <= MIN_WEST_TO_EAST) { + throw new IllegalStateException("서쪽으로 이동할 수 없습니다."); + } + + return find(westToEast + TO_WEST); + } + + private File find(int westToEast) { + return Arrays.stream(File.values()) + .filter(column -> column.westToEast == westToEast) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당하는 가로 위치가 없습니다.")); + } + + public int calculateDifference(File other) { + return other.westToEast - this.westToEast; + } +} diff --git a/src/main/java/chess/domain/position/Position.java b/src/main/java/chess/domain/position/Position.java new file mode 100644 index 00000000000..8e6b99e1643 --- /dev/null +++ b/src/main/java/chess/domain/position/Position.java @@ -0,0 +1,66 @@ +package chess.domain.position; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class Position { + + public static final List ALL_POSITIONS = Arrays.stream(Rank.values()) + .flatMap(row -> Arrays.stream(File.values()) + .map(column -> new Position(column, row))) + .toList(); + + private final File file; + private final Rank rank; + + public Position(File file, Rank rank) { + this.file = Objects.requireNonNull(file); + this.rank = Objects.requireNonNull(rank); + } + + public boolean isSameRank(Rank rank) { + return this.rank == rank; + } + + public int calculateFileDifference(Position other) { + return this.file.calculateDifference(other.file); + } + + public int calculateRankDifference(Position other) { + return this.rank.calculateDifference(other.rank); + } + + public Position moveToEast() { + return new Position(file.toEast(), rank); + } + + public Position moveToWest() { + return new Position(file.toWest(), rank); + } + + public Position moveToNorth() { + return new Position(file, rank.toNorth()); + } + + public Position moveToSouth() { + return new Position(file, rank.toSouth()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Position position = (Position) o; + return rank == position.rank && file == position.file; + } + + @Override + public int hashCode() { + return Objects.hash(rank, file); + } +} diff --git a/src/main/java/chess/domain/position/Rank.java b/src/main/java/chess/domain/position/Rank.java new file mode 100644 index 00000000000..97fd1485b25 --- /dev/null +++ b/src/main/java/chess/domain/position/Rank.java @@ -0,0 +1,53 @@ +package chess.domain.position; + +import java.util.Arrays; + +public enum Rank { + + ONE(1), + TWO(2), + THREE(3), + FOUR(4), + FIVE(5), + SIX(6), + SEVEN(7), + EIGHT(8); + + private static final int MIN_SOUTH_TO_NORTH = 1; + private static final int MAX_SOUTH_TO_NORTH = 8; + private static final int TO_NORTH = 1; + private static final int TO_SOUTH = -1; + + private final int southToNorth; + + Rank(int southToNorth) { + this.southToNorth = southToNorth; + } + + public Rank toNorth() { + if (southToNorth >= MAX_SOUTH_TO_NORTH) { + throw new IllegalStateException("북쪽으로 이동할 수 없습니다."); + } + + return find(southToNorth + TO_NORTH); + } + + public Rank toSouth() { + if (southToNorth <= MIN_SOUTH_TO_NORTH) { + throw new IllegalStateException("남쪽으로 이동할 수 없습니다."); + } + + return find(southToNorth + TO_SOUTH); + } + + private Rank find(int southToNorth) { + return Arrays.stream(Rank.values()) + .filter(row -> row.southToNorth == southToNorth) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당하는 가로 위치가 없습니다.")); + } + + public int calculateDifference(Rank other) { + return other.southToNorth - this.southToNorth; + } +} diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java new file mode 100644 index 00000000000..1f2eb01260b --- /dev/null +++ b/src/main/java/chess/dto/BoardDto.java @@ -0,0 +1,41 @@ +package chess.dto; + +import chess.domain.Board; +import chess.domain.piece.Piece; +import chess.domain.position.Position; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class BoardDto { + + private final Map boardDto; + + private BoardDto(Map boardDto) { + this.boardDto = boardDto; + } + + public static BoardDto of(Board board) { + List positions = Position.ALL_POSITIONS; + Map boardDto = new HashMap<>(); + positions.forEach(position -> addPiece(board, position, boardDto)); + return new BoardDto(boardDto); + } + + private static void addPiece(Board board, Position position, Map boardDto) { + Optional optionalPiece = board.find(position); + + if (optionalPiece.isEmpty()) { + return; + } + + Piece piece = optionalPiece.get(); + PieceDto pieceDto = PieceDto.from(piece); + boardDto.put(position, pieceDto); + } + + public Optional find(Position position) { + return Optional.ofNullable(boardDto.get(position)); + } +} diff --git a/src/main/java/chess/dto/PieceDto.java b/src/main/java/chess/dto/PieceDto.java new file mode 100644 index 00000000000..bc7230741ec --- /dev/null +++ b/src/main/java/chess/dto/PieceDto.java @@ -0,0 +1,29 @@ +package chess.dto; + +import chess.domain.piece.Piece; + +public class PieceDto { + + private final PieceType type; + private final boolean isBlack; + + private PieceDto(PieceType type, boolean isBlack) { + this.type = type; + this.isBlack = isBlack; + } + + static PieceDto from(Piece piece) { + PieceType type = PieceType.from(piece); + boolean isBlackTeam = piece.isBlackTeam(); + + return new PieceDto(type, isBlackTeam); + } + + public PieceType type() { + return type; + } + + public boolean isBlack() { + return isBlack; + } +} diff --git a/src/main/java/chess/dto/PieceType.java b/src/main/java/chess/dto/PieceType.java new file mode 100644 index 00000000000..9427ec01a70 --- /dev/null +++ b/src/main/java/chess/dto/PieceType.java @@ -0,0 +1,33 @@ +package chess.dto; + +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 java.util.Arrays; + +public enum PieceType { + + KING(King.class), + QUEEN(Queen.class), + ROOK(Rook.class), + BISHOP(Bishop.class), + KNIGHT(Knight.class), + PAWN(Pawn.class); + + private final Class category; + + PieceType(Class category) { + this.category = category; + } + + public static PieceType from(Piece piece) { + return Arrays.stream(PieceType.values()) + .filter(pieceType -> pieceType.category == piece.getClass()) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당 기물이 존재하지 않습니다.")); + } +} diff --git a/src/main/java/chess/view/GameCommand.java b/src/main/java/chess/view/GameCommand.java new file mode 100644 index 00000000000..eddecf7a225 --- /dev/null +++ b/src/main/java/chess/view/GameCommand.java @@ -0,0 +1,23 @@ +package chess.view; + +import java.util.Arrays; + +public enum GameCommand { + + START("start"), + END("end"), + MOVE("move"); + + private final String command; + + GameCommand(String command) { + this.command = command; + } + + public static GameCommand from(String input) { + return Arrays.stream(GameCommand.values()) + .filter(gameCommand -> gameCommand.command.equals(input)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 커멘드입니다.")); + } +} diff --git a/src/main/java/chess/view/InputView.java b/src/main/java/chess/view/InputView.java new file mode 100644 index 00000000000..836613a97cf --- /dev/null +++ b/src/main/java/chess/view/InputView.java @@ -0,0 +1,62 @@ +package chess.view; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.Map; +import java.util.Scanner; + +public class InputView { + + private static final Scanner SCANNER = new Scanner(System.in); + private static final Map FILE_INPUT = Map.of( + 'a', File.A, 'b', File.B, 'c', File.C, 'd', File.D, + 'e', File.E, 'f', File.F, 'g', File.G, 'h', File.H); + private static final Map RANK_INPUT = Map.of( + '1', Rank.ONE, '2', Rank.TWO, '3', Rank.THREE, '4', Rank.FOUR, + '5', Rank.FIVE, '6', Rank.SIX, '7', Rank.SEVEN, '8', Rank.EIGHT); + private static final int POSITION_INPUT_LENGTH = 2; + private static final int FILE_INDEX = 0; + private static final int RANK_INDEX = 1; + + public GameCommand readCommand() { + String input = SCANNER.next(); + return GameCommand.from(input); + } + + public Position readPosition() { + String input = SCANNER.next(); + validatePositionInputLength(input); + + File file = convertToFile(input); + Rank rank = convertToRank(input); + + return new Position(file, rank); + } + + private void validatePositionInputLength(String input) { + if (input.length() != POSITION_INPUT_LENGTH) { + throw new IllegalArgumentException("위치 형식이 올바르지 않습니다."); + } + } + + private File convertToFile(String input) { + char fileInput = input.charAt(FILE_INDEX); + File file = FILE_INPUT.get(fileInput); + if (file == null) { + throw new IllegalArgumentException("위치 형식이 올바르지 않습니다."); + } + + return file; + } + + private Rank convertToRank(String input) { + char rankInput = input.charAt(RANK_INDEX); + Rank rank = RANK_INPUT.get(rankInput); + if (rank == null) { + throw new IllegalArgumentException("위치 형식이 올바르지 않습니다."); + } + + return rank; + } +} diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java new file mode 100644 index 00000000000..7515b51f114 --- /dev/null +++ b/src/main/java/chess/view/OutputView.java @@ -0,0 +1,73 @@ +package chess.view; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.dto.BoardDto; +import chess.dto.PieceDto; +import chess.dto.PieceType; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class OutputView { + + private static final List RANK_ORDER = List.of( + Rank.EIGHT, Rank.SEVEN, Rank.SIX, Rank.FIVE, Rank.FOUR, Rank.THREE, Rank.TWO, Rank.ONE); + private static final List FILE_ORDER = List.of( + File.A, File.B, File.C, File.D, File.E, File.F, File.G, File.H); + private static final Map PIECE_DISPLAY = Map.of( + PieceType.KING, "K", PieceType.QUEEN, "Q", PieceType.KNIGHT, "N", + PieceType.BISHOP, "B", PieceType.ROOK, "R", PieceType.PAWN, "P"); + private static final String EMPTY_SPACE = "."; + private static final String ERROR_PREFIX = "[ERROR] "; + + public void printStartGame() { + System.out.println(""" + > 체스 게임을 시작합니다. + > 게임 시작 : start + > 게임 종료 : end + > 게임 이동 : move source위치 target위치 - 예. move b2 b3"""); + } + + public void printBoard(BoardDto boardDto) { + for (Rank rank : RANK_ORDER) { + printBoardOneLine(boardDto, rank); + } + System.out.println(); + } + + private void printBoardOneLine(BoardDto boardDto, Rank rank) { + for (File file : FILE_ORDER) { + Optional piece = boardDto.find(new Position(file, rank)); + piece.ifPresentOrElse(this::printPiece, this::printEmptySpace); + } + System.out.println(); + } + + private void printPiece(PieceDto pieceDto) { + if (pieceDto.isBlack()) { + printBlackPiece(pieceDto.type()); + return; + } + printWhitePiece(pieceDto.type()); + } + + private void printBlackPiece(PieceType type) { + String display = PIECE_DISPLAY.get(type); + System.out.print(display.toUpperCase()); + } + + private void printWhitePiece(PieceType type) { + String display = PIECE_DISPLAY.get(type); + System.out.print(display.toLowerCase()); + } + + private void printEmptySpace() { + System.out.print(EMPTY_SPACE); + } + + public void printExceptionMessage(Exception exception) { + System.out.println(ERROR_PREFIX + exception.getMessage()); + } +} diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/test/java/chess/domain/BoardFactoryTest.java b/src/test/java/chess/domain/BoardFactoryTest.java new file mode 100644 index 00000000000..2de34a81974 --- /dev/null +++ b/src/test/java/chess/domain/BoardFactoryTest.java @@ -0,0 +1,89 @@ +package chess.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +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.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class BoardFactoryTest { + + private static final Board BOARD = BoardFactory.createInitialBoard(); + + @ParameterizedTest + @CsvSource({ + "TWO, A", "TWO, B", "TWO, C", "TWO, D", "TWO, E", "TWO, F", "TWO, G", "TWO, H", + "SEVEN, A", "SEVEN, B", "SEVEN, C", "SEVEN, D", "SEVEN, E", "SEVEN, F", "SEVEN, G", "SEVEN, H" + }) + @DisplayName("폰이 초기 위치에 있다.") + void pawnTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Pawn.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, B", "ONE, G", "EIGHT, B", "EIGHT, G" + }) + @DisplayName("나이트가 초기 위치에 있다.") + void knightTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Knight.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, C", "ONE, F", "EIGHT, C", "EIGHT, F" + }) + @DisplayName("비숍이 초기 위치에 있다.") + void BishopTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Bishop.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, A", "ONE, H", "EIGHT, A", "EIGHT, H" + }) + @DisplayName("륙이 초기 위치에 있다.") + void rookTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Rook.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, D", "EIGHT, D" + }) + @DisplayName("퀸이 초기 위치에 있다.") + void queenTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Queen.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, E", "EIGHT, E" + }) + @DisplayName("킹이 초기 위치에 있다.") + void kingTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(King.class); + } +} diff --git a/src/test/java/chess/domain/BoardTest.java b/src/test/java/chess/domain/BoardTest.java new file mode 100644 index 00000000000..c001ddef0b7 --- /dev/null +++ b/src/test/java/chess/domain/BoardTest.java @@ -0,0 +1,106 @@ +package chess.domain; + +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.King; +import chess.domain.piece.Piece; +import chess.domain.piece.Queen; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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.Nested; +import org.junit.jupiter.api.Test; + +class BoardTest { + + @Test + @DisplayName("특정 위치에 어떤 말이 있는지 알려준다.") + void findTest_whenPieceExist() { + Position position = new Position(File.D, Rank.TWO); + King king = new King(Team.WHITE); + Map map = Map.of(position, king); + Board board = new Board(map); + + assertThat(board.find(position)).isEqualTo(Optional.of(king)); + } + + @Test + @DisplayName("특정 위치에 어떤 말이 있는지 알려준다.") + void findTest_whenPieceNotExist() { + Position position = new Position(File.E, Rank.TWO); + King king = new King(Team.WHITE); + Map map = Map.of(position, king); + Position notExistPosition = new Position(File.D, Rank.TWO); + Board board = new Board(map); + + assertThat(board.find(notExistPosition)).isEqualTo(Optional.empty()); + } + + @Nested + @DisplayName("말 이동 테스트") + class MovingTest { + + private static final Position START_KING = new Position(File.E, Rank.FOUR); + private static final King KING = new King(Team.WHITE); + private static final Position START_QUEEN = new Position(File.E, Rank.TWO); + private static final Queen QUEEN = new Queen(Team.WHITE); + private static final Map MAP = Map.of(START_KING, KING, START_QUEEN, QUEEN); + private Board board; + + @BeforeEach + void beforeEach() { + board = new Board(MAP); + } + + @Test + @DisplayName("시작 위치에 있는 말을 도착 위치로 움직인다.") + void moveTest() { + Position possibleEnd = new Position(File.E, Rank.THREE); + + board.tryMove(START_KING, possibleEnd); + + assertAll( + () -> assertThat(board.find(possibleEnd)).isEqualTo(Optional.of(KING)), + () -> assertThat(board.find(START_KING)).isEqualTo(Optional.empty()) + ); + } + + @Test + @DisplayName("시작 위치에 말이 없을 경우, 예외가 발생한다.") + void moveTest_whenPieceNotExist_throwException() { + Position emptyPosition = new Position(File.F, Rank.EIGHT); + Position end = new Position(File.F, Rank.TWO); + + assertThatThrownBy(() -> board.tryMove(emptyPosition, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("해당 위치에 말이 없습니다."); + } + + @Test + @DisplayName("말의 이동 반경을 벗어날 경우, 예외가 발생한다.") + void moveTest_whenOutOfMovement_throwException() { + Position impossibleEnd = new Position(File.F, Rank.EIGHT); + + assertThatThrownBy(() -> board.tryMove(START_KING, impossibleEnd)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } + + @Test + @DisplayName("이동 경로 위에 다른 말이 존재할 경우, 예외가 발생한다.") + void moveTest_whenBlocked_throwException() { + Position impossibleEnd = new Position(File.E, Rank.FIVE); + + assertThatThrownBy(() -> board.tryMove(START_QUEEN, impossibleEnd)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("다른 말이 있어 이동 불가능합니다."); + } + } +} diff --git a/src/test/java/chess/domain/movement/continuous/EastMovementTest.java b/src/test/java/chess/domain/movement/continuous/EastMovementTest.java new file mode 100644 index 00000000000..b2de9a86dcd --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/EastMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EastMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.E, Rank.FOUR); + EastMovement eastMovement = new EastMovement(); + + assertThat(eastMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.E, Rank.THREE); + EastMovement eastMovement = new EastMovement(); + + assertThat(eastMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.F, Rank.FOUR); + EastMovement eastMovement = new EastMovement(); + + assertThat(eastMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.D, Rank.FOUR), new Position(File.E, Rank.FOUR)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/NorthEastMovementTest.java b/src/test/java/chess/domain/movement/continuous/NorthEastMovementTest.java new file mode 100644 index 00000000000..57ab7ec1fd4 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/NorthEastMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NorthEastMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.E, Rank.SIX); + NorthEastMovement northEastMovement = new NorthEastMovement(); + + assertThat(northEastMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.D, Rank.SIX); + NorthEastMovement northEastMovement = new NorthEastMovement(); + + assertThat(northEastMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.F, Rank.SEVEN); + NorthEastMovement northEastMovement = new NorthEastMovement(); + + assertThat(northEastMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.D, Rank.FIVE), new Position(File.E, Rank.SIX)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/NorthMovementTest.java b/src/test/java/chess/domain/movement/continuous/NorthMovementTest.java new file mode 100644 index 00000000000..176e5319470 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/NorthMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NorthMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.C, Rank.SIX); + NorthMovement northMovement = new NorthMovement(); + + assertThat(northMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.B, Rank.SIX); + NorthMovement northMovement = new NorthMovement(); + + assertThat(northMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.C, Rank.SEVEN); + NorthMovement northMovement = new NorthMovement(); + + assertThat(northMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.FIVE), new Position(File.C, Rank.SIX)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/NorthWestMovementTest.java b/src/test/java/chess/domain/movement/continuous/NorthWestMovementTest.java new file mode 100644 index 00000000000..6c0fb7a2732 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/NorthWestMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NorthWestMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.A, Rank.SIX); + NorthWestMovement northWestMovement = new NorthWestMovement(); + + assertThat(northWestMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.B, Rank.SIX); + NorthWestMovement northWestMovement = new NorthWestMovement(); + + assertThat(northWestMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.SEVEN); + NorthWestMovement northWestMovement = new NorthWestMovement(); + + assertThat(northWestMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.FIVE), new Position(File.B, Rank.SIX)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/SouthEastMovementTest.java b/src/test/java/chess/domain/movement/continuous/SouthEastMovementTest.java new file mode 100644 index 00000000000..fca6518d92a --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/SouthEastMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SouthEastMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.E, Rank.TWO); + SouthEastMovement southEastMovement = new SouthEastMovement(); + + assertThat(southEastMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.D, Rank.TWO); + SouthEastMovement southEastMovement = new SouthEastMovement(); + + assertThat(southEastMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.F, Rank.ONE); + SouthEastMovement southEastMovement = new SouthEastMovement(); + + assertThat(southEastMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.D, Rank.THREE), new Position(File.E, Rank.TWO)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/SouthMovementTest.java b/src/test/java/chess/domain/movement/continuous/SouthMovementTest.java new file mode 100644 index 00000000000..aee841095b1 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/SouthMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SouthMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.C, Rank.TWO); + SouthMovement southMovement = new SouthMovement(); + + assertThat(southMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.B, Rank.TWO); + SouthMovement southMovement = new SouthMovement(); + + assertThat(southMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.C, Rank.ONE); + SouthMovement southMovement = new SouthMovement(); + + assertThat(southMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.THREE), new Position(File.C, Rank.TWO)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/SouthWestMovementTest.java b/src/test/java/chess/domain/movement/continuous/SouthWestMovementTest.java new file mode 100644 index 00000000000..d4f75a55ce1 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/SouthWestMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SouthWestMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.A, Rank.TWO); + SouthWestMovement southWestMovement = new SouthWestMovement(); + + assertThat(southWestMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.B, Rank.TWO); + SouthWestMovement southWestMovement = new SouthWestMovement(); + + assertThat(southWestMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.ONE); + SouthWestMovement southWestMovement = new SouthWestMovement(); + + assertThat(southWestMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.THREE), new Position(File.B, Rank.TWO)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/WestMovementTest.java b/src/test/java/chess/domain/movement/continuous/WestMovementTest.java new file mode 100644 index 00000000000..d5e44a27f7d --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/WestMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class WestMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.A, Rank.FOUR); + WestMovement westMovement = new WestMovement(); + + assertThat(westMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.A, Rank.THREE); + WestMovement westMovement = new WestMovement(); + + assertThat(westMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.FOUR); + WestMovement westMovement = new WestMovement(); + + assertThat(westMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.FOUR), new Position(File.B, Rank.FOUR)); + } +} diff --git a/src/test/java/chess/domain/movement/discrete/KingMovementTest.java b/src/test/java/chess/domain/movement/discrete/KingMovementTest.java new file mode 100644 index 00000000000..dd964c94f4f --- /dev/null +++ b/src/test/java/chess/domain/movement/discrete/KingMovementTest.java @@ -0,0 +1,49 @@ +package chess.domain.movement.discrete; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class KingMovementTest { + + private static final boolean HAS_ENEMY = false; + + @ParameterizedTest + @CsvSource({"D, FIVE", "E, FIVE", "E, FOUR", "E, THREE", "D, THREE", "C, THREE", "C, FOUR", "C, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + KingMovement kingMovement = new KingMovement(); + + assertThat(kingMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "B, TWO", "F, TWO"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + KingMovement kingMovement = new KingMovement(); + + assertThat(kingMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.FIVE); + KingMovement kingMovement = new KingMovement(); + + assertThat(kingMovement.findPath(start, end, HAS_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/discrete/KnightMovementTest.java b/src/test/java/chess/domain/movement/discrete/KnightMovementTest.java new file mode 100644 index 00000000000..b02ebb0ea02 --- /dev/null +++ b/src/test/java/chess/domain/movement/discrete/KnightMovementTest.java @@ -0,0 +1,49 @@ +package chess.domain.movement.discrete; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class KnightMovementTest { + + private static final boolean HAS_ENEMY = false; + + @ParameterizedTest + @CsvSource({"B, FIVE", "C, SIX", "E, SIX", "F, FIVE", "B, THREE", "C, TWO", "E, TWO", "F, THREE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + KnightMovement knightMovement = new KnightMovement(); + + assertThat(knightMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"C, FIVE", "B, SIX", "F, SIX", "E, FIVE", "C, THREE", "B, TWO", "F, TWO", "E, THREE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + KnightMovement knightMovement = new KnightMovement(); + + assertThat(knightMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.B, Rank.FIVE); + KnightMovement knightMovement = new KnightMovement(); + + assertThat(knightMovement.findPath(start, end, HAS_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java new file mode 100644 index 00000000000..e6b7b3c1848 --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java @@ -0,0 +1,59 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class BlackPawnDefaultMovementTest { + + private static final boolean NOT_EXIST_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.THREE); + BlackPawnDefaultMovement blackPawnDefaultMovement = new BlackPawnDefaultMovement(); + + assertThat(blackPawnDefaultMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "D, TWO", "D, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + BlackPawnDefaultMovement blackPawnDefaultMovement = new BlackPawnDefaultMovement(); + + assertThat(blackPawnDefaultMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @Test + @DisplayName("도착 위치에 적이 있을 경우, 이동 불가능하다.") + void isMovableTest_existEnemy() { + Position start = new Position(File.B, Rank.FOUR); + Position end = start.moveToSouth(); + boolean hasEnemy = true; + BlackPawnDefaultMovement blackPawnDefaultMovement = new BlackPawnDefaultMovement(); + + assertThat(blackPawnDefaultMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.THREE); + BlackPawnDefaultMovement blackPawnDefaultMovement = new BlackPawnDefaultMovement(); + + assertThat(blackPawnDefaultMovement.findPath(start, end, NOT_EXIST_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java new file mode 100644 index 00000000000..1999fb81f99 --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java @@ -0,0 +1,61 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class BlackPawnDiagonalMovementTest { + + private static final boolean EXIST_ENEMY = true; + + @ParameterizedTest + @CsvSource({"C, THREE", "E, THREE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + BlackPawnDiagonalMovement blackPawnDiagonalMovement = new BlackPawnDiagonalMovement(); + + assertThat(blackPawnDiagonalMovement.isMovable(start, end, EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "D, TWO", "D, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + BlackPawnDiagonalMovement blackPawnDiagonalMovement = new BlackPawnDiagonalMovement(); + + assertThat(blackPawnDiagonalMovement.isMovable(start, end, EXIST_ENEMY)).isFalse(); + } + + @ParameterizedTest + @CsvSource({"C, THREE", "E, THREE"}) + @DisplayName("도착 위치에 적이 없을 경우, 이동 불가능하다.") + void isMovableTest_notExistEnemy(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + boolean hasEnemy = false; + BlackPawnDiagonalMovement blackPawnDiagonalMovement = new BlackPawnDiagonalMovement(); + + assertThat(blackPawnDiagonalMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.E, Rank.THREE); + BlackPawnDiagonalMovement blackPawnDiagonalMovement = new BlackPawnDiagonalMovement(); + + assertThat(blackPawnDiagonalMovement.findPath(start, end, EXIST_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java new file mode 100644 index 00000000000..c8a6dc047b0 --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java @@ -0,0 +1,72 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class BlackPawnFirstMovementTest { + + private static final boolean NOT_EXIST_ENEMY = false; + private static final Rank AVAILABLE_START_RANK = Rank.SEVEN; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position end = new Position(File.D, Rank.FIVE); + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"C, SEVEN", "D, SIX", "D, FOUR", "D, EIGHT"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position end = new Position(file, rank); + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @ParameterizedTest + @CsvSource({"C, FIVE", "D, SIX", "D, EIGHT"}) + @DisplayName("초기 위치가 아닐 경우, 이동 불가능하다.") + void isMovableTest_notMatchInitialPosition(File file, Rank rank) { + Position start = new Position(file, rank); + Position end = start.moveToSouth().moveToSouth(); + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @Test + @DisplayName("도착 위치에 적이 있을 경우, 이동 불가능하다.") + void isMovableTest_existEnemy() { + Position start = new Position(File.B, AVAILABLE_START_RANK); + Position end = start.moveToSouth().moveToSouth(); + boolean hasEnemy = true; + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position middle = new Position(File.D, Rank.SIX); + Position end = new Position(File.D, Rank.FIVE); + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.findPath(start, end, NOT_EXIST_ENEMY)) + .containsExactly(middle); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java new file mode 100644 index 00000000000..47fc7abcd0f --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java @@ -0,0 +1,59 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class WhitePawnDefaultMovementTest { + + private static final boolean NOT_EXIST_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.FIVE); + WhitePawnDefaultMovement whitePawnDefaultMovement = new WhitePawnDefaultMovement(); + + assertThat(whitePawnDefaultMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "D, THREE", "D, SIX"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + WhitePawnDefaultMovement whitePawnDefaultMovement = new WhitePawnDefaultMovement(); + + assertThat(whitePawnDefaultMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @Test + @DisplayName("도착 위치에 적이 있을 경우, 이동 불가능하다.") + void isMovableTest_existEnemy() { + Position start = new Position(File.B, Rank.FOUR); + Position end = start.moveToNorth(); + boolean hasEnemy = true; + WhitePawnDefaultMovement whitePawnDefaultMovement = new WhitePawnDefaultMovement(); + + assertThat(whitePawnDefaultMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.FIVE); + WhitePawnDefaultMovement whitePawnDefaultMovement = new WhitePawnDefaultMovement(); + + assertThat(whitePawnDefaultMovement.findPath(start, end, NOT_EXIST_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java new file mode 100644 index 00000000000..f42b32d87fd --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java @@ -0,0 +1,61 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class WhitePawnDiagonalMovementTest { + + private static final boolean EXIST_ENEMY = true; + + @ParameterizedTest + @CsvSource({"C, FIVE", "E, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + WhitePawnDiagonalMovement whitePawnDiagonalMovement = new WhitePawnDiagonalMovement(); + + assertThat(whitePawnDiagonalMovement.isMovable(start, end, EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "D, THREE", "D, SIX"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + WhitePawnDiagonalMovement whitePawnDiagonalMovement = new WhitePawnDiagonalMovement(); + + assertThat(whitePawnDiagonalMovement.isMovable(start, end, EXIST_ENEMY)).isFalse(); + } + + @ParameterizedTest + @CsvSource({"C, FIVE", "E, FIVE"}) + @DisplayName("도착 위치에 적이 없을 경우, 이동 불가능하다.") + void isMovableTest_notExistEnemy(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + boolean hasEnemy = false; + WhitePawnDiagonalMovement whitePawnDiagonalMovement = new WhitePawnDiagonalMovement(); + + assertThat(whitePawnDiagonalMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.FIVE); + WhitePawnDiagonalMovement whitePawnDiagonalMovement = new WhitePawnDiagonalMovement(); + + assertThat(whitePawnDiagonalMovement.findPath(start, end, EXIST_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java new file mode 100644 index 00000000000..dc87f503593 --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java @@ -0,0 +1,72 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class WhitePawnFirstMovementTest { + + private static final boolean NOT_EXIST_ENEMY = false; + private static final Rank AVAILABLE_START_RANK = Rank.TWO; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position end = new Position(File.D, Rank.FOUR); + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"C, SEVEN", "D, ONE", "D, THREE", "D, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position end = new Position(file, rank); + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @ParameterizedTest + @CsvSource({"C, ONE", "D, THREE", "D, FOUR"}) + @DisplayName("초기 위치가 아닐 경우, 이동 불가능하다.") + void isMovableTest_notMatchInitialPosition(File file, Rank rank) { + Position start = new Position(file, rank); + Position end = start.moveToNorth().moveToNorth(); + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @Test + @DisplayName("도착 위치에 적이 있을 경우, 이동 불가능하다.") + void isMovableTest_existEnemy() { + Position start = new Position(File.B, AVAILABLE_START_RANK); + Position end = start.moveToNorth().moveToNorth(); + boolean hasEnemy = true; + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.TWO); + Position middle = new Position(File.D, Rank.THREE); + Position end = new Position(File.D, Rank.FOUR); + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.findPath(start, end, NOT_EXIST_ENEMY)) + .containsExactly(middle); + } +} diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java new file mode 100644 index 00000000000..f90da85c424 --- /dev/null +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -0,0 +1,55 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class BishopTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Bishop bishop = new Bishop(Team.WHITE); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.ONE); + + assertThat(bishop.findPath(start, end, HAS_ENEMY)) + .containsExactly( + new Position(File.C, Rank.THREE), + new Position(File.B, Rank.TWO)); + } + + @ParameterizedTest + @CsvSource({"E, FIVE", "F, SIX", "G, ONE", "B, TWO", "E, THREE", "G, SEVEN", "A, ONE", "C, THREE"}) + @DisplayName("이동이 가능한 경우, 예외를 발생하지 않는다.") + void findPathTest_whenCanMove(File file, Rank rank) { + Bishop bishop = new Bishop(Team.WHITE); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatCode(() -> bishop.findPath(start, end, HAS_ENEMY)).doesNotThrowAnyException(); + } + + @ParameterizedTest + @CsvSource({"D, TWO", "F, FOUR", "C, TWO", "E, SIX"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Bishop bishop = new Bishop(Team.WHITE); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> bishop.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/KingTest.java new file mode 100644 index 00000000000..80d8ac174b3 --- /dev/null +++ b/src/test/java/chess/domain/piece/KingTest.java @@ -0,0 +1,41 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class KingTest { + + private static final boolean HAS_ENEMY = false; + + @ParameterizedTest + @CsvSource({"F, THREE", "F, FIVE", "E, THREE", "E, FOUR", "E, FIVE", "G, THREE", "G, FOUR", "G, FIVE"}) + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest(File file, Rank rank) { + King king = new King(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + + assertThat(king.findPath(start, end, HAS_ENEMY)) + .isEmpty(); + } + + @ParameterizedTest + @CsvSource({"F, TWO", "F, SIX", "D, THREE", "H, FOUR"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + King king = new King(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> king.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/KnightTest.java new file mode 100644 index 00000000000..62fe8de4fcb --- /dev/null +++ b/src/test/java/chess/domain/piece/KnightTest.java @@ -0,0 +1,41 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class KnightTest { + + private static final boolean HAS_ENEMY = false; + + @ParameterizedTest + @CsvSource({"G, SIX", "H, FIVE", "H, THREE", "G, TWO", "E, TWO", "D, THREE", "D, FIVE", "E, SIX"}) + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest(File file, Rank rank) { + Knight knight = new Knight(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + + assertThat(knight.findPath(start, end, HAS_ENEMY)) + .isEmpty(); + } + + @ParameterizedTest + @CsvSource({"G, FIVE", "H, FOUR", "D, TWO", "E, ONE"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Knight knight = new Knight(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> knight.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/PawnTest.java b/src/test/java/chess/domain/piece/PawnTest.java new file mode 100644 index 00000000000..22ce3a0669c --- /dev/null +++ b/src/test/java/chess/domain/piece/PawnTest.java @@ -0,0 +1,170 @@ +package chess.domain.piece; + +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.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class PawnTest { + + @Test + @DisplayName("검정 폰이 초기 위치에 있고 목적지에 적이 없을 경우, 남쪽으로 한 칸 또는 두 칸 이동한다.") + void findPathTest_whenBlackPawnInitPosition() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.SEVEN); + boolean hasEnemy = false; + + assertAll( + () -> assertThat(pawn.findPath(start, new Position(File.F, Rank.SIX), hasEnemy)) + .isEmpty(), + () -> assertThat(pawn.findPath(start, new Position(File.F, Rank.FIVE), hasEnemy)) + .containsExactly(new Position(File.F, Rank.SIX)) + ); + } + + @Test + @DisplayName("검정 폰이 초기 위치가 아닌 곳에 있고 목적지에 적이 없을 경우, 남쪽으로 한 칸 이동한다.") + void findPathTest_whenBlackPawnNotInitPosition() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.SIX); + boolean hasEnemy = false; + + assertThat(pawn.findPath(start, new Position(File.F, Rank.FIVE), hasEnemy)) + .isEmpty(); + } + + @Test + @DisplayName("검정 폰의 목적지에 적이 있을 경우, 남쪽으로 이동할 수 없다.") + void findPathTest_whenMoveToSouthExistEnemy_throwException() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = true; + + assertThatThrownBy(() -> pawn.findPath(start, start.moveToSouth(), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } + + @Test + @DisplayName("검정 폰이 목적지에 적이 있을 경우, 남동쪽 혹은 남서쪽으로 한 칸 이동한다.") + void findPathTest_whenBlackPawnHaveEnemy() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = true; + + assertAll( + () -> assertThat(pawn.findPath(start, new Position(File.E, Rank.THREE), hasEnemy)) + .isEmpty(), + () -> assertThat(pawn.findPath(start, new Position(File.G, Rank.THREE), hasEnemy)) + .isEmpty() + ); + } + + @Test + @DisplayName("검정 폰이 목적지에 적이 없을 경우, 남동쪽 혹은 남서쪽으로 한 칸 이동할 수 없다.") + void findPathTest_whenBlackPawnMoveDiagonalNotExistEnemy() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = false; + + assertAll( + () -> assertThatThrownBy(() -> pawn.findPath(start, new Position(File.E, Rank.THREE), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."), + () -> assertThatThrownBy(() -> pawn.findPath(start, new Position(File.G, Rank.THREE), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다.") + ); + } + + @Test + @DisplayName("흰 폰이 초기 위치에 있고 목적지에 적이 없을 경우, 북쪽으로 한 칸 또는 두 칸 이동한다.") + void findPathTest_whenWhitePawnInitPosition() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.TWO); + boolean hasEnemy = false; + + assertAll( + () -> assertThat(pawn.findPath(start, new Position(File.F, Rank.THREE), hasEnemy)) + .isEmpty(), + () -> assertThat(pawn.findPath(start, new Position(File.F, Rank.FOUR), hasEnemy)) + .containsExactly(new Position(File.F, Rank.THREE)) + ); + } + + @Test + @DisplayName("흰 폰이 초기 위치가 아닌 곳에 있고 목적지에 적이 없을 경우, 북쪽으로 한 칸 이동한다.") + void findPathTest_whenWhitePawnNotInitPosition() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = false; + + assertThat(pawn.findPath(start, new Position(File.F, Rank.FIVE), hasEnemy)) + .isEmpty(); + } + + @Test + @DisplayName("흰 폰의 목적지에 적이 있을 경우, 북쪽으로 이동할 수 없다.") + void findPathTest_whenWhitePawnMoveToSouthExistEnemy_throwException() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = true; + + assertThatThrownBy(() -> pawn.findPath(start, start.moveToNorth(), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } + + @Test + @DisplayName("흰 폰이 목적지에 적이 있을 경우, 북동쪽 혹은 북서쪽으로 한 칸 이동한다.") + void findPathTest_whenWhitePawnHaveEnemy() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = true; + + assertAll( + () -> assertThat(pawn.findPath(start, new Position(File.E, Rank.FIVE), hasEnemy)) + .isEmpty(), + () -> assertThat(pawn.findPath(start, new Position(File.G, Rank.FIVE), hasEnemy)) + .isEmpty() + ); + } + + @Test + @DisplayName("흰 폰이 목적지에 적이 없을 경우, 북동쪽 혹은 북서쪽으로 한 칸 이동할 수 없다.") + void findPathTest_whenWhitePawnMoveDiagonalNotExistEnemy() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = false; + + assertAll( + () -> assertThatThrownBy(() -> pawn.findPath(start, new Position(File.E, Rank.FIVE), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."), + () -> assertThatThrownBy(() -> pawn.findPath(start, new Position(File.G, Rank.FIVE), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다.") + ); + } + + @ParameterizedTest + @CsvSource({"G, FOUR", "F, FIVE", "F, TWO"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + boolean hasEnemy = false; + + assertThatThrownBy(() -> pawn.findPath(start, end, hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java new file mode 100644 index 00000000000..e59dc14b868 --- /dev/null +++ b/src/test/java/chess/domain/piece/PieceTest.java @@ -0,0 +1,27 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class PieceTest { + + class ExamplePiece extends Piece { + + protected ExamplePiece(Team team) { + super(team, List.of()); + } + } + + @ParameterizedTest + @CsvSource({"BLACK, true", "WHITE, false"}) + @DisplayName("해당 팀이 검정 팀인지 확인한다.") + void isBlackTeamTest(Team team, boolean expected) { + ExamplePiece examplePiece = new ExamplePiece(team); + + assertThat(examplePiece.isBlackTeam()).isEqualTo(expected); + } +} diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/QueenTest.java new file mode 100644 index 00000000000..a2844a5ddb0 --- /dev/null +++ b/src/test/java/chess/domain/piece/QueenTest.java @@ -0,0 +1,55 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class QueenTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Queen queen = new Queen(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.ONE); + + assertThat(queen.findPath(start, end, HAS_ENEMY)) + .containsExactly( + new Position(File.C, Rank.THREE), + new Position(File.B, Rank.TWO)); + } + + @ParameterizedTest + @CsvSource({"F, SIX", "G, FOUR", "F, TWO", "D, TWO", "B, TWO", "B, FOUR", "B, SIX", "D, SIX"}) + @DisplayName("이동이 가능한 경우, 예외를 발생하지 않는다.") + void findPathTest_whenCanMove(File file, Rank rank) { + Queen queen = new Queen(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatCode(() -> queen.findPath(start, end, HAS_ENEMY)).doesNotThrowAnyException(); + } + + @ParameterizedTest + @CsvSource({"B, FIVE", "B, ONE", "C, TWO", "E, SIX"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Queen queen = new Queen(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> queen.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java new file mode 100644 index 00000000000..a2fee045b9a --- /dev/null +++ b/src/test/java/chess/domain/piece/RookTest.java @@ -0,0 +1,55 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class RookTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Rook rook = new Rook(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.FOUR); + + assertThat(rook.findPath(start, end, HAS_ENEMY)) + .containsExactly( + new Position(File.C, Rank.FOUR), + new Position(File.B, Rank.FOUR)); + } + + @ParameterizedTest + @CsvSource({"D, FIVE", "D, SIX", "D, ONE", "D, TWO", "E, FOUR", "G, FOUR", "A, FOUR", "C, FOUR"}) + @DisplayName("이동이 가능한 경우, 예외를 발생하지 않는다.") + void findPathTest_whenCanMove(File file, Rank rank) { + Rook rook = new Rook(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatCode(() -> rook.findPath(start, end, HAS_ENEMY)).doesNotThrowAnyException(); + } + + @ParameterizedTest + @CsvSource({"F, TWO", "F, SIX", "C, THREE", "E, FIVE"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Rook rook = new Rook(Team.WHITE); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> rook.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/position/FileTest.java b/src/test/java/chess/domain/position/FileTest.java new file mode 100644 index 00000000000..adfed56ee0f --- /dev/null +++ b/src/test/java/chess/domain/position/FileTest.java @@ -0,0 +1,49 @@ +package chess.domain.position; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class FileTest { + + @ParameterizedTest + @CsvSource({"G, H", "A, B"}) + @DisplayName("다음 동쪽 위치를 알 수 있다.") + void toEastTest(File before, File after) { + assertThat(before.toEast()).isEqualTo(after); + } + + @Test + @DisplayName("동쪽으로 갈 수 없으면, 예외가 발생한다.") + void toEastTest_whenCant() { + assertThatThrownBy(File.H::toEast) + .isInstanceOf(IllegalStateException.class) + .hasMessage("동쪽으로 이동할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"H, G", "B, A"}) + @DisplayName("다음 서쪽 위치를 알 수 있다.") + void toWestTest(File before, File after) { + assertThat(before.toWest()).isEqualTo(after); + } + + @Test + @DisplayName("서쪽으로 갈 수 없으면, 예외가 발생한다.") + void toWestTest_whenCant() { + assertThatThrownBy(File.A::toWest) + .isInstanceOf(IllegalStateException.class) + .hasMessage("서쪽으로 이동할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"A, H, 7", "H, A, -7", "C, D, 1", "D, C, -1"}) + @DisplayName("두 파일의 차이를 알 수 있다.") + void calculateDifferenceTest(File before, File after, int expected) { + assertThat(before.calculateDifference(after)).isEqualTo(expected); + } +} diff --git a/src/test/java/chess/domain/position/PositionTest.java b/src/test/java/chess/domain/position/PositionTest.java new file mode 100644 index 00000000000..0828b5b4aef --- /dev/null +++ b/src/test/java/chess/domain/position/PositionTest.java @@ -0,0 +1,36 @@ +package chess.domain.position; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PositionTest { + + @Test + @DisplayName("같은 행, 같은 열을 가르키면 같은 객체라고 판단한다.") + void equalsTest() { + Position one = new Position(File.A, Rank.SIX); + Position another = new Position(File.A, Rank.SIX); + Position different = new Position(File.C, Rank.SIX); + + assertThat(one) + .isEqualTo(another) + .isNotEqualTo(different) + .hasSameHashCodeAs(another); + } + + @Test + @DisplayName("동, 서, 남, 북으로 이동한 위치를 알 수 있다.") + void moveTest() { + Position position = new Position(File.D, Rank.FOUR); + + assertAll( + () -> assertThat(position.moveToEast()).isEqualTo(new Position(File.E, Rank.FOUR)), + () -> assertThat(position.moveToWest()).isEqualTo(new Position(File.C, Rank.FOUR)), + () -> assertThat(position.moveToSouth()).isEqualTo(new Position(File.D, Rank.THREE)), + () -> assertThat(position.moveToNorth()).isEqualTo(new Position(File.D, Rank.FIVE)) + ); + } +} diff --git a/src/test/java/chess/domain/position/RankTest.java b/src/test/java/chess/domain/position/RankTest.java new file mode 100644 index 00000000000..d8b959fb2c3 --- /dev/null +++ b/src/test/java/chess/domain/position/RankTest.java @@ -0,0 +1,49 @@ +package chess.domain.position; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class RankTest { + + @ParameterizedTest + @CsvSource({"SEVEN, EIGHT", "ONE, TWO"}) + @DisplayName("다음 북쪽 위치를 알 수 있다.") + void toNorthTest(Rank before, Rank after) { + assertThat(before.toNorth()).isEqualTo(after); + } + + @Test + @DisplayName("북쪽으로 갈 수 없으면, 예외가 발생한다.") + void toNorthTest_whenCant() { + assertThatThrownBy(Rank.EIGHT::toNorth) + .isInstanceOf(IllegalStateException.class) + .hasMessage("북쪽으로 이동할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"EIGHT, SEVEN", "TWO, ONE"}) + @DisplayName("다음 남쪽 위치를 알 수 있다.") + void toSouthTest(Rank before, Rank after) { + assertThat(before.toSouth()).isEqualTo(after); + } + + @Test + @DisplayName("남쪽으로 갈 수 없으면, 예외가 발생한다.") + void toSouthTest_whenCant() { + assertThatThrownBy(Rank.ONE::toSouth) + .isInstanceOf(IllegalStateException.class) + .hasMessage("남쪽으로 이동할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"ONE, EIGHT, 7", "EIGHT, ONE, -7", "THREE, FOUR, 1", "FOUR, THREE, -1"}) + @DisplayName("두 랭크의 차이를 알 수 있다.") + void calculateDifferenceTest(Rank before, Rank after, int expected) { + assertThat(before.calculateDifference(after)).isEqualTo(expected); + } +} From c3bb337a920c0fece151a9fc5888fc8f9efaba6c Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 18:08:53 +0900 Subject: [PATCH 02/36] =?UTF-8?q?feat(PieceType):=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EB=B3=84=20=EC=8A=A4=EC=BD=94=EC=96=B4=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++ .../{dto => domain/piece}/PieceType.java | 29 ++++++----- src/main/java/chess/dto/PieceDto.java | 1 + src/main/java/chess/view/OutputView.java | 2 +- .../chess/domain/piece/PieceTypeTest.java | 50 +++++++++++++++++++ 5 files changed, 73 insertions(+), 16 deletions(-) rename src/main/java/chess/{dto => domain/piece}/PieceType.java (51%) create mode 100644 src/test/java/chess/domain/piece/PieceTypeTest.java diff --git a/README.md b/README.md index 149d2490724..cdf5c905846 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ ### 체스 말 - [x] 무슨 팀인지 알려준다. +- [ ] 킹인지 확인한다. +- [ ] 폰인지 확인한다. + +### 체스 말 타입 +- [x] 무슨 타입인지 알려준다. +- [x] 타입별 점수를 알려준다. ### 움직임 - [x] 이동 가능한지 판단한다. @@ -28,6 +34,7 @@ - [x] 마지막 위치에 적 말이 있을 경우 잡는다. - [x] 폰의 경우 대각선이 아닌 앞으로 이동할 때에는 잡지 못한다. - [x] 흰색부터 번갈아가며 플레이한다. +- [ ] 각 팀의 점수를 계산한다. ### 위치 - [x] 가로 위치(왼쪽부터 a~h)를 저장한다. diff --git a/src/main/java/chess/dto/PieceType.java b/src/main/java/chess/domain/piece/PieceType.java similarity index 51% rename from src/main/java/chess/dto/PieceType.java rename to src/main/java/chess/domain/piece/PieceType.java index 9427ec01a70..e7dc8dfc986 100644 --- a/src/main/java/chess/dto/PieceType.java +++ b/src/main/java/chess/domain/piece/PieceType.java @@ -1,27 +1,22 @@ -package chess.dto; +package chess.domain.piece; -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 java.util.Arrays; public enum PieceType { - KING(King.class), - QUEEN(Queen.class), - ROOK(Rook.class), - BISHOP(Bishop.class), - KNIGHT(Knight.class), - PAWN(Pawn.class); + KING(King.class, 0), + QUEEN(Queen.class, 9), + ROOK(Rook.class, 5), + BISHOP(Bishop.class, 3), + KNIGHT(Knight.class, 2.5), + PAWN(Pawn.class, 1); private final Class category; + private final double score; - PieceType(Class category) { + PieceType(Class category, double score) { this.category = category; + this.score = score; } public static PieceType from(Piece piece) { @@ -30,4 +25,8 @@ public static PieceType from(Piece piece) { .findAny() .orElseThrow(() -> new IllegalArgumentException("해당 기물이 존재하지 않습니다.")); } + + public static double scoreOf(Piece piece) { + return from(piece).score; + } } diff --git a/src/main/java/chess/dto/PieceDto.java b/src/main/java/chess/dto/PieceDto.java index bc7230741ec..0c18797b227 100644 --- a/src/main/java/chess/dto/PieceDto.java +++ b/src/main/java/chess/dto/PieceDto.java @@ -1,6 +1,7 @@ package chess.dto; import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; public class PieceDto { diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 7515b51f114..61a0fae7f39 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,11 +1,11 @@ package chess.view; +import chess.domain.piece.PieceType; import chess.domain.position.File; import chess.domain.position.Position; import chess.domain.position.Rank; import chess.dto.BoardDto; import chess.dto.PieceDto; -import chess.dto.PieceType; import java.util.List; import java.util.Map; import java.util.Optional; diff --git a/src/test/java/chess/domain/piece/PieceTypeTest.java b/src/test/java/chess/domain/piece/PieceTypeTest.java new file mode 100644 index 00000000000..30af52e4298 --- /dev/null +++ b/src/test/java/chess/domain/piece/PieceTypeTest.java @@ -0,0 +1,50 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PieceTypeTest { + + @Test + @DisplayName("기물의 타입을 알 수 있다.") + void findTypeTest() { + King king = new King(Team.BLACK); + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.BLACK); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.WHITE); + Pawn pawn = new Pawn(Team.BLACK); + + assertAll( + () -> assertThat(PieceType.from(king)).isEqualTo(PieceType.KING), + () -> assertThat(PieceType.from(queen)).isEqualTo(PieceType.QUEEN), + () -> assertThat(PieceType.from(rook)).isEqualTo(PieceType.ROOK), + () -> assertThat(PieceType.from(bishop)).isEqualTo(PieceType.BISHOP), + () -> assertThat(PieceType.from(knight)).isEqualTo(PieceType.KNIGHT), + () -> assertThat(PieceType.from(pawn)).isEqualTo(PieceType.PAWN) + ); + } + + @Test + @DisplayName("기물의 점수를 알 수 있다.") + void findScoreTest() { + King king = new King(Team.BLACK); + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.BLACK); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.WHITE); + Pawn pawn = new Pawn(Team.BLACK); + + assertAll( + () -> assertThat(PieceType.scoreOf(king)).isEqualTo(0), + () -> assertThat(PieceType.scoreOf(queen)).isEqualTo(9), + () -> assertThat(PieceType.scoreOf(rook)).isEqualTo(5), + () -> assertThat(PieceType.scoreOf(bishop)).isEqualTo(3), + () -> assertThat(PieceType.scoreOf(knight)).isEqualTo(2.5), + () -> assertThat(PieceType.scoreOf(pawn)).isEqualTo(1) + ); + } +} From 643c556b828c3256161abbdebfc52bc4ad45b415 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 21:37:21 +0900 Subject: [PATCH 03/36] =?UTF-8?q?feat(Piece):=20=EA=B8=B0=EB=AC=BC?= =?UTF-8?q?=EC=9D=B4=20=EC=99=95=EC=9D=B8=EC=A7=80=20=ED=8F=B0=EC=9D=B8?= =?UTF-8?q?=EC=A7=80=20=EC=97=AC=EB=B6=80=20=ED=8C=90=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- src/main/java/chess/domain/piece/Piece.java | 8 +++ .../java/chess/domain/piece/PieceTest.java | 51 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cdf5c905846..023277ef946 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ ### 체스 말 - [x] 무슨 팀인지 알려준다. -- [ ] 킹인지 확인한다. -- [ ] 폰인지 확인한다. +- [x] 킹인지 확인한다. +- [x] 폰인지 확인한다. ### 체스 말 타입 - [x] 무슨 타입인지 알려준다. diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 601de0104f5..2e3b4147a69 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -29,4 +29,12 @@ public final List findPath(Position start, Position end, boolean hasEn public boolean isSameTeam(Team team) { return this.team == team; } + + public boolean isPawn() { + return PieceType.from(this) == PieceType.PAWN; + } + + public boolean isKing() { + return PieceType.from(this) == PieceType.KING; + } } diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java index e59dc14b868..adaab379813 100644 --- a/src/test/java/chess/domain/piece/PieceTest.java +++ b/src/test/java/chess/domain/piece/PieceTest.java @@ -1,9 +1,12 @@ package chess.domain.piece; 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 java.util.List; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -24,4 +27,52 @@ void isBlackTeamTest(Team team, boolean expected) { assertThat(examplePiece.isBlackTeam()).isEqualTo(expected); } + + @Test + @DisplayName("해당 기물이 킹인지 확인한다.") + void isKingTest() { + King king = new King(Team.BLACK); + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.BLACK); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.WHITE); + Pawn pawn = new Pawn(Team.BLACK); + ExamplePiece examplePiece = new ExamplePiece(Team.WHITE); + + assertAll( + () -> assertThat(king.isKing()).isTrue(), + () -> assertThat(queen.isKing()).isFalse(), + () -> assertThat(rook.isKing()).isFalse(), + () -> assertThat(bishop.isKing()).isFalse(), + () -> assertThat(knight.isKing()).isFalse(), + () -> assertThat(pawn.isKing()).isFalse(), + () -> assertThatThrownBy(examplePiece::isKing) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("해당 기물이 존재하지 않습니다.") + ); + } + + @Test + @DisplayName("해당 기물이 폰인지 확인한다.") + void isPawnTest() { + King king = new King(Team.BLACK); + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.BLACK); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.WHITE); + Pawn pawn = new Pawn(Team.BLACK); + ExamplePiece examplePiece = new ExamplePiece(Team.WHITE); + + assertAll( + () -> assertThat(king.isPawn()).isFalse(), + () -> assertThat(queen.isPawn()).isFalse(), + () -> assertThat(rook.isPawn()).isFalse(), + () -> assertThat(bishop.isPawn()).isFalse(), + () -> assertThat(knight.isPawn()).isFalse(), + () -> assertThat(pawn.isPawn()).isTrue(), + () -> assertThatThrownBy(examplePiece::isPawn) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("해당 기물이 존재하지 않습니다.") + ); + } } From 5a15ab041274538c7b24c287933d61e9f7db7e38 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 22:01:32 +0900 Subject: [PATCH 04/36] =?UTF-8?q?feat(Board):=20=ED=8C=80=EB=B3=84=20?= =?UTF-8?q?=EC=A0=90=EC=88=98=20=EA=B3=84=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- src/main/java/chess/domain/Board.java | 52 +++++++++++++++++----- src/test/java/chess/domain/BoardTest.java | 53 +++++++++++++++++++++++ 3 files changed, 97 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 023277ef946..d25bba342a6 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ - [x] 마지막 위치에 적 말이 있을 경우 잡는다. - [x] 폰의 경우 대각선이 아닌 앞으로 이동할 때에는 잡지 못한다. - [x] 흰색부터 번갈아가며 플레이한다. -- [ ] 각 팀의 점수를 계산한다. +- [x] 각 팀의 점수를 계산한다. +- [ ] 왕이 잡히면 게임이 끝난다. ### 위치 - [x] 가로 위치(왼쪽부터 a~h)를 저장한다. diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index 5f242b412ef..f13fbe38d27 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -1,6 +1,7 @@ package chess.domain; import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; import chess.domain.piece.Team; import chess.domain.position.Position; import java.util.HashMap; @@ -34,16 +35,6 @@ public void tryMove(Position start, Position end) { move(start, end, movingPiece); } - private void validatePath(List path) { - if (isBlocked(path)) { - throw new IllegalArgumentException("다른 말이 있어 이동 불가능합니다."); - } - } - - private boolean hasEnemy(Position end) { - return find(end).isPresent(); - } - private Piece findMovingPiece(Position start) { return find(start) .orElseThrow(() -> new IllegalArgumentException("해당 위치에 말이 없습니다.")); @@ -63,6 +54,17 @@ private boolean isSameTeamAtDestination(Position end) { .orElse(false); } + private boolean hasEnemy(Position end) { + return find(end).map(piece -> !piece.isSameTeam(turn)) + .orElse(false); + } + + private void validatePath(List path) { + if (isBlocked(path)) { + throw new IllegalArgumentException("다른 말이 있어 이동 불가능합니다."); + } + } + private boolean isBlocked(List path) { return path.stream() .anyMatch(board::containsKey); @@ -73,4 +75,34 @@ private void move(Position start, Position end, Piece movingPiece) { board.put(end, movingPiece); turn = turn.next(); } + + public double calculateScoreOf(Team team) { + double basicScore = calculateBasicScoreOf(team); + double minusScore = calculateMinusScoreOf(team); + + return basicScore - minusScore; + } + + private double calculateBasicScoreOf(Team team) { + return board.values().stream() + .filter(piece -> piece.isSameTeam(team)) + .mapToDouble(PieceType::scoreOf) + .sum(); + } + + private double calculateMinusScoreOf(Team team) { + int count = (int) findPawnEntryOf(team) + .filter(entry -> findPawnEntryOf(team) + .anyMatch(otherEntry -> !otherEntry.getKey().equals(entry.getKey()) + && otherEntry.getKey().isSameFile(entry.getKey()))) + .count(); + + return count * 0.5; + } + + private Stream> findPawnEntryOf(Team team) { + return board.entrySet().stream() + .filter(entry -> entry.getValue().isSameTeam(team)) + .filter(entry -> entry.getValue().isPawn()); + } } diff --git a/src/test/java/chess/domain/BoardTest.java b/src/test/java/chess/domain/BoardTest.java index c001ddef0b7..c04ee543545 100644 --- a/src/test/java/chess/domain/BoardTest.java +++ b/src/test/java/chess/domain/BoardTest.java @@ -4,9 +4,13 @@ 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.Queen; +import chess.domain.piece.Rook; import chess.domain.piece.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -103,4 +107,53 @@ void moveTest_whenBlocked_throwException() { .hasMessage("다른 말이 있어 이동 불가능합니다."); } } + + @Test + @DisplayName("점수를 계산할 수 있다.") + void calculateScoreTest() { + Map boardMap = Map.of( + new Position(File.B, Rank.TWO), new Queen(Team.WHITE), + new Position(File.F, Rank.TWO), new Rook(Team.WHITE), + new Position(File.F, Rank.SIX), new Bishop(Team.WHITE), + new Position(File.D, Rank.THREE), new Knight(Team.BLACK), + new Position(File.E, Rank.SIX), new Pawn(Team.BLACK), + new Position(File.A, Rank.FOUR), new King(Team.BLACK)); + Board board = new Board(boardMap); + + assertAll( + () -> assertThat(board.calculateScoreOf(Team.WHITE)).isEqualTo(17), + () -> assertThat(board.calculateScoreOf(Team.BLACK)).isEqualTo(3.5) + ); + } + + @Test + @DisplayName("같은 팀의 폰이 같은 파일에 있을 때 점수를 계산할 수 있다.") + void calculateScoreTest_whenPawnInSameFile() { + Map boardMap = new java.util.HashMap<>(Map.of( + new Position(File.B, Rank.EIGHT), new King(Team.BLACK), + new Position(File.C, Rank.EIGHT), new Rook(Team.BLACK), + new Position(File.A, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.C, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.D, Rank.SEVEN), new Bishop(Team.BLACK), + new Position(File.B, Rank.SIX), new Pawn(Team.BLACK), + new Position(File.E, Rank.SIX), new Queen(Team.BLACK), + new Position(File.F, Rank.FOUR), new Knight(Team.WHITE), + new Position(File.G, Rank.FOUR), new Queen(Team.WHITE), + new Position(File.F, Rank.THREE), new Pawn(Team.WHITE) + )); + Map addedBoard = Map.of( + new Position(File.H, Rank.THREE), new Pawn(Team.WHITE), + new Position(File.F, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.G, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.E, Rank.ONE), new Rook(Team.WHITE), + new Position(File.F, Rank.ONE), new King(Team.WHITE) + ); + boardMap.putAll(addedBoard); + Board board = new Board(boardMap); + + assertAll( + () -> assertThat(board.calculateScoreOf(Team.WHITE)).isEqualTo(19.5), + () -> assertThat(board.calculateScoreOf(Team.BLACK)).isEqualTo(20) + ); + } } From 023804af6de8b154c118d05674d0f25a908375be Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 22:07:52 +0900 Subject: [PATCH 05/36] =?UTF-8?q?feat(Position):=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC,=20=EB=9E=AD=ED=81=AC=20=EC=97=AC=EB=B6=80?= =?UTF-8?q?=20=ED=8C=90=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ .../java/chess/domain/position/Position.java | 4 +++ .../chess/domain/position/PositionTest.java | 26 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/README.md b/README.md index d25bba342a6..81a8dcefc6d 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ - [x] 세로 위치(아래부터 1~8)를 저장한다. - [x] 서로 같은 위치인지 판단한다. - [x] 다음 동, 서, 남, 북쪽 위치를 알려준다. +- [x] 서로 같은 랭크인지 판단한다. +- [x] 서로 같은 파일인지 판단한다. ### 출력 - [x] 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다. diff --git a/src/main/java/chess/domain/position/Position.java b/src/main/java/chess/domain/position/Position.java index 8e6b99e1643..ebf6dbb19bf 100644 --- a/src/main/java/chess/domain/position/Position.java +++ b/src/main/java/chess/domain/position/Position.java @@ -23,6 +23,10 @@ public boolean isSameRank(Rank rank) { return this.rank == rank; } + public boolean isSameFile(Position other) { + return this.file == other.file; + } + public int calculateFileDifference(Position other) { return this.file.calculateDifference(other.file); } diff --git a/src/test/java/chess/domain/position/PositionTest.java b/src/test/java/chess/domain/position/PositionTest.java index 0828b5b4aef..a837a6b3837 100644 --- a/src/test/java/chess/domain/position/PositionTest.java +++ b/src/test/java/chess/domain/position/PositionTest.java @@ -33,4 +33,30 @@ void moveTest() { () -> assertThat(position.moveToNorth()).isEqualTo(new Position(File.D, Rank.FIVE)) ); } + + @Test + @DisplayName("서로 같은 랭크인지 판단한다.") + void isSameRankTest() { + Position sample = new Position(File.F, Rank.FOUR); + Rank sameRank = Rank.FOUR; + Rank differentRank = Rank.SIX; + + assertAll( + () -> assertThat(sample.isSameRank(sameRank)).isTrue(), + () -> assertThat(sample.isSameRank(differentRank)).isFalse() + ); + } + + @Test + @DisplayName("서로 같은 파일인지 판단한다.") + void isSameFileTest() { + Position sample = new Position(File.F, Rank.FOUR); + Position sameFile = new Position(File.F, Rank.SIX); + Position differentFile = new Position(File.A, Rank.FOUR); + + assertAll( + () -> assertThat(sample.isSameFile(sameFile)).isTrue(), + () -> assertThat(sample.isSameFile(differentFile)).isFalse() + ); + } } From 44a9ffc505a7d3d99695d68248700e137335d67a Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 22:09:43 +0900 Subject: [PATCH 06/36] =?UTF-8?q?feat(GameCommand):=20=EC=BB=A4=EB=A9=98?= =?UTF-8?q?=EB=93=9C=EC=97=90=20status=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/view/GameCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/chess/view/GameCommand.java b/src/main/java/chess/view/GameCommand.java index eddecf7a225..2ecbdbe3b26 100644 --- a/src/main/java/chess/view/GameCommand.java +++ b/src/main/java/chess/view/GameCommand.java @@ -6,7 +6,8 @@ public enum GameCommand { START("start"), END("end"), - MOVE("move"); + MOVE("move"), + STATUS("status"); private final String command; From dff28c73e12bb41687ca1c2676a74584f6880560 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 22:13:20 +0900 Subject: [PATCH 07/36] =?UTF-8?q?feat(ChessGame):=20=EC=A0=90=EC=88=98=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EB=B0=8F=20=ED=82=B9=20=EC=9E=A1=ED=9E=90?= =?UTF-8?q?=EC=8B=9C=20=EA=B2=8C=EC=9E=84=20=EC=A2=85=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/chess/ChessGame.java | 40 ++++++++++++++++-------- src/main/java/chess/GameStatus.java | 17 ++++++++++ src/main/java/chess/domain/Board.java | 13 ++++++-- src/main/java/chess/view/OutputView.java | 22 +++++++++++++ 5 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 src/main/java/chess/GameStatus.java diff --git a/README.md b/README.md index 81a8dcefc6d..c10182725cf 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ - [x] 폰의 경우 대각선이 아닌 앞으로 이동할 때에는 잡지 못한다. - [x] 흰색부터 번갈아가며 플레이한다. - [x] 각 팀의 점수를 계산한다. -- [ ] 왕이 잡히면 게임이 끝난다. +- [x] 왕이 잡히면 게임이 끝난다. ### 위치 - [x] 가로 위치(왼쪽부터 a~h)를 저장한다. diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index 4cd196ea31e..7275a1dbca3 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -2,6 +2,7 @@ import chess.domain.Board; import chess.domain.BoardFactory; +import chess.domain.piece.Team; import chess.domain.position.Position; import chess.dto.BoardDto; import chess.view.GameCommand; @@ -36,45 +37,58 @@ private void start() { play(board); return; } - if (command == GameCommand.MOVE) { + if (command == GameCommand.MOVE || command == GameCommand.STATUS) { throw new IllegalArgumentException("아직 게임을 시작하지 않았습니다."); } } private void play(Board board) { - boolean gameEnd = false; - while (!gameEnd) { + GameStatus gameEnd = GameStatus.PLAY; + while (gameEnd == GameStatus.PLAY) { gameEnd = tryProcessTurn(board); } } - private boolean tryProcessTurn(Board board) { + private GameStatus tryProcessTurn(Board board) { try { - return processTurn(board); + GameCommand command = inputView.readCommand(); + return processTurn(command, board); } catch (IllegalArgumentException exception) { outputView.printExceptionMessage(exception); tryProcessTurn(board); } - return false; + return GameStatus.PLAY; } - private boolean processTurn(Board board) { - GameCommand command = inputView.readCommand(); + private GameStatus processTurn(GameCommand command, Board board) { if (command == GameCommand.START) { throw new IllegalArgumentException("이미 게임을 시작했습니다."); } if (command == GameCommand.END) { - return true; + return GameStatus.END; + } + if (command == GameCommand.STATUS) { + return showStatus(board); } - executeMove(board); - return false; + return executeMove(board); } - private void executeMove(Board board) { + private GameStatus showStatus(Board board) { + double blackScore = board.calculateScoreOf(Team.BLACK); + double whiteScore = board.calculateScoreOf(Team.WHITE); + outputView.printStatus(blackScore, whiteScore); + return GameStatus.PLAY; + } + + private GameStatus executeMove(Board board) { Position start = inputView.readPosition(); Position end = inputView.readPosition(); - board.tryMove(start, end); + GameStatus gameStatus = board.tryMove(start, end); showBoard(board); + if (gameStatus != GameStatus.PLAY) { + outputView.printWinner(gameStatus); + } + return gameStatus; } private void showBoard(Board board) { diff --git a/src/main/java/chess/GameStatus.java b/src/main/java/chess/GameStatus.java new file mode 100644 index 00000000000..abfb30ab731 --- /dev/null +++ b/src/main/java/chess/GameStatus.java @@ -0,0 +1,17 @@ +package chess; + +import chess.domain.piece.Team; + +public enum GameStatus { + PLAY, + END, + BLACK_WIN, + WHITE_WIN; + + public static GameStatus whenWin(Team team) { + if (team.isBlack()) { + return BLACK_WIN; + } + return WHITE_WIN; + } +} diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index f13fbe38d27..3b7e3878944 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -1,5 +1,6 @@ package chess.domain; +import chess.GameStatus; import chess.domain.piece.Piece; import chess.domain.piece.PieceType; import chess.domain.piece.Team; @@ -7,7 +8,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; +import java.util.stream.Stream; public class Board { @@ -24,7 +27,7 @@ public Optional find(Position position) { return Optional.ofNullable(piece); } - public void tryMove(Position start, Position end) { + public GameStatus tryMove(Position start, Position end) { Piece movingPiece = findMovingPiece(start); validateTeamRule(movingPiece, end); @@ -32,7 +35,7 @@ public void tryMove(Position start, Position end) { List path = movingPiece.findPath(start, end, hasEnemy); validatePath(path); - move(start, end, movingPiece); + return move(start, end, movingPiece); } private Piece findMovingPiece(Position start) { @@ -70,10 +73,14 @@ private boolean isBlocked(List path) { .anyMatch(board::containsKey); } - private void move(Position start, Position end, Piece movingPiece) { + private GameStatus move(Position start, Position end, Piece movingPiece) { board.remove(start); + if (find(end).map(piece -> !piece.isSameTeam(turn) && piece.isKing()).orElse(false)) { + return GameStatus.whenWin(turn); + } board.put(end, movingPiece); turn = turn.next(); + return GameStatus.PLAY; } public double calculateScoreOf(Team team) { diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 61a0fae7f39..41cf3c8fd63 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,5 +1,6 @@ package chess.view; +import chess.GameStatus; import chess.domain.piece.PieceType; import chess.domain.position.File; import chess.domain.position.Position; @@ -67,7 +68,28 @@ private void printEmptySpace() { System.out.print(EMPTY_SPACE); } + public void printStatus(double blackScore, double whiteScore) { + System.out.print("검은색: " + blackScore + ", 흰색: " + whiteScore); + if (blackScore > whiteScore) { + System.out.println(", 검은색 승리"); + return; + } + if (whiteScore > blackScore) { + System.out.println(", 흰색 승리"); + return; + } + System.out.println(", 무승부"); + } + public void printExceptionMessage(Exception exception) { System.out.println(ERROR_PREFIX + exception.getMessage()); } + + public void printWinner(GameStatus gameStatus) { + if (gameStatus == GameStatus.BLACK_WIN) { + System.out.println("검은색 승리"); + return; + } + System.out.println("흰색 승리"); + } } From 9eabc7b736f7656b6524f206a4d3328ad3d988a1 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 22:23:55 +0900 Subject: [PATCH 08/36] =?UTF-8?q?refactor(Board):=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/Board.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index 3b7e3878944..fd3d1f3d5dc 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -75,7 +75,7 @@ private boolean isBlocked(List path) { private GameStatus move(Position start, Position end, Piece movingPiece) { board.remove(start); - if (find(end).map(piece -> !piece.isSameTeam(turn) && piece.isKing()).orElse(false)) { + if (isOtherTeamKing(end)) { return GameStatus.whenWin(turn); } board.put(end, movingPiece); @@ -83,6 +83,14 @@ private GameStatus move(Position start, Position end, Piece movingPiece) { return GameStatus.PLAY; } + private boolean isOtherTeamKing(Position end) { + return find(end).map(this::isOtherTeamKing).orElse(false); + } + + private boolean isOtherTeamKing(Piece piece) { + return !piece.isSameTeam(turn) && piece.isKing(); + } + public double calculateScoreOf(Team team) { double basicScore = calculateBasicScoreOf(team); double minusScore = calculateMinusScoreOf(team); From 2ba0d88f8da63a23232848cfd42bb7a04ec6b446 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 22:29:50 +0900 Subject: [PATCH 09/36] =?UTF-8?q?refactor(PieceTypeTest):=20import=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/chess/domain/piece/PieceTypeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/chess/domain/piece/PieceTypeTest.java b/src/test/java/chess/domain/piece/PieceTypeTest.java index 30af52e4298..649766a1073 100644 --- a/src/test/java/chess/domain/piece/PieceTypeTest.java +++ b/src/test/java/chess/domain/piece/PieceTypeTest.java @@ -1,7 +1,7 @@ package chess.domain.piece; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 255cbb55e8cfd707f480132fc51ca3b16cc6d0ab Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 27 Mar 2024 22:30:19 +0900 Subject: [PATCH 10/36] =?UTF-8?q?style(GameStatus):=20=EA=B0=9C=ED=96=89?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/GameStatus.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/chess/GameStatus.java b/src/main/java/chess/GameStatus.java index abfb30ab731..f6ebe94dcc5 100644 --- a/src/main/java/chess/GameStatus.java +++ b/src/main/java/chess/GameStatus.java @@ -3,6 +3,7 @@ import chess.domain.piece.Team; public enum GameStatus { + PLAY, END, BLACK_WIN, From 05a3547e4b04bb750e13ab3b9720d0cb96aa0a04 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Thu, 28 Mar 2024 17:14:29 +0900 Subject: [PATCH 11/36] =?UTF-8?q?test(Team):=20=EB=8B=A4=EC=9D=8C=20?= =?UTF-8?q?=ED=8C=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/chess/domain/piece/TeamTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/test/java/chess/domain/piece/TeamTest.java diff --git a/src/test/java/chess/domain/piece/TeamTest.java b/src/test/java/chess/domain/piece/TeamTest.java new file mode 100644 index 00000000000..9f99e3d36eb --- /dev/null +++ b/src/test/java/chess/domain/piece/TeamTest.java @@ -0,0 +1,19 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class TeamTest { + + @Test + @DisplayName("다음 팀을 찾을 수 있다.") + void findNextTeamTest() { + assertAll( + () -> assertThat(Team.BLACK.next()).isEqualTo(Team.WHITE), + () -> assertThat(Team.WHITE.next()).isEqualTo(Team.BLACK) + ); + } +} From 724c9ef7921968d8fbc9d65fcf6b03ede1052677 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 21:59:39 +0900 Subject: [PATCH 12/36] =?UTF-8?q?feat(GameCommand):=20=EC=8B=9C=EC=9E=91?= =?UTF-8?q?=ED=95=A0=20=EB=95=8C=20=EB=B6=88=EA=B0=80=EB=8A=A5=ED=95=9C=20?= =?UTF-8?q?=EC=BB=A4=EB=A7=A8=EB=93=9C=20=EC=9E=85=EB=A0=A5=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/ChessGame.java | 2 +- src/main/java/chess/view/GameCommand.java | 4 ++++ src/test/java/chess/view/GameCommandTest.java | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/test/java/chess/view/GameCommandTest.java diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index 7275a1dbca3..c6f9eb29645 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -37,7 +37,7 @@ private void start() { play(board); return; } - if (command == GameCommand.MOVE || command == GameCommand.STATUS) { + if (GameCommand.isImpossibleBeforeStartGame(command)) { throw new IllegalArgumentException("아직 게임을 시작하지 않았습니다."); } } diff --git a/src/main/java/chess/view/GameCommand.java b/src/main/java/chess/view/GameCommand.java index 2ecbdbe3b26..4f641937aca 100644 --- a/src/main/java/chess/view/GameCommand.java +++ b/src/main/java/chess/view/GameCommand.java @@ -21,4 +21,8 @@ public static GameCommand from(String input) { .findAny() .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 커멘드입니다.")); } + + public static boolean isImpossibleBeforeStartGame(GameCommand command) { + return command == MOVE || command == STATUS; + } } diff --git a/src/test/java/chess/view/GameCommandTest.java b/src/test/java/chess/view/GameCommandTest.java new file mode 100644 index 00000000000..1d0fb693bcb --- /dev/null +++ b/src/test/java/chess/view/GameCommandTest.java @@ -0,0 +1,22 @@ +package chess.view; + +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 org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GameCommandTest { + + @Test + @DisplayName("게임 시작 전 불가능한 커맨드인지 확인한다.") + void isImpossibleCommandBeforeStartGameTest() { + assertAll( + () -> assertThat(GameCommand.isImpossibleBeforeStartGame(GameCommand.START)).isFalse(), + () -> assertThat(GameCommand.isImpossibleBeforeStartGame(GameCommand.END)).isFalse(), + () -> assertThat(GameCommand.isImpossibleBeforeStartGame(GameCommand.MOVE)).isTrue(), + () -> assertThat(GameCommand.isImpossibleBeforeStartGame(GameCommand.STATUS)).isTrue() + ); + } +} From 46f55fc1ca3e5639d6246d567c74e67c1df95991 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 22:01:15 +0900 Subject: [PATCH 13/36] =?UTF-8?q?refactor(Position):=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/Board.java | 2 +- .../domain/movement/pawn/BlackPawnFirstMovement.java | 2 +- .../domain/movement/pawn/WhitePawnFirstMovement.java | 2 +- src/main/java/chess/domain/position/Position.java | 4 ++-- src/test/java/chess/domain/position/PositionTest.java | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index fd3d1f3d5dc..7e02a53b0f3 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -109,7 +109,7 @@ private double calculateMinusScoreOf(Team team) { int count = (int) findPawnEntryOf(team) .filter(entry -> findPawnEntryOf(team) .anyMatch(otherEntry -> !otherEntry.getKey().equals(entry.getKey()) - && otherEntry.getKey().isSameFile(entry.getKey()))) + && otherEntry.getKey().isOnSameFile(entry.getKey()))) .count(); return count * 0.5; diff --git a/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java b/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java index d11949b8420..2e788c22cd8 100644 --- a/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java @@ -11,7 +11,7 @@ public boolean isMovable(Position start, Position end, boolean hasEnemy) { int rankDifference = start.calculateRankDifference(end); int fileDifference = start.calculateFileDifference(end); - return !hasEnemy && start.isSameRank(Rank.SEVEN) && rankDifference == -2 && fileDifference == 0; + return !hasEnemy && start.isOnSameRank(Rank.SEVEN) && rankDifference == -2 && fileDifference == 0; } public List findPath(Position start, Position end, boolean hasEnemy) { diff --git a/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java b/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java index 67c5f685fdd..5abd4dd5340 100644 --- a/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java @@ -11,7 +11,7 @@ public boolean isMovable(Position start, Position end, boolean hasEnemy) { int rankDifference = start.calculateRankDifference(end); int fileDifference = start.calculateFileDifference(end); - return !hasEnemy && start.isSameRank(Rank.TWO) && rankDifference == 2 && fileDifference == 0; + return !hasEnemy && start.isOnSameRank(Rank.TWO) && rankDifference == 2 && fileDifference == 0; } public List findPath(Position start, Position end, boolean hasEnemy) { diff --git a/src/main/java/chess/domain/position/Position.java b/src/main/java/chess/domain/position/Position.java index ebf6dbb19bf..49401aa525a 100644 --- a/src/main/java/chess/domain/position/Position.java +++ b/src/main/java/chess/domain/position/Position.java @@ -19,11 +19,11 @@ public Position(File file, Rank rank) { this.rank = Objects.requireNonNull(rank); } - public boolean isSameRank(Rank rank) { + public boolean isOnSameRank(Rank rank) { return this.rank == rank; } - public boolean isSameFile(Position other) { + public boolean isOnSameFile(Position other) { return this.file == other.file; } diff --git a/src/test/java/chess/domain/position/PositionTest.java b/src/test/java/chess/domain/position/PositionTest.java index a837a6b3837..0d43b436d01 100644 --- a/src/test/java/chess/domain/position/PositionTest.java +++ b/src/test/java/chess/domain/position/PositionTest.java @@ -42,8 +42,8 @@ void isSameRankTest() { Rank differentRank = Rank.SIX; assertAll( - () -> assertThat(sample.isSameRank(sameRank)).isTrue(), - () -> assertThat(sample.isSameRank(differentRank)).isFalse() + () -> assertThat(sample.isOnSameRank(sameRank)).isTrue(), + () -> assertThat(sample.isOnSameRank(differentRank)).isFalse() ); } @@ -55,8 +55,8 @@ void isSameFileTest() { Position differentFile = new Position(File.A, Rank.FOUR); assertAll( - () -> assertThat(sample.isSameFile(sameFile)).isTrue(), - () -> assertThat(sample.isSameFile(differentFile)).isFalse() + () -> assertThat(sample.isOnSameFile(sameFile)).isTrue(), + () -> assertThat(sample.isOnSameFile(differentFile)).isFalse() ); } } From 69aca9daa085648914194d4191ce26b385a5ef5c Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 22:04:16 +0900 Subject: [PATCH 14/36] =?UTF-8?q?refactor(GameStatus):=20=EC=97=B4?= =?UTF-8?q?=EA=B1=B0=ED=98=95=20=EC=83=81=EC=88=98=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/ChessGame.java | 10 +++++----- src/main/java/chess/GameStatus.java | 2 +- src/main/java/chess/domain/Board.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index c6f9eb29645..ff6628da89a 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -43,8 +43,8 @@ private void start() { } private void play(Board board) { - GameStatus gameEnd = GameStatus.PLAY; - while (gameEnd == GameStatus.PLAY) { + GameStatus gameEnd = GameStatus.PLAYING; + while (gameEnd == GameStatus.PLAYING) { gameEnd = tryProcessTurn(board); } } @@ -57,7 +57,7 @@ private GameStatus tryProcessTurn(Board board) { outputView.printExceptionMessage(exception); tryProcessTurn(board); } - return GameStatus.PLAY; + return GameStatus.PLAYING; } private GameStatus processTurn(GameCommand command, Board board) { @@ -77,7 +77,7 @@ private GameStatus showStatus(Board board) { double blackScore = board.calculateScoreOf(Team.BLACK); double whiteScore = board.calculateScoreOf(Team.WHITE); outputView.printStatus(blackScore, whiteScore); - return GameStatus.PLAY; + return GameStatus.PLAYING; } private GameStatus executeMove(Board board) { @@ -85,7 +85,7 @@ private GameStatus executeMove(Board board) { Position end = inputView.readPosition(); GameStatus gameStatus = board.tryMove(start, end); showBoard(board); - if (gameStatus != GameStatus.PLAY) { + if (gameStatus != GameStatus.PLAYING) { outputView.printWinner(gameStatus); } return gameStatus; diff --git a/src/main/java/chess/GameStatus.java b/src/main/java/chess/GameStatus.java index f6ebe94dcc5..4e9854b4523 100644 --- a/src/main/java/chess/GameStatus.java +++ b/src/main/java/chess/GameStatus.java @@ -4,7 +4,7 @@ public enum GameStatus { - PLAY, + PLAYING, END, BLACK_WIN, WHITE_WIN; diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index 7e02a53b0f3..47165dc28b7 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -80,7 +80,7 @@ private GameStatus move(Position start, Position end, Piece movingPiece) { } board.put(end, movingPiece); turn = turn.next(); - return GameStatus.PLAY; + return GameStatus.PLAYING; } private boolean isOtherTeamKing(Position end) { From 6edbedde58d1feaaeb7f30129d9815ef09e0d42c Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 22:11:25 +0900 Subject: [PATCH 15/36] =?UTF-8?q?feat(GameStatus):=20=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=20=EC=A4=91=EC=9D=B8=20=EC=83=81=ED=83=9C=EC=9D=B8?= =?UTF-8?q?=EC=A7=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/ChessGame.java | 2 +- src/main/java/chess/GameStatus.java | 4 ++++ src/test/java/chess/GameStatusTest.java | 21 +++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/test/java/chess/GameStatusTest.java diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index ff6628da89a..adcee3c31d8 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -44,7 +44,7 @@ private void start() { private void play(Board board) { GameStatus gameEnd = GameStatus.PLAYING; - while (gameEnd == GameStatus.PLAYING) { + while (GameStatus.isPlaying(gameEnd)) { gameEnd = tryProcessTurn(board); } } diff --git a/src/main/java/chess/GameStatus.java b/src/main/java/chess/GameStatus.java index 4e9854b4523..4de7872540a 100644 --- a/src/main/java/chess/GameStatus.java +++ b/src/main/java/chess/GameStatus.java @@ -15,4 +15,8 @@ public static GameStatus whenWin(Team team) { } return WHITE_WIN; } + + public static boolean isPlaying(GameStatus gameStatus) { + return gameStatus == GameStatus.PLAYING; + } } diff --git a/src/test/java/chess/GameStatusTest.java b/src/test/java/chess/GameStatusTest.java new file mode 100644 index 00000000000..0b0a8f4c10d --- /dev/null +++ b/src/test/java/chess/GameStatusTest.java @@ -0,0 +1,21 @@ +package chess; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GameStatusTest { + + @Test + @DisplayName("플레이 중인 상태인지 확인한다.") + void isPlayingTest() { + assertAll( + () -> assertThat(GameStatus.isPlaying(GameStatus.PLAYING)).isTrue(), + () -> assertThat(GameStatus.isPlaying(GameStatus.END)).isFalse(), + () -> assertThat(GameStatus.isPlaying(GameStatus.BLACK_WIN)).isFalse(), + () -> assertThat(GameStatus.isPlaying(GameStatus.WHITE_WIN)).isFalse() + ); + } +} From 0b14727111d5bb755fae379d083e7a5c5643ac50 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 22:27:16 +0900 Subject: [PATCH 16/36] =?UTF-8?q?test(GameCommand):=20=ED=95=B4=EB=8B=B9?= =?UTF-8?q?=20=EC=BB=A4=EB=A7=A8=EB=93=9C=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/chess/view/GameCommandTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/chess/view/GameCommandTest.java b/src/test/java/chess/view/GameCommandTest.java index 1d0fb693bcb..4ffb94b5fb1 100644 --- a/src/test/java/chess/view/GameCommandTest.java +++ b/src/test/java/chess/view/GameCommandTest.java @@ -9,6 +9,25 @@ class GameCommandTest { + @Test + @DisplayName("해당하는 커맨드를 찾는다.") + void findCommandFromStringTest() { + assertAll( + () -> assertThat(GameCommand.from("start")).isEqualTo(GameCommand.START), + () -> assertThat(GameCommand.from("end")).isEqualTo(GameCommand.END), + () -> assertThat(GameCommand.from("move")).isEqualTo(GameCommand.MOVE), + () -> assertThat(GameCommand.from("status")).isEqualTo(GameCommand.STATUS) + ); + } + + @Test + @DisplayName("해당하는 커맨드가 없으면 예외를 발생시킨다.") + void findCommandFromStringTest_whenNotExist() { + assertThatThrownBy(() -> GameCommand.from("notExistCommand")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("존재하지 않는 커멘드입니다."); + } + @Test @DisplayName("게임 시작 전 불가능한 커맨드인지 확인한다.") void isImpossibleCommandBeforeStartGameTest() { From b9b36cbe0c20f7ebd027fe404b8e06efc198773f Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 22:32:18 +0900 Subject: [PATCH 17/36] =?UTF-8?q?refactor(ChessGame):=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/ChessGame.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index adcee3c31d8..026ba90845c 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -43,9 +43,9 @@ private void start() { } private void play(Board board) { - GameStatus gameEnd = GameStatus.PLAYING; - while (GameStatus.isPlaying(gameEnd)) { - gameEnd = tryProcessTurn(board); + GameStatus gameStatus = GameStatus.PLAYING; + while (GameStatus.isPlaying(gameStatus)) { + gameStatus = tryProcessTurn(board); } } From c198908fd8e28e29ff47af7c3605bc87d1f3ebac Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 22:34:09 +0900 Subject: [PATCH 18/36] =?UTF-8?q?refactor(GameStatus):=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/GameStatus.java | 2 +- src/main/java/chess/domain/Board.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/chess/GameStatus.java b/src/main/java/chess/GameStatus.java index 4de7872540a..4cc949c9be3 100644 --- a/src/main/java/chess/GameStatus.java +++ b/src/main/java/chess/GameStatus.java @@ -9,7 +9,7 @@ public enum GameStatus { BLACK_WIN, WHITE_WIN; - public static GameStatus whenWin(Team team) { + public static GameStatus winBy(Team team) { if (team.isBlack()) { return BLACK_WIN; } diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index 47165dc28b7..38ea16e739c 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -76,7 +76,7 @@ private boolean isBlocked(List path) { private GameStatus move(Position start, Position end, Piece movingPiece) { board.remove(start); if (isOtherTeamKing(end)) { - return GameStatus.whenWin(turn); + return GameStatus.winBy(turn); } board.put(end, movingPiece); turn = turn.next(); From 6017958b0edad288e207a28ba0c3f55621e7a090 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 22:38:13 +0900 Subject: [PATCH 19/36] =?UTF-8?q?test(GameStatus):=20=EC=9D=B4=EA=B8=B4=20?= =?UTF-8?q?=ED=8C=80=EC=9D=84=20=EC=9D=B4=EC=9A=A9=ED=95=B4=20=EA=B2=8C?= =?UTF-8?q?=EC=9E=84=20=EC=83=81=ED=83=9C=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/chess/GameStatusTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/chess/GameStatusTest.java b/src/test/java/chess/GameStatusTest.java index 0b0a8f4c10d..9dbda072d91 100644 --- a/src/test/java/chess/GameStatusTest.java +++ b/src/test/java/chess/GameStatusTest.java @@ -3,11 +3,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import chess.domain.piece.Team; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class GameStatusTest { + @Test + @DisplayName("이긴 팀으로 해당 상태를 판단한다.") + void winByWhichTeamTest() { + assertAll( + () -> assertThat(GameStatus.winBy(Team.BLACK)).isEqualTo(GameStatus.BLACK_WIN), + () -> assertThat(GameStatus.winBy(Team.WHITE)).isEqualTo(GameStatus.WHITE_WIN) + ); + } + @Test @DisplayName("플레이 중인 상태인지 확인한다.") void isPlayingTest() { From 53e2cd6499c4f3583e6286e975a49977ac1a7150 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sat, 30 Mar 2024 23:08:38 +0900 Subject: [PATCH 20/36] =?UTF-8?q?refactor(Board):=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EA=B0=92=EB=A7=8C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=84=B4=20=ED=98=95=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/Board.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index 38ea16e739c..26ccb32c89e 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -106,18 +106,19 @@ private double calculateBasicScoreOf(Team team) { } private double calculateMinusScoreOf(Team team) { - int count = (int) findPawnEntryOf(team) - .filter(entry -> findPawnEntryOf(team) - .anyMatch(otherEntry -> !otherEntry.getKey().equals(entry.getKey()) - && otherEntry.getKey().isOnSameFile(entry.getKey()))) + int count = (int) getPawnPositionOf(team) + .filter(position -> getPawnPositionOf(team) + .anyMatch(other -> !other.equals(position) + && other.isOnSameFile(position))) .count(); return count * 0.5; } - private Stream> findPawnEntryOf(Team team) { + private Stream getPawnPositionOf(Team team) { return board.entrySet().stream() .filter(entry -> entry.getValue().isSameTeam(team)) - .filter(entry -> entry.getValue().isPawn()); + .filter(entry -> entry.getValue().isPawn()) + .map(Entry::getKey); } } From b7486376bf8d53396e5d41b469a3a09a20e4f0bd Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sun, 31 Mar 2024 04:01:03 +0900 Subject: [PATCH 21/36] =?UTF-8?q?feat(ScoreCalculator):=20=EC=8A=A4?= =?UTF-8?q?=EC=BD=94=EC=96=B4=20=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/ChessGame.java | 11 ++- src/main/java/chess/domain/Board.java | 26 +------ .../java/chess/domain/ScoreCalculator.java | 57 +++++++++++++++ src/main/java/chess/view/OutputView.java | 28 +++---- src/test/java/chess/domain/BoardTest.java | 53 -------------- .../chess/domain/ScoreCalculatorTest.java | 73 +++++++++++++++++++ 6 files changed, 156 insertions(+), 92 deletions(-) create mode 100644 src/main/java/chess/domain/ScoreCalculator.java create mode 100644 src/test/java/chess/domain/ScoreCalculatorTest.java diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index 026ba90845c..237baf870a4 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -2,6 +2,7 @@ import chess.domain.Board; import chess.domain.BoardFactory; +import chess.domain.ScoreCalculator; import chess.domain.piece.Team; import chess.domain.position.Position; import chess.dto.BoardDto; @@ -74,9 +75,13 @@ private GameStatus processTurn(GameCommand command, Board board) { } private GameStatus showStatus(Board board) { - double blackScore = board.calculateScoreOf(Team.BLACK); - double whiteScore = board.calculateScoreOf(Team.WHITE); - outputView.printStatus(blackScore, whiteScore); + ScoreCalculator scoreCalculator = new ScoreCalculator(board); + + double blackScore = scoreCalculator.getBlackScore(); + double whiteScore = scoreCalculator.getWhiteScore(); + Team winner = scoreCalculator.chooseWinner(); + + outputView.printStatus(blackScore, whiteScore, winner); return GameStatus.PLAYING; } diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index 26ccb32c89e..051c054f45d 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -2,7 +2,6 @@ import chess.GameStatus; import chess.domain.piece.Piece; -import chess.domain.piece.PieceType; import chess.domain.piece.Team; import chess.domain.position.Position; import java.util.HashMap; @@ -91,31 +90,12 @@ private boolean isOtherTeamKing(Piece piece) { return !piece.isSameTeam(turn) && piece.isKing(); } - public double calculateScoreOf(Team team) { - double basicScore = calculateBasicScoreOf(team); - double minusScore = calculateMinusScoreOf(team); - - return basicScore - minusScore; - } - - private double calculateBasicScoreOf(Team team) { + public Stream getPiecesOf(Team team) { return board.values().stream() - .filter(piece -> piece.isSameTeam(team)) - .mapToDouble(PieceType::scoreOf) - .sum(); - } - - private double calculateMinusScoreOf(Team team) { - int count = (int) getPawnPositionOf(team) - .filter(position -> getPawnPositionOf(team) - .anyMatch(other -> !other.equals(position) - && other.isOnSameFile(position))) - .count(); - - return count * 0.5; + .filter(piece -> piece.isSameTeam(team)); } - private Stream getPawnPositionOf(Team team) { + public Stream getPawnPositionsOf(Team team) { return board.entrySet().stream() .filter(entry -> entry.getValue().isSameTeam(team)) .filter(entry -> entry.getValue().isPawn()) diff --git a/src/main/java/chess/domain/ScoreCalculator.java b/src/main/java/chess/domain/ScoreCalculator.java new file mode 100644 index 00000000000..51aac2b0102 --- /dev/null +++ b/src/main/java/chess/domain/ScoreCalculator.java @@ -0,0 +1,57 @@ +package chess.domain; + +import chess.domain.piece.PieceType; +import chess.domain.piece.Team; + +public class ScoreCalculator { + + private final double blackScore; + private final double whiteScore; + + public ScoreCalculator(Board board) { + blackScore = calculateScoreOf(board, Team.BLACK); + whiteScore = calculateScoreOf(board, Team.WHITE); + } + + private double calculateScoreOf(Board board, Team team) { + double basicScore = calculateBasicScoreOf(board, team); + double minusScore = calculateMinusScoreOf(board, team); + + return basicScore - minusScore; + } + + private double calculateBasicScoreOf(Board board, Team team) { + return board.getPiecesOf(team) + .filter(piece -> piece.isSameTeam(team)) + .mapToDouble(PieceType::scoreOf) + .sum(); + } + + private double calculateMinusScoreOf(Board board, Team team) { + int count = (int) board.getPawnPositionsOf(team) + .filter(position -> board.getPawnPositionsOf(team) + .anyMatch(other -> !other.equals(position) + && other.isOnSameFile(position))) + .count(); + + return count * 0.5; + } + + public Team chooseWinner() { + if (blackScore > whiteScore) { + return Team.BLACK; + } + if (whiteScore > blackScore) { + return Team.WHITE; + } + return null; + } + + public double getBlackScore() { + return blackScore; + } + + public double getWhiteScore() { + return whiteScore; + } +} diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 41cf3c8fd63..27f26e46d43 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -2,6 +2,7 @@ import chess.GameStatus; import chess.domain.piece.PieceType; +import chess.domain.piece.Team; import chess.domain.position.File; import chess.domain.position.Position; import chess.domain.position.Rank; @@ -20,6 +21,8 @@ public class OutputView { private static final Map PIECE_DISPLAY = Map.of( PieceType.KING, "K", PieceType.QUEEN, "Q", PieceType.KNIGHT, "N", PieceType.BISHOP, "B", PieceType.ROOK, "R", PieceType.PAWN, "P"); + private static final Map WINNER_DISPLAY = Map.of( + Team.BLACK, "검은색 승리", Team.WHITE, "흰색 승리"); private static final String EMPTY_SPACE = "."; private static final String ERROR_PREFIX = "[ERROR] "; @@ -68,17 +71,9 @@ private void printEmptySpace() { System.out.print(EMPTY_SPACE); } - public void printStatus(double blackScore, double whiteScore) { - System.out.print("검은색: " + blackScore + ", 흰색: " + whiteScore); - if (blackScore > whiteScore) { - System.out.println(", 검은색 승리"); - return; - } - if (whiteScore > blackScore) { - System.out.println(", 흰색 승리"); - return; - } - System.out.println(", 무승부"); + public void printStatus(double blackScore, double whiteScore, Team winner) { + System.out.println("검은색: " + blackScore + ", 흰색: " + whiteScore); + System.out.println(getWinnerDisplay(winner)); } public void printExceptionMessage(Exception exception) { @@ -87,9 +82,16 @@ public void printExceptionMessage(Exception exception) { public void printWinner(GameStatus gameStatus) { if (gameStatus == GameStatus.BLACK_WIN) { - System.out.println("검은색 승리"); + System.out.println(getWinnerDisplay(Team.BLACK)); return; } - System.out.println("흰색 승리"); + System.out.println(getWinnerDisplay(Team.WHITE)); + } + + private static String getWinnerDisplay(Team winner) { + if (winner == null) { + return "무승부"; + } + return WINNER_DISPLAY.get(winner); } } diff --git a/src/test/java/chess/domain/BoardTest.java b/src/test/java/chess/domain/BoardTest.java index c04ee543545..c001ddef0b7 100644 --- a/src/test/java/chess/domain/BoardTest.java +++ b/src/test/java/chess/domain/BoardTest.java @@ -4,13 +4,9 @@ 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.Queen; -import chess.domain.piece.Rook; import chess.domain.piece.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -107,53 +103,4 @@ void moveTest_whenBlocked_throwException() { .hasMessage("다른 말이 있어 이동 불가능합니다."); } } - - @Test - @DisplayName("점수를 계산할 수 있다.") - void calculateScoreTest() { - Map boardMap = Map.of( - new Position(File.B, Rank.TWO), new Queen(Team.WHITE), - new Position(File.F, Rank.TWO), new Rook(Team.WHITE), - new Position(File.F, Rank.SIX), new Bishop(Team.WHITE), - new Position(File.D, Rank.THREE), new Knight(Team.BLACK), - new Position(File.E, Rank.SIX), new Pawn(Team.BLACK), - new Position(File.A, Rank.FOUR), new King(Team.BLACK)); - Board board = new Board(boardMap); - - assertAll( - () -> assertThat(board.calculateScoreOf(Team.WHITE)).isEqualTo(17), - () -> assertThat(board.calculateScoreOf(Team.BLACK)).isEqualTo(3.5) - ); - } - - @Test - @DisplayName("같은 팀의 폰이 같은 파일에 있을 때 점수를 계산할 수 있다.") - void calculateScoreTest_whenPawnInSameFile() { - Map boardMap = new java.util.HashMap<>(Map.of( - new Position(File.B, Rank.EIGHT), new King(Team.BLACK), - new Position(File.C, Rank.EIGHT), new Rook(Team.BLACK), - new Position(File.A, Rank.SEVEN), new Pawn(Team.BLACK), - new Position(File.C, Rank.SEVEN), new Pawn(Team.BLACK), - new Position(File.D, Rank.SEVEN), new Bishop(Team.BLACK), - new Position(File.B, Rank.SIX), new Pawn(Team.BLACK), - new Position(File.E, Rank.SIX), new Queen(Team.BLACK), - new Position(File.F, Rank.FOUR), new Knight(Team.WHITE), - new Position(File.G, Rank.FOUR), new Queen(Team.WHITE), - new Position(File.F, Rank.THREE), new Pawn(Team.WHITE) - )); - Map addedBoard = Map.of( - new Position(File.H, Rank.THREE), new Pawn(Team.WHITE), - new Position(File.F, Rank.TWO), new Pawn(Team.WHITE), - new Position(File.G, Rank.TWO), new Pawn(Team.WHITE), - new Position(File.E, Rank.ONE), new Rook(Team.WHITE), - new Position(File.F, Rank.ONE), new King(Team.WHITE) - ); - boardMap.putAll(addedBoard); - Board board = new Board(boardMap); - - assertAll( - () -> assertThat(board.calculateScoreOf(Team.WHITE)).isEqualTo(19.5), - () -> assertThat(board.calculateScoreOf(Team.BLACK)).isEqualTo(20) - ); - } } diff --git a/src/test/java/chess/domain/ScoreCalculatorTest.java b/src/test/java/chess/domain/ScoreCalculatorTest.java new file mode 100644 index 00000000000..73832087fe5 --- /dev/null +++ b/src/test/java/chess/domain/ScoreCalculatorTest.java @@ -0,0 +1,73 @@ +package chess.domain; + +import static org.assertj.core.api.Assertions.assertThat; +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.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 java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ScoreCalculatorTest { + + @Test + @DisplayName("점수를 계산할 수 있다.") + void calculateScoreTest() { + Map boardMap = Map.of( + new Position(File.B, Rank.TWO), new Queen(Team.WHITE), + new Position(File.F, Rank.TWO), new Rook(Team.WHITE), + new Position(File.F, Rank.SIX), new Bishop(Team.WHITE), + new Position(File.D, Rank.THREE), new Knight(Team.BLACK), + new Position(File.E, Rank.SIX), new Pawn(Team.BLACK), + new Position(File.A, Rank.FOUR), new King(Team.BLACK)); + Board board = new Board(boardMap); + ScoreCalculator scoreCalculator = new ScoreCalculator(board); + + assertAll( + () -> assertThat(scoreCalculator.getWhiteScore()).isEqualTo(17), + () -> assertThat(scoreCalculator.getBlackScore()).isEqualTo(3.5) + ); + } + + @Test + @DisplayName("같은 팀의 폰이 같은 파일에 있을 때 점수를 계산할 수 있다.") + void calculateScoreTest_whenPawnInSameFile() { + Map boardMap = new java.util.HashMap<>(Map.of( + new Position(File.B, Rank.EIGHT), new King(Team.BLACK), + new Position(File.C, Rank.EIGHT), new Rook(Team.BLACK), + new Position(File.A, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.C, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.D, Rank.SEVEN), new Bishop(Team.BLACK), + new Position(File.B, Rank.SIX), new Pawn(Team.BLACK), + new Position(File.E, Rank.SIX), new Queen(Team.BLACK), + new Position(File.F, Rank.FOUR), new Knight(Team.WHITE), + new Position(File.G, Rank.FOUR), new Queen(Team.WHITE), + new Position(File.F, Rank.THREE), new Pawn(Team.WHITE) + )); + Map addedBoard = Map.of( + new Position(File.H, Rank.THREE), new Pawn(Team.WHITE), + new Position(File.F, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.G, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.E, Rank.ONE), new Rook(Team.WHITE), + new Position(File.F, Rank.ONE), new King(Team.WHITE) + ); + boardMap.putAll(addedBoard); + Board board = new Board(boardMap); + ScoreCalculator scoreCalculator = new ScoreCalculator(board); + + assertAll( + () -> assertThat(scoreCalculator.getWhiteScore()).isEqualTo(19.5), + () -> assertThat(scoreCalculator.getBlackScore()).isEqualTo(20) + ); + } +} From 16a3341d0a953891b3e8ea96fbef60319568dd6b Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sun, 31 Mar 2024 04:19:52 +0900 Subject: [PATCH 22/36] =?UTF-8?q?test(Board):=20=ED=8A=B9=EC=A0=95=20?= =?UTF-8?q?=ED=8C=80=EC=9D=98=20=EB=A7=90=20=EB=B0=8F=20=ED=8F=B0=EC=9D=98?= =?UTF-8?q?=20=EC=9C=84=EC=B9=98=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/chess/domain/BoardTest.java | 71 +++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/test/java/chess/domain/BoardTest.java b/src/test/java/chess/domain/BoardTest.java index c001ddef0b7..4173814a468 100644 --- a/src/test/java/chess/domain/BoardTest.java +++ b/src/test/java/chess/domain/BoardTest.java @@ -4,9 +4,13 @@ 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.Queen; +import chess.domain.piece.Rook; import chess.domain.piece.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -103,4 +107,71 @@ void moveTest_whenBlocked_throwException() { .hasMessage("다른 말이 있어 이동 불가능합니다."); } } + + @Test + @DisplayName("특정 팀의 말들을 알 수 있다.") + void getPiecesOfTest() { + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.WHITE); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.BLACK); + Pawn pawn = new Pawn(Team.BLACK); + King king = new King(Team.BLACK); + Map boardMap = Map.of( + new Position(File.B, Rank.TWO), queen, + new Position(File.F, Rank.TWO), rook, + new Position(File.F, Rank.SIX), bishop, + new Position(File.D, Rank.THREE), knight, + new Position(File.E, Rank.SIX), pawn, + new Position(File.A, Rank.FOUR), king); + Board board = new Board(boardMap); + + assertAll( + () -> assertThat(board.getPiecesOf(Team.WHITE).toList()) + .containsExactlyInAnyOrder(queen, rook, bishop), + () -> assertThat(board.getPiecesOf(Team.BLACK).toList()) + .containsExactlyInAnyOrder(knight, pawn, king) + ); + } + + @Test + @DisplayName("특정 팀의 폰의 위치들을 알 수 있다.") + void getPawnPositionsOfTest() { + Map boardMap = new java.util.HashMap<>(Map.of( + new Position(File.B, Rank.EIGHT), new King(Team.BLACK), + new Position(File.C, Rank.EIGHT), new Rook(Team.BLACK), + new Position(File.A, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.C, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.D, Rank.SEVEN), new Bishop(Team.BLACK), + new Position(File.B, Rank.SIX), new Pawn(Team.BLACK), + new Position(File.E, Rank.SIX), new Queen(Team.BLACK) + )); + Map addedBoard = Map.of( + new Position(File.F, Rank.FOUR), new Knight(Team.WHITE), + new Position(File.G, Rank.FOUR), new Queen(Team.WHITE), + new Position(File.F, Rank.THREE), new Pawn(Team.WHITE), + new Position(File.H, Rank.THREE), new Pawn(Team.WHITE), + new Position(File.F, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.G, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.E, Rank.ONE), new Rook(Team.WHITE), + new Position(File.F, Rank.ONE), new King(Team.WHITE) + ); + boardMap.putAll(addedBoard); + Board board = new Board(boardMap); + + assertAll( + () -> assertThat(board.getPawnPositionsOf(Team.BLACK).toList()) + .containsExactlyInAnyOrder( + new Position(File.A, Rank.SEVEN), + new Position(File.C, Rank.SEVEN), + new Position(File.B, Rank.SIX)), + () -> assertThat(board.getPawnPositionsOf(Team.WHITE).toList()) + .containsExactlyInAnyOrder( + new Position(File.F, Rank.THREE), + new Position(File.H, Rank.THREE), + new Position(File.F, Rank.TWO), + new Position(File.G, Rank.TWO) + ) + ); + } } From dfeb489630f45d078e7e84c4125a85e29cfe3e8a Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sun, 31 Mar 2024 04:36:01 +0900 Subject: [PATCH 23/36] =?UTF-8?q?refactor(ScoreCalculator):=20=EA=B0=80?= =?UTF-8?q?=EB=8F=85=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=B4=20=EC=83=81?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/ScoreCalculator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/chess/domain/ScoreCalculator.java b/src/main/java/chess/domain/ScoreCalculator.java index 51aac2b0102..7817bd1fb36 100644 --- a/src/main/java/chess/domain/ScoreCalculator.java +++ b/src/main/java/chess/domain/ScoreCalculator.java @@ -5,6 +5,8 @@ public class ScoreCalculator { + private static final double PAWN_PENALTY_SCORE = 0.5; + private final double blackScore; private final double whiteScore; @@ -34,7 +36,7 @@ private double calculateMinusScoreOf(Board board, Team team) { && other.isOnSameFile(position))) .count(); - return count * 0.5; + return count * PAWN_PENALTY_SCORE; } public Team chooseWinner() { From 38e8cada82a56fee4d21340e1697f14579c5fb0a Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Sun, 31 Mar 2024 04:40:39 +0900 Subject: [PATCH 24/36] =?UTF-8?q?refactor(ScoreCalculator):=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=A4=91=EB=B3=B5=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/ScoreCalculator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/chess/domain/ScoreCalculator.java b/src/main/java/chess/domain/ScoreCalculator.java index 7817bd1fb36..f908dc09e5a 100644 --- a/src/main/java/chess/domain/ScoreCalculator.java +++ b/src/main/java/chess/domain/ScoreCalculator.java @@ -24,7 +24,6 @@ private double calculateScoreOf(Board board, Team team) { private double calculateBasicScoreOf(Board board, Team team) { return board.getPiecesOf(team) - .filter(piece -> piece.isSameTeam(team)) .mapToDouble(PieceType::scoreOf) .sum(); } From e49381253845f11cfbf3019f58d4ca8c064f52a3 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Mon, 1 Apr 2024 16:02:17 +0900 Subject: [PATCH 25/36] =?UTF-8?q?feat(docker-compose):=20=EB=8F=84?= =?UTF-8?q?=EC=BB=A4=20=EC=8B=A4=ED=96=89=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +--- docker-compose.yml | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore index 5e0f049c593..68f20bee5ae 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,4 @@ out/ ### VS Code ### .vscode/ -db/ - -docker-compose.yml +db/mysql/data/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000000..558a1d5a53f --- /dev/null +++ b/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 From 499e57bdec8fb468ecb26e4d64d123b73d5a04cb Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Mon, 1 Apr 2024 16:04:33 +0900 Subject: [PATCH 26/36] =?UTF-8?q?docs:=20=EC=8B=A4=ED=96=89=20=EB=B0=A9?= =?UTF-8?q?=EB=B2=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c10182725cf..7e6dbe7fdd5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # java-chess +## 실행 방법 +`java-chess`에서 `docker-compose -p chess up -d` 명령어 입력 + ## 페어와 지킬 컨벤션 1. 클래스 정의 다음 줄은 공백으로 한다. 2. test code에 사용하는 메서드는 `static import`한다. From 43dbfde68547c4a92d2f4372d5c44000223087f3 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Mon, 1 Apr 2024 16:07:04 +0900 Subject: [PATCH 27/36] =?UTF-8?q?feat(init):=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=84=A4=EC=A0=95=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/mysql/init/init.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 db/mysql/init/init.sql diff --git a/db/mysql/init/init.sql b/db/mysql/init/init.sql new file mode 100644 index 00000000000..b9fb428cceb --- /dev/null +++ b/db/mysql/init/init.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS game ( + gameId INT PRIMARY KEY, + turn VARCHAR(5) NOT NULL +); + +CREATE TABLE IF NOT EXISTS board ( + gameId INT, + file CHAR(5) NOT NULL, + `rank` CHAR(5) NOT NULL, + type VARCHAR(10) NOT NULL, + team VARCHAR(5) NOT NULL +); From 69a4af0b3957c001da687428f955faaf69282a4d Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Mon, 1 Apr 2024 16:08:15 +0900 Subject: [PATCH 28/36] =?UTF-8?q?feat(build.gradle):=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EA=B4=80=EA=B3=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 3697236c6fb..8c605072e94 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ dependencies { testImplementation platform('org.assertj:assertj-bom:3.25.1') testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.assertj:assertj-core') + runtimeOnly("com.mysql:mysql-connector-j:8.3.0") } java { From da26f3044df831037ef19a30be566955b10639a2 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Mon, 1 Apr 2024 16:21:47 +0900 Subject: [PATCH 29/36] =?UTF-8?q?feat(BoardDao):=20DB=20=EC=82=BD=EC=9E=85?= =?UTF-8?q?,=20=EC=82=AD=EC=A0=9C,=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/BoardDao.java | 164 ++++++++++++++++++ src/main/java/chess/domain/Board.java | 10 +- .../java/chess/domain/piece/PieceType.java | 21 ++- .../java/chess/domain/position/Position.java | 8 + src/main/java/chess/dto/BoardDto.java | 16 +- src/main/java/chess/dto/PieceDto.java | 12 ++ 6 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 src/main/java/chess/BoardDao.java diff --git a/src/main/java/chess/BoardDao.java b/src/main/java/chess/BoardDao.java new file mode 100644 index 00000000000..36aeff7ceea --- /dev/null +++ b/src/main/java/chess/BoardDao.java @@ -0,0 +1,164 @@ +package chess; + +import chess.domain.Board; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.dto.BoardDto; +import chess.dto.PieceDto; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class BoardDao { + + private static final String SERVER = "localhost:13306"; // MySQL 서버 주소 + private static final String DATABASE = "chess"; // MySQL DATABASE 이름 + private static final String OPTION = "?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; + private static final String USERNAME = "root"; // MySQL 서버 아이디 + private static final String PASSWORD = "root"; // MySQL 서버 비밀번호 + + public Connection getConnection() { + try { + return DriverManager.getConnection("jdbc:mysql://" + SERVER + "/" + DATABASE + OPTION, USERNAME, PASSWORD); + } catch (final SQLException e) { + System.err.println("DB 연결 오류:" + e.getMessage()); + e.printStackTrace(); + return null; + } + } + + public void add(final int gameId, final BoardDto boardDto) { + addGame(gameId, boardDto); + addBoard(gameId, boardDto); + } + + private void addGame(final int gameId, final BoardDto boardDto) { + final var query = "INSERT INTO game VALUES(?, ?)"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + preparedStatement.setString(2, boardDto.getTurn()); + preparedStatement.executeUpdate(); + } catch (final SQLException e) { + throw new RuntimeException(e); + } + } + + private void addBoard(final int gameId, final BoardDto boardDto) { + Map board = boardDto.getBoardDto(); + + final var query = "INSERT INTO board VALUES(?, ?, ?, ?, ?)"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + for (Entry pieceEntry : board.entrySet()) { + preparedStatement.setInt(1, gameId); + preparedStatement.setString(2, pieceEntry.getKey().getFile()); + preparedStatement.setString(3, pieceEntry.getKey().getRank()); + preparedStatement.setString(4, pieceEntry.getValue().getType()); + preparedStatement.setString(5, pieceEntry.getValue().getTeam()); + preparedStatement.executeUpdate(); + } + } catch (final SQLException e) { + throw new RuntimeException(e); + } + } + + public void delete(int gameId) { + deleteGame(gameId); + deleteBoard(gameId); + } + + private void deleteGame(int gameId) { + final var query = "DELETE FROM game WHERE gameId = ?"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private void deleteBoard(int gameId) { + final var query = "DELETE FROM board WHERE gameId = ?"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public Board loadBoard(int gameId) { + Team turn = getGameTurn(gameId); + Map pieces = getBoardPieces(gameId); + return new Board(pieces, turn); + } + + private Team getGameTurn(int gameId) { + final String query = "SELECT turn FROM game WHERE gameId = ?"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + try (ResultSet resultSet = preparedStatement.executeQuery()) { + if (resultSet.next()) { + String turn = resultSet.getString("turn"); + return Team.valueOf(turn.toUpperCase()); + } + } + } catch (SQLException e) { + throw new RuntimeException("게임 턴 정보 조회 실패", e); + } + throw new IllegalArgumentException("해당 게임 ID를 찾을 수 없습니다."); + } + + private Map getBoardPieces(int gameId) { + Map pieces = new HashMap<>(); + final String query = "SELECT file, `rank`, type, team FROM board WHERE gameId = ?"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + try (ResultSet resultSet = preparedStatement.executeQuery()) { + while (resultSet.next()) { + File file = File.valueOf(resultSet.getString("file").toUpperCase()); + Rank rank = Rank.valueOf(resultSet.getString("rank").toUpperCase()); + PieceType type = PieceType.valueOf(resultSet.getString("type").toUpperCase()); + Team team = Team.valueOf(resultSet.getString("team").toUpperCase()); + Position position = new Position(file, rank); + Piece piece = type.createPiece(team); + pieces.put(position, piece); + } + } + } catch (SQLException e) { + throw new RuntimeException("보드 정보 조회 실패", e); + } + return pieces; + } + + public boolean isExistBoard(int gameId) { + final String query = "SELECT EXISTS(SELECT 1 FROM game WHERE gameId = ?) AS Exist"; + boolean exists = false; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + try (ResultSet rs = preparedStatement.executeQuery()) { + if (rs.next()) { + exists = rs.getBoolean("Exist"); + } + return exists; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return exists; + } +} diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index 051c054f45d..88487715148 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -17,8 +17,12 @@ public class Board { private Team turn; public Board(Map board) { + this(board, Team.WHITE); + } + + public Board(Map board, Team turn) { this.board = new HashMap<>(board); - this.turn = Team.WHITE; + this.turn = turn; } public Optional find(Position position) { @@ -101,4 +105,8 @@ public Stream getPawnPositionsOf(Team team) { .filter(entry -> entry.getValue().isPawn()) .map(Entry::getKey); } + + public Team getTurn() { + return turn; + } } diff --git a/src/main/java/chess/domain/piece/PieceType.java b/src/main/java/chess/domain/piece/PieceType.java index e7dc8dfc986..f5bec7320b4 100644 --- a/src/main/java/chess/domain/piece/PieceType.java +++ b/src/main/java/chess/domain/piece/PieceType.java @@ -1,22 +1,25 @@ package chess.domain.piece; import java.util.Arrays; +import java.util.function.Function; public enum PieceType { - KING(King.class, 0), - QUEEN(Queen.class, 9), - ROOK(Rook.class, 5), - BISHOP(Bishop.class, 3), - KNIGHT(Knight.class, 2.5), - PAWN(Pawn.class, 1); + KING(King.class, 0, King::new), + QUEEN(Queen.class, 9, Queen::new), + ROOK(Rook.class, 5, Rook::new), + BISHOP(Bishop.class, 3, Bishop::new), + KNIGHT(Knight.class, 2.5, Knight::new), + PAWN(Pawn.class, 1, Pawn::new); private final Class category; private final double score; + private final Function pieceFactory; - PieceType(Class category, double score) { + PieceType(Class category, double score, Function pieceFactory) { this.category = category; this.score = score; + this.pieceFactory = pieceFactory; } public static PieceType from(Piece piece) { @@ -29,4 +32,8 @@ public static PieceType from(Piece piece) { public static double scoreOf(Piece piece) { return from(piece).score; } + + public Piece createPiece(Team team) { + return pieceFactory.apply(team); + } } diff --git a/src/main/java/chess/domain/position/Position.java b/src/main/java/chess/domain/position/Position.java index 49401aa525a..ce2676fc35d 100644 --- a/src/main/java/chess/domain/position/Position.java +++ b/src/main/java/chess/domain/position/Position.java @@ -51,6 +51,14 @@ public Position moveToSouth() { return new Position(file, rank.toSouth()); } + public String getFile() { + return file.toString(); + } + + public String getRank() { + return rank.toString(); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java index 1f2eb01260b..bc9dea44388 100644 --- a/src/main/java/chess/dto/BoardDto.java +++ b/src/main/java/chess/dto/BoardDto.java @@ -2,6 +2,7 @@ import chess.domain.Board; import chess.domain.piece.Piece; +import chess.domain.piece.Team; import chess.domain.position.Position; import java.util.HashMap; import java.util.List; @@ -11,16 +12,19 @@ public class BoardDto { private final Map boardDto; + private final Team turn; - private BoardDto(Map boardDto) { + private BoardDto(Map boardDto, Team turn) { this.boardDto = boardDto; + this.turn = turn; } public static BoardDto of(Board board) { List positions = Position.ALL_POSITIONS; Map boardDto = new HashMap<>(); positions.forEach(position -> addPiece(board, position, boardDto)); - return new BoardDto(boardDto); + Team turn = board.getTurn(); + return new BoardDto(boardDto, turn); } private static void addPiece(Board board, Position position, Map boardDto) { @@ -38,4 +42,12 @@ private static void addPiece(Board board, Position position, Map find(Position position) { return Optional.ofNullable(boardDto.get(position)); } + + public Map getBoardDto() { + return boardDto; + } + + public String getTurn() { + return turn.toString(); + } } diff --git a/src/main/java/chess/dto/PieceDto.java b/src/main/java/chess/dto/PieceDto.java index 0c18797b227..f563ab90e0b 100644 --- a/src/main/java/chess/dto/PieceDto.java +++ b/src/main/java/chess/dto/PieceDto.java @@ -2,6 +2,7 @@ import chess.domain.piece.Piece; import chess.domain.piece.PieceType; +import chess.domain.piece.Team; public class PieceDto { @@ -27,4 +28,15 @@ public PieceType type() { public boolean isBlack() { return isBlack; } + + public String getType() { + return type.toString(); + } + + public String getTeam() { + if (isBlack) { + return Team.BLACK.toString(); + } + return Team.WHITE.toString(); + } } From 29404909d21a3cd9b1a20df789aa8d6531e2c640 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Mon, 1 Apr 2024 16:22:40 +0900 Subject: [PATCH 30/36] =?UTF-8?q?feat(ChessGame):=20Dao=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=B4=20=EC=A0=95=EB=B3=B4=20=EC=A0=80=EC=9E=A5,?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/ChessGame.java | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index 237baf870a4..832c1791ad1 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -12,6 +12,8 @@ public class ChessGame { + private static final int ONE_GAME = 1; + private final InputView inputView; private final OutputView outputView; @@ -33,7 +35,7 @@ public void tryStart() { private void start() { GameCommand command = inputView.readCommand(); if (command == GameCommand.START) { - Board board = BoardFactory.createInitialBoard(); + Board board = getBoard(); showBoard(board); play(board); return; @@ -43,6 +45,14 @@ private void start() { } } + private Board getBoard() { + BoardDao boardDao = new BoardDao(); + if (boardDao.isExistBoard(ONE_GAME)) { + return boardDao.loadBoard(ONE_GAME); + } + return BoardFactory.createInitialBoard(); + } + private void play(Board board) { GameStatus gameStatus = GameStatus.PLAYING; while (GameStatus.isPlaying(gameStatus)) { @@ -66,7 +76,7 @@ private GameStatus processTurn(GameCommand command, Board board) { throw new IllegalArgumentException("이미 게임을 시작했습니다."); } if (command == GameCommand.END) { - return GameStatus.END; + return endGame(board); } if (command == GameCommand.STATUS) { return showStatus(board); @@ -74,6 +84,16 @@ private GameStatus processTurn(GameCommand command, Board board) { return executeMove(board); } + private GameStatus endGame(Board board) { + BoardDao boardDao = new BoardDao(); + boardDao.delete(ONE_GAME); + + BoardDto boardDto = BoardDto.of(board); + boardDao.add(ONE_GAME, boardDto); + + return GameStatus.END; + } + private GameStatus showStatus(Board board) { ScoreCalculator scoreCalculator = new ScoreCalculator(board); @@ -92,6 +112,8 @@ private GameStatus executeMove(Board board) { showBoard(board); if (gameStatus != GameStatus.PLAYING) { outputView.printWinner(gameStatus); + BoardDao boardDao = new BoardDao(); + boardDao.delete(ONE_GAME); } return gameStatus; } From 426d51d196900e2eb8ce72e83a81355e963f1407 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Mon, 1 Apr 2024 17:24:37 +0900 Subject: [PATCH 31/36] =?UTF-8?q?refactor(BoardDao):=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/BoardDao.java | 105 ++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 34 deletions(-) diff --git a/src/main/java/chess/BoardDao.java b/src/main/java/chess/BoardDao.java index 36aeff7ceea..f7599cb3114 100644 --- a/src/main/java/chess/BoardDao.java +++ b/src/main/java/chess/BoardDao.java @@ -11,6 +11,7 @@ import chess.dto.PieceDto; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; @@ -58,19 +59,24 @@ private void addBoard(final int gameId, final BoardDto boardDto) { final var query = "INSERT INTO board VALUES(?, ?, ?, ?, ?)"; try (final var connection = getConnection(); final var preparedStatement = connection.prepareStatement(query)) { - for (Entry pieceEntry : board.entrySet()) { - preparedStatement.setInt(1, gameId); - preparedStatement.setString(2, pieceEntry.getKey().getFile()); - preparedStatement.setString(3, pieceEntry.getKey().getRank()); - preparedStatement.setString(4, pieceEntry.getValue().getType()); - preparedStatement.setString(5, pieceEntry.getValue().getTeam()); - preparedStatement.executeUpdate(); - } + addPiece(gameId, board, preparedStatement); } catch (final SQLException e) { throw new RuntimeException(e); } } + private void addPiece(int gameId, Map board, PreparedStatement preparedStatement) + throws SQLException { + for (Entry pieceEntry : board.entrySet()) { + preparedStatement.setInt(1, gameId); + preparedStatement.setString(2, pieceEntry.getKey().getFile()); + preparedStatement.setString(3, pieceEntry.getKey().getRank()); + preparedStatement.setString(4, pieceEntry.getValue().getType()); + preparedStatement.setString(5, pieceEntry.getValue().getTeam()); + preparedStatement.executeUpdate(); + } + } + public void delete(int gameId) { deleteGame(gameId); deleteBoard(gameId); @@ -109,16 +115,32 @@ private Team getGameTurn(int gameId) { try (final var connection = getConnection(); final var preparedStatement = connection.prepareStatement(query)) { preparedStatement.setInt(1, gameId); - try (ResultSet resultSet = preparedStatement.executeQuery()) { - if (resultSet.next()) { - String turn = resultSet.getString("turn"); - return Team.valueOf(turn.toUpperCase()); - } - } + Team turn = getTeam(preparedStatement); + validateExistTeam(turn); + return turn; } catch (SQLException e) { throw new RuntimeException("게임 턴 정보 조회 실패", e); } - throw new IllegalArgumentException("해당 게임 ID를 찾을 수 없습니다."); + } + + private Team getTeam(PreparedStatement preparedStatement) throws SQLException { + try (ResultSet resultSet = preparedStatement.executeQuery()) { + return getTeam(resultSet); + } + } + + private Team getTeam(ResultSet resultSet) throws SQLException { + if (resultSet.next()) { + String turn = resultSet.getString("turn"); + return Team.valueOf(turn.toUpperCase()); + } + return null; + } + + private void validateExistTeam(Team turn) { + if (turn == null) { + throw new IllegalArgumentException("해당 게임 ID를 찾을 수 없습니다."); + } } private Map getBoardPieces(int gameId) { @@ -127,38 +149,53 @@ private Map getBoardPieces(int gameId) { try (final var connection = getConnection(); final var preparedStatement = connection.prepareStatement(query)) { preparedStatement.setInt(1, gameId); - try (ResultSet resultSet = preparedStatement.executeQuery()) { - while (resultSet.next()) { - File file = File.valueOf(resultSet.getString("file").toUpperCase()); - Rank rank = Rank.valueOf(resultSet.getString("rank").toUpperCase()); - PieceType type = PieceType.valueOf(resultSet.getString("type").toUpperCase()); - Team team = Team.valueOf(resultSet.getString("team").toUpperCase()); - Position position = new Position(file, rank); - Piece piece = type.createPiece(team); - pieces.put(position, piece); - } - } + getOnePiece(preparedStatement, pieces); } catch (SQLException e) { throw new RuntimeException("보드 정보 조회 실패", e); } return pieces; } + private void getOnePiece(PreparedStatement preparedStatement, Map pieces) throws SQLException { + try (ResultSet resultSet = preparedStatement.executeQuery()) { + getOnePiece(pieces, resultSet); + } + } + + private void getOnePiece(Map pieces, ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + File file = File.valueOf(resultSet.getString("file").toUpperCase()); + Rank rank = Rank.valueOf(resultSet.getString("rank").toUpperCase()); + PieceType type = PieceType.valueOf(resultSet.getString("type").toUpperCase()); + Team team = Team.valueOf(resultSet.getString("team").toUpperCase()); + Position position = new Position(file, rank); + Piece piece = type.createPiece(team); + pieces.put(position, piece); + } + } + public boolean isExistBoard(int gameId) { final String query = "SELECT EXISTS(SELECT 1 FROM game WHERE gameId = ?) AS Exist"; - boolean exists = false; try (final var connection = getConnection(); final var preparedStatement = connection.prepareStatement(query)) { preparedStatement.setInt(1, gameId); - try (ResultSet rs = preparedStatement.executeQuery()) { - if (rs.next()) { - exists = rs.getBoolean("Exist"); - } - return exists; - } + return getExist(preparedStatement); } catch (SQLException e) { e.printStackTrace(); } - return exists; + return false; + } + + private boolean getExist(PreparedStatement preparedStatement) throws SQLException { + try (ResultSet rs = preparedStatement.executeQuery()) { + return getExist(rs); + } + } + + private boolean getExist(ResultSet rs) throws SQLException { + if (rs.next()) { + return rs.getBoolean("Exist"); + } + return false; } } From 85ad50b6826c35f2729cef9562e73e5ac9fdf6e6 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Mon, 1 Apr 2024 17:31:57 +0900 Subject: [PATCH 32/36] =?UTF-8?q?docs:=20=EA=B2=8C=EC=9E=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EB=B0=A9=EB=B2=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 7e6dbe7fdd5..c1738367015 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,27 @@ ### 출력 - [x] 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다. + +### 게임 진행 +- [x] start 커맨드를 입력하면 게임에 사용할 보드를 생성한다. + - [x] 이미 보드 정보가 존재할 경우, 그 보드 정보를 불러온다. + - [x] 보드가 존재하지 않을 경우 새 보드를 생성한다. +- [x] end 커맨드를 입력하면 보드의 정보를 저장한다. +- [x] 왕이 잡혀 게임이 종료되면 저장된 보드의 정보를 삭제한다. + +----------- +## 왕이 잡혀 게임이 종료되는 예시 +### 흑 승리 +move f2 f3 +move e7 e5 +move g2 g4 +move d8 h4 +move h2 h3 +move h4 e1 + +### 백 승리 +move e2 e3 +move f7 f6 +move d1 h5 +move g8 h6 +move h5 e8 From f70c0915ce7f6d9a4eeb08ed9f09e3adf7decad3 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 3 Apr 2024 17:30:21 +0900 Subject: [PATCH 33/36] =?UTF-8?q?refactor(GameStatus):=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20static=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/ChessGame.java | 2 +- src/main/java/chess/GameStatus.java | 4 ++-- src/test/java/chess/GameStatusTest.java | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index 832c1791ad1..43eeb04349f 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -55,7 +55,7 @@ private Board getBoard() { private void play(Board board) { GameStatus gameStatus = GameStatus.PLAYING; - while (GameStatus.isPlaying(gameStatus)) { + while (gameStatus.isPlaying()) { gameStatus = tryProcessTurn(board); } } diff --git a/src/main/java/chess/GameStatus.java b/src/main/java/chess/GameStatus.java index 4cc949c9be3..aaf2f3b1fe7 100644 --- a/src/main/java/chess/GameStatus.java +++ b/src/main/java/chess/GameStatus.java @@ -16,7 +16,7 @@ public static GameStatus winBy(Team team) { return WHITE_WIN; } - public static boolean isPlaying(GameStatus gameStatus) { - return gameStatus == GameStatus.PLAYING; + public boolean isPlaying() { + return this == GameStatus.PLAYING; } } diff --git a/src/test/java/chess/GameStatusTest.java b/src/test/java/chess/GameStatusTest.java index 9dbda072d91..8db51bf6b16 100644 --- a/src/test/java/chess/GameStatusTest.java +++ b/src/test/java/chess/GameStatusTest.java @@ -22,10 +22,10 @@ void winByWhichTeamTest() { @DisplayName("플레이 중인 상태인지 확인한다.") void isPlayingTest() { assertAll( - () -> assertThat(GameStatus.isPlaying(GameStatus.PLAYING)).isTrue(), - () -> assertThat(GameStatus.isPlaying(GameStatus.END)).isFalse(), - () -> assertThat(GameStatus.isPlaying(GameStatus.BLACK_WIN)).isFalse(), - () -> assertThat(GameStatus.isPlaying(GameStatus.WHITE_WIN)).isFalse() + () -> assertThat(GameStatus.PLAYING.isPlaying()).isTrue(), + () -> assertThat(GameStatus.END.isPlaying()).isFalse(), + () -> assertThat(GameStatus.BLACK_WIN.isPlaying()).isFalse(), + () -> assertThat(GameStatus.WHITE_WIN.isPlaying()).isFalse() ); } } From ec85ee9695106da6d4f2d3ec714e894b101c7052 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 3 Apr 2024 17:30:57 +0900 Subject: [PATCH 34/36] =?UTF-8?q?refactor(ChessGame):=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B4=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/ChessGame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index 43eeb04349f..36942ccdcaf 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -110,7 +110,7 @@ private GameStatus executeMove(Board board) { Position end = inputView.readPosition(); GameStatus gameStatus = board.tryMove(start, end); showBoard(board); - if (gameStatus != GameStatus.PLAYING) { + if (!gameStatus.isPlaying()) { outputView.printWinner(gameStatus); BoardDao boardDao = new BoardDao(); boardDao.delete(ONE_GAME); From ed1a5baf06f794d8efaa9ef5c75739ffd2fc904c Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 3 Apr 2024 17:36:00 +0900 Subject: [PATCH 35/36] =?UTF-8?q?refactor(ScoreCalculator):=20predicate?= =?UTF-8?q?=EC=9D=84=20=EB=B6=84=EB=A6=AC=ED=95=98=EC=97=AC=20=EA=B0=80?= =?UTF-8?q?=EB=8F=85=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/ScoreCalculator.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/chess/domain/ScoreCalculator.java b/src/main/java/chess/domain/ScoreCalculator.java index f908dc09e5a..0077a6dc30b 100644 --- a/src/main/java/chess/domain/ScoreCalculator.java +++ b/src/main/java/chess/domain/ScoreCalculator.java @@ -2,6 +2,8 @@ import chess.domain.piece.PieceType; import chess.domain.piece.Team; +import chess.domain.position.Position; +import java.util.function.Predicate; public class ScoreCalculator { @@ -29,10 +31,11 @@ private double calculateBasicScoreOf(Board board, Team team) { } private double calculateMinusScoreOf(Board board, Team team) { + Predicate isOnSameFile = position -> board.getPawnPositionsOf(team) + .anyMatch(other -> !other.equals(position) && other.isOnSameFile(position)); + int count = (int) board.getPawnPositionsOf(team) - .filter(position -> board.getPawnPositionsOf(team) - .anyMatch(other -> !other.equals(position) - && other.isOnSameFile(position))) + .filter(isOnSameFile) .count(); return count * PAWN_PENALTY_SCORE; From 294479663e94849c3933ecaabd970cfa5ad78472 Mon Sep 17 00:00:00 2001 From: hyxrxn Date: Wed, 3 Apr 2024 23:31:42 +0900 Subject: [PATCH 36/36] =?UTF-8?q?refactor(BoardDao):=20=EC=8B=A4=ED=8C=A8?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/BoardDao.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/java/chess/BoardDao.java b/src/main/java/chess/BoardDao.java index f7599cb3114..49379d3b92a 100644 --- a/src/main/java/chess/BoardDao.java +++ b/src/main/java/chess/BoardDao.java @@ -30,9 +30,7 @@ public Connection getConnection() { try { return DriverManager.getConnection("jdbc:mysql://" + SERVER + "/" + DATABASE + OPTION, USERNAME, PASSWORD); } catch (final SQLException e) { - System.err.println("DB 연결 오류:" + e.getMessage()); - e.printStackTrace(); - return null; + throw new RuntimeException("DB 연결 실패", e); } } @@ -49,7 +47,7 @@ private void addGame(final int gameId, final BoardDto boardDto) { preparedStatement.setString(2, boardDto.getTurn()); preparedStatement.executeUpdate(); } catch (final SQLException e) { - throw new RuntimeException(e); + throw new RuntimeException("게임 정보 저장 실패", e); } } @@ -61,7 +59,7 @@ private void addBoard(final int gameId, final BoardDto boardDto) { final var preparedStatement = connection.prepareStatement(query)) { addPiece(gameId, board, preparedStatement); } catch (final SQLException e) { - throw new RuntimeException(e); + throw new RuntimeException("보드 정보 저장 실패", e); } } @@ -89,7 +87,7 @@ private void deleteGame(int gameId) { preparedStatement.setInt(1, gameId); preparedStatement.executeUpdate(); } catch (SQLException e) { - throw new RuntimeException(e); + throw new RuntimeException("게임 정보 삭제 실패", e); } } @@ -100,7 +98,7 @@ private void deleteBoard(int gameId) { preparedStatement.setInt(1, gameId); preparedStatement.executeUpdate(); } catch (SQLException e) { - throw new RuntimeException(e); + throw new RuntimeException("보드 정보 삭제 실패", e); } } @@ -181,9 +179,8 @@ public boolean isExistBoard(int gameId) { preparedStatement.setInt(1, gameId); return getExist(preparedStatement); } catch (SQLException e) { - e.printStackTrace(); + throw new RuntimeException("보드 존재 여부 확인 실패", e); } - return false; } private boolean getExist(PreparedStatement preparedStatement) throws SQLException {