From f96ec497ba08bf6561361eaa12742bcc1c3d835e Mon Sep 17 00:00:00 2001 From: "Sunguk Yang (Kelly)" Date: Tue, 26 Mar 2024 15:37:50 +0900 Subject: [PATCH 01/18] =?UTF-8?q?[1,2=EB=8B=A8=EA=B3=84=20-=20=EC=B2=B4?= =?UTF-8?q?=EC=8A=A4]=20=EC=BC=88=EB=A6=AC(=EC=96=91=EC=84=B1=EC=9A=B1)=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(#643)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 기능 명세서 초안 작성 * feat(position): Position 인스턴스 생성 로직 구현 Co-authored-by: Hyunguk Ryu * feat(inputView): 사용자로부터 명령을 입력받는 기능 구현 Co-authored-by: Hyunguk Ryu * fix(position): 위치 객체에서 체스 도메인 용어를 사용하도록 변경 Co-authored-by: Hyunguk Ryu * feat(board): 체스판의 말을 시작 위치로 배치하는 기능 구현 Co-authored-by: Hyunguk Ryu * feat(outputView): 체스판 출력 기능 구현 Co-authored-by: Hyunguk Ryu * docs(readme): 2단계 기능 요구사항 추가 Co-authored-by: Hyunguk Ryu * refactor(/position): 위치정보와 관련된 클래스들을 한 패키지로 모음 Co-authored-by: Hyunguk Ryu * refactor(position): Position 생성자 파라미터 순서 변경 Co-authored-by: Hyunguk Ryu * feat(unitVector): 8방위를 나타내는 단위 벡터 객체 구현 Co-authored-by: Hyunguk Ryu * feat(position): 현재 위치에서 백터를 누적한 새로운 위치를 생성하는 로직 구현 Co-authored-by: Hyunguk Ryu * feat(orthogonalMoveStrategy): 직선 방향으로 이동 가능 여부를 확인하는 전략 구현 Co-authored-by: Hyunguk Ryu * feat(continuousMoveStrategy): 이동 가능한 방향벡터와 이동 횟수 제한을 가지는 연속 이동 전략 구현 Co-authored-by: Hyunguk Ryu * refactor(boardInitializer):체스판 초기화 책임 분리 Co-authored-by: Hyunguk Ryu * refactor(continuousMoveStrategy): 스트림 내의 Predicate 메서드 분리 Co-authored-by: Hyunguk Ryu * feat(board): 체스 말을 이동시키는 기능 구현 Co-authored-by: Hyunguk Ryu * feat(knightMoveStrategy): 나이트 이동 전략 구현 Co-authored-by: Hyunguk Ryu * feat(pawnMoveStrategy): 폰의 이동 전략 구현 Co-authored-by: Hyunguk Ryu * test(boardTest): 기물을 이동시키는 로직의 테스트 추가 Co-authored-by: Hyunguk Ryu * refactor(board): 이동 불가능한 경우별 예외에 메시지 구분 Co-authored-by: Hyunguk Ryu * feat(inputView): 사용자의 명령을 입력받는 기능 구현 Co-authored-by: Hyunguk Ryu * feat(chessController): 팀을 번갈아 가며 게임을 진행하는 기능 구현 Co-authored-by: Hyunguk Ryu * test(knightMoveStrategyTest): 나이트 움직임에 대한 테스트 코드 추가 Co-authored-by: Hyunguk Ryu * test(pawnMoveStrategyTest): 폰 움직임에 대한 테스트 코드 추가 Co-authored-by: Hyunguk Ryu * style(positionTest): 테스트 설명 수정 Co-authored-by: Hyunguk Ryu * feat(pieceFactory): PieceType을 전달받아 올바른 Piece를 생성하는 객체 추가 Co-authored-by: Hyunguk Ryu * test(boardTest): Board 출발지 -> 목적지 기물 배치 성공 & 실패 테스트 추가 Co-authored-by: Hyunguk Ryu * refactor(continuousMoveStrategy): 메서드 이름 변경 Co-authored-by: Hyunguk Ryu * style(position): 미사용 TODO 삭제 Co-authored-by: Hyunguk Ryu * refactor(chessController): 사용자 입력에 따라 게임을 진행하는 기능 구현 Co-authored-by: Hyunguk Ryu * refactor(requestDto): 명령 인자에서 Optional 제거 Co-authored-by: Hyunguk Ryu * fix(pawnMoveStrategy): 폰이 직선 이동에서 상대 기물을 공격하는 버그 수정 Co-authored-by: Hyunguk Ryu * refactor: position 도메인 객체 수정 - 기존 클래스명을 더 명확하게 변경함. - 부족한 테스트 케이스를 추가함. - 메서드 파라미터를 더 명확한 타입으로 개선함. Co-authored-by: Hyunguk Ryu * refactor: direction 도메인 추상화 - direction enum들을 MovementDirection으로 추상화 함. - 테스트를 수정함. Co-authored-by: Hyunguk Ryu * feat: Bishop 구현 Co-authored-by: Hyunguk Ryu * feat: Rook 구현 Co-authored-현by: Hyunguk Ryu * feat: King 구현 Co-authore현d-by: Hyunguk Ryu * feat: Queen 구현 Co-authore현d-by: Hyunguk Ryu * feat: Knight 구현 * feat: Pawn 구현 Co-authored-현by: Hyunguk Ryu * refactor: board 패키지 생성 * refactor: Piece 도메인 클래스에 Type 추가 Co-authored-by: Hyu성nguk Ryu * remove: 기존 코드 제거 Co-authored-by: Hyunguk Ryu <가hw0603@naver.com> * refactor: piece 패키지 정리 * refactor: board 도메인 개선 * refactor: view & dto 개선 * docs: 기능 명세서 개선 * refactor: 상태 페턴 적용 * other: 상태에 Controller를 넘기는 케이스 코드 추가 * refactor: import 와일드 카드 제거 * refactor: 기물 이동 메서드명 변경 및 불필요한 메서드 분리 제거 * refactor: Piece 이동 시 Board 클래스를 입력받도록 리펙토링 * refactor: Bishop & Rook의 방향 유효성 검증을 CommonMovementDirection에 위임 * refactor: BoardInitializer 개선 --------- Co-authored-by: Hyunguk Ryu Co-authored-by: Hyunguk Ryu <가hw0603@naver.com> --- .gitmessage.txt | 4 + docs/README.md | 63 +++++++++ src/main/java/Application.java | 12 ++ src/main/java/controller/GameController.java | 62 ++++++++ src/main/java/domain/board/Board.java | 52 +++++++ .../java/domain/board/BoardInitializer.java | 87 ++++++++++++ src/main/java/domain/board/File.java | 38 +++++ src/main/java/domain/board/Position.java | 40 ++++++ src/main/java/domain/board/Rank.java | 52 +++++++ src/main/java/domain/game/ChessGame.java | 47 +++++++ src/main/java/domain/game/End.java | 9 ++ src/main/java/domain/game/GameCommand.java | 5 + .../java/domain/game/GameCommandType.java | 48 +++++++ src/main/java/domain/game/Move.java | 22 +++ src/main/java/domain/game/Start.java | 13 ++ src/main/java/domain/piece/Bishop.java | 54 +++++++ .../domain/piece/CommonMovementDirection.java | 74 ++++++++++ src/main/java/domain/piece/King.java | 45 ++++++ src/main/java/domain/piece/Knight.java | 34 +++++ .../domain/piece/KnightMovementDirection.java | 62 ++++++++ .../java/domain/piece/MovementDirection.java | 8 ++ src/main/java/domain/piece/Pawn.java | 69 +++++++++ .../domain/piece/PawnMovementDirection.java | 84 +++++++++++ src/main/java/domain/piece/Piece.java | 24 ++++ src/main/java/domain/piece/PieceColor.java | 13 ++ src/main/java/domain/piece/PieceType.java | 10 ++ src/main/java/domain/piece/Queen.java | 53 +++++++ src/main/java/domain/piece/Rook.java | 55 ++++++++ src/main/java/dto/BoardDto.java | 19 +++ src/main/java/dto/PieceDto.java | 12 ++ src/main/java/dto/PositionDto.java | 14 ++ src/main/java/othercase/End.java | 9 ++ src/main/java/othercase/GameCommand.java | 5 + src/main/java/othercase/GameCommandType.java | 48 +++++++ src/main/java/othercase/GameController.java | 80 +++++++++++ src/main/java/othercase/Move.java | 18 +++ src/main/java/othercase/Start.java | 9 ++ src/main/java/view/InputView.java | 20 +++ src/main/java/view/OutputView.java | 79 +++++++++++ src/test/java/domain/board/BoardTest.java | 107 ++++++++++++++ src/test/java/domain/piece/BishopTest.java | 110 +++++++++++++++ src/test/java/domain/piece/KingTest.java | 84 +++++++++++ src/test/java/domain/piece/KnightTest.java | 31 ++++ src/test/java/domain/piece/PawnTest.java | 132 ++++++++++++++++++ src/test/java/domain/piece/QueenTest.java | 92 ++++++++++++ src/test/java/domain/piece/RookTest.java | 111 +++++++++++++++ .../position/CommonMovementDirectionTest.java | 67 +++++++++ src/test/java/domain/position/FileTest.java | 36 +++++ .../position/KnightMovementDirectionTest.java | 75 ++++++++++ .../position/PawnMovementDirectionTest.java | 81 +++++++++++ .../java/domain/position/PositionTest.java | 41 ++++++ src/test/java/domain/position/RankTest.java | 36 +++++ 52 files changed, 2455 insertions(+) create mode 100644 .gitmessage.txt create mode 100644 docs/README.md create mode 100644 src/main/java/Application.java create mode 100644 src/main/java/controller/GameController.java create mode 100644 src/main/java/domain/board/Board.java create mode 100644 src/main/java/domain/board/BoardInitializer.java create mode 100644 src/main/java/domain/board/File.java create mode 100644 src/main/java/domain/board/Position.java create mode 100644 src/main/java/domain/board/Rank.java create mode 100644 src/main/java/domain/game/ChessGame.java create mode 100644 src/main/java/domain/game/End.java create mode 100644 src/main/java/domain/game/GameCommand.java create mode 100644 src/main/java/domain/game/GameCommandType.java create mode 100644 src/main/java/domain/game/Move.java create mode 100644 src/main/java/domain/game/Start.java create mode 100644 src/main/java/domain/piece/Bishop.java create mode 100644 src/main/java/domain/piece/CommonMovementDirection.java create mode 100644 src/main/java/domain/piece/King.java create mode 100644 src/main/java/domain/piece/Knight.java create mode 100644 src/main/java/domain/piece/KnightMovementDirection.java create mode 100644 src/main/java/domain/piece/MovementDirection.java create mode 100644 src/main/java/domain/piece/Pawn.java create mode 100644 src/main/java/domain/piece/PawnMovementDirection.java create mode 100644 src/main/java/domain/piece/Piece.java create mode 100644 src/main/java/domain/piece/PieceColor.java create mode 100644 src/main/java/domain/piece/PieceType.java create mode 100644 src/main/java/domain/piece/Queen.java create mode 100644 src/main/java/domain/piece/Rook.java create mode 100644 src/main/java/dto/BoardDto.java create mode 100644 src/main/java/dto/PieceDto.java create mode 100644 src/main/java/dto/PositionDto.java create mode 100644 src/main/java/othercase/End.java create mode 100644 src/main/java/othercase/GameCommand.java create mode 100644 src/main/java/othercase/GameCommandType.java create mode 100644 src/main/java/othercase/GameController.java create mode 100644 src/main/java/othercase/Move.java create mode 100644 src/main/java/othercase/Start.java create mode 100644 src/main/java/view/InputView.java create mode 100644 src/main/java/view/OutputView.java create mode 100644 src/test/java/domain/board/BoardTest.java create mode 100644 src/test/java/domain/piece/BishopTest.java create mode 100644 src/test/java/domain/piece/KingTest.java create mode 100644 src/test/java/domain/piece/KnightTest.java create mode 100644 src/test/java/domain/piece/PawnTest.java create mode 100644 src/test/java/domain/piece/QueenTest.java create mode 100644 src/test/java/domain/piece/RookTest.java create mode 100644 src/test/java/domain/position/CommonMovementDirectionTest.java create mode 100644 src/test/java/domain/position/FileTest.java create mode 100644 src/test/java/domain/position/KnightMovementDirectionTest.java create mode 100644 src/test/java/domain/position/PawnMovementDirectionTest.java create mode 100644 src/test/java/domain/position/PositionTest.java create mode 100644 src/test/java/domain/position/RankTest.java diff --git a/.gitmessage.txt b/.gitmessage.txt new file mode 100644 index 00000000000..18e39abc3d9 --- /dev/null +++ b/.gitmessage.txt @@ -0,0 +1,4 @@ + + +Co-authored-by: Hyunguk Ryu + diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..8ad0f4d0ad9 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,63 @@ +## 프로그램 흐름 + +- 사용자로부터 게임 시작 여부를 입력받는다. + - `start`를 입력받으면 게임을 생성하고 초기 체스판의 상태를 출력한다. + - `end`를 입력받으면 프로그램을 종료한다. +- 기물 이동 여부를 입력받는다. + - `move b2 b4`와 같이 입력받으면 `b2`에 존재하는 기물을 `b4`로 이동시킨다. + - `end`를 입력받으면 프로그램을 종료한다. + +## 도메인별 기능 + +### Piece + +- [x] 출발지 & 목적지 정보를 토대로 기물이 이동할 수 있는지 검사한다. +- [x] 입력받은 색상을 토대로 자신의 적 여부를 반환한다. + +#### Rook + +- 상/하/좌/우 거리 제한 없이 이동 가능 +- 비었거나 적 기물이 존재하는 칸으로만 이동 가능 +- 이동 경로에 기물이 존재하면 이동 불가 + +#### Bishop + +- 대각선 방향 거리 제한 없이 이동 가능 +- 비었거나 적 기물이 존재하는 칸으로만 이동 가능 +- 이동 경로에 기물이 존재하면 이동 불가 + +#### Queen + +- 상/하/좌/우, 대각선 방향 거리 제한 없이 이동 가능 +- 비었거나 적 기물이 존재하는 칸으로만 이동 가능 +- 이동 경로에 기물이 존재하면 이동 불가 + +#### King + +- 상/하/좌/우, 대각선 방향 중 한 칸만 이동 가능 +- 비었거나 적 기물이 존재하는 칸으로만 이동 가능 + +#### Knight + +- 상/하/좌/우 중 한 칸 이동 후 전진 방향의 대각석으로 한 칸 이동 가능 +- 비었거나 적 기물이 존재하는 칸으로만 이동 가능 +- 이동 경로에 기물이 존재해도 이동 가능 + +#### Pawn + +- 특정 조건에 따라 이동 선택 + - 기본적으로 한 칸 전진 가능 + - 출발지가 초기 위치일 경우 두 칸 전진 가능 + - 전진 경로 혹은 목적지에 기물이 존재하면 이동 불가 + - 전진 방향 대각선 한 칸에 적 기물이 존재시 이동 가능 + +## Board + +- [x] 특정 색상의 기물 이동 + - `출발지` == `도착지`인 경우 예외 처리 + - 출발지에 기물이 존재하지 않으면 예외 처리 + - 상대 색상의 기물을 이동시키려 할 시 예외 처리 + +## ChessGame + +- [x] `흰색` 플레이어와 `검은색` 플레이어의 게임 순서 관리 및 `Board`에 기물 이동 요청 전달 diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000000..ff3accb42d4 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,12 @@ +import controller.GameController; +import domain.game.ChessGame; +import view.InputView; +import view.OutputView; + +public class Application { + public static void main(String[] args) { + ChessGame chessGame = ChessGame.initGame(); + GameController gameController = new GameController(new InputView(), new OutputView(), chessGame); + gameController.run(); + } +} diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java new file mode 100644 index 00000000000..605348a46cf --- /dev/null +++ b/src/main/java/controller/GameController.java @@ -0,0 +1,62 @@ +package controller; + +import domain.game.ChessGame; +import domain.game.GameCommand; +import domain.game.GameCommandType; +import dto.BoardDto; +import view.InputView; +import view.OutputView; + +public class GameController { + private final InputView inputView; + private final OutputView outputView; + private final ChessGame chessGame; + + public GameController(final InputView inputView, final OutputView outputView, final ChessGame chessGame) { + this.inputView = inputView; + this.outputView = outputView; + this.chessGame = chessGame; + } + + public void run() { + initGame(); + + if (chessGame.isGameRunning()) { + gameStart(); + } + } + + private void initGame() { + try { + GameCommand gameCommand = inputCommand(); + gameCommand.execute(chessGame); + } catch (Exception e) { + outputView.printErrorMessage(e.getMessage()); + initGame(); + } + } + + private GameCommand inputCommand() { + String[] inputValues = inputView.inputCommand().split(" "); + return GameCommandType.of(inputValues); + } + + private void gameStart() { + outputView.printWelcomeMessage(); + while (chessGame.isGameRunning()) { + BoardDto boardDto = BoardDto.from(chessGame.piecePositions()); + outputView.printBoard(boardDto); + playTurn(); + } + } + + private void playTurn() { + try { + GameCommand gameCommand = inputCommand(); + gameCommand.execute(chessGame); + } catch (Exception e) { + outputView.printErrorMessage(e.getMessage()); + playTurn(); + } + } +} diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java new file mode 100644 index 00000000000..fd6885878f9 --- /dev/null +++ b/src/main/java/domain/board/Board.java @@ -0,0 +1,52 @@ +package domain.board; + +import domain.piece.Piece; +import domain.piece.PieceColor; + +import java.util.Collections; +import java.util.Map; + +public class Board { + private final Map piecePositions; + + public Board(final Map piecePositions) { + this.piecePositions = piecePositions; + } + + public void movePiece(final PieceColor pieceColor, final Position source, final Position destination) { + validatePosition(pieceColor, source, destination); + + Piece targetPiece = piecePositions.get(source); + targetPiece.move(source, destination, this); + + piecePositions.put(destination, targetPiece); + piecePositions.remove(source); + } + + private void validatePosition(final PieceColor pieceColor, final Position source, final Position destination) { + if (source.equals(destination)) { + throw new IllegalArgumentException("출발지와 목적지가 같을 수 없습니다."); + } + + if (!piecePositions.containsKey(source)) { + throw new IllegalArgumentException("출발지에 기물이 존재하지 않습니다."); + } + + if (!piecePositions.get(source).isTeam(pieceColor)) { + throw new IllegalArgumentException("상대방의 기물을 이동시킬 수 없습니다."); + } + } + + public boolean existPiece(final Position position) { + return piecePositions.containsKey(position); + } + + public boolean existTeamColor(final Position position, final PieceColor teamColor) { + return piecePositions.get(position).isTeam(teamColor); + + } + + public Map piecePositions() { + return Collections.unmodifiableMap(piecePositions); + } +} diff --git a/src/main/java/domain/board/BoardInitializer.java b/src/main/java/domain/board/BoardInitializer.java new file mode 100644 index 00000000000..967329d6e62 --- /dev/null +++ b/src/main/java/domain/board/BoardInitializer.java @@ -0,0 +1,87 @@ +package domain.board; + +import domain.piece.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static domain.board.File.D; +import static domain.board.File.E; +import static domain.board.File.A; +import static domain.board.File.B; +import static domain.board.File.C; +import static domain.board.File.F; +import static domain.board.File.G; +import static domain.board.File.H; +import static domain.board.Rank.ONE; +import static domain.board.Rank.TWO; +import static domain.board.Rank.SEVEN; +import static domain.board.Rank.EIGHT; +import static domain.piece.PieceColor.BLACK; +import static domain.piece.PieceColor.WHITE; + +public class BoardInitializer { + public static Board initBoard() { + return new Board(new HashMap<>(initPieces())); + } + + private static Map initPieces() { + final Map piecePositions = new HashMap<>(); + initKing(piecePositions); + initQueen(piecePositions); + initBishop(piecePositions); + initKnight(piecePositions); + initRook(piecePositions); + initBlackPawns(piecePositions); + initWhitePawns(piecePositions); + + return piecePositions; + } + + private static void initKing(final Map piecePositions) { + piecePositions.put(position(E, ONE), new King(WHITE)); + piecePositions.put(position(E, EIGHT), new King(BLACK)); + } + + private static void initQueen(final Map piecePositions) { + piecePositions.put(position(D, ONE), new Queen(WHITE)); + piecePositions.put(position(D, EIGHT), new Queen(BLACK)); + } + + private static void initBishop(final Map piecePositions) { + piecePositions.put(position(C, ONE), new Bishop(WHITE)); + piecePositions.put(position(F, ONE), new Bishop(WHITE)); + piecePositions.put(position(C, EIGHT), new Bishop(BLACK)); + piecePositions.put(position(F, EIGHT), new Bishop(BLACK)); + } + + private static void initKnight(final Map piecePositions) { + piecePositions.put(position(B, ONE), new Knight(WHITE)); + piecePositions.put(position(G, ONE), new Knight(WHITE)); + piecePositions.put(position(B, EIGHT), new Knight(BLACK)); + piecePositions.put(position(G, EIGHT), new Knight(BLACK)); + } + + private static void initRook(final Map piecePositions) { + piecePositions.put(position(A, ONE), new Rook(WHITE)); + piecePositions.put(position(H, ONE), new Rook(WHITE)); + piecePositions.put(position(A, EIGHT), new Rook(BLACK)); + piecePositions.put(position(H, EIGHT), new Rook(BLACK)); + + } + + private static void initWhitePawns(final Map piecePositions) { + Arrays.stream(File.values()) + .forEach(file -> piecePositions.put(position(file, TWO), new Pawn(WHITE))); + } + + private static void initBlackPawns(final Map piecePositions) { + Arrays.stream(File.values()) + .forEach(file -> piecePositions.put(position(file, SEVEN), new Pawn(BLACK))); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/main/java/domain/board/File.java b/src/main/java/domain/board/File.java new file mode 100644 index 00000000000..f4504ddfb76 --- /dev/null +++ b/src/main/java/domain/board/File.java @@ -0,0 +1,38 @@ +package domain.board; + +import java.util.Arrays; + +public enum File { + A(0), + B(1), + C(2), + D(3), + E(4), + F(5), + G(6), + H(7); + + private final int index; + + File(final int index) { + this.index = index; + } + + public static File of(final String value) { + return Arrays.stream(values()) + .filter(file -> file.name().equals(value.toUpperCase())) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 File 값입니다.")); + } + + public static File of(final int index) { + return Arrays.stream(values()) + .filter(file -> file.getIndex() == index) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 인덱스입니다.")); + } + + public int getIndex() { + return this.index; + } +} diff --git a/src/main/java/domain/board/Position.java b/src/main/java/domain/board/Position.java new file mode 100644 index 00000000000..51b2270f99f --- /dev/null +++ b/src/main/java/domain/board/Position.java @@ -0,0 +1,40 @@ +package domain.board; + +import domain.piece.MovementDirection; + +public record Position(File file, Rank rank) { + private static final int POSITIONS_VALUES_SIZE = 2; + private static final int FILE_INDEX = 0; + private static final int RANK_INDEX = 1; + + public static Position of(final String value) { + String[] positionValues = value.split(""); + validatePositionValue(positionValues); + + File file = File.of(positionValues[FILE_INDEX]); + Rank rank = Rank.of(positionValues[RANK_INDEX]); + + return new Position(file, rank); + } + + private static void validatePositionValue(final String[] values) { + if (values.length != POSITIONS_VALUES_SIZE) { + throw new IllegalArgumentException("잘못된 위치값입니다."); + } + } + + public Position next(final MovementDirection movementDirection) { + File nextFile = File.of(this.columnIndex() + movementDirection.getColumnDistance()); + Rank nextRank = Rank.of(this.rowIndex() + movementDirection.getRowDistance()); + + return new Position(nextFile, nextRank); + } + + public int rowIndex() { + return rank.getIndex(); + } + + public int columnIndex() { + return file.getIndex(); + } +} diff --git a/src/main/java/domain/board/Rank.java b/src/main/java/domain/board/Rank.java new file mode 100644 index 00000000000..32697ddf3b7 --- /dev/null +++ b/src/main/java/domain/board/Rank.java @@ -0,0 +1,52 @@ +package domain.board; + +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 BASE_INDEX = 8; + + private final int value; + + Rank(final int value) { + this.value = value; + } + + public static Rank of(final String value) { + return Arrays.stream(Rank.values()) + .filter(rank -> rank.value() == parseNumber(value)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 Rank값입니다.")); + } + + private static int parseNumber(final String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Rank는 정수만 입력할 수 있습니다."); + } + } + + public static Rank of(final int index) { + return Arrays.stream(Rank.values()) + .filter(rank -> rank.getIndex() == index) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 인덱스입니다.")); + } + + public int value() { + return value; + } + + public int getIndex() { + return BASE_INDEX - value; + } +} diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java new file mode 100644 index 00000000000..837e3c9595e --- /dev/null +++ b/src/main/java/domain/game/ChessGame.java @@ -0,0 +1,47 @@ +package domain.game; + +import domain.board.Board; +import domain.board.BoardInitializer; +import domain.board.Position; +import domain.piece.Piece; +import domain.piece.PieceColor; + +import java.util.Map; + +import static domain.piece.PieceColor.WHITE; + +public class ChessGame { + private final Board board; + + private PieceColor currentColor = WHITE; + private boolean isGameRunning = false; + + private ChessGame(final Board board) { + this.board = board; + } + + public static ChessGame initGame() { + return new ChessGame(BoardInitializer.initBoard()); + } + + public void gameStart() { + isGameRunning = true; + } + + public void gameEnd() { + isGameRunning = false; + } + + public boolean isGameRunning() { + return isGameRunning; + } + + public Map piecePositions() { + return board.piecePositions(); + } + + public void movePiece(final Position source, final Position destination) { + board.movePiece(currentColor, source, destination); + currentColor = currentColor.toggle(); + } +} diff --git a/src/main/java/domain/game/End.java b/src/main/java/domain/game/End.java new file mode 100644 index 00000000000..0e381c82c82 --- /dev/null +++ b/src/main/java/domain/game/End.java @@ -0,0 +1,9 @@ +package domain.game; + +public class End implements GameCommand { + + @Override + public void execute(final ChessGame chessGame) { + chessGame.gameEnd(); + } +} diff --git a/src/main/java/domain/game/GameCommand.java b/src/main/java/domain/game/GameCommand.java new file mode 100644 index 00000000000..80c0ac6f9a2 --- /dev/null +++ b/src/main/java/domain/game/GameCommand.java @@ -0,0 +1,5 @@ +package domain.game; + +public interface GameCommand { + void execute(ChessGame chessGame); +} diff --git a/src/main/java/domain/game/GameCommandType.java b/src/main/java/domain/game/GameCommandType.java new file mode 100644 index 00000000000..f11cab99098 --- /dev/null +++ b/src/main/java/domain/game/GameCommandType.java @@ -0,0 +1,48 @@ +package domain.game; + +import domain.board.Position; + +import java.util.Arrays; +import java.util.function.Function; + +public enum GameCommandType { + START("start", command -> new Start()), + MOVE("move", GameCommandType::toMove), + END("end", command -> new End()); + + private static final int COMMAND_INDEX = 0; + private static final int BEFORE_POSITION = 1; + private static final int AFTER_POSITION = 2; + private static final int COMMAND_WITH_POSITIONS_SIZE = 3; + + private final String value; + private final Function gameCommand; + + GameCommandType(final String value, final Function gameCommand) { + this.value = value; + this.gameCommand = gameCommand; + } + + public static GameCommand of(final String[] values) { + return Arrays.stream(GameCommandType.values()) + .filter(it -> it.value.equalsIgnoreCase(values[COMMAND_INDEX])) + .map(it -> it.gameCommand.apply(values)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 명령어입니다!")); + } + + private static Move toMove(final String[] values) { + validatePositionValue(values); + + Position source = Position.of(values[BEFORE_POSITION]); + Position destination = Position.of(values[AFTER_POSITION]); + + return new Move(source, destination); + } + + private static void validatePositionValue(final String[] values) { + if (values.length != COMMAND_WITH_POSITIONS_SIZE) { + throw new IllegalArgumentException("잘못된 출발지/도착지입니다."); + } + } +} diff --git a/src/main/java/domain/game/Move.java b/src/main/java/domain/game/Move.java new file mode 100644 index 00000000000..754477df975 --- /dev/null +++ b/src/main/java/domain/game/Move.java @@ -0,0 +1,22 @@ +package domain.game; + +import domain.board.Position; + +public class Move implements GameCommand { + private final Position source; + private final Position destination; + + public Move(final Position source, final Position destination) { + this.source = source; + this.destination = destination; + } + + @Override + public void execute(final ChessGame chessGame) { + if (!chessGame.isGameRunning()) { + throw new IllegalArgumentException("아직 게임이 시작되지 않았습니다."); + } + + chessGame.movePiece(source, destination); + } +} diff --git a/src/main/java/domain/game/Start.java b/src/main/java/domain/game/Start.java new file mode 100644 index 00000000000..c895ce4142c --- /dev/null +++ b/src/main/java/domain/game/Start.java @@ -0,0 +1,13 @@ +package domain.game; + +public class Start implements GameCommand { + + @Override + public void execute(final ChessGame chessGame) { + if (chessGame.isGameRunning()) { + throw new IllegalArgumentException("이미 게임이 진행중입니다."); + } + + chessGame.gameStart(); + } +} diff --git a/src/main/java/domain/piece/Bishop.java b/src/main/java/domain/piece/Bishop.java new file mode 100644 index 00000000000..90893491c4e --- /dev/null +++ b/src/main/java/domain/piece/Bishop.java @@ -0,0 +1,54 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.Position; + +import java.util.List; +import java.util.stream.Stream; + +import static domain.piece.CommonMovementDirection.calculateDirection; +import static domain.piece.PieceType.BISHOP; + +public class Bishop extends Piece { + private static final PieceType PIECE_TYPE = BISHOP; + + public Bishop(final PieceColor color) { + super(color); + } + + @Override + public void move(final Position source, final Position destination, final Board board) { + CommonMovementDirection movementDirection = calculateDirection(source, destination); + movementDirection.checkBishopMovableMovement(); + + List movePaths = Stream.iterate(source, current -> current.next(movementDirection)) + .takeWhile(current -> current.equals(source) || isContinuable(current, destination, board)) + .toList(); + + Position alivePosition = movePaths.get(movePaths.size() - 1).next(movementDirection); + checkAlivePosition(alivePosition, board); + } + + private boolean isContinuable(final Position current, final Position destination, Board board) { + if (current.equals(destination)) { + return false; + } + + if (board.existPiece(current)) { + throw new IllegalArgumentException("목적지 경로에 기물이 존재하여 이동할 수 없습니다."); + } + + return true; + } + + private void checkAlivePosition(final Position alivePosition, final Board board) { + if (board.existPiece(alivePosition) && board.existTeamColor(alivePosition, color)) { + throw new IllegalArgumentException("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + } + + @Override + public PieceType pieceType() { + return PIECE_TYPE; + } +} diff --git a/src/main/java/domain/piece/CommonMovementDirection.java b/src/main/java/domain/piece/CommonMovementDirection.java new file mode 100644 index 00000000000..0ddb40fda33 --- /dev/null +++ b/src/main/java/domain/piece/CommonMovementDirection.java @@ -0,0 +1,74 @@ +package domain.piece; + +import domain.board.Position; + +import java.util.Arrays; +import java.util.List; +import java.util.function.BiPredicate; + +public enum CommonMovementDirection implements MovementDirection { + UP(-1, 0, (rowDistance, columnDistance) -> rowDistance < 0 && columnDistance == 0), + DOWN(1, 0, (rowDistance, columnDistance) -> rowDistance > 0 && columnDistance == 0), + RIGHT(0, 1, (rowDistance, columnDistance) -> rowDistance == 0 && columnDistance > 0), + LEFT(0, -1, (rowDistance, columnDistance) -> rowDistance == 0 && columnDistance < 0), + UP_RIGHT(-1, 1, (rowDistance, columnDistance) -> rowDistance < 0 && columnDistance > 0), + UP_LEFT(-1, -1, (rowDistance, columnDistance) -> rowDistance < 0 && columnDistance < 0), + DOWN_RIGHT(1, 1, (rowDistance, columnDistance) -> rowDistance > 0 && columnDistance > 0), + DOWN_LEFT(1, -1, (rowDistance, columnDistance) -> rowDistance > 0 && columnDistance < 0); + + private final int rowDistance; + private final int columnDistance; + private final BiPredicate condition; + + CommonMovementDirection(final int rowDistance, final int columnDistance, final BiPredicate condition) { + this.rowDistance = rowDistance; + this.columnDistance = columnDistance; + this.condition = condition; + } + + public static CommonMovementDirection calculateDirection(final Position source, final Position destination) { + final int rowDifference = destination.rowIndex() - source.rowIndex(); + final int columnDifference = destination.columnIndex() - source.columnIndex(); + + validateDistance(rowDifference, columnDifference); + + return Arrays.stream(CommonMovementDirection.values()) + .filter(unitVector -> unitVector.condition.test(rowDifference, columnDifference)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("상/하/좌/우 혹은 대각선으로 이동할 수 없는 칸입니다.")); + } + + private static void validateDistance(final int rowDistance, final int columnDistance) { + if (rowDistance == 0 && columnDistance == 0 + || (!(Math.abs(rowDistance) == Math.abs(columnDistance)) + && !(rowDistance == 0 || columnDistance == 0))) { + throw new IllegalArgumentException(("상/하/좌/우 혹은 대각선으로 이동할 수 없는 칸입니다.")); + } + } + + public void checkBishopMovableMovement() { + List bishopMovableMovement = List.of(UP_RIGHT, UP_LEFT, DOWN_RIGHT, DOWN_LEFT); + + if (!bishopMovableMovement.contains(this)) { + throw new IllegalArgumentException("비숍이 이동할 수 있는 방향이 아닙니다."); + } + } + + public void checkRookMovableMovement() { + List rookMovableMovement = List.of(UP, DOWN, RIGHT, LEFT); + + if (!rookMovableMovement.contains(this)) { + throw new IllegalArgumentException("룩이 이동할 수 있는 방향이 아닙니다."); + } + } + + @Override + public int getRowDistance() { + return rowDistance; + } + + @Override + public int getColumnDistance() { + return columnDistance; + } +} diff --git a/src/main/java/domain/piece/King.java b/src/main/java/domain/piece/King.java new file mode 100644 index 00000000000..8bf984368ba --- /dev/null +++ b/src/main/java/domain/piece/King.java @@ -0,0 +1,45 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.Position; + +import static domain.piece.PieceType.KING; +import static domain.piece.CommonMovementDirection.calculateDirection; + +public class King extends Piece { + private static final PieceType PIECE_TYPE = KING; + + public King(final PieceColor color) { + super(color); + } + + @Override + public void move(final Position source, final Position destination, final Board board) { + CommonMovementDirection movementDirection = calculateDirection(source, destination); + checkMoveDistance(source, destination, movementDirection); + + Position alivePosition = source.next(movementDirection); + + checkAlivePosition(alivePosition, board); + } + + private void checkMoveDistance(final Position source, final Position destination, final CommonMovementDirection movementDirection) { + int rowDistance = destination.rowIndex() - source.rowIndex(); + int columnDistance = destination.columnIndex() - source.columnIndex(); + + if (movementDirection.getRowDistance() != rowDistance || movementDirection.getColumnDistance() != columnDistance) { + throw new IllegalArgumentException("이동할 수 없는 거리입니다."); + } + } + + private void checkAlivePosition(final Position alivePosition, final Board board) { + if (board.existPiece(alivePosition) && board.existTeamColor(alivePosition, color)) { + throw new IllegalArgumentException("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + } + + @Override + public PieceType pieceType() { + return PIECE_TYPE; + } +} diff --git a/src/main/java/domain/piece/Knight.java b/src/main/java/domain/piece/Knight.java new file mode 100644 index 00000000000..18c92df1faa --- /dev/null +++ b/src/main/java/domain/piece/Knight.java @@ -0,0 +1,34 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.Position; + +import static domain.piece.PieceType.KNIGHT; +import static domain.piece.KnightMovementDirection.calculateDirection; + +public class Knight extends Piece { + private static final PieceType PIECE_TYPE = KNIGHT; + + public Knight(final PieceColor color) { + super(color); + } + + @Override + public void move(final Position source, final Position destination, final Board board) { + KnightMovementDirection movementDirection = calculateDirection(source, destination); + + Position alivePosition = source.next(movementDirection); + checkAlivePosition(alivePosition, board); + } + + private void checkAlivePosition(final Position alivePosition, final Board board) { + if (board.existPiece(alivePosition) && board.existTeamColor(alivePosition, color)) { + throw new IllegalArgumentException("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + } + + @Override + public PieceType pieceType() { + return PIECE_TYPE; + } +} diff --git a/src/main/java/domain/piece/KnightMovementDirection.java b/src/main/java/domain/piece/KnightMovementDirection.java new file mode 100644 index 00000000000..eda0dd7d451 --- /dev/null +++ b/src/main/java/domain/piece/KnightMovementDirection.java @@ -0,0 +1,62 @@ +package domain.piece; + +import domain.board.Position; + +import java.util.Arrays; + +public enum KnightMovementDirection implements MovementDirection { + UP_RIGHT(-2, 1), + UP_LEFT(-2, -1), + DOWN_RIGHT(2, 1), + DOWN_LEFT(2, -1), + RIGHT_UP(-1, 2), + RIGHT_DOWN(1, 2), + LEFT_UP(-1, -2), + LEFT_DOWN(1, -2); + + private final int rowDistance; + private final int columnDistance; + + KnightMovementDirection(final int rowDistance, final int columnDistance) { + this.rowDistance = rowDistance; + this.columnDistance = columnDistance; + } + + public static KnightMovementDirection calculateDirection(final Position source, final Position destination) { + final int rowDifference = destination.rowIndex() - source.rowIndex(); + final int columnDifference = destination.columnIndex() - source.columnIndex(); + + return Arrays.stream(values()) + .filter(knightMovementDirection -> knightMovementDirection.matchDistance(rowDifference, columnDifference)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("나이트가 이동할 수 없는 방향입니다.")); + } + + private boolean matchDistance(final int rowDistance, final int columnDistance) { + return this.rowDistance == rowDistance && this.columnDistance == columnDistance; + } + + @Override + public int getRowDistance() { + return rowDistance; + } + + @Override + public int getColumnDistance() { + return columnDistance; + } + + + // TODO : 레거시 + public static boolean isMovableDirection(final Position source, final Position destination) { + return Arrays.stream(values()) + .anyMatch(knightDirection -> knightDirection.find(source, destination)); + } + + private boolean find(final Position source, final Position destination) { + final int rowDistance = destination.rowIndex() - source.rowIndex(); + final int columnDistance = destination.columnIndex() - source.columnIndex(); + + return this.rowDistance == rowDistance && this.columnDistance == columnDistance; + } +} diff --git a/src/main/java/domain/piece/MovementDirection.java b/src/main/java/domain/piece/MovementDirection.java new file mode 100644 index 00000000000..e0413387989 --- /dev/null +++ b/src/main/java/domain/piece/MovementDirection.java @@ -0,0 +1,8 @@ +package domain.piece; + +public interface MovementDirection { + + int getRowDistance(); + + int getColumnDistance(); +} diff --git a/src/main/java/domain/piece/Pawn.java b/src/main/java/domain/piece/Pawn.java new file mode 100644 index 00000000000..31f4a61b9a0 --- /dev/null +++ b/src/main/java/domain/piece/Pawn.java @@ -0,0 +1,69 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.Position; + +import static domain.board.Rank.SEVEN; +import static domain.board.Rank.TWO; +import static domain.piece.PieceType.PAWN; +import static domain.piece.PawnMovementDirection.calculateDirection; +import static domain.piece.PieceColor.BLACK; +import static domain.piece.PieceColor.WHITE; + +public class Pawn extends Piece { + private static final PieceType PIECE_TYPE = PAWN; + + public Pawn(final PieceColor color) { + super(color); + } + + @Override + public void move(final Position source, final Position destination, final Board board) { + PawnMovementDirection movementDirection = calculateDirection(color, source, destination); + if (movementDirection.isCrossStep()) { + checkAlivePositionOfCrossStep(source.next(movementDirection), board); + return; + } + + if (movementDirection.isTwoStep()) { + checkIsStartPosition(source); + } + checkMovePaths(source, movementDirection, board); + } + + private void checkAlivePositionOfCrossStep(final Position alivePosition, final Board board) { + if (!board.existPiece(alivePosition) || board.existTeamColor(alivePosition, color)) { + throw new IllegalArgumentException("적 기물이 존재하지 않으면 대각선으로 이동할 수 없습니다"); + } + } + + private void checkIsStartPosition(final Position source) { + if ((color == WHITE && source.rank() != TWO) || (color == BLACK && source.rank() != SEVEN)) { + throw new IllegalArgumentException("시작 위치가 아니면 두 칸 이동할 수 없습니다."); + } + } + + private void checkMovePaths( + final Position source, + final PawnMovementDirection movementDirection, + final Board board + ) { + Position current = source; + int moveDistance = Math.abs(movementDirection.getRowDistance()); + for (int i = 0; i < moveDistance; i++) { + current = current.next(movementDirection.convertOneStep()); + checkPathHasPiece(current, board); + } + } + + private void checkPathHasPiece(final Position path, final Board board) { + if (board.existPiece(path)) { + throw new IllegalArgumentException("전진시 기물이 존재하는 경로 혹은 목적지로 이동할 수 없습니다."); + } + } + + @Override + public PieceType pieceType() { + return PIECE_TYPE; + } +} diff --git a/src/main/java/domain/piece/PawnMovementDirection.java b/src/main/java/domain/piece/PawnMovementDirection.java new file mode 100644 index 00000000000..d539720861c --- /dev/null +++ b/src/main/java/domain/piece/PawnMovementDirection.java @@ -0,0 +1,84 @@ +package domain.piece; + +import domain.board.Position; + +import java.util.Arrays; + +import static domain.piece.PieceColor.BLACK; +import static domain.piece.PieceColor.WHITE; + +public enum PawnMovementDirection implements MovementDirection { + UP_ONE_STEP(-1, 0, WHITE), + UP_TWO_STEP(-2, 0, WHITE), + UP_RIGHT(-1, 1, WHITE), + UP_LEFT(-1, -1, WHITE), + DOWN_ONE_STEP(1, 0, BLACK), + DOWN_TWO_STEP(2, 0, BLACK), + DOWN_RIGHT(1, 1, BLACK), + DOWN_LEFT(1, -1, BLACK); + + private final int rowDistance; + private final int columnDistance; + private final PieceColor pieceColor; + + PawnMovementDirection(final int rowDistance, final int columnDistance, final PieceColor pieceColor) { + this.rowDistance = rowDistance; + this.columnDistance = columnDistance; + this.pieceColor = pieceColor; + } + + public static PawnMovementDirection calculateDirection(final PieceColor pieceColor, final Position source, final Position destination) { + final int rowDifference = destination.rowIndex() - source.rowIndex(); + final int columnDifference = destination.columnIndex() - source.columnIndex(); + + return Arrays.stream(values()) + .filter(knightMovementDirection -> knightMovementDirection.matchDistance(pieceColor, rowDifference, columnDifference)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(pieceColor.name() + "색상의 폰이 이동할 수 없는 방향입니다.")); + } + + private boolean matchDistance(final PieceColor pieceColor, final int rowDistance, final int columnDistance) { + return this.pieceColor == pieceColor + && this.rowDistance == rowDistance + && this.columnDistance == columnDistance; + } + + public boolean isCrossStep() { + return this == UP_RIGHT || this == UP_LEFT + || this == DOWN_RIGHT || this == DOWN_LEFT; + } + + public boolean isTwoStep() { + return this == UP_TWO_STEP || this == DOWN_TWO_STEP; + } + + public PawnMovementDirection convertOneStep() { + if (!this.isTwoStep() && !this.isOneStep()) { + throw new IllegalArgumentException("전진 방향만 변환할 수 있습니다."); + } + + if (this == UP_TWO_STEP) { + return UP_ONE_STEP; + } + + if (this == DOWN_TWO_STEP) { + return DOWN_ONE_STEP; + } + + return this; + } + + private boolean isOneStep() { + return this == UP_ONE_STEP || this == DOWN_ONE_STEP; + } + + @Override + public int getRowDistance() { + return rowDistance; + } + + @Override + public int getColumnDistance() { + return columnDistance; + } +} diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java new file mode 100644 index 00000000000..00babe0fbc8 --- /dev/null +++ b/src/main/java/domain/piece/Piece.java @@ -0,0 +1,24 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.Position; + +public abstract class Piece { + protected final PieceColor color; + + public Piece(final PieceColor color) { + this.color = color; + } + + public abstract void move(final Position source, final Position destination, final Board board); + + public abstract PieceType pieceType(); + + public boolean isTeam(final PieceColor teamColor) { + return this.color == teamColor; + } + + public PieceColor pieceColor() { + return color; + } +} diff --git a/src/main/java/domain/piece/PieceColor.java b/src/main/java/domain/piece/PieceColor.java new file mode 100644 index 00000000000..d4cb0e67321 --- /dev/null +++ b/src/main/java/domain/piece/PieceColor.java @@ -0,0 +1,13 @@ +package domain.piece; + +public enum PieceColor { + BLACK, WHITE; + + public PieceColor toggle() { + if (this == BLACK) { + return WHITE; + } + + return BLACK; + } +} diff --git a/src/main/java/domain/piece/PieceType.java b/src/main/java/domain/piece/PieceType.java new file mode 100644 index 00000000000..3002a47b4e6 --- /dev/null +++ b/src/main/java/domain/piece/PieceType.java @@ -0,0 +1,10 @@ +package domain.piece; + +public enum PieceType { + PAWN, + ROOK, + KNIGHT, + BISHOP, + QUEEN, + KING +} diff --git a/src/main/java/domain/piece/Queen.java b/src/main/java/domain/piece/Queen.java new file mode 100644 index 00000000000..84af535703b --- /dev/null +++ b/src/main/java/domain/piece/Queen.java @@ -0,0 +1,53 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.Position; + +import java.util.List; +import java.util.stream.Stream; + +import static domain.piece.CommonMovementDirection.calculateDirection; +import static domain.piece.PieceType.QUEEN; + +public class Queen extends Piece { + private static final PieceType PIECE_TYPE = QUEEN; + + public Queen(final PieceColor color) { + super(color); + } + + @Override + public void move(final Position source, final Position destination, final Board board) { + CommonMovementDirection movementDirection = calculateDirection(source, destination); + + List movePaths = Stream.iterate(source, current -> current.next(movementDirection)) + .takeWhile(current -> current.equals(source) || isContinuable(current, destination, board)) + .toList(); + + Position alivePosition = movePaths.get(movePaths.size() - 1).next(movementDirection); + checkAlivePosition(alivePosition, board); + } + + private boolean isContinuable(final Position current, final Position destination, final Board board) { + if (current.equals(destination)) { + return false; + } + + if (board.existPiece(current)) { + throw new IllegalArgumentException("목적지 경로에 기물이 존재하여 이동할 수 없습니다."); + } + + return true; + } + + private void checkAlivePosition(final Position alivePosition, final Board board) { + if (board.existPiece(alivePosition) && board.existTeamColor(alivePosition, color)) { + throw new IllegalArgumentException("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + } + + @Override + public PieceType pieceType() { + return PIECE_TYPE; + } +} diff --git a/src/main/java/domain/piece/Rook.java b/src/main/java/domain/piece/Rook.java new file mode 100644 index 00000000000..6f5fbbf6e3a --- /dev/null +++ b/src/main/java/domain/piece/Rook.java @@ -0,0 +1,55 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.Position; + +import java.util.List; +import java.util.stream.Stream; + +import static domain.piece.CommonMovementDirection.calculateDirection; +import static domain.piece.PieceType.ROOK; + + +public class Rook extends Piece { + private static final PieceType PIECE_TYPE = ROOK; + + public Rook(final PieceColor color) { + super(color); + } + + @Override + public void move(final Position source, final Position destination, final Board board) { + CommonMovementDirection movementDirection = calculateDirection(source, destination); + movementDirection.checkRookMovableMovement(); + + List movePaths = Stream.iterate(source, current -> current.next(movementDirection)) + .takeWhile(current -> current.equals(source) || isContinuable(current, destination, board)) + .toList(); + + Position alivePosition = movePaths.get(movePaths.size() - 1).next(movementDirection); + checkAlivePosition(alivePosition, board); + } + + private boolean isContinuable(final Position current, final Position destination, final Board board) { + if (current.equals(destination)) { + return false; + } + + if (board.existPiece(current)) { + throw new IllegalArgumentException("목적지 경로에 기물이 존재하여 이동할 수 없습니다."); + } + + return true; + } + + private void checkAlivePosition(final Position alivePosition, final Board board) { + if (board.existPiece(alivePosition) && board.existTeamColor(alivePosition, color)) { + throw new IllegalArgumentException("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + } + + @Override + public PieceType pieceType() { + return PIECE_TYPE; + } +} diff --git a/src/main/java/dto/BoardDto.java b/src/main/java/dto/BoardDto.java new file mode 100644 index 00000000000..c41de62ca33 --- /dev/null +++ b/src/main/java/dto/BoardDto.java @@ -0,0 +1,19 @@ +package dto; + +import domain.board.Position; +import domain.piece.Piece; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public record BoardDto(Map value) { + + public static BoardDto from(Map piecePositions) { + Map value = new HashMap<>(); + piecePositions.forEach(((position, piece) -> + value.put(PositionDto.from(position), PieceDto.from(piece)))); + + return new BoardDto(Collections.unmodifiableMap(value)); + } +} diff --git a/src/main/java/dto/PieceDto.java b/src/main/java/dto/PieceDto.java new file mode 100644 index 00000000000..66a33e05564 --- /dev/null +++ b/src/main/java/dto/PieceDto.java @@ -0,0 +1,12 @@ +package dto; + +import domain.piece.PieceType; +import domain.piece.Piece; +import domain.piece.PieceColor; + +public record PieceDto(PieceType type, PieceColor color) { + + public static PieceDto from(final Piece piece) { + return new PieceDto(piece.pieceType(), piece.pieceColor()); + } +} diff --git a/src/main/java/dto/PositionDto.java b/src/main/java/dto/PositionDto.java new file mode 100644 index 00000000000..6f4eb347c09 --- /dev/null +++ b/src/main/java/dto/PositionDto.java @@ -0,0 +1,14 @@ +package dto; + +import domain.board.Position; + +public record PositionDto(int row, int column) { + + public static PositionDto from(final Position position) { + return new PositionDto(position.rowIndex(), position.columnIndex()); + } + + public static PositionDto emptyPosition() { + return new PositionDto(0, 0); + } +} diff --git a/src/main/java/othercase/End.java b/src/main/java/othercase/End.java new file mode 100644 index 00000000000..b4e6cb85eaa --- /dev/null +++ b/src/main/java/othercase/End.java @@ -0,0 +1,9 @@ +package othercase; + +public class End implements GameCommand { + + @Override + public void execute(final GameController gameController) { + gameController.endGame(); + } +} diff --git a/src/main/java/othercase/GameCommand.java b/src/main/java/othercase/GameCommand.java new file mode 100644 index 00000000000..3572688467c --- /dev/null +++ b/src/main/java/othercase/GameCommand.java @@ -0,0 +1,5 @@ +package othercase; + +public interface GameCommand { + void execute(GameController gameController); +} diff --git a/src/main/java/othercase/GameCommandType.java b/src/main/java/othercase/GameCommandType.java new file mode 100644 index 00000000000..3a0fef8b272 --- /dev/null +++ b/src/main/java/othercase/GameCommandType.java @@ -0,0 +1,48 @@ +package othercase; + +import domain.board.Position; + +import java.util.Arrays; +import java.util.function.Function; + +public enum GameCommandType { + START("start", command -> new Start()), + MOVE("move", GameCommandType::toMove), + END("end", command -> new End()); + + private static final int COMMAND_INDEX = 0; + private static final int BEFORE_POSITION = 1; + private static final int AFTER_POSITION = 2; + private static final int COMMAND_WITH_POSITIONS_SIZE = 3; + + private final String value; + private final Function gameCommand; + + GameCommandType(final String value, final Function gameCommand) { + this.value = value; + this.gameCommand = gameCommand; + } + + public static GameCommand of(final String[] values) { + return Arrays.stream(GameCommandType.values()) + .filter(it -> it.value.equalsIgnoreCase(values[COMMAND_INDEX])) + .map(it -> it.gameCommand.apply(values)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 명령어입니다!")); + } + + private static Move toMove(final String[] values) { + validatePositionValue(values); + + Position source = Position.of(values[BEFORE_POSITION]); + Position destination = Position.of(values[AFTER_POSITION]); + + return new Move(source, destination); + } + + private static void validatePositionValue(final String[] values) { + if (values.length != COMMAND_WITH_POSITIONS_SIZE) { + throw new IllegalArgumentException("잘못된 출발지/도착지입니다."); + } + } +} diff --git a/src/main/java/othercase/GameController.java b/src/main/java/othercase/GameController.java new file mode 100644 index 00000000000..3fe6d1070d9 --- /dev/null +++ b/src/main/java/othercase/GameController.java @@ -0,0 +1,80 @@ +package othercase; + +import domain.board.Position; +import domain.game.ChessGame; +import dto.BoardDto; +import view.InputView; +import view.OutputView; + +public class GameController { + private final InputView inputView; + private final OutputView outputView; + private final ChessGame chessGame; + + public GameController(final InputView inputView, final OutputView outputView, final ChessGame chessGame) { + this.inputView = inputView; + this.outputView = outputView; + this.chessGame = chessGame; + } + + public void run() { + try { + GameCommand gameCommand = inputCommand(); + gameCommand.execute(this); + } catch (Exception e) { + outputView.printErrorMessage(e.getMessage()); + run(); + } + } + + private GameCommand inputCommand() { + try { + String[] inputValues = inputView.inputCommand().split(" "); + return GameCommandType.of(inputValues); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + return inputCommand(); + } + } + + public void gameStart() { + if (isGameRunning()) { + throw new IllegalArgumentException("이미 게임이 진행중입니다."); + } + + chessGame.gameStart(); + outputView.printWelcomeMessage(); + + while (chessGame.isGameRunning()) { + BoardDto boardDto = BoardDto.from(chessGame.piecePositions()); + outputView.printBoard(boardDto); + playTurn(); + } + } + + private boolean isGameRunning() { + return chessGame.isGameRunning(); + } + + private void playTurn() { + try { + GameCommand gameCommand = inputCommand(); + gameCommand.execute(this); + } catch (Exception e) { + outputView.printErrorMessage(e.getMessage()); + playTurn(); + } + } + + public void movePiece(final Position source, final Position destination) { + if (!chessGame.isGameRunning()) { + throw new IllegalArgumentException("게임이 시작되지 않았습니다."); + } + + chessGame.movePiece(source, destination); + } + + public void endGame() { + chessGame.gameEnd(); + } +} diff --git a/src/main/java/othercase/Move.java b/src/main/java/othercase/Move.java new file mode 100644 index 00000000000..44c565bf99d --- /dev/null +++ b/src/main/java/othercase/Move.java @@ -0,0 +1,18 @@ +package othercase; + +import domain.board.Position; + +public class Move implements GameCommand { + private final Position source; + private final Position destination; + + public Move(final Position source, final Position destination) { + this.source = source; + this.destination = destination; + } + + @Override + public void execute(final GameController gameController) { + gameController.movePiece(source, destination); + } +} diff --git a/src/main/java/othercase/Start.java b/src/main/java/othercase/Start.java new file mode 100644 index 00000000000..47de1c23a48 --- /dev/null +++ b/src/main/java/othercase/Start.java @@ -0,0 +1,9 @@ +package othercase; + +public class Start implements GameCommand { + + @Override + public void execute(final GameController gameController) { + gameController.gameStart(); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000000..5ff300cb63c --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,20 @@ +package view; + +import java.util.Scanner; + +public class InputView { + private final Scanner sc = new Scanner(System.in); + + public String inputCommand() { + String input = sc.nextLine(); + validateEmpty(input); + + return input; + } + + private void validateEmpty(final String input) { + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("공백을 입력할 수 없습니다."); + } + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000000..5d3c3ab8436 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,79 @@ +package view; + +import domain.piece.PieceColor; +import domain.piece.PieceType; +import dto.BoardDto; +import dto.PieceDto; +import dto.PositionDto; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static domain.piece.PieceType.PAWN; +import static domain.piece.PieceType.ROOK; +import static domain.piece.PieceType.KNIGHT; +import static domain.piece.PieceType.BISHOP; +import static domain.piece.PieceType.QUEEN; +import static domain.piece.PieceType.KING; + +public class OutputView { + private static final Map pieceFormat = Map.ofEntries( + Map.entry(ROOK, "R"), + Map.entry(KNIGHT, "N"), + Map.entry(BISHOP, "B"), + Map.entry(QUEEN, "Q"), + Map.entry(KING, "K"), + Map.entry(PAWN, "P") + ); + + public void printErrorMessage(final String message) { + System.out.println(message); + System.out.println(); + } + + public void printWelcomeMessage() { + System.out.println("> 체스 게임을 시작합니다."); + System.out.println("> 게임 시작: start"); + System.out.println("> 게임 종료: end"); + System.out.println("> 게임 이동: move source위치 target위치 - 예. move b2 b3"); + } + + public void printBoard(final BoardDto boardDto) { + List boardStatus = convertBoardStatus(boardDto.value()); + boardStatus.forEach(System.out::println); + System.out.println(); + } + + private List convertBoardStatus(final Map boardStatus) { + String[][] boardMessage = initEmptyBoardMessage(); + boardStatus.forEach((position, piece) -> addPieceStatusInBoardMessage(boardMessage, position, piece)); + + return Arrays.stream(boardMessage) + .map(rowString -> String.join("", rowString)) + .toList(); + } + + private static String[][] initEmptyBoardMessage() { + String[][] strings = new String[8][8]; + for (String[] row : strings) { + Arrays.fill(row, "."); + } + + return strings; + } + + private void addPieceStatusInBoardMessage(final String[][] boardMessage, final PositionDto position, final PieceDto piece) { + boardMessage[position.row()][position.column()] = convertPieceTypeToString(piece.type(), piece.color()); + } + + private String convertPieceTypeToString(final PieceType pieceType, final PieceColor pieceColor) { + String pieceMessage = pieceFormat.get(pieceType); + + if (pieceColor == PieceColor.WHITE) { + return pieceMessage.toLowerCase(); + } + + return pieceMessage; + } +} diff --git a/src/test/java/domain/board/BoardTest.java b/src/test/java/domain/board/BoardTest.java new file mode 100644 index 00000000000..3afc8f424ab --- /dev/null +++ b/src/test/java/domain/board/BoardTest.java @@ -0,0 +1,107 @@ +package domain.board; + +import domain.piece.Pawn; +import domain.piece.Piece; +import domain.piece.Rook; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static domain.board.File.*; +import static domain.board.Rank.*; +import static domain.piece.PieceColor.BLACK; +import static domain.piece.PieceColor.WHITE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BoardTest { + + @DisplayName("기물을 이동시킨다.") + @Test + void movePieceTest() { + // Given + Position source = position(B, TWO); + Position destination = position(B, FOUR); + Map piecePositions = new HashMap<>(Map.of(source, new Pawn(WHITE))); + Board board = new Board(piecePositions); + + // When + board.movePiece(WHITE, source, destination); + boolean existsPieceOnSource = piecePositions.containsKey(source); + boolean existsPieceOnDestination = piecePositions.containsKey(destination); + + // Then + assertThat(existsPieceOnSource).isFalse(); + assertThat(existsPieceOnDestination).isTrue(); + } + + @DisplayName("목적지에 적 기물이 존재하면 적 기물을 제거하고 이동시킨다.") + @Test + void movePieceWithRemoveEnemyPieceTest() { + // Given + Position source = position(B, TWO); + Position destination = position(B, FOUR); + Rook myPiece = new Rook(WHITE); + Pawn enemyPiece = new Pawn(BLACK); + Map piecePositions = new HashMap<>(Map.of(source, myPiece, destination, enemyPiece)); + Board board = new Board(piecePositions); + + // When + board.movePiece(WHITE, source, destination); + boolean existsPieceOnSource = piecePositions.containsKey(source); + Piece destinationPiece = piecePositions.get(destination); + + // Then + assertThat(existsPieceOnSource).isFalse(); + assertThat(destinationPiece).isNotEqualTo(enemyPiece); + assertThat(destinationPiece).isEqualTo(myPiece); + } + + @DisplayName("입력된 출발지와 목적지의 위치가 같으면 예외를 발생시킨다.") + @Test + void throwExceptionWhenSourceEqualsDestinationTest() { + // Given + Board board = BoardInitializer.initBoard(); + Position source = new Position(B, TWO); + Position destination = new Position(B, TWO); + + // When & Then + assertThatThrownBy(() -> board.movePiece(WHITE, source, destination)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("출발지와 목적지가 같을 수 없습니다."); + } + + @DisplayName("기물이 존재하지 않는 출발지 위치가 입력되면 예외를 발생시킨다.") + @Test + void throwExceptionWhenNotExistPieceSourceTest() { + // Given + Board board = BoardInitializer.initBoard(); + Position source = new Position(D, FIVE); + Position destination = new Position(B, TWO); + + // When & Then + assertThatThrownBy(() -> board.movePiece(WHITE, source, destination)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("출발지에 기물이 존재하지 않습니다."); + } + + @DisplayName("상대방의 기물을 이동시키려고 하면 예외를 발생시킨다.") + @Test + void throwExceptionWhenMoveEnemyPieceTest() { + // Given + Board board = BoardInitializer.initBoard(); + Position source = new Position(B, SEVEN); + Position destination = new Position(B, SIX); + + // When & Then + assertThatThrownBy(() -> board.movePiece(WHITE, source, destination)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상대방의 기물을 이동시킬 수 없습니다."); + } + + private Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/piece/BishopTest.java b/src/test/java/domain/piece/BishopTest.java new file mode 100644 index 00000000000..eaa0094bc04 --- /dev/null +++ b/src/test/java/domain/piece/BishopTest.java @@ -0,0 +1,110 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.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.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BishopTest { + + @DisplayName("주어진 출발지 -> 도착지를 Bishop이 이동할 수 있는지 검증한다.") + @MethodSource("checkMovableTestCase") + @ParameterizedTest + void checkMovableTest(final Position source, final Position destination) { + // Given + Bishop bishop = new Bishop(PieceColor.WHITE); + Map piecePositions = Map.of(position(File.C, Rank.ONE), new Rook(PieceColor.BLACK)); + Board board = new Board(piecePositions); + + // When & Then + assertThatCode(() -> bishop.move(source, destination, board)) + .doesNotThrowAnyException(); + } + + private static Stream checkMovableTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.E, Rank.FIVE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.THREE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.ONE)), + Arguments.of(position(File.B, Rank.TWO), position(File.C, Rank.ONE)) + ); + } + + @DisplayName("Bishop이 이동할 수 없는 방향의 도착지가 입력되면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenInvalidDirectionTestCase") + @ParameterizedTest + void throwExceptionWhenInvalidDirectionTest(final Position source, final Position destination) { + // Given + Bishop bishop = new Bishop(PieceColor.WHITE); + Map piecePositions = Collections.emptyMap(); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() ->bishop.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("비숍이 이동할 수 있는 방향이 아닙니다."); + } + + private static Stream throwExceptionWhenInvalidDirectionTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.FIVE)), + Arguments.of(position(File.B, Rank.TWO), position(File.G, Rank.TWO)) + ); + } + + @DisplayName("이동 경로에 기물이 존재하면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenPathsHasPieceTestCase") + @ParameterizedTest + void throwExceptionWhenPathsHasPieceTest(final Position source, final Position destination) { + // Given + Bishop bishop = new Bishop(PieceColor.WHITE); + Map piecePositions = Map.of( + position(File.C, Rank.THREE), new Bishop(PieceColor.BLACK) + ); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> bishop.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("목적지 경로에 기물이 존재하여 이동할 수 없습니다."); + } + + private static Stream throwExceptionWhenPathsHasPieceTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.E, Rank.FIVE)), + Arguments.of(position(File.D, Rank.FOUR), position(File.B, Rank.TWO)) + ); + } + + @DisplayName("도착지에 아군 기물이 존재하면 예외를 발생시킨다.") + @Test + void throwExceptionWhenDestinationHasTeamPieceTest() { + // Given + Position source = position(File.B, Rank.TWO); + Position destination = position(File.D, Rank.FOUR); + Bishop bishop = new Bishop(PieceColor.WHITE); + Map piecePositions = Map.of(destination, new Bishop(PieceColor.WHITE)); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> bishop.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/piece/KingTest.java b/src/test/java/domain/piece/KingTest.java new file mode 100644 index 00000000000..654784165c2 --- /dev/null +++ b/src/test/java/domain/piece/KingTest.java @@ -0,0 +1,84 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.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.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class KingTest { + + @DisplayName("주어진 출발지 -> 도착지를 Rook이 이동할 수 있는지 검증한다.") + @MethodSource("checkMovableTestCase") + @ParameterizedTest + void checkMovableTest(final Position source, final Position destination) { + // Given + King king = new King(PieceColor.WHITE); + Map piecePositions = Map.of(position(File.D, Rank.TWO), new Rook(PieceColor.BLACK)); + Board board = new Board(piecePositions); + + // When & Then + assertThatCode(() -> king.move(source, destination, board)) + .doesNotThrowAnyException(); + } + + private static Stream checkMovableTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.THREE)), + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.ONE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.TWO)), + Arguments.of(position(File.B, Rank.TWO), position(File.C, Rank.TWO)), + Arguments.of(position(File.B, Rank.TWO), position(File.C, Rank.THREE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.THREE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.ONE)), + Arguments.of(position(File.B, Rank.TWO), position(File.C, Rank.ONE)) + ); + } + + @DisplayName("출발지에서 목적지까지 거리가 두 칸 이상이면 예외를 발생시킨다.") + @Test + void throwExceptionWhenDistanceOverOrEqualTwo() { + // Given + Position source = position(File.B, Rank.THREE); + Position destination = position(File.B, Rank.SIX); + King king = new King(PieceColor.WHITE); + Map piecePositions = Collections.emptyMap(); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> king.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이동할 수 없는 거리입니다."); + } + + @DisplayName("도착지에 아군 기물이 존재하면 예외를 발생시킨다.") + @Test + void throwExceptionWhenDestinationHasTeamPieceTest() { + // Given + Position source = position(File.B, Rank.TWO); + Position destination = position(File.B, Rank.THREE); + King king = new King(PieceColor.WHITE); + Map piecePositions = Map.of(destination, new Rook(PieceColor.WHITE)); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> king.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/piece/KnightTest.java b/src/test/java/domain/piece/KnightTest.java new file mode 100644 index 00000000000..0b5bd219f08 --- /dev/null +++ b/src/test/java/domain/piece/KnightTest.java @@ -0,0 +1,31 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class KnightTest { + + @DisplayName("도착지에 아군 기물이 존재하면 예외를 발생시킨다.") + @Test + void throwExceptionWhenDestinationHasTeamPieceTest() { + // Given + Position source = new Position(File.C, Rank.FOUR); + Position destination = new Position(File.D, Rank.SIX); + Knight knight = new Knight(PieceColor.WHITE); + Map piecePositions = Map.of(destination, new Rook(PieceColor.WHITE)); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> knight.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } +} diff --git a/src/test/java/domain/piece/PawnTest.java b/src/test/java/domain/piece/PawnTest.java new file mode 100644 index 00000000000..cb10e4144bc --- /dev/null +++ b/src/test/java/domain/piece/PawnTest.java @@ -0,0 +1,132 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import static domain.piece.PieceColor.BLACK; +import static domain.piece.PieceColor.WHITE; +import static domain.board.File.*; +import static domain.piece.PawnMovementDirection.*; +import static domain.board.Rank.*; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PawnTest { + + @DisplayName("주어진 출발지 -> 도착지를 Pawn이 이동할 수 있는지 검증한다.") + @MethodSource("checkMovableTestCase") + @ParameterizedTest + void checkMovableTest(final PieceColor pieceColor, final PieceColor enemyColor, final Position source, final Position destination) { + // Given + Pawn pawn = new Pawn(pieceColor); + Map piecePositions = Map.of( + position(E, SIX), new Rook(enemyColor), + position(C, SIX), new Rook(enemyColor), + position(E, FOUR), new Rook(enemyColor), + position(C, FOUR), new Rook(enemyColor) + ); + Board board = new Board(piecePositions); + + // When & Then + assertThatCode(() -> pawn.move(source, destination, board)) + .doesNotThrowAnyException(); + } + + private static Stream checkMovableTestCase() { + return Stream.of( + Arguments.of(WHITE, BLACK, position(F, TWO), position(F, FOUR), UP_TWO_STEP), + Arguments.of(WHITE, BLACK, position(C, FOUR), position(C, FIVE), UP_ONE_STEP), + Arguments.of(WHITE, BLACK, position(D, FIVE), position(E, SIX), UP_RIGHT), + Arguments.of(WHITE, BLACK, position(D, FIVE), position(C, SIX), UP_LEFT), + Arguments.of(BLACK, WHITE, position(G, SEVEN), position(G, FIVE), DOWN_TWO_STEP), + Arguments.of(BLACK, WHITE, position(C, FOUR), position(C, THREE), DOWN_ONE_STEP), + Arguments.of(BLACK, WHITE, position(D, FIVE), position(E, FOUR), DOWN_RIGHT), + Arguments.of(BLACK, WHITE, position(D, FIVE), position(C, FOUR), DOWN_LEFT) + ); + } + + @DisplayName("대각석으로 이동할 경우 목적지에 적 기물이 존재하지 않으면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenCrossStepDestinationNotHasEnemyTestCase") + @ParameterizedTest + void throwExceptionWhenCrossStepDestinationNotHasEnemyTest(final PieceColor pieceColor, final Position source, final Position destination) { + // Given + Pawn pawn = new Pawn(pieceColor); + Map piecePositions = Collections.emptyMap(); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> pawn.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("적 기물이 존재하지 않으면 대각선으로 이동할 수 없습니다"); + } + + private static Stream throwExceptionWhenCrossStepDestinationNotHasEnemyTestCase() { + return Stream.of( + Arguments.of(WHITE, position(D, FIVE), position(E, SIX), UP_RIGHT), + Arguments.of(WHITE, position(D, FIVE), position(C, SIX), UP_LEFT), + Arguments.of(BLACK, position(D, FIVE), position(E, FOUR), DOWN_RIGHT), + Arguments.of(BLACK, position(D, FIVE), position(C, FOUR), DOWN_LEFT) + ); + } + + @DisplayName("두 칸 전진할 경우 출발지가 폰의 시작 위치가 아니면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenOneStepSourceIsNotStartPositionTestCase") + @ParameterizedTest + void throwExceptionWhenOneStepSourceIsNotStartPositionTest(final PieceColor pieceColor, final Position source, final Position destination) { + // Given + Pawn pawn = new Pawn(pieceColor); + Map piecePositions = Collections.emptyMap(); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> pawn.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("시작 위치가 아니면 두 칸 이동할 수 없습니다."); + } + + private static Stream throwExceptionWhenOneStepSourceIsNotStartPositionTestCase() { + return Stream.of( + Arguments.of(WHITE, position(C, THREE), position(C, FIVE)), + Arguments.of(BLACK, position(C, FIVE), position(C, THREE)) + ); + } + + @DisplayName("폰이 전진할 시 경로에 기물이 존재하면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenForwardPathHasPieceTestCase") + @ParameterizedTest + void throwExceptionWhenForwardPathHasPieceTest(final PieceColor pieceColor, final Position source, final Position destination) { + // Given + Pawn pawn = new Pawn(pieceColor); + Map piecePositions = Map.of( + position(D, THREE), new Rook(WHITE), + position(C, THREE), new Rook(BLACK) + ); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> pawn.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("전진시 기물이 존재하는 경로 혹은 목적지로 이동할 수 없습니다."); + } + + private static Stream throwExceptionWhenForwardPathHasPieceTestCase() { + return Stream.of( + Arguments.of(WHITE, position(D, TWO), position(D, FOUR)), + Arguments.of(BLACK, position(C, FOUR), position(C, THREE)) + ); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/piece/QueenTest.java b/src/test/java/domain/piece/QueenTest.java new file mode 100644 index 00000000000..2d9c3f23116 --- /dev/null +++ b/src/test/java/domain/piece/QueenTest.java @@ -0,0 +1,92 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.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.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class QueenTest { + + @DisplayName("주어진 출발지 -> 도착지를 Rook이 이동할 수 있는지 검증한다.") + @MethodSource("checkMovableTestCase") + @ParameterizedTest + void checkMovableTest(final Position source, final Position destination) { + // Given + Queen queen = new Queen(PieceColor.WHITE); + Map piecePositions = Map.of(position(File.D, Rank.TWO), new Rook(PieceColor.BLACK)); + Board board = new Board(piecePositions); + + // When & Then + assertThatCode(() -> queen.move(source, destination, board)) + .doesNotThrowAnyException(); + } + + private static Stream checkMovableTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.SIX)), + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.ONE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.TWO)), + Arguments.of(position(File.B, Rank.TWO), position(File.D, Rank.TWO)), + Arguments.of(position(File.B, Rank.TWO), position(File.E, Rank.FIVE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.THREE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.ONE)), + Arguments.of(position(File.B, Rank.TWO), position(File.C, Rank.ONE)) + ); + } + + @DisplayName("이동 경로에 기물이 존재하면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenPathsHasPieceTestCase") + @ParameterizedTest + void throwExceptionWhenPathsHasPieceTest(final Position source, final Position destination) { + // Given + Queen queen = new Queen(PieceColor.WHITE); + Map piecePositions = Map.of( + position(File.B, Rank.FOUR), new Bishop(PieceColor.BLACK), + position(File.D, Rank.TWO), new Rook(PieceColor.WHITE) + ); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> queen.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("목적지 경로에 기물이 존재하여 이동할 수 없습니다."); + } + + private static Stream throwExceptionWhenPathsHasPieceTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.SIX)), + Arguments.of(position(File.B, Rank.TWO), position(File.G, Rank.TWO)) + ); + } + + @DisplayName("도착지에 아군 기물이 존재하면 예외를 발생시킨다.") + @Test + void throwExceptionWhenDestinationHasTeamPieceTest() { + // Given + Position source = position(File.B, Rank.TWO); + Position destination = position(File.B, Rank.SIX); + Queen queen = new Queen(PieceColor.WHITE); + Map piecePositions = Map.of(destination, new Rook(PieceColor.WHITE)); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> queen.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/piece/RookTest.java b/src/test/java/domain/piece/RookTest.java new file mode 100644 index 00000000000..cd934d4523b --- /dev/null +++ b/src/test/java/domain/piece/RookTest.java @@ -0,0 +1,111 @@ +package domain.piece; + +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.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.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RookTest { + + @DisplayName("주어진 출발지 -> 도착지를 Rook이 이동할 수 있는지 검증한다.") + @MethodSource("checkMovableTestCase") + @ParameterizedTest + void checkMovableTest(final Position source, final Position destination) { + // Given + Rook rook = new Rook(PieceColor.WHITE); + Map piecePositions = Map.of(position(File.D, Rank.TWO), new Rook(PieceColor.BLACK)); + Board board = new Board(piecePositions); + + // When & Then + assertThatCode(() -> rook.move(source, destination, board)) + .doesNotThrowAnyException(); + } + + private static Stream checkMovableTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.SIX)), + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.ONE)), + Arguments.of(position(File.B, Rank.TWO), position(File.A, Rank.TWO)), + Arguments.of(position(File.B, Rank.TWO), position(File.D, Rank.TWO)) + ); + } + + @DisplayName("Rook이 이동할 수 없는 방향의 도착지가 입력되면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenInvalidDirectionTestCase") + @ParameterizedTest + void throwExceptionWhenInvalidDirectionTest(final Position source, final Position destination) { + // Given + Rook rook = new Rook(PieceColor.WHITE); + Map piecePositions = Collections.emptyMap(); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() ->rook.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("룩이 이동할 수 있는 방향이 아닙니다."); + } + + private static Stream throwExceptionWhenInvalidDirectionTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.E, Rank.FIVE)), + Arguments.of(position(File.B, Rank.TWO), position(File.G, Rank.SEVEN)) + ); + } + + @DisplayName("이동 경로에 기물이 존재하면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenPathsHasPieceTestCase") + @ParameterizedTest + void throwExceptionWhenPathsHasPieceTest(final Position source, final Position destination) { + // Given + Rook rook = new Rook(PieceColor.WHITE); + Map piecePositions = Map.of( + position(File.B, Rank.FOUR), new Rook(PieceColor.BLACK), + position(File.D, Rank.TWO), new Rook(PieceColor.WHITE) + ); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> rook.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("목적지 경로에 기물이 존재하여 이동할 수 없습니다."); + } + + private static Stream throwExceptionWhenPathsHasPieceTestCase() { + return Stream.of( + Arguments.of(position(File.B, Rank.TWO), position(File.B, Rank.SIX)), + Arguments.of(position(File.B, Rank.TWO), position(File.G, Rank.TWO)) + ); + } + + @DisplayName("도착지에 아군 기물이 존재하면 예외를 발생시킨다.") + @Test + void throwExceptionWhenDestinationHasTeamPieceTest() { + // Given + Position source = position(File.B, Rank.TWO); + Position destination = position(File.B, Rank.SIX); + Rook rook = new Rook(PieceColor.WHITE); + Map piecePositions = Map.of(destination, new Rook(PieceColor.WHITE)); + Board board = new Board(piecePositions); + + // When & Then + assertThatThrownBy(() -> rook.move(source, destination, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("아군 기물이 위치한 칸으로는 이동할 수 없습니다."); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/position/CommonMovementDirectionTest.java b/src/test/java/domain/position/CommonMovementDirectionTest.java new file mode 100644 index 00000000000..5b3adb309cd --- /dev/null +++ b/src/test/java/domain/position/CommonMovementDirectionTest.java @@ -0,0 +1,67 @@ +package domain.position; + +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.piece.CommonMovementDirection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static domain.board.File.*; +import static domain.piece.CommonMovementDirection.*; +import static domain.board.Rank.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CommonMovementDirectionTest { + + @DisplayName("입력받은 출발지/목적지의 거리를 바탕으로 이동방향을 반환한다.") + @MethodSource("findMovementDirectionTestCase") + @ParameterizedTest + void findMovementDirection(final Position source, final Position destination, final CommonMovementDirection expect) { + // When + CommonMovementDirection commonMovementDirection = CommonMovementDirection.calculateDirection(source, destination); + + // Then + assertThat(commonMovementDirection).isEqualTo(expect); + } + + private static Stream findMovementDirectionTestCase() { + return Stream.of( + Arguments.of(position(B, TWO), position(B, SIX), UP), + Arguments.of(position(B, FIVE), position(B, TWO), DOWN), + Arguments.of(position(B, TWO), position(G, TWO), RIGHT), + Arguments.of(position(E, FIVE), position(B, FIVE), LEFT), + Arguments.of(position(C, TWO), position(E, FOUR), UP_RIGHT), + Arguments.of(position(F, TWO), position(C, FIVE), UP_LEFT), + Arguments.of(position(D, FOUR), position(F, TWO), DOWN_RIGHT), + Arguments.of(position(E, FOUR), position(C, TWO), DOWN_LEFT) + ); + } + + @DisplayName("방향 계산이 불가능한 출발지/목적지 위치 정보가 입력되면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenInvalidSourceAndDestinationTestCase") + @ParameterizedTest + void throwExceptionWhenInvalidSourceAndDestination(final Position source, final Position destination) { + // When & THen + assertThatThrownBy(() -> CommonMovementDirection.calculateDirection(source, destination)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상/하/좌/우 혹은 대각선으로 이동할 수 없는 칸입니다."); + } + + private static Stream throwExceptionWhenInvalidSourceAndDestinationTestCase() { + return Stream.of( + Arguments.of(position(B, TWO), position(B, TWO)), + Arguments.of(position(B, ONE), position(C, FOUR)), + Arguments.of(position(A, EIGHT), position(G, ONE)) + ); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/position/FileTest.java b/src/test/java/domain/position/FileTest.java new file mode 100644 index 00000000000..56b8f21370f --- /dev/null +++ b/src/test/java/domain/position/FileTest.java @@ -0,0 +1,36 @@ +package domain.position; + +import domain.board.File; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class FileTest { + + @DisplayName("인덱스를 입력하면 해당하는 File을 반환한다.") + @Test + void getFileTest() { + // Given + final int index = 2; + + // When + File file = File.of(index); + + // Then + assertThat(file).isEqualTo(File.C); + } + + @DisplayName("유효하지 않은 인덱스를 입력하면 예외를 발생시킨다.") + @Test + void throwExceptionWhenInputInvalidIndex() { + // Given + final int index = 13; + + // When & Then + assertThatThrownBy(() -> File.of(index)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효하지 않은 인덱스입니다."); + } +} diff --git a/src/test/java/domain/position/KnightMovementDirectionTest.java b/src/test/java/domain/position/KnightMovementDirectionTest.java new file mode 100644 index 00000000000..c6586bf5697 --- /dev/null +++ b/src/test/java/domain/position/KnightMovementDirectionTest.java @@ -0,0 +1,75 @@ +package domain.position; + +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.piece.KnightMovementDirection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static domain.board.File.*; +import static domain.piece.KnightMovementDirection.*; +import static domain.board.Rank.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class KnightMovementDirectionTest { + + @DisplayName("입력된 출발지 & 목적지를 계산해서 나이트의 이동 방향을 반환한다.") + @MethodSource("calculateDirectionTestCase") + @ParameterizedTest + void calculateDirectionTest(final Position source, final Position destination, final KnightMovementDirection expect) { + // When + KnightMovementDirection knightMovementDirection = calculateDirection(source, destination); + + // Then + assertThat(knightMovementDirection).isEqualTo(expect); + } + + private static Stream calculateDirectionTestCase() { + return Stream.of( + Arguments.of(position(C, FOUR), position(D, SIX), UP_RIGHT), + Arguments.of(position(C, FOUR), position(B, SIX), UP_LEFT), + Arguments.of(position(D, FIVE), position(E, THREE), DOWN_RIGHT), + Arguments.of(position(D, FIVE), position(C, THREE), DOWN_LEFT), + Arguments.of(position(C, FOUR), position(E, FIVE), RIGHT_UP), + Arguments.of(position(C, FOUR), position(E, THREE), RIGHT_DOWN), + Arguments.of(position(E, FOUR), position(C, FIVE), LEFT_UP), + Arguments.of(position(E, FOUR), position(C, THREE), LEFT_DOWN) + ); + } + + @DisplayName("나이트가 이동할 수 없는 출발지/도착지 위치 정보가 입력되면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenInvalidLocationTestCase") + @ParameterizedTest + void throwExceptionWhenInvalidLocationTest(final Position source, final Position destination) { + // When & Then + assertThatThrownBy(() -> KnightMovementDirection.calculateDirection(source, destination)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("나이트가 이동할 수 없는 방향입니다."); + } + + private static Stream throwExceptionWhenInvalidLocationTestCase() { + return Stream.of( + Arguments.of(position(B, TWO), position(B, SIX)), + Arguments.of(position(B, FIVE), position(B, TWO)), + Arguments.of(position(B, TWO), position(G, TWO)), + Arguments.of(position(E, FIVE), position(B, FIVE)), + Arguments.of(position(C, TWO), position(E, FOUR)), + Arguments.of(position(F, TWO), position(C, FIVE)), + Arguments.of(position(D, FOUR), position(F, TWO)), + Arguments.of(position(E, FOUR), position(C, TWO)), + Arguments.of(position(B, TWO), position(B, TWO)), + Arguments.of(position(B, ONE), position(C, FOUR)), + Arguments.of(position(A, EIGHT), position(G, ONE)) + ); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/position/PawnMovementDirectionTest.java b/src/test/java/domain/position/PawnMovementDirectionTest.java new file mode 100644 index 00000000000..b9d8893ddcb --- /dev/null +++ b/src/test/java/domain/position/PawnMovementDirectionTest.java @@ -0,0 +1,81 @@ +package domain.position; + +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.piece.PawnMovementDirection; +import domain.piece.PieceColor; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static domain.piece.PieceColor.*; +import static domain.board.File.*; +import static domain.piece.PawnMovementDirection.*; +import static domain.board.Rank.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PawnMovementDirectionTest { + + @DisplayName("입력된 출발지 & 목적지를 계산해서 폰의 이동 방향을 반환한다.") + @MethodSource("calculateDirectionTestCase") + @ParameterizedTest + void calculateDirectionTest(final PieceColor pieceColor, final Position source, final Position destination, final PawnMovementDirection expect) { + // When + PawnMovementDirection movementDirection = calculateDirection(pieceColor, source, destination); + + // Then + assertThat(movementDirection).isEqualTo(expect); + } + + private static Stream calculateDirectionTestCase() { + return Stream.of( + Arguments.of(WHITE, position(C, TWO), position(C, FOUR), UP_TWO_STEP), + Arguments.of(WHITE, position(C, FOUR), position(C, FIVE), UP_ONE_STEP), + Arguments.of(WHITE, position(D, FIVE), position(E, SIX), UP_RIGHT), + Arguments.of(WHITE, position(D, FIVE), position(C, SIX), UP_LEFT), + Arguments.of(BLACK, position(C, SEVEN), position(C, FIVE), DOWN_TWO_STEP), + Arguments.of(BLACK, position(C, FOUR), position(C, THREE), DOWN_ONE_STEP), + Arguments.of(BLACK, position(D, FIVE), position(E, FOUR), DOWN_RIGHT), + Arguments.of(BLACK, position(D, FIVE), position(C, FOUR), DOWN_LEFT) + ); + } + + @DisplayName("색상별 방향을 계산할 수 없는 춮발지 & 목적지가 입력되면 예외를 발생시킨다.") + @MethodSource("throwExceptionWhenInputInvalidPositionTestCase") + @ParameterizedTest + void throwExceptionWhenInputInvalidPositionTest(final PieceColor pieceColor, final Position source, final Position destination) { + // When & Then + assertThatThrownBy(() -> PawnMovementDirection.calculateDirection(pieceColor, source, destination)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(pieceColor.name() + "색상의 폰이 이동할 수 없는 방향입니다."); + } + + private static Stream throwExceptionWhenInputInvalidPositionTestCase() { + return Stream.of( + Arguments.of(BLACK, position(C, TWO), position(C, FOUR)), + Arguments.of(BLACK, position(C, FOUR), position(C, FIVE)), + Arguments.of(BLACK, position(D, FIVE), position(E, SIX)), + Arguments.of(BLACK, position(D, FIVE), position(C, SIX)), + Arguments.of(WHITE, position(C, SEVEN), position(C, FIVE)), + Arguments.of(WHITE, position(C, FOUR), position(C, THREE)), + Arguments.of(WHITE, position(D, FIVE), position(E, FOUR)), + Arguments.of(WHITE, position(D, FIVE), position(C, FOUR)), + Arguments.of(WHITE, position(B, TWO), position(B, SIX)), + Arguments.of(WHITE, position(B, TWO), position(B, ONE)), + Arguments.of(WHITE, position(B, TWO), position(A, TWO)), + Arguments.of(WHITE, position(B, TWO), position(C, ONE)), + Arguments.of(BLACK, position(B, TWO), position(D, TWO)), + Arguments.of(BLACK, position(B, TWO), position(E, FIVE)), + Arguments.of(BLACK, position(B, TWO), position(A, THREE)) + ); + } + + private static Position position(final File file, final Rank rank) { + return new Position(file, rank); + } +} diff --git a/src/test/java/domain/position/PositionTest.java b/src/test/java/domain/position/PositionTest.java new file mode 100644 index 00000000000..121870d3032 --- /dev/null +++ b/src/test/java/domain/position/PositionTest.java @@ -0,0 +1,41 @@ +package domain.position; + +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.piece.CommonMovementDirection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PositionTest { + @DisplayName("행과 열 정보를 가진 Position 인스턴스를 생성한다.") + @Test + void createPositionTest() { + // Given + File file = File.A; + Rank rank = Rank.ONE; + + // When + Position position = new Position(file, rank); + + // Then + assertThat(position.rank()).isEqualTo(rank); + assertThat(position.file()).isEqualTo(file); + } + + @DisplayName("이동 방향을 전달하면 새로운 위치의 Position을 반환한다.") + @Test + void addPositionTest() { + // Given + Position position = new Position(File.D, Rank.TWO); + + // When + Position newPosition = position.next(CommonMovementDirection.UP_RIGHT); + + // Then + assertThat(newPosition.file()).isEqualTo(File.E); + assertThat(newPosition.rank()).isEqualTo(Rank.THREE); + } +} diff --git a/src/test/java/domain/position/RankTest.java b/src/test/java/domain/position/RankTest.java new file mode 100644 index 00000000000..6411e9401c9 --- /dev/null +++ b/src/test/java/domain/position/RankTest.java @@ -0,0 +1,36 @@ +package domain.position; + +import domain.board.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RankTest { + + @DisplayName("인덱스를 입력하면 해당하는 Rank를 반환한다.") + @Test + void getFileTest() { + // Given + final int index = 2; + + // When + Rank file = Rank.of(index); + + // Then + assertThat(file).isEqualTo(Rank.SIX); + } + + @DisplayName("유효하지 않은 인덱스를 입력하면 예외를 발생시킨다.") + @Test + void throwExceptionWhenInputInvalidIndex() { + // Given + final int index = 13; + + // When & Then + assertThatThrownBy(() -> Rank.of(index)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효하지 않은 인덱스입니다."); + } +} From 7367e538c19f3b6332c356943895152ea42acf50 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Tue, 26 Mar 2024 21:45:03 +0900 Subject: [PATCH 02/18] =?UTF-8?q?refactor:=20ChessGame=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A5=BC=20=EB=B3=84=EB=8F=84=20enum=EC=9C=BC?= =?UTF-8?q?=EB=A1=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/controller/GameController.java | 4 +- src/main/java/domain/board/Board.java | 7 + src/main/java/domain/game/ChessGame.java | 18 ++- src/main/java/domain/game/GameStatus.java | 5 + src/main/java/domain/game/Move.java | 2 +- src/main/java/domain/game/Start.java | 2 +- src/main/java/othercase/End.java | 18 +-- src/main/java/othercase/GameCommand.java | 10 +- src/main/java/othercase/GameCommandType.java | 96 +++++------ src/main/java/othercase/GameController.java | 160 +++++++++---------- src/main/java/othercase/Move.java | 36 ++--- src/main/java/othercase/Start.java | 18 +-- 12 files changed, 199 insertions(+), 177 deletions(-) create mode 100644 src/main/java/domain/game/GameStatus.java diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index 605348a46cf..070a55578cc 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -21,7 +21,7 @@ public GameController(final InputView inputView, final OutputView outputView, fi public void run() { initGame(); - if (chessGame.isGameRunning()) { + if (chessGame.isRunning()) { gameStart(); } } @@ -43,7 +43,7 @@ private GameCommand inputCommand() { private void gameStart() { outputView.printWelcomeMessage(); - while (chessGame.isGameRunning()) { + while (chessGame.isRunning()) { BoardDto boardDto = BoardDto.from(chessGame.piecePositions()); outputView.printBoard(boardDto); playTurn(); diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java index fd6885878f9..052d36e823f 100644 --- a/src/main/java/domain/board/Board.java +++ b/src/main/java/domain/board/Board.java @@ -2,6 +2,7 @@ import domain.piece.Piece; import domain.piece.PieceColor; +import domain.piece.PieceType; import java.util.Collections; import java.util.Map; @@ -49,4 +50,10 @@ public boolean existTeamColor(final Position position, final PieceColor teamColo public Map piecePositions() { return Collections.unmodifiableMap(piecePositions); } + + public boolean isKingAlive(final PieceColor targetColor) { + return piecePositions.values() + .stream() + .anyMatch(piece -> piece.isTeam(targetColor) && piece.pieceType() == PieceType.KING); + } } diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index 837e3c9595e..0bd133c8740 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -12,16 +12,18 @@ public class ChessGame { private final Board board; + private final GameStatus gameStatus; private PieceColor currentColor = WHITE; private boolean isGameRunning = false; - private ChessGame(final Board board) { + public ChessGame(final Board board, final GameStatus gameStatus) { this.board = board; + this.gameStatus = gameStatus; } public static ChessGame initGame() { - return new ChessGame(BoardInitializer.initBoard()); + return new ChessGame(BoardInitializer.initBoard(), GameStatus.WAITING); } public void gameStart() { @@ -32,8 +34,16 @@ public void gameEnd() { isGameRunning = false; } - public boolean isGameRunning() { - return isGameRunning; + public boolean isWaiting() { + return gameStatus == GameStatus.WAITING; + } + + public boolean isRunning() { + return gameStatus == GameStatus.RUNNING; + } + + public boolean isEnd() { + return gameStatus == GameStatus.END; } public Map piecePositions() { diff --git a/src/main/java/domain/game/GameStatus.java b/src/main/java/domain/game/GameStatus.java new file mode 100644 index 00000000000..97b3de94418 --- /dev/null +++ b/src/main/java/domain/game/GameStatus.java @@ -0,0 +1,5 @@ +package domain.game; + +public enum GameStatus { + WAITING, RUNNING, END; +} diff --git a/src/main/java/domain/game/Move.java b/src/main/java/domain/game/Move.java index 754477df975..f4f394eddba 100644 --- a/src/main/java/domain/game/Move.java +++ b/src/main/java/domain/game/Move.java @@ -13,7 +13,7 @@ public Move(final Position source, final Position destination) { @Override public void execute(final ChessGame chessGame) { - if (!chessGame.isGameRunning()) { + if (!chessGame.isRunning()) { throw new IllegalArgumentException("아직 게임이 시작되지 않았습니다."); } diff --git a/src/main/java/domain/game/Start.java b/src/main/java/domain/game/Start.java index c895ce4142c..ba3445f5850 100644 --- a/src/main/java/domain/game/Start.java +++ b/src/main/java/domain/game/Start.java @@ -4,7 +4,7 @@ public class Start implements GameCommand { @Override public void execute(final ChessGame chessGame) { - if (chessGame.isGameRunning()) { + if (!chessGame.isWaiting()) { throw new IllegalArgumentException("이미 게임이 진행중입니다."); } diff --git a/src/main/java/othercase/End.java b/src/main/java/othercase/End.java index b4e6cb85eaa..2214b817495 100644 --- a/src/main/java/othercase/End.java +++ b/src/main/java/othercase/End.java @@ -1,9 +1,9 @@ -package othercase; - -public class End implements GameCommand { - - @Override - public void execute(final GameController gameController) { - gameController.endGame(); - } -} +//package othercase; +// +//public class End implements GameCommand { +// +// @Override +// public void execute(final GameController gameController) { +// gameController.endGame(); +// } +//} diff --git a/src/main/java/othercase/GameCommand.java b/src/main/java/othercase/GameCommand.java index 3572688467c..6195b3b3291 100644 --- a/src/main/java/othercase/GameCommand.java +++ b/src/main/java/othercase/GameCommand.java @@ -1,5 +1,5 @@ -package othercase; - -public interface GameCommand { - void execute(GameController gameController); -} +//package othercase; +// +//public interface GameCommand { +// void execute(GameController gameController); +//} diff --git a/src/main/java/othercase/GameCommandType.java b/src/main/java/othercase/GameCommandType.java index 3a0fef8b272..3a48b2d95ef 100644 --- a/src/main/java/othercase/GameCommandType.java +++ b/src/main/java/othercase/GameCommandType.java @@ -1,48 +1,48 @@ -package othercase; - -import domain.board.Position; - -import java.util.Arrays; -import java.util.function.Function; - -public enum GameCommandType { - START("start", command -> new Start()), - MOVE("move", GameCommandType::toMove), - END("end", command -> new End()); - - private static final int COMMAND_INDEX = 0; - private static final int BEFORE_POSITION = 1; - private static final int AFTER_POSITION = 2; - private static final int COMMAND_WITH_POSITIONS_SIZE = 3; - - private final String value; - private final Function gameCommand; - - GameCommandType(final String value, final Function gameCommand) { - this.value = value; - this.gameCommand = gameCommand; - } - - public static GameCommand of(final String[] values) { - return Arrays.stream(GameCommandType.values()) - .filter(it -> it.value.equalsIgnoreCase(values[COMMAND_INDEX])) - .map(it -> it.gameCommand.apply(values)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 명령어입니다!")); - } - - private static Move toMove(final String[] values) { - validatePositionValue(values); - - Position source = Position.of(values[BEFORE_POSITION]); - Position destination = Position.of(values[AFTER_POSITION]); - - return new Move(source, destination); - } - - private static void validatePositionValue(final String[] values) { - if (values.length != COMMAND_WITH_POSITIONS_SIZE) { - throw new IllegalArgumentException("잘못된 출발지/도착지입니다."); - } - } -} +//package othercase; +// +//import domain.board.Position; +// +//import java.util.Arrays; +//import java.util.function.Function; +// +//public enum GameCommandType { +// START("start", command -> new Start()), +// MOVE("move", GameCommandType::toMove), +// END("end", command -> new End()); +// +// private static final int COMMAND_INDEX = 0; +// private static final int BEFORE_POSITION = 1; +// private static final int AFTER_POSITION = 2; +// private static final int COMMAND_WITH_POSITIONS_SIZE = 3; +// +// private final String value; +// private final Function gameCommand; +// +// GameCommandType(final String value, final Function gameCommand) { +// this.value = value; +// this.gameCommand = gameCommand; +// } +// +// public static GameCommand of(final String[] values) { +// return Arrays.stream(GameCommandType.values()) +// .filter(it -> it.value.equalsIgnoreCase(values[COMMAND_INDEX])) +// .map(it -> it.gameCommand.apply(values)) +// .findAny() +// .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 명령어입니다!")); +// } +// +// private static Move toMove(final String[] values) { +// validatePositionValue(values); +// +// Position source = Position.of(values[BEFORE_POSITION]); +// Position destination = Position.of(values[AFTER_POSITION]); +// +// return new Move(source, destination); +// } +// +// private static void validatePositionValue(final String[] values) { +// if (values.length != COMMAND_WITH_POSITIONS_SIZE) { +// throw new IllegalArgumentException("잘못된 출발지/도착지입니다."); +// } +// } +//} diff --git a/src/main/java/othercase/GameController.java b/src/main/java/othercase/GameController.java index 3fe6d1070d9..e7f31a1a962 100644 --- a/src/main/java/othercase/GameController.java +++ b/src/main/java/othercase/GameController.java @@ -1,80 +1,80 @@ -package othercase; - -import domain.board.Position; -import domain.game.ChessGame; -import dto.BoardDto; -import view.InputView; -import view.OutputView; - -public class GameController { - private final InputView inputView; - private final OutputView outputView; - private final ChessGame chessGame; - - public GameController(final InputView inputView, final OutputView outputView, final ChessGame chessGame) { - this.inputView = inputView; - this.outputView = outputView; - this.chessGame = chessGame; - } - - public void run() { - try { - GameCommand gameCommand = inputCommand(); - gameCommand.execute(this); - } catch (Exception e) { - outputView.printErrorMessage(e.getMessage()); - run(); - } - } - - private GameCommand inputCommand() { - try { - String[] inputValues = inputView.inputCommand().split(" "); - return GameCommandType.of(inputValues); - } catch (IllegalArgumentException e) { - outputView.printErrorMessage(e.getMessage()); - return inputCommand(); - } - } - - public void gameStart() { - if (isGameRunning()) { - throw new IllegalArgumentException("이미 게임이 진행중입니다."); - } - - chessGame.gameStart(); - outputView.printWelcomeMessage(); - - while (chessGame.isGameRunning()) { - BoardDto boardDto = BoardDto.from(chessGame.piecePositions()); - outputView.printBoard(boardDto); - playTurn(); - } - } - - private boolean isGameRunning() { - return chessGame.isGameRunning(); - } - - private void playTurn() { - try { - GameCommand gameCommand = inputCommand(); - gameCommand.execute(this); - } catch (Exception e) { - outputView.printErrorMessage(e.getMessage()); - playTurn(); - } - } - - public void movePiece(final Position source, final Position destination) { - if (!chessGame.isGameRunning()) { - throw new IllegalArgumentException("게임이 시작되지 않았습니다."); - } - - chessGame.movePiece(source, destination); - } - - public void endGame() { - chessGame.gameEnd(); - } -} +//package othercase; +// +//import domain.board.Position; +//import domain.game.ChessGame; +//import dto.BoardDto; +//import view.InputView; +//import view.OutputView; +// +//public class GameController { +// private final InputView inputView; +// private final OutputView outputView; +// private final ChessGame chessGame; +// +// public GameController(final InputView inputView, final OutputView outputView, final ChessGame chessGame) { +// this.inputView = inputView; +// this.outputView = outputView; +// this.chessGame = chessGame; +// } +// +// public void run() { +// try { +// GameCommand gameCommand = inputCommand(); +// gameCommand.execute(this); +// } catch (Exception e) { +// outputView.printErrorMessage(e.getMessage()); +// run(); +// } +// } +// +// private GameCommand inputCommand() { +// try { +// String[] inputValues = inputView.inputCommand().split(" "); +// return GameCommandType.of(inputValues); +// } catch (IllegalArgumentException e) { +// outputView.printErrorMessage(e.getMessage()); +// return inputCommand(); +// } +// } +// +// public void gameStart() { +// if (isGameRunning()) { +// throw new IllegalArgumentException("이미 게임이 진행중입니다."); +// } +// +// chessGame.gameStart(); +// outputView.printWelcomeMessage(); +// +// while (chessGame.isRunning()) { +// BoardDto boardDto = BoardDto.from(chessGame.piecePositions()); +// outputView.printBoard(boardDto); +// playTurn(); +// } +// } +// +// private boolean isGameRunning() { +// return chessGame.isGameRunning(); +// } +// +// private void playTurn() { +// try { +// GameCommand gameCommand = inputCommand(); +// gameCommand.execute(this); +// } catch (Exception e) { +// outputView.printErrorMessage(e.getMessage()); +// playTurn(); +// } +// } +// +// public void movePiece(final Position source, final Position destination) { +// if (!chessGame.isGameRunning()) { +// throw new IllegalArgumentException("게임이 시작되지 않았습니다."); +// } +// +// chessGame.movePiece(source, destination); +// } +// +// public void endGame() { +// chessGame.gameEnd(); +// } +//} diff --git a/src/main/java/othercase/Move.java b/src/main/java/othercase/Move.java index 44c565bf99d..81d595dc2bf 100644 --- a/src/main/java/othercase/Move.java +++ b/src/main/java/othercase/Move.java @@ -1,18 +1,18 @@ -package othercase; - -import domain.board.Position; - -public class Move implements GameCommand { - private final Position source; - private final Position destination; - - public Move(final Position source, final Position destination) { - this.source = source; - this.destination = destination; - } - - @Override - public void execute(final GameController gameController) { - gameController.movePiece(source, destination); - } -} +//package othercase; +// +//import domain.board.Position; +// +//public class Move implements GameCommand { +// private final Position source; +// private final Position destination; +// +// public Move(final Position source, final Position destination) { +// this.source = source; +// this.destination = destination; +// } +// +// @Override +// public void execute(final GameController gameController) { +// gameController.movePiece(source, destination); +// } +//} diff --git a/src/main/java/othercase/Start.java b/src/main/java/othercase/Start.java index 47de1c23a48..19bfeb460df 100644 --- a/src/main/java/othercase/Start.java +++ b/src/main/java/othercase/Start.java @@ -1,9 +1,9 @@ -package othercase; - -public class Start implements GameCommand { - - @Override - public void execute(final GameController gameController) { - gameController.gameStart(); - } -} +//package othercase; +// +//public class Start implements GameCommand { +// +// @Override +// public void execute(final GameController gameController) { +// gameController.gameStart(); +// } +//} From 30a24f404ab5456613bf7b87b03508db3b165e17 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Tue, 26 Mar 2024 21:46:11 +0900 Subject: [PATCH 03/18] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=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/othercase/End.java | 9 --- src/main/java/othercase/GameCommand.java | 5 -- src/main/java/othercase/GameCommandType.java | 48 ------------ src/main/java/othercase/GameController.java | 80 -------------------- src/main/java/othercase/Move.java | 18 ----- src/main/java/othercase/Start.java | 9 --- 6 files changed, 169 deletions(-) delete mode 100644 src/main/java/othercase/End.java delete mode 100644 src/main/java/othercase/GameCommand.java delete mode 100644 src/main/java/othercase/GameCommandType.java delete mode 100644 src/main/java/othercase/GameController.java delete mode 100644 src/main/java/othercase/Move.java delete mode 100644 src/main/java/othercase/Start.java diff --git a/src/main/java/othercase/End.java b/src/main/java/othercase/End.java deleted file mode 100644 index 2214b817495..00000000000 --- a/src/main/java/othercase/End.java +++ /dev/null @@ -1,9 +0,0 @@ -//package othercase; -// -//public class End implements GameCommand { -// -// @Override -// public void execute(final GameController gameController) { -// gameController.endGame(); -// } -//} diff --git a/src/main/java/othercase/GameCommand.java b/src/main/java/othercase/GameCommand.java deleted file mode 100644 index 6195b3b3291..00000000000 --- a/src/main/java/othercase/GameCommand.java +++ /dev/null @@ -1,5 +0,0 @@ -//package othercase; -// -//public interface GameCommand { -// void execute(GameController gameController); -//} diff --git a/src/main/java/othercase/GameCommandType.java b/src/main/java/othercase/GameCommandType.java deleted file mode 100644 index 3a48b2d95ef..00000000000 --- a/src/main/java/othercase/GameCommandType.java +++ /dev/null @@ -1,48 +0,0 @@ -//package othercase; -// -//import domain.board.Position; -// -//import java.util.Arrays; -//import java.util.function.Function; -// -//public enum GameCommandType { -// START("start", command -> new Start()), -// MOVE("move", GameCommandType::toMove), -// END("end", command -> new End()); -// -// private static final int COMMAND_INDEX = 0; -// private static final int BEFORE_POSITION = 1; -// private static final int AFTER_POSITION = 2; -// private static final int COMMAND_WITH_POSITIONS_SIZE = 3; -// -// private final String value; -// private final Function gameCommand; -// -// GameCommandType(final String value, final Function gameCommand) { -// this.value = value; -// this.gameCommand = gameCommand; -// } -// -// public static GameCommand of(final String[] values) { -// return Arrays.stream(GameCommandType.values()) -// .filter(it -> it.value.equalsIgnoreCase(values[COMMAND_INDEX])) -// .map(it -> it.gameCommand.apply(values)) -// .findAny() -// .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 명령어입니다!")); -// } -// -// private static Move toMove(final String[] values) { -// validatePositionValue(values); -// -// Position source = Position.of(values[BEFORE_POSITION]); -// Position destination = Position.of(values[AFTER_POSITION]); -// -// return new Move(source, destination); -// } -// -// private static void validatePositionValue(final String[] values) { -// if (values.length != COMMAND_WITH_POSITIONS_SIZE) { -// throw new IllegalArgumentException("잘못된 출발지/도착지입니다."); -// } -// } -//} diff --git a/src/main/java/othercase/GameController.java b/src/main/java/othercase/GameController.java deleted file mode 100644 index e7f31a1a962..00000000000 --- a/src/main/java/othercase/GameController.java +++ /dev/null @@ -1,80 +0,0 @@ -//package othercase; -// -//import domain.board.Position; -//import domain.game.ChessGame; -//import dto.BoardDto; -//import view.InputView; -//import view.OutputView; -// -//public class GameController { -// private final InputView inputView; -// private final OutputView outputView; -// private final ChessGame chessGame; -// -// public GameController(final InputView inputView, final OutputView outputView, final ChessGame chessGame) { -// this.inputView = inputView; -// this.outputView = outputView; -// this.chessGame = chessGame; -// } -// -// public void run() { -// try { -// GameCommand gameCommand = inputCommand(); -// gameCommand.execute(this); -// } catch (Exception e) { -// outputView.printErrorMessage(e.getMessage()); -// run(); -// } -// } -// -// private GameCommand inputCommand() { -// try { -// String[] inputValues = inputView.inputCommand().split(" "); -// return GameCommandType.of(inputValues); -// } catch (IllegalArgumentException e) { -// outputView.printErrorMessage(e.getMessage()); -// return inputCommand(); -// } -// } -// -// public void gameStart() { -// if (isGameRunning()) { -// throw new IllegalArgumentException("이미 게임이 진행중입니다."); -// } -// -// chessGame.gameStart(); -// outputView.printWelcomeMessage(); -// -// while (chessGame.isRunning()) { -// BoardDto boardDto = BoardDto.from(chessGame.piecePositions()); -// outputView.printBoard(boardDto); -// playTurn(); -// } -// } -// -// private boolean isGameRunning() { -// return chessGame.isGameRunning(); -// } -// -// private void playTurn() { -// try { -// GameCommand gameCommand = inputCommand(); -// gameCommand.execute(this); -// } catch (Exception e) { -// outputView.printErrorMessage(e.getMessage()); -// playTurn(); -// } -// } -// -// public void movePiece(final Position source, final Position destination) { -// if (!chessGame.isGameRunning()) { -// throw new IllegalArgumentException("게임이 시작되지 않았습니다."); -// } -// -// chessGame.movePiece(source, destination); -// } -// -// public void endGame() { -// chessGame.gameEnd(); -// } -//} diff --git a/src/main/java/othercase/Move.java b/src/main/java/othercase/Move.java deleted file mode 100644 index 81d595dc2bf..00000000000 --- a/src/main/java/othercase/Move.java +++ /dev/null @@ -1,18 +0,0 @@ -//package othercase; -// -//import domain.board.Position; -// -//public class Move implements GameCommand { -// private final Position source; -// private final Position destination; -// -// public Move(final Position source, final Position destination) { -// this.source = source; -// this.destination = destination; -// } -// -// @Override -// public void execute(final GameController gameController) { -// gameController.movePiece(source, destination); -// } -//} diff --git a/src/main/java/othercase/Start.java b/src/main/java/othercase/Start.java deleted file mode 100644 index 19bfeb460df..00000000000 --- a/src/main/java/othercase/Start.java +++ /dev/null @@ -1,9 +0,0 @@ -//package othercase; -// -//public class Start implements GameCommand { -// -// @Override -// public void execute(final GameController gameController) { -// gameController.gameStart(); -// } -//} From 227468ef0e4a4d997c8a88cbd4c7cef686a35ed2 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Tue, 26 Mar 2024 21:48:33 +0900 Subject: [PATCH 04/18] =?UTF-8?q?refactor:=20ChessGame=20=EC=9E=98?= =?UTF-8?q?=EB=AA=BB=EB=90=9C=20=EB=A1=9C=EC=A7=81=20=EC=A0=95=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/game/ChessGame.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index 0bd133c8740..411786f6dfb 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -12,26 +12,24 @@ public class ChessGame { private final Board board; - private final GameStatus gameStatus; private PieceColor currentColor = WHITE; - private boolean isGameRunning = false; + private GameStatus gameStatus = GameStatus.WAITING; - public ChessGame(final Board board, final GameStatus gameStatus) { + public ChessGame(final Board board) { this.board = board; - this.gameStatus = gameStatus; } public static ChessGame initGame() { - return new ChessGame(BoardInitializer.initBoard(), GameStatus.WAITING); + return new ChessGame(BoardInitializer.initBoard()); } public void gameStart() { - isGameRunning = true; + gameStatus = GameStatus.RUNNING; } public void gameEnd() { - isGameRunning = false; + gameStatus = GameStatus.END; } public boolean isWaiting() { From 1587af30ac39cbe0b4552990356509790bcef952 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Tue, 26 Mar 2024 22:19:00 +0900 Subject: [PATCH 05/18] =?UTF-8?q?refactor:=20=EC=83=81=ED=83=9C=20Context?= =?UTF-8?q?=EB=A5=BC=20Controller=EB=A1=9C=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/controller/GameController.java | 23 ++++++++++++++++---- src/main/java/domain/game/ChessGame.java | 9 ++++++++ src/main/java/domain/game/End.java | 11 ++++++++-- src/main/java/domain/game/GameCommand.java | 4 +++- src/main/java/domain/game/Move.java | 7 +++--- src/main/java/domain/game/Start.java | 8 ++++--- 6 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index 070a55578cc..bb0f4da38ee 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -1,8 +1,10 @@ package controller; +import domain.board.Position; import domain.game.ChessGame; import domain.game.GameCommand; import domain.game.GameCommandType; +import domain.game.GameStatus; import dto.BoardDto; import view.InputView; import view.OutputView; @@ -22,14 +24,14 @@ public void run() { initGame(); if (chessGame.isRunning()) { - gameStart(); + start(); } } private void initGame() { try { GameCommand gameCommand = inputCommand(); - gameCommand.execute(chessGame); + gameCommand.execute(this); } catch (Exception e) { outputView.printErrorMessage(e.getMessage()); initGame(); @@ -41,7 +43,8 @@ private GameCommand inputCommand() { return GameCommandType.of(inputValues); } - private void gameStart() { + public void start() { + chessGame.gameStart(); outputView.printWelcomeMessage(); while (chessGame.isRunning()) { BoardDto boardDto = BoardDto.from(chessGame.piecePositions()); @@ -53,10 +56,22 @@ private void gameStart() { private void playTurn() { try { GameCommand gameCommand = inputCommand(); - gameCommand.execute(chessGame); + gameCommand.execute(this); } catch (Exception e) { outputView.printErrorMessage(e.getMessage()); playTurn(); } } + + public GameStatus gameStatus() { + return chessGame.gameStatus(); + } + + public void movePiece(final Position source, final Position destination) { + chessGame.movePiece(source, destination); + } + + public void end() { + chessGame.gameEnd(); + } } diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index 411786f6dfb..ca014193f2e 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -50,6 +50,15 @@ public Map piecePositions() { public void movePiece(final Position source, final Position destination) { board.movePiece(currentColor, source, destination); + if (!board.isKingAlive(currentColor.toggle())) { + gameEnd(); + return; + } + currentColor = currentColor.toggle(); } + + public GameStatus gameStatus() { + return gameStatus; + } } diff --git a/src/main/java/domain/game/End.java b/src/main/java/domain/game/End.java index 0e381c82c82..4adbd3b3590 100644 --- a/src/main/java/domain/game/End.java +++ b/src/main/java/domain/game/End.java @@ -1,9 +1,16 @@ package domain.game; +import controller.GameController; + public class End implements GameCommand { @Override - public void execute(final ChessGame chessGame) { - chessGame.gameEnd(); + public void execute(final GameController gameController) { + GameStatus gameStatus = gameController.gameStatus(); + if (gameController.gameStatus() == GameStatus.END) { + throw new IllegalArgumentException("게임이 이미 종료되었습니다."); + } + + gameController.end(); } } diff --git a/src/main/java/domain/game/GameCommand.java b/src/main/java/domain/game/GameCommand.java index 80c0ac6f9a2..14211dea2fb 100644 --- a/src/main/java/domain/game/GameCommand.java +++ b/src/main/java/domain/game/GameCommand.java @@ -1,5 +1,7 @@ package domain.game; +import controller.GameController; + public interface GameCommand { - void execute(ChessGame chessGame); + void execute(GameController gameController); } diff --git a/src/main/java/domain/game/Move.java b/src/main/java/domain/game/Move.java index f4f394eddba..d42c24a60fa 100644 --- a/src/main/java/domain/game/Move.java +++ b/src/main/java/domain/game/Move.java @@ -1,5 +1,6 @@ package domain.game; +import controller.GameController; import domain.board.Position; public class Move implements GameCommand { @@ -12,11 +13,11 @@ public Move(final Position source, final Position destination) { } @Override - public void execute(final ChessGame chessGame) { - if (!chessGame.isRunning()) { + public void execute(final GameController gameController) { + if (gameController.gameStatus() != GameStatus.RUNNING) { throw new IllegalArgumentException("아직 게임이 시작되지 않았습니다."); } - chessGame.movePiece(source, destination); + gameController.movePiece(source, destination); } } diff --git a/src/main/java/domain/game/Start.java b/src/main/java/domain/game/Start.java index ba3445f5850..8f8ed6b713b 100644 --- a/src/main/java/domain/game/Start.java +++ b/src/main/java/domain/game/Start.java @@ -1,13 +1,15 @@ package domain.game; +import controller.GameController; + public class Start implements GameCommand { @Override - public void execute(final ChessGame chessGame) { - if (!chessGame.isWaiting()) { + public void execute(final GameController gameController) { + if (gameController.gameStatus() != GameStatus.WAITING) { throw new IllegalArgumentException("이미 게임이 진행중입니다."); } - chessGame.gameStart(); + gameController.start(); } } From a83fbb95eb5b1b949b982c61ed8e4f4a89f3a187 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Wed, 27 Mar 2024 20:44:20 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20=EC=B2=B4=EC=8A=A4=EA=B2=8C?= =?UTF-8?q?=EC=9E=84=20=EC=A4=91=EA=B0=84=20=EC=A0=90=EC=88=98=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=B0=8F=20=EC=8A=B9=ED=8C=A8=20=ED=8C=90=EB=8B=A8?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/GameController.java | 13 ++++-- src/main/java/domain/board/Board.java | 42 +++++++++++++++++++ src/main/java/domain/game/ChessGame.java | 16 +++---- .../java/domain/game/GameCommandType.java | 1 + src/main/java/domain/game/GameResult.java | 17 ++++++++ src/main/java/domain/game/GameScore.java | 8 ++++ src/main/java/domain/game/Score.java | 8 ++++ src/main/java/domain/game/Status.java | 15 +++++++ src/main/java/domain/piece/Bishop.java | 10 +++++ src/main/java/domain/piece/King.java | 10 +++++ src/main/java/domain/piece/Knight.java | 10 +++++ src/main/java/domain/piece/Pawn.java | 10 +++++ src/main/java/domain/piece/Piece.java | 4 ++ src/main/java/domain/piece/PieceType.java | 22 +++++++--- src/main/java/domain/piece/Queen.java | 10 +++++ src/main/java/domain/piece/Rook.java | 10 +++++ src/main/java/view/OutputView.java | 21 ++++++++++ 17 files changed, 209 insertions(+), 18 deletions(-) create mode 100644 src/main/java/domain/game/GameResult.java create mode 100644 src/main/java/domain/game/GameScore.java create mode 100644 src/main/java/domain/game/Score.java create mode 100644 src/main/java/domain/game/Status.java diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index bb0f4da38ee..5e9a8e6083d 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -1,10 +1,7 @@ package controller; import domain.board.Position; -import domain.game.ChessGame; -import domain.game.GameCommand; -import domain.game.GameCommandType; -import domain.game.GameStatus; +import domain.game.*; import dto.BoardDto; import view.InputView; import view.OutputView; @@ -74,4 +71,12 @@ public void movePiece(final Position source, final Position destination) { public void end() { chessGame.gameEnd(); } + + public void printGameStatus() { + GameScore gameScore = chessGame.getGameResult(); + outputView.printGameResult( + gameScore.whiteTeamScore(), + gameScore.blackTeamScore(), + gameScore.gameResult()); + } } diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java index 052d36e823f..7cc2c0fd063 100644 --- a/src/main/java/domain/board/Board.java +++ b/src/main/java/domain/board/Board.java @@ -5,6 +5,7 @@ import domain.piece.PieceType; import java.util.Collections; +import java.util.List; import java.util.Map; public class Board { @@ -56,4 +57,45 @@ public boolean isKingAlive(final PieceColor targetColor) { .stream() .anyMatch(piece -> piece.isTeam(targetColor) && piece.pieceType() == PieceType.KING); } + + public double calculateTeamScore(final PieceColor teamColor) { + double existTargetColorPiecesScoreSum = sumExistTargetPiecesScore(teamColor); + + return existTargetColorPiecesScoreSum - calculateDecreaseScoreForExistSameFilePawns(teamColor); + } + + private double sumExistTargetPiecesScore(final PieceColor pieceColor) { + return piecePositions.values() + .stream() + .filter(piece -> piece.isTeam(pieceColor)) + .mapToDouble(Piece::score) + .sum(); + } + + private double calculateDecreaseScoreForExistSameFilePawns(final PieceColor pieceColor) { + List targetColorPawnPositions = piecePositions.entrySet() + .stream() + .filter(entry -> entry.getValue().isTeam(pieceColor) && entry.getValue().matchPieceType(PieceType.PAWN)) + .map(Map.Entry::getKey) + .toList(); + + int sameFilePawnCount = 0; + for (File file : File.values()) { + sameFilePawnCount += calculateSameFilePawnCount(targetColorPawnPositions, file); + } + + return 0.5 * sameFilePawnCount; + } + + private int calculateSameFilePawnCount(final List pawnPositions, final File file) { + int sameFilePawnCount = (int) pawnPositions.stream() + .filter(position -> position.file() == file) + .count(); + + if (sameFilePawnCount <= 1) { + return 0; + } + + return sameFilePawnCount; + } } diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index ca014193f2e..b5bb43a1ae6 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -8,6 +8,7 @@ import java.util.Map; +import static domain.piece.PieceColor.BLACK; import static domain.piece.PieceColor.WHITE; public class ChessGame { @@ -32,18 +33,10 @@ public void gameEnd() { gameStatus = GameStatus.END; } - public boolean isWaiting() { - return gameStatus == GameStatus.WAITING; - } - public boolean isRunning() { return gameStatus == GameStatus.RUNNING; } - public boolean isEnd() { - return gameStatus == GameStatus.END; - } - public Map piecePositions() { return board.piecePositions(); } @@ -61,4 +54,11 @@ public void movePiece(final Position source, final Position destination) { public GameStatus gameStatus() { return gameStatus; } + + public GameScore getGameResult() { + double whiteTeamScore = board.calculateTeamScore(WHITE); + double blackTeamScore = board.calculateTeamScore(BLACK); + + return new GameScore(new Score(whiteTeamScore), new Score(blackTeamScore)); + } } diff --git a/src/main/java/domain/game/GameCommandType.java b/src/main/java/domain/game/GameCommandType.java index f11cab99098..23b2272f7b6 100644 --- a/src/main/java/domain/game/GameCommandType.java +++ b/src/main/java/domain/game/GameCommandType.java @@ -8,6 +8,7 @@ public enum GameCommandType { START("start", command -> new Start()), MOVE("move", GameCommandType::toMove), + STATUS("status", command -> new Status()), END("end", command -> new End()); private static final int COMMAND_INDEX = 0; diff --git a/src/main/java/domain/game/GameResult.java b/src/main/java/domain/game/GameResult.java new file mode 100644 index 00000000000..4ff5592436e --- /dev/null +++ b/src/main/java/domain/game/GameResult.java @@ -0,0 +1,17 @@ +package domain.game; + +public enum GameResult { + BLACK_WIN, WHITE_WIN, DRAW; + + public static GameResult of(final Score whiteScore, final Score blackScore) { + if (blackScore.isBigger(whiteScore)) { + return BLACK_WIN; + } + + if (whiteScore.isBigger(blackScore)) { + return WHITE_WIN; + } + + return DRAW; + } +} diff --git a/src/main/java/domain/game/GameScore.java b/src/main/java/domain/game/GameScore.java new file mode 100644 index 00000000000..0e1b5d35edd --- /dev/null +++ b/src/main/java/domain/game/GameScore.java @@ -0,0 +1,8 @@ +package domain.game; + +public record GameScore(Score whiteTeamScore, Score blackTeamScore) { + + public GameResult gameResult() { + return GameResult.of(whiteTeamScore, blackTeamScore); + } +} diff --git a/src/main/java/domain/game/Score.java b/src/main/java/domain/game/Score.java new file mode 100644 index 00000000000..aabc7c72c55 --- /dev/null +++ b/src/main/java/domain/game/Score.java @@ -0,0 +1,8 @@ +package domain.game; + +public record Score(double value) { + + public boolean isBigger(final Score other) { + return this.value > other.value; + } +} diff --git a/src/main/java/domain/game/Status.java b/src/main/java/domain/game/Status.java new file mode 100644 index 00000000000..07158c5c488 --- /dev/null +++ b/src/main/java/domain/game/Status.java @@ -0,0 +1,15 @@ +package domain.game; + +import controller.GameController; + +public class Status implements GameCommand { + + @Override + public void execute(final GameController gameController) { + if (gameController.gameStatus() != GameStatus.RUNNING) { + throw new IllegalArgumentException("status 명령어는 게임 중에만 실행할 수 있습니다."); + } + + gameController.printGameStatus(); + } +} diff --git a/src/main/java/domain/piece/Bishop.java b/src/main/java/domain/piece/Bishop.java index 90893491c4e..04c7d433810 100644 --- a/src/main/java/domain/piece/Bishop.java +++ b/src/main/java/domain/piece/Bishop.java @@ -51,4 +51,14 @@ private void checkAlivePosition(final Position alivePosition, final Board board) public PieceType pieceType() { return PIECE_TYPE; } + + @Override + public double score() { + return pieceType().score(); + } + + @Override + public boolean matchPieceType(final PieceType target) { + return PIECE_TYPE == target; + } } diff --git a/src/main/java/domain/piece/King.java b/src/main/java/domain/piece/King.java index 8bf984368ba..a1fcdff0a95 100644 --- a/src/main/java/domain/piece/King.java +++ b/src/main/java/domain/piece/King.java @@ -42,4 +42,14 @@ private void checkAlivePosition(final Position alivePosition, final Board board) public PieceType pieceType() { return PIECE_TYPE; } + + @Override + public double score() { + return pieceType().score(); + } + + @Override + public boolean matchPieceType(final PieceType target) { + return PIECE_TYPE == target; + } } diff --git a/src/main/java/domain/piece/Knight.java b/src/main/java/domain/piece/Knight.java index 18c92df1faa..82aa70a7678 100644 --- a/src/main/java/domain/piece/Knight.java +++ b/src/main/java/domain/piece/Knight.java @@ -31,4 +31,14 @@ private void checkAlivePosition(final Position alivePosition, final Board board) public PieceType pieceType() { return PIECE_TYPE; } + + @Override + public double score() { + return pieceType().score(); + } + + @Override + public boolean matchPieceType(final PieceType target) { + return PIECE_TYPE == target; + } } diff --git a/src/main/java/domain/piece/Pawn.java b/src/main/java/domain/piece/Pawn.java index 31f4a61b9a0..f73c7f06750 100644 --- a/src/main/java/domain/piece/Pawn.java +++ b/src/main/java/domain/piece/Pawn.java @@ -66,4 +66,14 @@ private void checkPathHasPiece(final Position path, final Board board) { public PieceType pieceType() { return PIECE_TYPE; } + + @Override + public double score() { + return pieceType().score(); + } + + @Override + public boolean matchPieceType(final PieceType target) { + return PIECE_TYPE == target; + } } diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 00babe0fbc8..231fd8e8f87 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -14,6 +14,10 @@ public Piece(final PieceColor color) { public abstract PieceType pieceType(); + public abstract double score(); + + public abstract boolean matchPieceType(PieceType pieceType); + public boolean isTeam(final PieceColor teamColor) { return this.color == teamColor; } diff --git a/src/main/java/domain/piece/PieceType.java b/src/main/java/domain/piece/PieceType.java index 3002a47b4e6..f0a28bd6f6f 100644 --- a/src/main/java/domain/piece/PieceType.java +++ b/src/main/java/domain/piece/PieceType.java @@ -1,10 +1,20 @@ package domain.piece; public enum PieceType { - PAWN, - ROOK, - KNIGHT, - BISHOP, - QUEEN, - KING + PAWN(1), + ROOK(5), + KNIGHT(2.5), + BISHOP(3), + QUEEN(9), + KING(0); + + private double score; + + PieceType(final double score) { + this.score = score; + } + + public double score() { + return score; + } } diff --git a/src/main/java/domain/piece/Queen.java b/src/main/java/domain/piece/Queen.java index 84af535703b..322f356dc04 100644 --- a/src/main/java/domain/piece/Queen.java +++ b/src/main/java/domain/piece/Queen.java @@ -50,4 +50,14 @@ private void checkAlivePosition(final Position alivePosition, final Board board) public PieceType pieceType() { return PIECE_TYPE; } + + @Override + public double score() { + return pieceType().score(); + } + + @Override + public boolean matchPieceType(final PieceType target) { + return PIECE_TYPE == target; + } } diff --git a/src/main/java/domain/piece/Rook.java b/src/main/java/domain/piece/Rook.java index 6f5fbbf6e3a..87dff8960d3 100644 --- a/src/main/java/domain/piece/Rook.java +++ b/src/main/java/domain/piece/Rook.java @@ -52,4 +52,14 @@ private void checkAlivePosition(final Position alivePosition, final Board board) public PieceType pieceType() { return PIECE_TYPE; } + + @Override + public double score() { + return pieceType().score(); + } + + @Override + public boolean matchPieceType(final PieceType target) { + return PIECE_TYPE == target; + } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 5d3c3ab8436..628b1cec30d 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,5 +1,7 @@ package view; +import domain.game.GameResult; +import domain.game.Score; import domain.piece.PieceColor; import domain.piece.PieceType; import dto.BoardDto; @@ -76,4 +78,23 @@ private String convertPieceTypeToString(final PieceType pieceType, final PieceCo return pieceMessage; } + + public void printGameResult(final Score whiteTeamScore, final Score blackTeamScore, final GameResult gameResult) { + System.out.println("White Score : " + whiteTeamScore.value()); + System.out.println("Black Score : " + blackTeamScore.value()); + System.out.println(convertToGameResultMessage(gameResult)); + System.out.println(); + } + + private String convertToGameResultMessage(final GameResult gameResult) { + if (gameResult == GameResult.BLACK_WIN) { + return "블랙이 이기고 있습니다!"; + } + + if (gameResult == GameResult.WHITE_WIN) { + return "화이트가 이기고 있습니다!"; + } + + return "막상막하입니다!"; + } } From 829536b58cfe5a37ba3f73d710b61bbecd47227a Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Thu, 28 Mar 2024 17:52:33 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat:=20=ED=98=84=EC=9E=AC=20=EC=B0=A8?= =?UTF-8?q?=EB=A1=80=EC=9D=98=20=EC=83=89=EC=83=81=EC=9D=84=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/GameController.java | 4 +++- src/main/java/domain/game/ChessGame.java | 4 ++++ src/main/java/view/InputView.java | 1 + src/main/java/view/OutputView.java | 6 +++++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index 5e9a8e6083d..396dddfcc59 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -2,6 +2,7 @@ import domain.board.Position; import domain.game.*; +import domain.piece.PieceColor; import dto.BoardDto; import view.InputView; import view.OutputView; @@ -45,7 +46,8 @@ public void start() { outputView.printWelcomeMessage(); while (chessGame.isRunning()) { BoardDto boardDto = BoardDto.from(chessGame.piecePositions()); - outputView.printBoard(boardDto); + PieceColor currentPlayTeamColor = chessGame.currentPlayTeamColor(); + outputView.printTurnStatus(boardDto, currentPlayTeamColor); playTurn(); } } diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index b5bb43a1ae6..1596681eed9 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -61,4 +61,8 @@ public GameScore getGameResult() { return new GameScore(new Score(whiteTeamScore), new Score(blackTeamScore)); } + + public PieceColor currentPlayTeamColor() { + return currentColor; + } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 5ff300cb63c..7aecc363a09 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -8,6 +8,7 @@ public class InputView { public String inputCommand() { String input = sc.nextLine(); validateEmpty(input); + System.out.println(); return input; } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 628b1cec30d..3a6c7b8426b 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -39,9 +39,13 @@ public void printWelcomeMessage() { System.out.println("> 게임 시작: start"); System.out.println("> 게임 종료: end"); System.out.println("> 게임 이동: move source위치 target위치 - 예. move b2 b3"); + System.out.println(); } - public void printBoard(final BoardDto boardDto) { + public void printTurnStatus(final BoardDto boardDto, final PieceColor pieceColor) { + System.out.println("====Status====="); + System.out.println(pieceColor.name() + "팀 차례입니다."); + System.out.println("==============="); List boardStatus = convertBoardStatus(boardDto.value()); boardStatus.forEach(System.out::println); System.out.println(); From 4ccb6b097d6512dc3cb48a4e99d923a7f10f1afe Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Fri, 29 Mar 2024 15:26:26 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20DB=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=8C=EC=9E=84=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + src/main/java/Application.java | 18 ++- src/main/java/controller/GameController.java | 27 +++- src/main/java/dao/CurrentPlayerColorDao.java | 14 ++ .../java/dao/MysqlCurrentPlayerColorDao.java | 81 +++++++++++ src/main/java/dao/MysqlPieceDao.java | 134 ++++++++++++++++++ src/main/java/dao/PieceDao.java | 21 +++ src/main/java/dao/PieceEntity.java | 9 ++ .../java/database/DatabaseConfiguration.java | 31 ++++ .../java/database/JdbcConnectionPool.java | 79 +++++++++++ src/main/java/domain/board/Board.java | 42 +++++- .../java/domain/board/BoardInitializer.java | 18 +-- src/main/java/domain/game/ChessGame.java | 35 +++-- src/main/java/domain/game/Continue.java | 16 +++ .../java/domain/game/GameCommandType.java | 2 + src/main/java/domain/game/New.java | 16 +++ src/main/java/domain/game/Start.java | 2 +- src/main/java/domain/piece/PieceColor.java | 9 ++ src/main/java/domain/piece/PieceType.java | 32 +++-- src/main/java/view/OutputView.java | 7 + src/test/java/domain/board/BoardTest.java | 15 +- src/test/java/domain/board/TestPieceDao.java | 40 ++++++ src/test/java/domain/piece/BishopTest.java | 17 +-- src/test/java/domain/piece/KingTest.java | 15 +- src/test/java/domain/piece/KnightTest.java | 7 +- src/test/java/domain/piece/PawnTest.java | 17 +-- src/test/java/domain/piece/QueenTest.java | 15 +- src/test/java/domain/piece/RookTest.java | 17 +-- 28 files changed, 653 insertions(+), 85 deletions(-) create mode 100644 src/main/java/dao/CurrentPlayerColorDao.java create mode 100644 src/main/java/dao/MysqlCurrentPlayerColorDao.java create mode 100644 src/main/java/dao/MysqlPieceDao.java create mode 100644 src/main/java/dao/PieceDao.java create mode 100644 src/main/java/dao/PieceEntity.java create mode 100644 src/main/java/database/DatabaseConfiguration.java create mode 100644 src/main/java/database/JdbcConnectionPool.java create mode 100644 src/main/java/domain/game/Continue.java create mode 100644 src/main/java/domain/game/New.java create mode 100644 src/test/java/domain/board/TestPieceDao.java diff --git a/build.gradle b/build.gradle index 3697236c6fb..20ad08a5a5e 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,8 @@ 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 { diff --git a/src/main/java/Application.java b/src/main/java/Application.java index ff3accb42d4..3b1f3b9eab7 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,11 +1,27 @@ import controller.GameController; +import dao.MysqlCurrentPlayerColorDao; +import dao.MysqlPieceDao; +import database.JdbcConnectionPool; +import domain.board.Board; +import domain.board.BoardInitializer; +import domain.board.Position; import domain.game.ChessGame; +import domain.piece.Piece; import view.InputView; import view.OutputView; +import java.util.Map; + public class Application { public static void main(String[] args) { - ChessGame chessGame = ChessGame.initGame(); + JdbcConnectionPool connectionPool = JdbcConnectionPool.getInstance(); + Map initialPiecePositions = BoardInitializer.initBoard(); + MysqlPieceDao mysqlPieceDao = new MysqlPieceDao(connectionPool); + Board board = new Board(mysqlPieceDao, initialPiecePositions); + + MysqlCurrentPlayerColorDao mysqlCurrentPlayerColorDao = new MysqlCurrentPlayerColorDao(connectionPool); + ChessGame chessGame = new ChessGame(mysqlCurrentPlayerColorDao, board); + GameController gameController = new GameController(new InputView(), new OutputView(), chessGame); gameController.run(); } diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index 396dddfcc59..12b0713ee15 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -41,6 +41,25 @@ private GameCommand inputCommand() { return GameCommandType.of(inputValues); } + public void buildGame() { + if (chessGame.existPrevGame()) { + outputView.printInputRoadGameMessage(); + GameCommand gameCommand = inputCommand(); + gameCommand.execute(this); + return; + } + createNewGame(); + start(); + } + + public void createNewGame() { + chessGame.createNewGame(); + } + + public void roadPrevGame() { + chessGame.roadPrevGame(); + } + public void start() { chessGame.gameStart(); outputView.printWelcomeMessage(); @@ -62,14 +81,14 @@ private void playTurn() { } } - public GameStatus gameStatus() { - return chessGame.gameStatus(); - } - public void movePiece(final Position source, final Position destination) { chessGame.movePiece(source, destination); } + public GameStatus gameStatus() { + return chessGame.gameStatus(); + } + public void end() { chessGame.gameEnd(); } diff --git a/src/main/java/dao/CurrentPlayerColorDao.java b/src/main/java/dao/CurrentPlayerColorDao.java new file mode 100644 index 00000000000..0967ce86189 --- /dev/null +++ b/src/main/java/dao/CurrentPlayerColorDao.java @@ -0,0 +1,14 @@ +package dao; + +import domain.piece.PieceColor; + +public interface CurrentPlayerColorDao { + + PieceColor findPlayerColor(); + + void savePlayerColor(PieceColor playerColor); + + void updatePlayerColor(PieceColor changeColor); + + void deletePlayerColor(); +} diff --git a/src/main/java/dao/MysqlCurrentPlayerColorDao.java b/src/main/java/dao/MysqlCurrentPlayerColorDao.java new file mode 100644 index 00000000000..851596f48b4 --- /dev/null +++ b/src/main/java/dao/MysqlCurrentPlayerColorDao.java @@ -0,0 +1,81 @@ +package dao; + +import database.JdbcConnectionPool; +import domain.piece.PieceColor; + +import java.sql.*; + +public class MysqlCurrentPlayerColorDao implements CurrentPlayerColorDao { + private final JdbcConnectionPool connectionPool; + + public MysqlCurrentPlayerColorDao(final JdbcConnectionPool connectionPool) { + this.connectionPool = connectionPool; + } + + @Override + public PieceColor findPlayerColor() { + final String query = "SELECT * FROM current_player_color"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { + ResultSet resultSet = preparedStatement.executeQuery(); + return convertPieceColor(resultSet); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } + + @Override + public void savePlayerColor(final PieceColor playerColor) { + final String query = "INSERT INTO current_player_color (player_color) VALUES (?)"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) { + preparedStatement.setString(1, playerColor.name()); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } + + private PieceColor convertPieceColor(final ResultSet resultSet) throws SQLException { + if (resultSet.next()) { + return PieceColor.of(resultSet.getString(2)); + } + + throw new IllegalArgumentException("현재 플레이어의 색상 정보가 존재하지 않습니다."); + } + + @Override + public void updatePlayerColor(final PieceColor changeColor) { + final String query = "UPDATE current_player_color SET player_color=?"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setString(1, changeColor.name()); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } + + @Override + public void deletePlayerColor() { + final String query = "DELETE FROM current_player_color"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { + preparedStatement.execute(); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } +} diff --git a/src/main/java/dao/MysqlPieceDao.java b/src/main/java/dao/MysqlPieceDao.java new file mode 100644 index 00000000000..3ca573c6b0e --- /dev/null +++ b/src/main/java/dao/MysqlPieceDao.java @@ -0,0 +1,134 @@ +package dao; + +import database.JdbcConnectionPool; +import domain.board.File; +import domain.board.Rank; +import domain.piece.PieceColor; +import domain.piece.PieceType; + +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class MysqlPieceDao implements PieceDao { + private final JdbcConnectionPool connectionPool; + + public MysqlPieceDao(final JdbcConnectionPool connectionPool) { + this.connectionPool = connectionPool; + } + + @Override + public List findAll() { + final String query = "SELECT * FROM piece"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { + ResultSet resultSet = preparedStatement.executeQuery(); + return createPieceEntities(resultSet); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } + + private List createPieceEntities(final ResultSet resultSet) throws SQLException { + List pieceEntities = new ArrayList<>(); + while (resultSet.next()) { + File file = File.of(resultSet.getString("file")); + Rank rank = Rank.of(resultSet.getString("rank")); + PieceType type = PieceType.of(resultSet.getString("type")); + PieceColor color = PieceColor.of(resultSet.getString("color")); + + pieceEntities.add(new PieceEntity(type, color, file, rank)); + } + + return pieceEntities; + } + + @Override + public boolean existPiecePositions() { + final String query = "SELECT COUNT(*) FROM piece"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { + ResultSet resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + int pieceCount = resultSet.getInt(1); + return pieceCount > 0; + } + + return false; + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } + + @Override + public void savePiece(final PieceEntity piece) { + final String query = "INSERT INTO piece (file, `rank`, type, color) VALUES (?, ?, ?, ?)"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) { + preparedStatement.setString(1, piece.file().name()); + preparedStatement.setInt(2, piece.rank().value()); + preparedStatement.setString(3, piece.pieceType().name()); + preparedStatement.setString(4, piece.pieceColor().name()); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } + + @Override + public void updatePiecePosition(final File sourceFile, final Rank sourceRank, final File destinationFile, final Rank destinationRank) { + final String query = "UPDATE piece SET file=?, `rank`=? WHERE file=? AND `rank`=?"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setString(1, destinationFile.name()); + preparedStatement.setInt(2, destinationRank.value()); + preparedStatement.setString(3, sourceFile.name()); + preparedStatement.setInt(4, sourceRank.value()); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } + + @Override + public void deleteByFileAndRank(final File file, final Rank rank) { + final String query = "DELETE FROM piece WHERE file=? AND `rank`=?"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setString(1, file.name()); + preparedStatement.setInt(2, rank.value()); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } + + @Override + public void deleteAll() { + final String query = "DELETE FROM piece"; + Connection connection = connectionPool.getConnection(); + + try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { + preparedStatement.execute(); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + connectionPool.releaseConnection(connection); + } + } +} diff --git a/src/main/java/dao/PieceDao.java b/src/main/java/dao/PieceDao.java new file mode 100644 index 00000000000..103b4041f88 --- /dev/null +++ b/src/main/java/dao/PieceDao.java @@ -0,0 +1,21 @@ +package dao; + +import domain.board.File; +import domain.board.Rank; + +import java.util.List; + +public interface PieceDao { + + List findAll(); + + boolean existPiecePositions(); + + void savePiece(PieceEntity piece); + + void updatePiecePosition(File sourceFile, Rank sourceRank, File destinationFile, Rank destinationRank); + + void deleteByFileAndRank(File file, Rank rank); + + void deleteAll(); +} diff --git a/src/main/java/dao/PieceEntity.java b/src/main/java/dao/PieceEntity.java new file mode 100644 index 00000000000..4a593d110f2 --- /dev/null +++ b/src/main/java/dao/PieceEntity.java @@ -0,0 +1,9 @@ +package dao; + +import domain.board.File; +import domain.board.Rank; +import domain.piece.PieceColor; +import domain.piece.PieceType; + +public record PieceEntity(PieceType pieceType, PieceColor pieceColor, File file, Rank rank) { +} diff --git a/src/main/java/database/DatabaseConfiguration.java b/src/main/java/database/DatabaseConfiguration.java new file mode 100644 index 00000000000..39982e9c84f --- /dev/null +++ b/src/main/java/database/DatabaseConfiguration.java @@ -0,0 +1,31 @@ +package database; + +public class DatabaseConfiguration { + private static final DatabaseConfiguration INSTANCE = new DatabaseConfiguration(); + private final String MYSQL_CONNECT_URL_FORMAT = "jdbc:mysql://%s:%s/%s"; + private final String host = "localhost"; + private final String post = "3306"; + private final String database = "chess"; + private final String option = "?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"; + private final String username = "kelly"; + private final String password = "kellyPw1234!"; + + private DatabaseConfiguration() { + } + + public static DatabaseConfiguration getInstance() { + return INSTANCE; + } + + public String getUrl() { + return String.format(MYSQL_CONNECT_URL_FORMAT, host, post, database + option); + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/database/JdbcConnectionPool.java b/src/main/java/database/JdbcConnectionPool.java new file mode 100644 index 00000000000..9dd94ce44bf --- /dev/null +++ b/src/main/java/database/JdbcConnectionPool.java @@ -0,0 +1,79 @@ +package database; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public class JdbcConnectionPool { + private static final String FAILED_INITIALIZE = "커넥션 풀 초기화에 실패했습니다."; + private static final String FAILED_TO_GET_CONNECTION = "커넥션 획득에 실패했습니다."; + private static final String FAILED_TO_TERMINATE = "종료에 실패했습니다."; + private static final String FAILED_RELEASE = "커넥션 해제에 실패했습니다."; + private static final int INITIAL_POOL_SIZE = 2; + private static final JdbcConnectionPool INSTANCE = new JdbcConnectionPool(); + private final DatabaseConfiguration configuration; + private BlockingQueue pool; + + private JdbcConnectionPool() { + configuration = DatabaseConfiguration.getInstance(); + initializeConnectionPool(); + } + + public static JdbcConnectionPool getInstance() { + return INSTANCE; + } + + private void initializeConnectionPool() { + pool = new ArrayBlockingQueue<>(INITIAL_POOL_SIZE); + try { + for (int i = 0; i < INITIAL_POOL_SIZE; i++) { + pool.offer(createNewConnection()); + } + } catch (SQLException e) { + throw new RuntimeException(FAILED_INITIALIZE); + } + } + + private Connection createNewConnection() throws SQLException { + return DriverManager.getConnection( + configuration.getUrl(), + configuration.getUsername(), + configuration.getPassword() + ); + } + + public Connection getConnection() { + try { + if (pool.isEmpty()) { + return createNewConnection(); + } else { + return pool.take(); + } + } catch (InterruptedException | SQLException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(FAILED_TO_GET_CONNECTION); + } + } + + public void releaseConnection(final Connection connection) { + try { + pool.put(connection); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(FAILED_RELEASE); + } + } + + public void shutdown() { + try { + for (Connection connection : pool) { + connection.close(); + } + } catch (SQLException e) { + throw new RuntimeException(FAILED_TO_TERMINATE); + } + pool.clear(); + } +} diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java index 7cc2c0fd063..3f98ae79460 100644 --- a/src/main/java/domain/board/Board.java +++ b/src/main/java/domain/board/Board.java @@ -1,5 +1,7 @@ package domain.board; +import dao.PieceDao; +import dao.PieceEntity; import domain.piece.Piece; import domain.piece.PieceColor; import domain.piece.PieceType; @@ -9,12 +11,47 @@ import java.util.Map; public class Board { + private final PieceDao pieceDao; private final Map piecePositions; - public Board(final Map piecePositions) { + public Board(final PieceDao pieceDao, final Map piecePositions) { + this.pieceDao = pieceDao; this.piecePositions = piecePositions; } + public boolean existPrevPiecePositionsData() { + return pieceDao.existPiecePositions(); + } + + public void createNewPiecePositions() { + pieceDao.deleteAll(); + piecePositions.entrySet() + .stream() + .map(entry -> convertPieceEntity(entry.getValue(), entry.getKey())) + .forEach(pieceDao::savePiece); + } + + public void roadPrevPiecePositions() { + piecePositions.clear(); + List all = pieceDao.findAll(); + all.forEach(this::addPiecePosition); + } + + private void addPiecePosition(final PieceEntity pieceEntity) { + Position position = new Position(pieceEntity.file(), pieceEntity.rank()); + Piece piece = pieceEntity.pieceType() + .createPiece(pieceEntity.pieceColor()); + piecePositions.put(position, piece); + } + + public void clear() { + pieceDao.deleteAll(); + } + + private static PieceEntity convertPieceEntity(final Piece piece, final Position position) { + return new PieceEntity(piece.pieceType(), piece.pieceColor(), position.file(), position.rank()); + } + public void movePiece(final PieceColor pieceColor, final Position source, final Position destination) { validatePosition(pieceColor, source, destination); @@ -23,6 +60,9 @@ public void movePiece(final PieceColor pieceColor, final Position source, final piecePositions.put(destination, targetPiece); piecePositions.remove(source); + + pieceDao.deleteByFileAndRank(destination.file(), destination.rank()); + pieceDao.updatePiecePosition(source.file(), source.rank(), destination.file(), destination.rank()); } private void validatePosition(final PieceColor pieceColor, final Position source, final Position destination) { diff --git a/src/main/java/domain/board/BoardInitializer.java b/src/main/java/domain/board/BoardInitializer.java index 967329d6e62..352624171bd 100644 --- a/src/main/java/domain/board/BoardInitializer.java +++ b/src/main/java/domain/board/BoardInitializer.java @@ -6,24 +6,14 @@ import java.util.HashMap; import java.util.Map; -import static domain.board.File.D; -import static domain.board.File.E; -import static domain.board.File.A; -import static domain.board.File.B; -import static domain.board.File.C; -import static domain.board.File.F; -import static domain.board.File.G; -import static domain.board.File.H; -import static domain.board.Rank.ONE; -import static domain.board.Rank.TWO; -import static domain.board.Rank.SEVEN; -import static domain.board.Rank.EIGHT; +import static domain.board.File.*; +import static domain.board.Rank.*; import static domain.piece.PieceColor.BLACK; import static domain.piece.PieceColor.WHITE; public class BoardInitializer { - public static Board initBoard() { - return new Board(new HashMap<>(initPieces())); + public static Map initBoard() { + return new HashMap<>(initPieces()); } private static Map initPieces() { diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index 1596681eed9..dae1ef479d4 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -1,7 +1,7 @@ package domain.game; +import dao.CurrentPlayerColorDao; import domain.board.Board; -import domain.board.BoardInitializer; import domain.board.Position; import domain.piece.Piece; import domain.piece.PieceColor; @@ -12,25 +12,34 @@ import static domain.piece.PieceColor.WHITE; public class ChessGame { + private final CurrentPlayerColorDao currentPlayerColorDao; private final Board board; private PieceColor currentColor = WHITE; private GameStatus gameStatus = GameStatus.WAITING; - public ChessGame(final Board board) { + public ChessGame(final CurrentPlayerColorDao currentPlayerColorDao, final Board board) { + this.currentPlayerColorDao = currentPlayerColorDao; this.board = board; } - public static ChessGame initGame() { - return new ChessGame(BoardInitializer.initBoard()); + public boolean existPrevGame() { + return board.existPrevPiecePositionsData(); } - public void gameStart() { - gameStatus = GameStatus.RUNNING; + public void createNewGame() { + board.createNewPiecePositions(); + currentPlayerColorDao.deletePlayerColor(); + currentPlayerColorDao.savePlayerColor(currentColor); } - public void gameEnd() { - gameStatus = GameStatus.END; + public void roadPrevGame() { + board.roadPrevPiecePositions(); + currentColor = currentPlayerColorDao.findPlayerColor(); + } + + public void gameStart() { + gameStatus = GameStatus.RUNNING; } public boolean isRunning() { @@ -49,6 +58,16 @@ public void movePiece(final Position source, final Position destination) { } currentColor = currentColor.toggle(); + currentPlayerColorDao.updatePlayerColor(currentColor); + } + + public void gameEnd() { + if (!board.isKingAlive(currentColor) || !board.isKingAlive(currentColor.toggle())) { + board.clear(); + currentPlayerColorDao.deletePlayerColor(); + } + + gameStatus = GameStatus.END; } public GameStatus gameStatus() { diff --git a/src/main/java/domain/game/Continue.java b/src/main/java/domain/game/Continue.java new file mode 100644 index 00000000000..037f14496bf --- /dev/null +++ b/src/main/java/domain/game/Continue.java @@ -0,0 +1,16 @@ +package domain.game; + +import controller.GameController; + +public class Continue implements GameCommand { + + @Override + public void execute(final GameController gameController) { + if (gameController.gameStatus() != GameStatus.WAITING) { + throw new IllegalArgumentException("지금 실행할 수 있는 명령어가 아닙니다."); + } + + gameController.roadPrevGame(); + gameController.start(); + } +} diff --git a/src/main/java/domain/game/GameCommandType.java b/src/main/java/domain/game/GameCommandType.java index 23b2272f7b6..be613f6a9ea 100644 --- a/src/main/java/domain/game/GameCommandType.java +++ b/src/main/java/domain/game/GameCommandType.java @@ -7,6 +7,8 @@ public enum GameCommandType { START("start", command -> new Start()), + CONTINUE("continue", command -> new Continue()), + NEW("new", command -> new New()), MOVE("move", GameCommandType::toMove), STATUS("status", command -> new Status()), END("end", command -> new End()); diff --git a/src/main/java/domain/game/New.java b/src/main/java/domain/game/New.java new file mode 100644 index 00000000000..6b2cf079469 --- /dev/null +++ b/src/main/java/domain/game/New.java @@ -0,0 +1,16 @@ +package domain.game; + +import controller.GameController; + +public class New implements GameCommand { + + @Override + public void execute(final GameController gameController) { + if (gameController.gameStatus() != GameStatus.WAITING) { + throw new IllegalArgumentException("지금 실행할 수 있는 명령어가 아닙니다."); + } + + gameController.createNewGame(); + gameController.start(); + } +} diff --git a/src/main/java/domain/game/Start.java b/src/main/java/domain/game/Start.java index 8f8ed6b713b..a577f0f2fce 100644 --- a/src/main/java/domain/game/Start.java +++ b/src/main/java/domain/game/Start.java @@ -10,6 +10,6 @@ public void execute(final GameController gameController) { throw new IllegalArgumentException("이미 게임이 진행중입니다."); } - gameController.start(); + gameController.buildGame(); } } diff --git a/src/main/java/domain/piece/PieceColor.java b/src/main/java/domain/piece/PieceColor.java index d4cb0e67321..f9f7d5b880b 100644 --- a/src/main/java/domain/piece/PieceColor.java +++ b/src/main/java/domain/piece/PieceColor.java @@ -1,8 +1,17 @@ package domain.piece; +import java.util.Arrays; + public enum PieceColor { BLACK, WHITE; + public static PieceColor of(final String value) { + return Arrays.stream(PieceColor.values()) + .filter(pieceColor -> pieceColor.name().equals(value)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않은 PieceColor 입니다.")); + } + public PieceColor toggle() { if (this == BLACK) { return WHITE; diff --git a/src/main/java/domain/piece/PieceType.java b/src/main/java/domain/piece/PieceType.java index f0a28bd6f6f..18c84e40024 100644 --- a/src/main/java/domain/piece/PieceType.java +++ b/src/main/java/domain/piece/PieceType.java @@ -1,20 +1,36 @@ package domain.piece; +import java.util.Arrays; +import java.util.function.Function; + public enum PieceType { - PAWN(1), - ROOK(5), - KNIGHT(2.5), - BISHOP(3), - QUEEN(9), - KING(0); + PAWN(1, Pawn::new), + ROOK(5, Rook::new), + KNIGHT(2.5, Knight::new), + BISHOP(3, Bishop::new), + QUEEN(9, Queen::new), + KING(0, King::new); - private double score; + private final double score; + private final Function convertPiece; - PieceType(final double score) { + PieceType(final double score, final Function convertPiece) { this.score = score; + this.convertPiece = convertPiece; + } + + public static PieceType of(final String value) { + return Arrays.stream(PieceType.values()) + .filter(pieceType -> pieceType.name().equals(value.toUpperCase())) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않은 PieceType 입니다.")); } public double score() { return score; } + + public Piece createPiece(final PieceColor pieceColor) { + return convertPiece.apply(pieceColor); + } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 3a6c7b8426b..690a10e1980 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -101,4 +101,11 @@ private String convertToGameResultMessage(final GameResult gameResult) { return "막상막하입니다!"; } + + public void printInputRoadGameMessage() { + System.out.println("> 이전 게임이 존재합니다. 불러오시겠습니까?"); + System.out.println("> 불러오기 : continue"); + System.out.println("> 새게임 : new"); + System.out.println(); + } } diff --git a/src/test/java/domain/board/BoardTest.java b/src/test/java/domain/board/BoardTest.java index 3afc8f424ab..55636a84eab 100644 --- a/src/test/java/domain/board/BoardTest.java +++ b/src/test/java/domain/board/BoardTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,7 +26,7 @@ void movePieceTest() { Position source = position(B, TWO); Position destination = position(B, FOUR); Map piecePositions = new HashMap<>(Map.of(source, new Pawn(WHITE))); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When board.movePiece(WHITE, source, destination); @@ -46,7 +47,7 @@ void movePieceWithRemoveEnemyPieceTest() { Rook myPiece = new Rook(WHITE); Pawn enemyPiece = new Pawn(BLACK); Map piecePositions = new HashMap<>(Map.of(source, myPiece, destination, enemyPiece)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When board.movePiece(WHITE, source, destination); @@ -63,9 +64,9 @@ void movePieceWithRemoveEnemyPieceTest() { @Test void throwExceptionWhenSourceEqualsDestinationTest() { // Given - Board board = BoardInitializer.initBoard(); Position source = new Position(B, TWO); Position destination = new Position(B, TWO); + Board board = board(Collections.emptyMap()); // When & Then assertThatThrownBy(() -> board.movePiece(WHITE, source, destination)) @@ -77,7 +78,7 @@ void throwExceptionWhenSourceEqualsDestinationTest() { @Test void throwExceptionWhenNotExistPieceSourceTest() { // Given - Board board = BoardInitializer.initBoard(); + Board board = board(Collections.emptyMap()); Position source = new Position(D, FIVE); Position destination = new Position(B, TWO); @@ -91,9 +92,9 @@ void throwExceptionWhenNotExistPieceSourceTest() { @Test void throwExceptionWhenMoveEnemyPieceTest() { // Given - Board board = BoardInitializer.initBoard(); Position source = new Position(B, SEVEN); Position destination = new Position(B, SIX); + Board board = board(Map.of(source, new Pawn(BLACK))); // When & Then assertThatThrownBy(() -> board.movePiece(WHITE, source, destination)) @@ -104,4 +105,8 @@ void throwExceptionWhenMoveEnemyPieceTest() { private Position position(final File file, final Rank rank) { return new Position(file, rank); } + + private Board board(final Map piecePositions) { + return new Board(new TestPieceDao(), piecePositions); + } } diff --git a/src/test/java/domain/board/TestPieceDao.java b/src/test/java/domain/board/TestPieceDao.java new file mode 100644 index 00000000000..de8cf93384f --- /dev/null +++ b/src/test/java/domain/board/TestPieceDao.java @@ -0,0 +1,40 @@ +package domain.board; + +import dao.PieceDao; +import dao.PieceEntity; + +import java.util.Collections; +import java.util.List; + +public class TestPieceDao implements PieceDao { + + @Override + public List findAll() { + return Collections.emptyList(); + } + + @Override + public boolean existPiecePositions() { + return false; + } + + @Override + public void savePiece(final PieceEntity piece) { + + } + + @Override + public void updatePiecePosition(final File sourceFile, final Rank sourceRank, final File destinationFile, final Rank destinationRank) { + + } + + @Override + public void deleteByFileAndRank(final File file, final Rank rank) { + + } + + @Override + public void deleteAll() { + + } +} diff --git a/src/test/java/domain/piece/BishopTest.java b/src/test/java/domain/piece/BishopTest.java index eaa0094bc04..155d683c0c5 100644 --- a/src/test/java/domain/piece/BishopTest.java +++ b/src/test/java/domain/piece/BishopTest.java @@ -1,9 +1,6 @@ package domain.piece; -import domain.board.Board; -import domain.board.File; -import domain.board.Position; -import domain.board.Rank; +import domain.board.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -26,7 +23,7 @@ void checkMovableTest(final Position source, final Position destination) { // Given Bishop bishop = new Bishop(PieceColor.WHITE); Map piecePositions = Map.of(position(File.C, Rank.ONE), new Rook(PieceColor.BLACK)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatCode(() -> bishop.move(source, destination, board)) @@ -49,7 +46,7 @@ void throwExceptionWhenInvalidDirectionTest(final Position source, final Positio // Given Bishop bishop = new Bishop(PieceColor.WHITE); Map piecePositions = Collections.emptyMap(); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() ->bishop.move(source, destination, board)) @@ -73,7 +70,7 @@ void throwExceptionWhenPathsHasPieceTest(final Position source, final Position d Map piecePositions = Map.of( position(File.C, Rank.THREE), new Bishop(PieceColor.BLACK) ); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> bishop.move(source, destination, board)) @@ -96,7 +93,7 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { Position destination = position(File.D, Rank.FOUR); Bishop bishop = new Bishop(PieceColor.WHITE); Map piecePositions = Map.of(destination, new Bishop(PieceColor.WHITE)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> bishop.move(source, destination, board)) @@ -107,4 +104,8 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { private static Position position(final File file, final Rank rank) { return new Position(file, rank); } + + private static Board board(final Map piecePositions) { + return new Board(new TestPieceDao(), piecePositions); + } } diff --git a/src/test/java/domain/piece/KingTest.java b/src/test/java/domain/piece/KingTest.java index 654784165c2..b1eb8a7c759 100644 --- a/src/test/java/domain/piece/KingTest.java +++ b/src/test/java/domain/piece/KingTest.java @@ -1,9 +1,6 @@ package domain.piece; -import domain.board.Board; -import domain.board.File; -import domain.board.Position; -import domain.board.Rank; +import domain.board.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -26,7 +23,7 @@ void checkMovableTest(final Position source, final Position destination) { // Given King king = new King(PieceColor.WHITE); Map piecePositions = Map.of(position(File.D, Rank.TWO), new Rook(PieceColor.BLACK)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatCode(() -> king.move(source, destination, board)) @@ -54,7 +51,7 @@ void throwExceptionWhenDistanceOverOrEqualTwo() { Position destination = position(File.B, Rank.SIX); King king = new King(PieceColor.WHITE); Map piecePositions = Collections.emptyMap(); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> king.move(source, destination, board)) @@ -70,7 +67,7 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { Position destination = position(File.B, Rank.THREE); King king = new King(PieceColor.WHITE); Map piecePositions = Map.of(destination, new Rook(PieceColor.WHITE)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> king.move(source, destination, board)) @@ -81,4 +78,8 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { private static Position position(final File file, final Rank rank) { return new Position(file, rank); } + + private Board board(final Map piecePositions) { + return new Board(new TestPieceDao(), piecePositions); + } } diff --git a/src/test/java/domain/piece/KnightTest.java b/src/test/java/domain/piece/KnightTest.java index 0b5bd219f08..13172152a60 100644 --- a/src/test/java/domain/piece/KnightTest.java +++ b/src/test/java/domain/piece/KnightTest.java @@ -1,9 +1,6 @@ package domain.piece; -import domain.board.Board; -import domain.board.File; -import domain.board.Position; -import domain.board.Rank; +import domain.board.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -21,7 +18,7 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { Position destination = new Position(File.D, Rank.SIX); Knight knight = new Knight(PieceColor.WHITE); Map piecePositions = Map.of(destination, new Rook(PieceColor.WHITE)); - Board board = new Board(piecePositions); + Board board = new Board(new TestPieceDao(), piecePositions); // When & Then assertThatThrownBy(() -> knight.move(source, destination, board)) diff --git a/src/test/java/domain/piece/PawnTest.java b/src/test/java/domain/piece/PawnTest.java index cb10e4144bc..c9c5da0f885 100644 --- a/src/test/java/domain/piece/PawnTest.java +++ b/src/test/java/domain/piece/PawnTest.java @@ -1,9 +1,6 @@ package domain.piece; -import domain.board.Board; -import domain.board.File; -import domain.board.Position; -import domain.board.Rank; +import domain.board.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -35,7 +32,7 @@ void checkMovableTest(final PieceColor pieceColor, final PieceColor enemyColor, position(E, FOUR), new Rook(enemyColor), position(C, FOUR), new Rook(enemyColor) ); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatCode(() -> pawn.move(source, destination, board)) @@ -62,7 +59,7 @@ void throwExceptionWhenCrossStepDestinationNotHasEnemyTest(final PieceColor piec // Given Pawn pawn = new Pawn(pieceColor); Map piecePositions = Collections.emptyMap(); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> pawn.move(source, destination, board)) @@ -86,7 +83,7 @@ void throwExceptionWhenOneStepSourceIsNotStartPositionTest(final PieceColor piec // Given Pawn pawn = new Pawn(pieceColor); Map piecePositions = Collections.emptyMap(); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> pawn.move(source, destination, board)) @@ -111,7 +108,7 @@ void throwExceptionWhenForwardPathHasPieceTest(final PieceColor pieceColor, fina position(D, THREE), new Rook(WHITE), position(C, THREE), new Rook(BLACK) ); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> pawn.move(source, destination, board)) @@ -129,4 +126,8 @@ private static Stream throwExceptionWhenForwardPathHasPieceTestCase() private static Position position(final File file, final Rank rank) { return new Position(file, rank); } + + private static Board board(final Map piecePositions) { + return new Board(new TestPieceDao(), piecePositions); + } } diff --git a/src/test/java/domain/piece/QueenTest.java b/src/test/java/domain/piece/QueenTest.java index 2d9c3f23116..75f5d5ddd3d 100644 --- a/src/test/java/domain/piece/QueenTest.java +++ b/src/test/java/domain/piece/QueenTest.java @@ -1,9 +1,6 @@ package domain.piece; -import domain.board.Board; -import domain.board.File; -import domain.board.Position; -import domain.board.Rank; +import domain.board.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -25,7 +22,7 @@ void checkMovableTest(final Position source, final Position destination) { // Given Queen queen = new Queen(PieceColor.WHITE); Map piecePositions = Map.of(position(File.D, Rank.TWO), new Rook(PieceColor.BLACK)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatCode(() -> queen.move(source, destination, board)) @@ -55,7 +52,7 @@ void throwExceptionWhenPathsHasPieceTest(final Position source, final Position d position(File.B, Rank.FOUR), new Bishop(PieceColor.BLACK), position(File.D, Rank.TWO), new Rook(PieceColor.WHITE) ); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> queen.move(source, destination, board)) @@ -78,7 +75,7 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { Position destination = position(File.B, Rank.SIX); Queen queen = new Queen(PieceColor.WHITE); Map piecePositions = Map.of(destination, new Rook(PieceColor.WHITE)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> queen.move(source, destination, board)) @@ -89,4 +86,8 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { private static Position position(final File file, final Rank rank) { return new Position(file, rank); } + + private static Board board(final Map piecePositions) { + return new Board(new TestPieceDao(), piecePositions); + } } diff --git a/src/test/java/domain/piece/RookTest.java b/src/test/java/domain/piece/RookTest.java index cd934d4523b..f27d3d4acfe 100644 --- a/src/test/java/domain/piece/RookTest.java +++ b/src/test/java/domain/piece/RookTest.java @@ -1,9 +1,6 @@ package domain.piece; -import domain.board.Board; -import domain.board.File; -import domain.board.Position; -import domain.board.Rank; +import domain.board.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -26,7 +23,7 @@ void checkMovableTest(final Position source, final Position destination) { // Given Rook rook = new Rook(PieceColor.WHITE); Map piecePositions = Map.of(position(File.D, Rank.TWO), new Rook(PieceColor.BLACK)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatCode(() -> rook.move(source, destination, board)) @@ -49,7 +46,7 @@ void throwExceptionWhenInvalidDirectionTest(final Position source, final Positio // Given Rook rook = new Rook(PieceColor.WHITE); Map piecePositions = Collections.emptyMap(); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() ->rook.move(source, destination, board)) @@ -74,7 +71,7 @@ void throwExceptionWhenPathsHasPieceTest(final Position source, final Position d position(File.B, Rank.FOUR), new Rook(PieceColor.BLACK), position(File.D, Rank.TWO), new Rook(PieceColor.WHITE) ); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> rook.move(source, destination, board)) @@ -97,7 +94,7 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { Position destination = position(File.B, Rank.SIX); Rook rook = new Rook(PieceColor.WHITE); Map piecePositions = Map.of(destination, new Rook(PieceColor.WHITE)); - Board board = new Board(piecePositions); + Board board = board(piecePositions); // When & Then assertThatThrownBy(() -> rook.move(source, destination, board)) @@ -108,4 +105,8 @@ void throwExceptionWhenDestinationHasTeamPieceTest() { private static Position position(final File file, final Rank rank) { return new Position(file, rank); } + + private static Board board(final Map piecePositions) { + return new Board(new TestPieceDao(), piecePositions); + } } From 1e00559ac173b7945933855c6e27a9b4ac0cfb17 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Fri, 29 Mar 2024 16:37:03 +0900 Subject: [PATCH 09/18] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B0=8F=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=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/database/JdbcConnectionPool.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/database/JdbcConnectionPool.java b/src/main/java/database/JdbcConnectionPool.java index 9dd94ce44bf..cc2222edd10 100644 --- a/src/main/java/database/JdbcConnectionPool.java +++ b/src/main/java/database/JdbcConnectionPool.java @@ -9,7 +9,6 @@ public class JdbcConnectionPool { private static final String FAILED_INITIALIZE = "커넥션 풀 초기화에 실패했습니다."; private static final String FAILED_TO_GET_CONNECTION = "커넥션 획득에 실패했습니다."; - private static final String FAILED_TO_TERMINATE = "종료에 실패했습니다."; private static final String FAILED_RELEASE = "커넥션 해제에 실패했습니다."; private static final int INITIAL_POOL_SIZE = 2; private static final JdbcConnectionPool INSTANCE = new JdbcConnectionPool(); @@ -65,15 +64,4 @@ public void releaseConnection(final Connection connection) { throw new RuntimeException(FAILED_RELEASE); } } - - public void shutdown() { - try { - for (Connection connection : pool) { - connection.close(); - } - } catch (SQLException e) { - throw new RuntimeException(FAILED_TO_TERMINATE); - } - pool.clear(); - } } From 0cc9b842f020c5d2cc92c726d9b8d9ef19c20a9e Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Fri, 29 Mar 2024 22:22:35 +0900 Subject: [PATCH 10/18] =?UTF-8?q?docs:=20DDL=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/database/init.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/database/init.sql diff --git a/src/main/java/database/init.sql b/src/main/java/database/init.sql new file mode 100644 index 00000000000..0b324a27339 --- /dev/null +++ b/src/main/java/database/init.sql @@ -0,0 +1,15 @@ +create table current_player_color +( + id bigint auto_increment primary key, + player_color varchar(50) not null +); + +create table piece +( + type varchar(50) not null, + `rank` varchar(50) not null, + file varchar(50) not null, + color varchar(50) not null, + id int auto_increment primary key +); + From 2a0e6d19adf097543389b5d854361b4b2cce9d58 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Sun, 31 Mar 2024 16:19:22 +0900 Subject: [PATCH 11/18] =?UTF-8?q?refactor:=20import=20=EC=99=80=EC=9D=BC?= =?UTF-8?q?=EB=93=9C=EC=B9=B4=EB=93=9C=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/controller/GameController.java | 6 +++- .../java/dao/MysqlCurrentPlayerColorDao.java | 6 +++- src/main/java/dao/MysqlPieceDao.java | 6 +++- .../java/domain/board/BoardInitializer.java | 23 +++++++++++++-- src/main/java/domain/piece/Rook.java | 1 - src/test/java/domain/board/BoardTest.java | 9 ++++-- src/test/java/domain/piece/BishopTest.java | 6 +++- src/test/java/domain/piece/KingTest.java | 6 +++- src/test/java/domain/piece/KnightTest.java | 6 +++- src/test/java/domain/piece/PawnTest.java | 28 +++++++++++++++--- src/test/java/domain/piece/QueenTest.java | 6 +++- src/test/java/domain/piece/RookTest.java | 6 +++- .../position/CommonMovementDirectionTest.java | 24 +++++++++++++-- .../position/KnightMovementDirectionTest.java | 28 +++++++++++++++--- .../position/PawnMovementDirectionTest.java | 29 +++++++++++++++---- 15 files changed, 160 insertions(+), 30 deletions(-) diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index 12b0713ee15..3a473669b2c 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -1,7 +1,11 @@ package controller; import domain.board.Position; -import domain.game.*; +import domain.game.ChessGame; +import domain.game.GameCommand; +import domain.game.GameCommandType; +import domain.game.GameScore; +import domain.game.GameStatus; import domain.piece.PieceColor; import dto.BoardDto; import view.InputView; diff --git a/src/main/java/dao/MysqlCurrentPlayerColorDao.java b/src/main/java/dao/MysqlCurrentPlayerColorDao.java index 851596f48b4..9932ff447aa 100644 --- a/src/main/java/dao/MysqlCurrentPlayerColorDao.java +++ b/src/main/java/dao/MysqlCurrentPlayerColorDao.java @@ -3,7 +3,11 @@ import database.JdbcConnectionPool; import domain.piece.PieceColor; -import java.sql.*; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; public class MysqlCurrentPlayerColorDao implements CurrentPlayerColorDao { private final JdbcConnectionPool connectionPool; diff --git a/src/main/java/dao/MysqlPieceDao.java b/src/main/java/dao/MysqlPieceDao.java index 3ca573c6b0e..b495fe3eea8 100644 --- a/src/main/java/dao/MysqlPieceDao.java +++ b/src/main/java/dao/MysqlPieceDao.java @@ -6,7 +6,11 @@ import domain.piece.PieceColor; import domain.piece.PieceType; -import java.sql.*; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/board/BoardInitializer.java b/src/main/java/domain/board/BoardInitializer.java index 352624171bd..9667c1857e4 100644 --- a/src/main/java/domain/board/BoardInitializer.java +++ b/src/main/java/domain/board/BoardInitializer.java @@ -1,13 +1,30 @@ package domain.board; -import domain.piece.*; + +import domain.piece.Bishop; +import domain.piece.King; +import domain.piece.Knight; +import domain.piece.Pawn; +import domain.piece.Piece; +import domain.piece.Queen; +import domain.piece.Rook; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import static domain.board.File.*; -import static domain.board.Rank.*; +import static domain.board.File.A; +import static domain.board.File.B; +import static domain.board.File.C; +import static domain.board.File.D; +import static domain.board.File.E; +import static domain.board.File.F; +import static domain.board.File.G; +import static domain.board.File.H; +import static domain.board.Rank.EIGHT; +import static domain.board.Rank.ONE; +import static domain.board.Rank.SEVEN; +import static domain.board.Rank.TWO; import static domain.piece.PieceColor.BLACK; import static domain.piece.PieceColor.WHITE; diff --git a/src/main/java/domain/piece/Rook.java b/src/main/java/domain/piece/Rook.java index 87dff8960d3..24729881d37 100644 --- a/src/main/java/domain/piece/Rook.java +++ b/src/main/java/domain/piece/Rook.java @@ -9,7 +9,6 @@ import static domain.piece.CommonMovementDirection.calculateDirection; import static domain.piece.PieceType.ROOK; - public class Rook extends Piece { private static final PieceType PIECE_TYPE = ROOK; diff --git a/src/test/java/domain/board/BoardTest.java b/src/test/java/domain/board/BoardTest.java index 55636a84eab..6967864eeb3 100644 --- a/src/test/java/domain/board/BoardTest.java +++ b/src/test/java/domain/board/BoardTest.java @@ -10,8 +10,13 @@ import java.util.HashMap; import java.util.Map; -import static domain.board.File.*; -import static domain.board.Rank.*; +import static domain.board.File.B; +import static domain.board.File.D; +import static domain.board.Rank.FIVE; +import static domain.board.Rank.FOUR; +import static domain.board.Rank.SEVEN; +import static domain.board.Rank.SIX; +import static domain.board.Rank.TWO; import static domain.piece.PieceColor.BLACK; import static domain.piece.PieceColor.WHITE; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/domain/piece/BishopTest.java b/src/test/java/domain/piece/BishopTest.java index 155d683c0c5..7844d6e9fcc 100644 --- a/src/test/java/domain/piece/BishopTest.java +++ b/src/test/java/domain/piece/BishopTest.java @@ -1,6 +1,10 @@ package domain.piece; -import domain.board.*; +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.board.TestPieceDao; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/domain/piece/KingTest.java b/src/test/java/domain/piece/KingTest.java index b1eb8a7c759..a402b31ee42 100644 --- a/src/test/java/domain/piece/KingTest.java +++ b/src/test/java/domain/piece/KingTest.java @@ -1,6 +1,10 @@ package domain.piece; -import domain.board.*; +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.board.TestPieceDao; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/domain/piece/KnightTest.java b/src/test/java/domain/piece/KnightTest.java index 13172152a60..a69fdc1d288 100644 --- a/src/test/java/domain/piece/KnightTest.java +++ b/src/test/java/domain/piece/KnightTest.java @@ -1,6 +1,10 @@ package domain.piece; -import domain.board.*; +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.board.TestPieceDao; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/piece/PawnTest.java b/src/test/java/domain/piece/PawnTest.java index c9c5da0f885..ea7435e796a 100644 --- a/src/test/java/domain/piece/PawnTest.java +++ b/src/test/java/domain/piece/PawnTest.java @@ -1,6 +1,10 @@ package domain.piece; -import domain.board.*; +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.board.TestPieceDao; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -10,11 +14,27 @@ import java.util.Map; import java.util.stream.Stream; +import static domain.board.File.C; +import static domain.board.File.D; +import static domain.board.File.E; +import static domain.board.File.F; +import static domain.board.File.G; +import static domain.board.Rank.FIVE; +import static domain.board.Rank.FOUR; +import static domain.board.Rank.SEVEN; +import static domain.board.Rank.SIX; +import static domain.board.Rank.THREE; +import static domain.board.Rank.TWO; +import static domain.piece.PawnMovementDirection.DOWN_LEFT; +import static domain.piece.PawnMovementDirection.DOWN_ONE_STEP; +import static domain.piece.PawnMovementDirection.DOWN_RIGHT; +import static domain.piece.PawnMovementDirection.DOWN_TWO_STEP; +import static domain.piece.PawnMovementDirection.UP_LEFT; +import static domain.piece.PawnMovementDirection.UP_ONE_STEP; +import static domain.piece.PawnMovementDirection.UP_RIGHT; +import static domain.piece.PawnMovementDirection.UP_TWO_STEP; import static domain.piece.PieceColor.BLACK; import static domain.piece.PieceColor.WHITE; -import static domain.board.File.*; -import static domain.piece.PawnMovementDirection.*; -import static domain.board.Rank.*; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/domain/piece/QueenTest.java b/src/test/java/domain/piece/QueenTest.java index 75f5d5ddd3d..3bb443f865d 100644 --- a/src/test/java/domain/piece/QueenTest.java +++ b/src/test/java/domain/piece/QueenTest.java @@ -1,6 +1,10 @@ package domain.piece; -import domain.board.*; +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.board.TestPieceDao; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/domain/piece/RookTest.java b/src/test/java/domain/piece/RookTest.java index f27d3d4acfe..7cdfebc7ba4 100644 --- a/src/test/java/domain/piece/RookTest.java +++ b/src/test/java/domain/piece/RookTest.java @@ -1,6 +1,10 @@ package domain.piece; -import domain.board.*; +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.board.Rank; +import domain.board.TestPieceDao; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/domain/position/CommonMovementDirectionTest.java b/src/test/java/domain/position/CommonMovementDirectionTest.java index 5b3adb309cd..7d0bf22eb45 100644 --- a/src/test/java/domain/position/CommonMovementDirectionTest.java +++ b/src/test/java/domain/position/CommonMovementDirectionTest.java @@ -11,9 +11,27 @@ import java.util.stream.Stream; -import static domain.board.File.*; -import static domain.piece.CommonMovementDirection.*; -import static domain.board.Rank.*; +import static domain.board.File.A; +import static domain.board.File.B; +import static domain.board.File.C; +import static domain.board.File.D; +import static domain.board.File.E; +import static domain.board.File.F; +import static domain.board.File.G; +import static domain.board.Rank.EIGHT; +import static domain.board.Rank.FIVE; +import static domain.board.Rank.FOUR; +import static domain.board.Rank.ONE; +import static domain.board.Rank.SIX; +import static domain.board.Rank.TWO; +import static domain.piece.CommonMovementDirection.DOWN; +import static domain.piece.CommonMovementDirection.DOWN_LEFT; +import static domain.piece.CommonMovementDirection.DOWN_RIGHT; +import static domain.piece.CommonMovementDirection.LEFT; +import static domain.piece.CommonMovementDirection.RIGHT; +import static domain.piece.CommonMovementDirection.UP; +import static domain.piece.CommonMovementDirection.UP_LEFT; +import static domain.piece.CommonMovementDirection.UP_RIGHT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/domain/position/KnightMovementDirectionTest.java b/src/test/java/domain/position/KnightMovementDirectionTest.java index c6586bf5697..a322b2a0ecb 100644 --- a/src/test/java/domain/position/KnightMovementDirectionTest.java +++ b/src/test/java/domain/position/KnightMovementDirectionTest.java @@ -11,9 +11,29 @@ import java.util.stream.Stream; -import static domain.board.File.*; -import static domain.piece.KnightMovementDirection.*; -import static domain.board.Rank.*; +import static domain.board.File.A; +import static domain.board.File.B; +import static domain.board.File.C; +import static domain.board.File.D; +import static domain.board.File.E; +import static domain.board.File.F; +import static domain.board.File.G; +import static domain.board.Rank.EIGHT; +import static domain.board.Rank.FIVE; +import static domain.board.Rank.FOUR; +import static domain.board.Rank.ONE; +import static domain.board.Rank.SIX; +import static domain.board.Rank.THREE; +import static domain.board.Rank.TWO; +import static domain.piece.KnightMovementDirection.DOWN_LEFT; +import static domain.piece.KnightMovementDirection.DOWN_RIGHT; +import static domain.piece.KnightMovementDirection.LEFT_DOWN; +import static domain.piece.KnightMovementDirection.LEFT_UP; +import static domain.piece.KnightMovementDirection.RIGHT_DOWN; +import static domain.piece.KnightMovementDirection.RIGHT_UP; +import static domain.piece.KnightMovementDirection.UP_LEFT; +import static domain.piece.KnightMovementDirection.UP_RIGHT; +import static domain.piece.KnightMovementDirection.calculateDirection; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -48,7 +68,7 @@ private static Stream calculateDirectionTestCase() { @ParameterizedTest void throwExceptionWhenInvalidLocationTest(final Position source, final Position destination) { // When & Then - assertThatThrownBy(() -> KnightMovementDirection.calculateDirection(source, destination)) + assertThatThrownBy(() -> calculateDirection(source, destination)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("나이트가 이동할 수 없는 방향입니다."); } diff --git a/src/test/java/domain/position/PawnMovementDirectionTest.java b/src/test/java/domain/position/PawnMovementDirectionTest.java index b9d8893ddcb..fa1dde372fd 100644 --- a/src/test/java/domain/position/PawnMovementDirectionTest.java +++ b/src/test/java/domain/position/PawnMovementDirectionTest.java @@ -12,10 +12,29 @@ import java.util.stream.Stream; -import static domain.piece.PieceColor.*; -import static domain.board.File.*; -import static domain.piece.PawnMovementDirection.*; -import static domain.board.Rank.*; +import static domain.board.File.A; +import static domain.board.File.B; +import static domain.board.File.C; +import static domain.board.File.D; +import static domain.board.File.E; +import static domain.board.Rank.FIVE; +import static domain.board.Rank.FOUR; +import static domain.board.Rank.ONE; +import static domain.board.Rank.SEVEN; +import static domain.board.Rank.SIX; +import static domain.board.Rank.THREE; +import static domain.board.Rank.TWO; +import static domain.piece.PawnMovementDirection.DOWN_LEFT; +import static domain.piece.PawnMovementDirection.DOWN_ONE_STEP; +import static domain.piece.PawnMovementDirection.DOWN_RIGHT; +import static domain.piece.PawnMovementDirection.DOWN_TWO_STEP; +import static domain.piece.PawnMovementDirection.UP_LEFT; +import static domain.piece.PawnMovementDirection.UP_ONE_STEP; +import static domain.piece.PawnMovementDirection.UP_RIGHT; +import static domain.piece.PawnMovementDirection.UP_TWO_STEP; +import static domain.piece.PawnMovementDirection.calculateDirection; +import static domain.piece.PieceColor.BLACK; +import static domain.piece.PieceColor.WHITE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -50,7 +69,7 @@ private static Stream calculateDirectionTestCase() { @ParameterizedTest void throwExceptionWhenInputInvalidPositionTest(final PieceColor pieceColor, final Position source, final Position destination) { // When & Then - assertThatThrownBy(() -> PawnMovementDirection.calculateDirection(pieceColor, source, destination)) + assertThatThrownBy(() -> calculateDirection(pieceColor, source, destination)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(pieceColor.name() + "색상의 폰이 이동할 수 없는 방향입니다."); } From 71b147861ec0c853a056740bb3675a6e2c9b9ebe Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Sun, 31 Mar 2024 16:20:31 +0900 Subject: [PATCH 12/18] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?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/controller/GameController.java | 6 +++--- src/main/java/domain/game/ChessGame.java | 2 +- src/main/java/domain/game/New.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index 3a473669b2c..49d54a3e639 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -52,12 +52,12 @@ public void buildGame() { gameCommand.execute(this); return; } - createNewGame(); + createChessGame(); start(); } - public void createNewGame() { - chessGame.createNewGame(); + public void createChessGame() { + chessGame.createChessGame(); } public void roadPrevGame() { diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index dae1ef479d4..ca29c0c73b9 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -27,7 +27,7 @@ public boolean existPrevGame() { return board.existPrevPiecePositionsData(); } - public void createNewGame() { + public void createChessGame() { board.createNewPiecePositions(); currentPlayerColorDao.deletePlayerColor(); currentPlayerColorDao.savePlayerColor(currentColor); diff --git a/src/main/java/domain/game/New.java b/src/main/java/domain/game/New.java index 6b2cf079469..c9a3c7a803b 100644 --- a/src/main/java/domain/game/New.java +++ b/src/main/java/domain/game/New.java @@ -10,7 +10,7 @@ public void execute(final GameController gameController) { throw new IllegalArgumentException("지금 실행할 수 있는 명령어가 아닙니다."); } - gameController.createNewGame(); + gameController.createChessGame(); gameController.start(); } } From 5fa072e9ee7700543fac6b9958d39a401f3e8273 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Sun, 31 Mar 2024 16:23:29 +0900 Subject: [PATCH 13/18] =?UTF-8?q?refactor:=20MysqlCurrentPlayerColorDao=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20&=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?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/Application.java | 6 +++--- src/main/java/dao/CurrentPlayerColorDao.java | 14 -------------- src/main/java/dao/PieceColorDao.java | 14 ++++++++++++++ ...orDao.java => ProductionPieceColorDao.java} | 12 ++++++------ src/main/java/domain/game/ChessGame.java | 18 +++++++++--------- 5 files changed, 32 insertions(+), 32 deletions(-) delete mode 100644 src/main/java/dao/CurrentPlayerColorDao.java create mode 100644 src/main/java/dao/PieceColorDao.java rename src/main/java/dao/{MysqlCurrentPlayerColorDao.java => ProductionPieceColorDao.java} (88%) diff --git a/src/main/java/Application.java b/src/main/java/Application.java index 3b1f3b9eab7..c0008ebeb66 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,5 +1,5 @@ import controller.GameController; -import dao.MysqlCurrentPlayerColorDao; +import dao.ProductionPieceColorDao; import dao.MysqlPieceDao; import database.JdbcConnectionPool; import domain.board.Board; @@ -19,8 +19,8 @@ public static void main(String[] args) { MysqlPieceDao mysqlPieceDao = new MysqlPieceDao(connectionPool); Board board = new Board(mysqlPieceDao, initialPiecePositions); - MysqlCurrentPlayerColorDao mysqlCurrentPlayerColorDao = new MysqlCurrentPlayerColorDao(connectionPool); - ChessGame chessGame = new ChessGame(mysqlCurrentPlayerColorDao, board); + ProductionPieceColorDao productionPieceColorDao = new ProductionPieceColorDao(connectionPool); + ChessGame chessGame = new ChessGame(productionPieceColorDao, board); GameController gameController = new GameController(new InputView(), new OutputView(), chessGame); gameController.run(); diff --git a/src/main/java/dao/CurrentPlayerColorDao.java b/src/main/java/dao/CurrentPlayerColorDao.java deleted file mode 100644 index 0967ce86189..00000000000 --- a/src/main/java/dao/CurrentPlayerColorDao.java +++ /dev/null @@ -1,14 +0,0 @@ -package dao; - -import domain.piece.PieceColor; - -public interface CurrentPlayerColorDao { - - PieceColor findPlayerColor(); - - void savePlayerColor(PieceColor playerColor); - - void updatePlayerColor(PieceColor changeColor); - - void deletePlayerColor(); -} diff --git a/src/main/java/dao/PieceColorDao.java b/src/main/java/dao/PieceColorDao.java new file mode 100644 index 00000000000..3c292f735d3 --- /dev/null +++ b/src/main/java/dao/PieceColorDao.java @@ -0,0 +1,14 @@ +package dao; + +import domain.piece.PieceColor; + +public interface PieceColorDao { + + PieceColor find(); + + void save(PieceColor playerColor); + + void update(PieceColor changeColor); + + void delete(); +} diff --git a/src/main/java/dao/MysqlCurrentPlayerColorDao.java b/src/main/java/dao/ProductionPieceColorDao.java similarity index 88% rename from src/main/java/dao/MysqlCurrentPlayerColorDao.java rename to src/main/java/dao/ProductionPieceColorDao.java index 9932ff447aa..9d07cb3db5d 100644 --- a/src/main/java/dao/MysqlCurrentPlayerColorDao.java +++ b/src/main/java/dao/ProductionPieceColorDao.java @@ -9,15 +9,15 @@ import java.sql.SQLException; import java.sql.Statement; -public class MysqlCurrentPlayerColorDao implements CurrentPlayerColorDao { +public class ProductionPieceColorDao implements PieceColorDao { private final JdbcConnectionPool connectionPool; - public MysqlCurrentPlayerColorDao(final JdbcConnectionPool connectionPool) { + public ProductionPieceColorDao(final JdbcConnectionPool connectionPool) { this.connectionPool = connectionPool; } @Override - public PieceColor findPlayerColor() { + public PieceColor find() { final String query = "SELECT * FROM current_player_color"; Connection connection = connectionPool.getConnection(); @@ -32,7 +32,7 @@ public PieceColor findPlayerColor() { } @Override - public void savePlayerColor(final PieceColor playerColor) { + public void save(final PieceColor playerColor) { final String query = "INSERT INTO current_player_color (player_color) VALUES (?)"; Connection connection = connectionPool.getConnection(); @@ -55,7 +55,7 @@ private PieceColor convertPieceColor(final ResultSet resultSet) throws SQLExcept } @Override - public void updatePlayerColor(final PieceColor changeColor) { + public void update(final PieceColor changeColor) { final String query = "UPDATE current_player_color SET player_color=?"; Connection connection = connectionPool.getConnection(); @@ -70,7 +70,7 @@ public void updatePlayerColor(final PieceColor changeColor) { } @Override - public void deletePlayerColor() { + public void delete() { final String query = "DELETE FROM current_player_color"; Connection connection = connectionPool.getConnection(); diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index ca29c0c73b9..9f4d7ab4d8d 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -1,6 +1,6 @@ package domain.game; -import dao.CurrentPlayerColorDao; +import dao.PieceColorDao; import domain.board.Board; import domain.board.Position; import domain.piece.Piece; @@ -12,14 +12,14 @@ import static domain.piece.PieceColor.WHITE; public class ChessGame { - private final CurrentPlayerColorDao currentPlayerColorDao; + private final PieceColorDao pieceColorDao; private final Board board; private PieceColor currentColor = WHITE; private GameStatus gameStatus = GameStatus.WAITING; - public ChessGame(final CurrentPlayerColorDao currentPlayerColorDao, final Board board) { - this.currentPlayerColorDao = currentPlayerColorDao; + public ChessGame(final PieceColorDao pieceColorDao, final Board board) { + this.pieceColorDao = pieceColorDao; this.board = board; } @@ -29,13 +29,13 @@ public boolean existPrevGame() { public void createChessGame() { board.createNewPiecePositions(); - currentPlayerColorDao.deletePlayerColor(); - currentPlayerColorDao.savePlayerColor(currentColor); + pieceColorDao.delete(); + pieceColorDao.save(currentColor); } public void roadPrevGame() { board.roadPrevPiecePositions(); - currentColor = currentPlayerColorDao.findPlayerColor(); + currentColor = pieceColorDao.find(); } public void gameStart() { @@ -58,13 +58,13 @@ public void movePiece(final Position source, final Position destination) { } currentColor = currentColor.toggle(); - currentPlayerColorDao.updatePlayerColor(currentColor); + pieceColorDao.update(currentColor); } public void gameEnd() { if (!board.isKingAlive(currentColor) || !board.isKingAlive(currentColor.toggle())) { board.clear(); - currentPlayerColorDao.deletePlayerColor(); + pieceColorDao.delete(); } gameStatus = GameStatus.END; From bd273506487dd6780b02385d8c85144538fd2fb9 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Sun, 31 Mar 2024 16:32:11 +0900 Subject: [PATCH 14/18] =?UTF-8?q?refactor:=20MysqlPieceDao=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20*=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=A7=A4=EC=A7=81=20?= =?UTF-8?q?=EB=A6=AC=ED=84=B0=EB=9F=B4=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/Application.java | 10 ++--- src/main/java/dao/PieceDao.java | 6 +-- ...lPieceDao.java => ProductionPieceDao.java} | 40 +++++++++++-------- ...orDao.java => ProductionTurnColorDao.java} | 16 +++++--- .../{PieceColorDao.java => TurnColorDao.java} | 2 +- src/main/java/domain/board/Board.java | 6 +-- src/main/java/domain/game/ChessGame.java | 18 ++++----- src/test/java/domain/board/TestPieceDao.java | 6 +-- 8 files changed, 59 insertions(+), 45 deletions(-) rename src/main/java/dao/{MysqlPieceDao.java => ProductionPieceDao.java} (73%) rename src/main/java/dao/{ProductionPieceColorDao.java => ProductionTurnColorDao.java} (83%) rename src/main/java/dao/{PieceColorDao.java => TurnColorDao.java} (84%) diff --git a/src/main/java/Application.java b/src/main/java/Application.java index c0008ebeb66..6fed251b7ff 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,6 +1,6 @@ import controller.GameController; -import dao.ProductionPieceColorDao; -import dao.MysqlPieceDao; +import dao.ProductionTurnColorDao; +import dao.ProductionPieceDao; import database.JdbcConnectionPool; import domain.board.Board; import domain.board.BoardInitializer; @@ -16,10 +16,10 @@ public class Application { public static void main(String[] args) { JdbcConnectionPool connectionPool = JdbcConnectionPool.getInstance(); Map initialPiecePositions = BoardInitializer.initBoard(); - MysqlPieceDao mysqlPieceDao = new MysqlPieceDao(connectionPool); - Board board = new Board(mysqlPieceDao, initialPiecePositions); + ProductionPieceDao productionPieceDao = new ProductionPieceDao(connectionPool); + Board board = new Board(productionPieceDao, initialPiecePositions); - ProductionPieceColorDao productionPieceColorDao = new ProductionPieceColorDao(connectionPool); + ProductionTurnColorDao productionPieceColorDao = new ProductionTurnColorDao(connectionPool); ChessGame chessGame = new ChessGame(productionPieceColorDao, board); GameController gameController = new GameController(new InputView(), new OutputView(), chessGame); diff --git a/src/main/java/dao/PieceDao.java b/src/main/java/dao/PieceDao.java index 103b4041f88..154bd61ffd5 100644 --- a/src/main/java/dao/PieceDao.java +++ b/src/main/java/dao/PieceDao.java @@ -11,11 +11,11 @@ public interface PieceDao { boolean existPiecePositions(); - void savePiece(PieceEntity piece); + void save(PieceEntity piece); - void updatePiecePosition(File sourceFile, Rank sourceRank, File destinationFile, Rank destinationRank); + void update(File sourceFile, Rank sourceRank, File destinationFile, Rank destinationRank); - void deleteByFileAndRank(File file, Rank rank); + void delete(File file, Rank rank); void deleteAll(); } diff --git a/src/main/java/dao/MysqlPieceDao.java b/src/main/java/dao/ProductionPieceDao.java similarity index 73% rename from src/main/java/dao/MysqlPieceDao.java rename to src/main/java/dao/ProductionPieceDao.java index b495fe3eea8..d444241e20b 100644 --- a/src/main/java/dao/MysqlPieceDao.java +++ b/src/main/java/dao/ProductionPieceDao.java @@ -14,10 +14,18 @@ import java.util.ArrayList; import java.util.List; -public class MysqlPieceDao implements PieceDao { +public class ProductionPieceDao implements PieceDao { + private static final int PIECE_COUNT_INDEX = 1; + private static final int FILE_INDEX = 1; + private static final int RANK_INDEX = 2; + private static final int PIECE_TYPE_INDEX = 3; + private static final int PIECE_COLOR_INDEX = 4; + private static final int DESTINATION_FILE_INDEX = 3; + private static final int DESTINATION_RANK_INDEX = 4; + private final JdbcConnectionPool connectionPool; - public MysqlPieceDao(final JdbcConnectionPool connectionPool) { + public ProductionPieceDao(final JdbcConnectionPool connectionPool) { this.connectionPool = connectionPool; } @@ -58,7 +66,7 @@ public boolean existPiecePositions() { try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { - int pieceCount = resultSet.getInt(1); + int pieceCount = resultSet.getInt(PIECE_COUNT_INDEX); return pieceCount > 0; } @@ -71,15 +79,15 @@ public boolean existPiecePositions() { } @Override - public void savePiece(final PieceEntity piece) { + public void save(final PieceEntity piece) { final String query = "INSERT INTO piece (file, `rank`, type, color) VALUES (?, ?, ?, ?)"; Connection connection = connectionPool.getConnection(); try (final PreparedStatement preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) { - preparedStatement.setString(1, piece.file().name()); - preparedStatement.setInt(2, piece.rank().value()); - preparedStatement.setString(3, piece.pieceType().name()); - preparedStatement.setString(4, piece.pieceColor().name()); + preparedStatement.setString(FILE_INDEX, piece.file().name()); + preparedStatement.setInt(RANK_INDEX, piece.rank().value()); + preparedStatement.setString(PIECE_TYPE_INDEX, piece.pieceType().name()); + preparedStatement.setString(PIECE_COLOR_INDEX, piece.pieceColor().name()); preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); @@ -89,15 +97,15 @@ public void savePiece(final PieceEntity piece) { } @Override - public void updatePiecePosition(final File sourceFile, final Rank sourceRank, final File destinationFile, final Rank destinationRank) { + public void update(final File sourceFile, final Rank sourceRank, final File destinationFile, final Rank destinationRank) { final String query = "UPDATE piece SET file=?, `rank`=? WHERE file=? AND `rank`=?"; Connection connection = connectionPool.getConnection(); try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { - preparedStatement.setString(1, destinationFile.name()); - preparedStatement.setInt(2, destinationRank.value()); - preparedStatement.setString(3, sourceFile.name()); - preparedStatement.setInt(4, sourceRank.value()); + preparedStatement.setString(FILE_INDEX, destinationFile.name()); + preparedStatement.setInt(RANK_INDEX, destinationRank.value()); + preparedStatement.setString(DESTINATION_FILE_INDEX, sourceFile.name()); + preparedStatement.setInt(DESTINATION_RANK_INDEX, sourceRank.value()); preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); @@ -107,13 +115,13 @@ public void updatePiecePosition(final File sourceFile, final Rank sourceRank, fi } @Override - public void deleteByFileAndRank(final File file, final Rank rank) { + public void delete(final File file, final Rank rank) { final String query = "DELETE FROM piece WHERE file=? AND `rank`=?"; Connection connection = connectionPool.getConnection(); try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) { - preparedStatement.setString(1, file.name()); - preparedStatement.setInt(2, rank.value()); + preparedStatement.setString(FILE_INDEX, file.name()); + preparedStatement.setInt(RANK_INDEX, rank.value()); preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); diff --git a/src/main/java/dao/ProductionPieceColorDao.java b/src/main/java/dao/ProductionTurnColorDao.java similarity index 83% rename from src/main/java/dao/ProductionPieceColorDao.java rename to src/main/java/dao/ProductionTurnColorDao.java index 9d07cb3db5d..a51177661c8 100644 --- a/src/main/java/dao/ProductionPieceColorDao.java +++ b/src/main/java/dao/ProductionTurnColorDao.java @@ -9,10 +9,12 @@ import java.sql.SQLException; import java.sql.Statement; -public class ProductionPieceColorDao implements PieceColorDao { +public class ProductionTurnColorDao implements TurnColorDao { + private static final int PIECE_COLOR_INDEX = 2; + private final JdbcConnectionPool connectionPool; - public ProductionPieceColorDao(final JdbcConnectionPool connectionPool) { + public ProductionTurnColorDao(final JdbcConnectionPool connectionPool) { this.connectionPool = connectionPool; } @@ -47,11 +49,15 @@ public void save(final PieceColor playerColor) { } private PieceColor convertPieceColor(final ResultSet resultSet) throws SQLException { - if (resultSet.next()) { - return PieceColor.of(resultSet.getString(2)); + if (!existData(resultSet)) { + throw new IllegalArgumentException("현재 플레이어의 색상 정보가 존재하지 않습니다."); } - throw new IllegalArgumentException("현재 플레이어의 색상 정보가 존재하지 않습니다."); + return PieceColor.of(resultSet.getString(PIECE_COLOR_INDEX)); + } + + private boolean existData(final ResultSet resultSet) throws SQLException { + return resultSet.next(); } @Override diff --git a/src/main/java/dao/PieceColorDao.java b/src/main/java/dao/TurnColorDao.java similarity index 84% rename from src/main/java/dao/PieceColorDao.java rename to src/main/java/dao/TurnColorDao.java index 3c292f735d3..a6b9c774159 100644 --- a/src/main/java/dao/PieceColorDao.java +++ b/src/main/java/dao/TurnColorDao.java @@ -2,7 +2,7 @@ import domain.piece.PieceColor; -public interface PieceColorDao { +public interface TurnColorDao { PieceColor find(); diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java index 3f98ae79460..4544d84040b 100644 --- a/src/main/java/domain/board/Board.java +++ b/src/main/java/domain/board/Board.java @@ -28,7 +28,7 @@ public void createNewPiecePositions() { piecePositions.entrySet() .stream() .map(entry -> convertPieceEntity(entry.getValue(), entry.getKey())) - .forEach(pieceDao::savePiece); + .forEach(pieceDao::save); } public void roadPrevPiecePositions() { @@ -61,8 +61,8 @@ public void movePiece(final PieceColor pieceColor, final Position source, final piecePositions.put(destination, targetPiece); piecePositions.remove(source); - pieceDao.deleteByFileAndRank(destination.file(), destination.rank()); - pieceDao.updatePiecePosition(source.file(), source.rank(), destination.file(), destination.rank()); + pieceDao.delete(destination.file(), destination.rank()); + pieceDao.update(source.file(), source.rank(), destination.file(), destination.rank()); } private void validatePosition(final PieceColor pieceColor, final Position source, final Position destination) { diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index 9f4d7ab4d8d..6054ea9ed0b 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -1,6 +1,6 @@ package domain.game; -import dao.PieceColorDao; +import dao.TurnColorDao; import domain.board.Board; import domain.board.Position; import domain.piece.Piece; @@ -12,14 +12,14 @@ import static domain.piece.PieceColor.WHITE; public class ChessGame { - private final PieceColorDao pieceColorDao; + private final TurnColorDao turnColorDao; private final Board board; private PieceColor currentColor = WHITE; private GameStatus gameStatus = GameStatus.WAITING; - public ChessGame(final PieceColorDao pieceColorDao, final Board board) { - this.pieceColorDao = pieceColorDao; + public ChessGame(final TurnColorDao turnColorDao, final Board board) { + this.turnColorDao = turnColorDao; this.board = board; } @@ -29,13 +29,13 @@ public boolean existPrevGame() { public void createChessGame() { board.createNewPiecePositions(); - pieceColorDao.delete(); - pieceColorDao.save(currentColor); + turnColorDao.delete(); + turnColorDao.save(currentColor); } public void roadPrevGame() { board.roadPrevPiecePositions(); - currentColor = pieceColorDao.find(); + currentColor = turnColorDao.find(); } public void gameStart() { @@ -58,13 +58,13 @@ public void movePiece(final Position source, final Position destination) { } currentColor = currentColor.toggle(); - pieceColorDao.update(currentColor); + turnColorDao.update(currentColor); } public void gameEnd() { if (!board.isKingAlive(currentColor) || !board.isKingAlive(currentColor.toggle())) { board.clear(); - pieceColorDao.delete(); + turnColorDao.delete(); } gameStatus = GameStatus.END; diff --git a/src/test/java/domain/board/TestPieceDao.java b/src/test/java/domain/board/TestPieceDao.java index de8cf93384f..ae5aa4d9aa1 100644 --- a/src/test/java/domain/board/TestPieceDao.java +++ b/src/test/java/domain/board/TestPieceDao.java @@ -19,17 +19,17 @@ public boolean existPiecePositions() { } @Override - public void savePiece(final PieceEntity piece) { + public void save(final PieceEntity piece) { } @Override - public void updatePiecePosition(final File sourceFile, final Rank sourceRank, final File destinationFile, final Rank destinationRank) { + public void update(final File sourceFile, final Rank sourceRank, final File destinationFile, final Rank destinationRank) { } @Override - public void deleteByFileAndRank(final File file, final Rank rank) { + public void delete(final File file, final Rank rank) { } From 215a75a86304f7513d69b2ed3705e9307ca610e0 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Sun, 31 Mar 2024 16:34:30 +0900 Subject: [PATCH 15/18] =?UTF-8?q?refactor:=20DB=20=EA=B3=84=EC=A0=95?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=9D=80=EB=8B=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/DatabaseConfiguration.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/database/DatabaseConfiguration.java b/src/main/java/database/DatabaseConfiguration.java index 39982e9c84f..28fd37bcc50 100644 --- a/src/main/java/database/DatabaseConfiguration.java +++ b/src/main/java/database/DatabaseConfiguration.java @@ -1,5 +1,8 @@ package database; +import static config.DBCredential.DB_ID; +import static config.DBCredential.DB_PASSWORD; + public class DatabaseConfiguration { private static final DatabaseConfiguration INSTANCE = new DatabaseConfiguration(); private final String MYSQL_CONNECT_URL_FORMAT = "jdbc:mysql://%s:%s/%s"; @@ -7,8 +10,8 @@ public class DatabaseConfiguration { private final String post = "3306"; private final String database = "chess"; private final String option = "?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"; - private final String username = "kelly"; - private final String password = "kellyPw1234!"; + private final String username = DB_ID; + private final String password = DB_PASSWORD; private DatabaseConfiguration() { } From 2c6e406c62d0cf34ccd1ee7d8cffbc71bfa7e074 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Sun, 31 Mar 2024 16:40:48 +0900 Subject: [PATCH 16/18] =?UTF-8?q?feat:=20=EC=BB=A4=EB=84=A5=EC=85=98?= =?UTF-8?q?=ED=92=80=20=EC=A2=85=EB=A3=8C=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Application.java | 2 ++ src/main/java/database/JdbcConnectionPool.java | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/Application.java b/src/main/java/Application.java index 6fed251b7ff..a3d60303428 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -24,5 +24,7 @@ public static void main(String[] args) { GameController gameController = new GameController(new InputView(), new OutputView(), chessGame); gameController.run(); + + connectionPool.close(); } } diff --git a/src/main/java/database/JdbcConnectionPool.java b/src/main/java/database/JdbcConnectionPool.java index cc2222edd10..a3709d33d3e 100644 --- a/src/main/java/database/JdbcConnectionPool.java +++ b/src/main/java/database/JdbcConnectionPool.java @@ -3,6 +3,7 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.Collections; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -10,6 +11,7 @@ public class JdbcConnectionPool { private static final String FAILED_INITIALIZE = "커넥션 풀 초기화에 실패했습니다."; private static final String FAILED_TO_GET_CONNECTION = "커넥션 획득에 실패했습니다."; private static final String FAILED_RELEASE = "커넥션 해제에 실패했습니다."; + private static final String FAILED_CLOSE = "커넥션 종료에 실패했습니다."; private static final int INITIAL_POOL_SIZE = 2; private static final JdbcConnectionPool INSTANCE = new JdbcConnectionPool(); private final DatabaseConfiguration configuration; @@ -64,4 +66,14 @@ public void releaseConnection(final Connection connection) { throw new RuntimeException(FAILED_RELEASE); } } + + public void close() { + try { + for (Connection collection : pool) { + collection.close(); + } + } catch (SQLException e) { + throw new RuntimeException(FAILED_CLOSE); + } + } } From f408e3e236008cd1ffede7da5b3cbc91f82c27a1 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Sun, 31 Mar 2024 16:43:19 +0900 Subject: [PATCH 17/18] =?UTF-8?q?refactor:=20DDL=EB=AC=B8=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/init.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/database/init.sql b/src/main/java/database/init.sql index 0b324a27339..b32adbadeb8 100644 --- a/src/main/java/database/init.sql +++ b/src/main/java/database/init.sql @@ -1,15 +1,15 @@ create table current_player_color ( id bigint auto_increment primary key, - player_color varchar(50) not null + player_color varchar(10) not null ); create table piece ( - type varchar(50) not null, - `rank` varchar(50) not null, - file varchar(50) not null, - color varchar(50) not null, + type varchar(10) not null, + `rank` varchar(10) not null, + file varchar(10) not null, + color varchar(10) not null, id int auto_increment primary key ); From 7a47024c2e226dbf50abbc01b85a17ef0ee5ed97 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 1 Apr 2024 03:02:34 +0900 Subject: [PATCH 18/18] =?UTF-8?q?refactor:=20=EA=B0=81=20=EC=A7=84?= =?UTF-8?q?=EC=98=81=EB=B3=84=20=EC=A0=90=EC=88=98=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=EC=9D=84=20Score=EC=97=90=20=EC=9C=84?= =?UTF-8?q?=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/database/JdbcConnectionPool.java | 1 - src/main/java/domain/board/Board.java | 41 -------------- src/main/java/domain/game/ChessGame.java | 6 +-- src/main/java/domain/game/Score.java | 53 +++++++++++++++++++ 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/src/main/java/database/JdbcConnectionPool.java b/src/main/java/database/JdbcConnectionPool.java index a3709d33d3e..1817bef61dd 100644 --- a/src/main/java/database/JdbcConnectionPool.java +++ b/src/main/java/database/JdbcConnectionPool.java @@ -3,7 +3,6 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; -import java.util.Collections; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java index 4544d84040b..fbf272fd6b3 100644 --- a/src/main/java/domain/board/Board.java +++ b/src/main/java/domain/board/Board.java @@ -97,45 +97,4 @@ public boolean isKingAlive(final PieceColor targetColor) { .stream() .anyMatch(piece -> piece.isTeam(targetColor) && piece.pieceType() == PieceType.KING); } - - public double calculateTeamScore(final PieceColor teamColor) { - double existTargetColorPiecesScoreSum = sumExistTargetPiecesScore(teamColor); - - return existTargetColorPiecesScoreSum - calculateDecreaseScoreForExistSameFilePawns(teamColor); - } - - private double sumExistTargetPiecesScore(final PieceColor pieceColor) { - return piecePositions.values() - .stream() - .filter(piece -> piece.isTeam(pieceColor)) - .mapToDouble(Piece::score) - .sum(); - } - - private double calculateDecreaseScoreForExistSameFilePawns(final PieceColor pieceColor) { - List targetColorPawnPositions = piecePositions.entrySet() - .stream() - .filter(entry -> entry.getValue().isTeam(pieceColor) && entry.getValue().matchPieceType(PieceType.PAWN)) - .map(Map.Entry::getKey) - .toList(); - - int sameFilePawnCount = 0; - for (File file : File.values()) { - sameFilePawnCount += calculateSameFilePawnCount(targetColorPawnPositions, file); - } - - return 0.5 * sameFilePawnCount; - } - - private int calculateSameFilePawnCount(final List pawnPositions, final File file) { - int sameFilePawnCount = (int) pawnPositions.stream() - .filter(position -> position.file() == file) - .count(); - - if (sameFilePawnCount <= 1) { - return 0; - } - - return sameFilePawnCount; - } } diff --git a/src/main/java/domain/game/ChessGame.java b/src/main/java/domain/game/ChessGame.java index 6054ea9ed0b..18c4c2a2ce4 100644 --- a/src/main/java/domain/game/ChessGame.java +++ b/src/main/java/domain/game/ChessGame.java @@ -75,10 +75,10 @@ public GameStatus gameStatus() { } public GameScore getGameResult() { - double whiteTeamScore = board.calculateTeamScore(WHITE); - double blackTeamScore = board.calculateTeamScore(BLACK); + Score whiteTeamScore = Score.of(board, WHITE); + Score blackTeamScore = Score.of(board, BLACK); - return new GameScore(new Score(whiteTeamScore), new Score(blackTeamScore)); + return new GameScore(whiteTeamScore, blackTeamScore); } public PieceColor currentPlayTeamColor() { diff --git a/src/main/java/domain/game/Score.java b/src/main/java/domain/game/Score.java index aabc7c72c55..b2ee2095962 100644 --- a/src/main/java/domain/game/Score.java +++ b/src/main/java/domain/game/Score.java @@ -1,7 +1,60 @@ package domain.game; +import domain.board.Board; +import domain.board.File; +import domain.board.Position; +import domain.piece.Piece; +import domain.piece.PieceColor; +import domain.piece.PieceType; + +import java.util.List; +import java.util.Map; + public record Score(double value) { + public static Score of(final Board board, final PieceColor pieceColor) { + Map piecePositions = board.piecePositions(); + double existTargetColorPiecesScoreSum = sumExistTargetPiecesScore(piecePositions, pieceColor); + double scoreValue = existTargetColorPiecesScoreSum - calculateDecreaseScoreForExistSameFilePawns(piecePositions, pieceColor); + + return new Score(scoreValue); + } + + private static double sumExistTargetPiecesScore(final Map piecePositions, final PieceColor pieceColor) { + return piecePositions.values() + .stream() + .filter(piece -> piece.isTeam(pieceColor)) + .mapToDouble(Piece::score) + .sum(); + } + + private static double calculateDecreaseScoreForExistSameFilePawns(final Map piecePositions, final PieceColor pieceColor) { + List targetColorPawnPositions = piecePositions.entrySet() + .stream() + .filter(entry -> entry.getValue().isTeam(pieceColor) && entry.getValue().matchPieceType(PieceType.PAWN)) + .map(Map.Entry::getKey) + .toList(); + + int sameFilePawnCount = 0; + for (File file : File.values()) { + sameFilePawnCount += calculateSameFilePawnCount(targetColorPawnPositions, file); + } + + return 0.5 * sameFilePawnCount; + } + + private static int calculateSameFilePawnCount(final List pawnPositions, final File file) { + int sameFilePawnCount = (int) pawnPositions.stream() + .filter(position -> position.file() == file) + .count(); + + if (sameFilePawnCount <= 1) { + return 0; + } + + return sameFilePawnCount; + } + public boolean isBigger(final Score other) { return this.value > other.value; }