diff --git a/.gitignore b/.gitignore index 6c01878138..ce976069bc 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ out/ ### VS Code ### .vscode/ + +# SQLite +*.db diff --git a/README.md b/README.md index 77d78d3e88..7f73110ce5 100644 --- a/README.md +++ b/README.md @@ -59,3 +59,100 @@ - [X] 타 진영 기물 선택 시 예외 - [X] 빈 칸 선택시 예외 - [X] 도착 지점에 아군이 있는 경우 예외 + +## 2.1단계 - 기물의 확장 + +### 궁성 영역 정의 + +- [x] 궁성(宮城) 영역 정의 + - 궁성 좌표 범위 정의 +- [x] 궁성 내 대각선 경로 정의 + - 궁성에는 대각선 선이 존재하며, 대각선 이동은 이 선 위의 좌표에서만 가능 + - 대각선 허용 좌표쌍 정의 +- [x] 특정 좌표가 궁성 내부인지 판별하는 기능 +- [x] 두 좌표 간 궁성 대각선 이동 가능 여부 판별 + +### 궁성 관련 기물 이동 규칙 변경 및 추가 + +- [x] 장(King) + - 궁성 내부에서만 이동 가능 + - 상하좌우 1칸 이동 (기존 유지) + - 궁성 대각선 경로에서 대각선 1칸 이동 가능 +- [x] 사(Advisor) + - 궁성 내부에서만 이동 가능 + - 상하좌우 1칸 이동 (기존 유지) + - 궁성 대각선 경로에서 대각선 1칸 이동 가능 +- [x] 차(Tank) + - 기존 직선 이동 규칙 유지 + - 궁성 내부에서 대각선 경로 이동 가능 +- [x] 포(Cannon) + - 기존 직선 + 1개 기물 넘기 규칙 유지 + - 궁성 내부에서 대각선 경로 이동 가능 +- [x] 졸(Soldier) + - 기존 전진 + 좌우 이동 유지 + - 궁성 내부에서 대각선 "전진" 이동 가능 + - 후진 대각선 불가 + +### 왕 잡힘 시 게임 종료 + +- [x] 이동 결과 도착 칸의 기물이 왕(King)인지 판별 +- [x] 왕이 잡혔을 때 승자 진영 결정 +- [x] JanggiGame에 게임 종료 상태 반영 + +### 점수 계산 + +- [x] Piece에 `score()` 추상 메서드 추가 +- [x] 기물별 점수 정의 +- [x] Board에서 특정 진영 점수 합산 기능 +- [x] 양 진영 점수 조회 기능 + +## 2.2단계 - DB 적용 + +### 1. DB 연결 설정 + +- [ ] SQLite JDBC 의존성 추가 +- [ ] DB 연결 관리 클래스 생성 + +### 2. 테이블 설계 및 적용 + +- [ ] game 테이블 — 게임 메타 정보 +- [ ] piece 테이블 — 기물 배치 정보 + +### 3. GameRepository 구현 + +- [ ] save + - 새 게임 생성 + - 생성된 gameID 반환 +- [ ] findById + - 특정 게임 조회 +- [ ] findPlayingGames + - 진행 중인 게임 목록 조회 +- [ ] updateTurn + - 턴 변경 시 갱신 +- [ ] updateFinished + - 게임 종료 시 갱신 + +### 4. PieceRepository 구현 + +- [ ] saveAll + - 게임의 모든 기물을 DB에 일괄 저장 + +- [ ] findByGameId + - 특정 게임의 모든 기물 조회 + +- [ ] movePiece + - 기물 이동 반영 + - 도착 칸 기물 삭제 + - 출발 칸 기물 좌표 갱신 + +### 5. Controller 흐름 변경 (새 게임 / 이어하기) + +- [ ] 프로그램 시작 시 + - DB에서 진행 중인 게임 목록 조회 + - 존재하면 → 이어하기 / 새 게임 선택 + - 없으면 → 새 게임 시작 +- [ ] 매 턴 이동 후 + - DB에 기물 이동 반영 + - DB에 턴 변경 반영 +- [ ] 게임 종료 시 + - DB에 게임 종료 상태 반영 diff --git a/build.gradle b/build.gradle index ce846f70cc..4930ffa71b 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,9 @@ repositories { } dependencies { + implementation 'org.xerial:sqlite-jdbc:3.45.1.0' + implementation 'org.slf4j:slf4j-simple:2.0.12' + testImplementation platform('org.junit:junit-bom:5.11.4') testImplementation platform('org.assertj:assertj-bom:3.27.3') testImplementation('org.junit.jupiter:junit-jupiter') diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java index 59fa0394a5..a56089f6e9 100644 --- a/src/main/java/janggi/Application.java +++ b/src/main/java/janggi/Application.java @@ -1,9 +1,11 @@ package janggi; import janggi.controller.JanggiController; +import janggi.infrastructure.DBInitializer; public class Application { public static void main(String[] args) { + DBInitializer.initialize(); JanggiController janggiController = new JanggiController(); janggiController.run(); } diff --git a/src/main/java/janggi/controller/JanggiController.java b/src/main/java/janggi/controller/JanggiController.java index 406a6e6c19..94ea8d3137 100644 --- a/src/main/java/janggi/controller/JanggiController.java +++ b/src/main/java/janggi/controller/JanggiController.java @@ -3,43 +3,111 @@ import janggi.domain.Board; import janggi.domain.JanggiGame; import janggi.domain.dto.MoveCommand; +import janggi.domain.piece.Piece; import janggi.domain.piece.Team; import janggi.domain.vo.Position; +import janggi.repository.GameRepository; +import janggi.repository.PieceRepository; import janggi.view.InputView; +import janggi.view.OutputView; +import java.util.List; public class JanggiController { private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + private final GameRepository gameRepository = new GameRepository(); + private final PieceRepository pieceRepository = new PieceRepository(); + + private JanggiGame janggiGame; + private Board board; public void run() { - Board board = new Board(); + initialize(); + playGame(); + printResult(); + } + + private void initialize() { + List playingGames = gameRepository.findPlayingGames(); - JanggiGame janggiGame = new JanggiGame(); + if (playingGames.isEmpty()) { + startNewGame(); + return; + } + + int choice = inputView.readGameChoice(playingGames); + if (choice == playingGames.size() + 1) { + startNewGame(); + return; + } + resumeGame(playingGames.get(choice - 1)); + } + + private void startNewGame() { + janggiGame = new JanggiGame(); + Long gameId = gameRepository.save(janggiGame); + janggiGame.assignId(gameId); + + board = new Board(); + pieceRepository.saveAll(gameId, board); + + outputView.printGameStart(gameId); + } + + private void resumeGame(JanggiGame game) { + janggiGame = game; + board = pieceRepository.findByGameId(game.findGameId()); + + outputView.printResume(game.findGameId(), game.findCurrentTeam()); + } + + private void playGame() { while (!janggiGame.isFinished()) { Team currentTeam = janggiGame.findCurrentTeam(); - attemptMove(board, currentTeam); - janggiGame.changeTurn(); + outputView.printCurrentTurn(currentTeam); + outputView.printBoard(board); + attemptMove(currentTeam); + + if (!janggiGame.isFinished()) { + janggiGame.changeTurn(); + gameRepository.updateTurn(janggiGame); + } } } - private void attemptMove(Board board, Team currentTeam) { + private void attemptMove(Team currentTeam) { boolean moved = false; while (!moved) { - moved = tryMove(board, currentTeam); + moved = tryMove(currentTeam); } } - private boolean tryMove(Board board, Team currentTeam) { + private boolean tryMove(Team currentTeam) { try { MoveCommand moveCommand = inputView.readMovePositions(); Position from = moveCommand.getFrom(); Position to = moveCommand.getTo(); - board.move(from, to, currentTeam); + + Piece captured = board.move(from, to, currentTeam); + pieceRepository.movePiece(janggiGame.findGameId(), from, to); + + janggiGame.processCaptured(captured); + if (janggiGame.isFinished()) { + gameRepository.updateFinished(janggiGame); + } + return true; } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); return false; } } + + private void printResult() { + outputView.printGameEnd(janggiGame.findWinner()); + outputView.printScore(Team.CHO, board.calculateScore(Team.CHO)); + outputView.printScore(Team.HAN, board.calculateScore(Team.HAN)); + } } diff --git a/src/main/java/janggi/domain/Board.java b/src/main/java/janggi/domain/Board.java index 2f2d72ba0d..c85a5f9467 100644 --- a/src/main/java/janggi/domain/Board.java +++ b/src/main/java/janggi/domain/Board.java @@ -4,6 +4,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Team; import janggi.domain.vo.Position; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,7 +44,27 @@ public boolean isEmptyPosition(Position position) { return findByPosition(position).isEmpty(); } - public void move(Position from, Position to, Team currentTeam) { + @Override + public boolean isInsidePalace(Position position) { + return Palace.isInsidePalace(position); + } + + @Override + public boolean canMoveDiagonallyInPalace(Position from, Position to) { + return Palace.canMoveDiagonally(from, to); + } + + @Override + public boolean isDiagonalInPalace(Position from, Position to) { + return Palace.isDiagonalInPalace(from, to); + } + + @Override + public Position getDiagonalMidpointInPalace(Position from, Position to) { + return Palace.getDiagonalMidpoint(from, to); + } + + public Piece move(Position from, Position to, Team currentTeam) { Piece fromPiece = findByPosition(from); Piece toPiece = findByPosition(to); @@ -55,6 +76,8 @@ public void move(Position from, Position to, Team currentTeam) { place(from, new EmptyPosition(Team.OTHER)); place(to, fromPiece); + + return toPiece; } private void place(Position position, Piece piece) { @@ -75,4 +98,35 @@ private void validateCommonMove(Team currentTeam, Piece fromPiece, Piece toPiece throw new IllegalArgumentException("이미 도착지점에 플레이어님의 진영 기물이 있습니다."); } } + + public int calculateScore(Team team) { + int totalScore = 0; + for (List row : board) { + for (Piece piece : row) { + if (piece.isSameTeam(team)) { + totalScore += piece.score(); + } + } + } + return totalScore; + } + + + // 빈곳은 db에 따로 저장안하려는데.... + // board 자료구조 자체가 map이었으면..... + public Map findAllPieces() { + Map pieces = new HashMap<>(); + for (int row = 0; row < board.size(); row++) { + for (int col = 0; col < board.get(row).size(); col++) { + Position position = new Position(row, col); + Piece piece = findByPosition(position); + if (!piece.isEmpty()) { + pieces.put(position, piece); + } + } + } + return pieces; + } + + } diff --git a/src/main/java/janggi/domain/BoardView.java b/src/main/java/janggi/domain/BoardView.java index e4bbad9255..743136c155 100644 --- a/src/main/java/janggi/domain/BoardView.java +++ b/src/main/java/janggi/domain/BoardView.java @@ -9,4 +9,12 @@ public interface BoardView { boolean isEmptyPosition(Position position); + boolean isInsidePalace(Position position); + + boolean canMoveDiagonallyInPalace(Position from, Position to); + + boolean isDiagonalInPalace(Position from, Position to); + + Position getDiagonalMidpointInPalace(Position from, Position to); + } diff --git a/src/main/java/janggi/domain/JanggiGame.java b/src/main/java/janggi/domain/JanggiGame.java index d9d7f87c68..10bdff41ab 100644 --- a/src/main/java/janggi/domain/JanggiGame.java +++ b/src/main/java/janggi/domain/JanggiGame.java @@ -1,10 +1,26 @@ package janggi.domain; +import janggi.domain.piece.King; +import janggi.domain.piece.Piece; import janggi.domain.piece.Team; public class JanggiGame { + private Long gameId; private Team currentTurn = Team.CHO; private boolean isFinished = false; + private Team winner = null; + + // 기본 생성자 - 새게임 + public JanggiGame() { + } + + // DB 복원용 + public JanggiGame(Long gameId, Team currentTurn, boolean isFinished, Team winner) { + this.gameId = gameId; + this.currentTurn = currentTurn; + this.isFinished = isFinished; + this.winner = winner; + } public boolean isFinished() { return isFinished; @@ -21,4 +37,33 @@ public void changeTurn() { } currentTurn = Team.CHO; } + + public Long findGameId() { + return gameId; + } + + public void assignId(Long gameId) { + this.gameId = gameId; + } + + + public Team findWinner() { + return winner; + } + + public void processCaptured(Piece capturedPiece) { + if (!(capturedPiece instanceof King)) { + return; + } + isFinished = true; + winner = findOpponent(capturedPiece.findTeam()); + } + + private Team findOpponent(Team team) { + if (team == Team.CHO) { + return Team.HAN; + } + return Team.CHO; + } + } diff --git a/src/main/java/janggi/domain/Palace.java b/src/main/java/janggi/domain/Palace.java new file mode 100644 index 0000000000..da30bfbca1 --- /dev/null +++ b/src/main/java/janggi/domain/Palace.java @@ -0,0 +1,101 @@ +package janggi.domain; + +import janggi.domain.vo.Position; + +public class Palace { + + private static final int HAN_MIN_ROW = 0; + private static final int HAN_MAX_ROW = 2; + private static final int CHO_MIN_ROW = 7; + private static final int CHO_MAX_ROW = 9; + private static final int MIN_COL = 3; + private static final int MAX_COL = 5; + + private static final Position HAN_CENTER = new Position(1, 4); + private static final Position CHO_CENTER = new Position(8, 4); + + public static boolean isInsidePalace(Position position) { + int row = position.getRow(); + int col = position.getCol(); + if (col < MIN_COL || col > MAX_COL) { + return false; + } + return isHanPalace(row) || isChoPalace(row); + } + + public static boolean canMoveDiagonally(Position from, Position to) { + if (!isInsidePalace(from) || !isInsidePalace(to)) { + return false; + } + if (!isDiagonalOneStep(from, to)) { + return false; + } + return involvesCenter(from, to); + } + + private static boolean isHanPalace(int row) { + return row >= HAN_MIN_ROW && row <= HAN_MAX_ROW; + } + + private static boolean isChoPalace(int row) { + return row >= CHO_MIN_ROW && row <= CHO_MAX_ROW; + } + + private static boolean isDiagonalOneStep(Position from, Position to) { + int rowDiff = Math.abs(to.getRow() - from.getRow()); + int colDiff = Math.abs(to.getCol() - from.getCol()); + return rowDiff == 1 && colDiff == 1; + } + + // 대각서 이동은 중앙을 무조건 지나야만 한다. + private static boolean involvesCenter(Position from, Position to) { + return from.equals(HAN_CENTER) || to.equals(HAN_CENTER) + || from.equals(CHO_CENTER) || to.equals(CHO_CENTER); + } + + public static boolean isDiagonalInPalace(Position from, Position to) { + if (!isInsidePalace(from) || !isInsidePalace(to)) { + return false; + } + if (!isSamePalace(from, to)) { + return false; + } + return canMoveDiagonally(from, to) || isTwoStepDiagonal(from, to); + } + + public static Position getDiagonalMidpoint(Position from, Position to) { + if (!isTwoStepDiagonal(from, to)) { + return null; + } + int midRow = (from.getRow() + to.getRow()) / 2; + int midCol = (from.getCol() + to.getCol()) / 2; + return new Position(midRow, midCol); + } + + private static boolean isSamePalace(Position a, Position b) { + return (isHanPalace(a.getRow()) && isHanPalace(b.getRow())) + || (isChoPalace(a.getRow()) && isChoPalace(b.getRow())); + } + + private static boolean isTwoStepDiagonal(Position from, Position to) { + if (!isInsidePalace(from) || !isInsidePalace(to)) { + return false; + } + if (!isSamePalace(from, to)) { + return false; + } + int rowDiff = Math.abs(to.getRow() - from.getRow()); + int colDiff = Math.abs(to.getCol() - from.getCol()); + if (rowDiff != 2 || colDiff != 2) { + return false; + } + + // 2칸 대각선이면 중간 경유지가 궁성 중앙이어야 함 + int midRow = (from.getRow() + to.getRow()) / 2; + int midCol = (from.getCol() + to.getCol()) / 2; + Position midpoint = new Position(midRow, midCol); + return midpoint.equals(HAN_CENTER) || midpoint.equals(CHO_CENTER); + } + + +} diff --git a/src/main/java/janggi/domain/mouveRule/AdvisorMoveRule.java b/src/main/java/janggi/domain/mouveRule/AdvisorMoveRule.java new file mode 100644 index 0000000000..b8e5681e25 --- /dev/null +++ b/src/main/java/janggi/domain/mouveRule/AdvisorMoveRule.java @@ -0,0 +1,21 @@ +package janggi.domain.mouveRule; + +import janggi.domain.BoardView; +import janggi.domain.vo.Position; + +public class AdvisorMoveRule implements MoveRule { + + @Override + public boolean canMove(Position from, Position to, BoardView board) { + if (!board.isInsidePalace(from) || !board.isInsidePalace(to)) { + return false; + } + return isStraightOneStep(from, to) || board.canMoveDiagonallyInPalace(from, to); + } + + private boolean isStraightOneStep(Position from, Position to) { + int rowDis = Math.abs(to.getRow() - from.getRow()); + int colDis = Math.abs(to.getCol() - from.getCol()); + return (rowDis + colDis) == 1; + } +} diff --git a/src/main/java/janggi/domain/mouveRule/CannonMoveRule.java b/src/main/java/janggi/domain/mouveRule/CannonMoveRule.java index 90eb9eefc7..282a0f8ba7 100644 --- a/src/main/java/janggi/domain/mouveRule/CannonMoveRule.java +++ b/src/main/java/janggi/domain/mouveRule/CannonMoveRule.java @@ -9,15 +9,38 @@ public class CannonMoveRule implements MoveRule { @Override public boolean canMove(Position from, Position to, BoardView board) { + if (isStraightLine(from, to)) { + return canMoveStraight(from, to, board); + } + if (board.isDiagonalInPalace(from, to)) { + return canMovePalaceDiagonal(from, to, board); + } + + return true; + } + + private boolean canMovePalaceDiagonal(Position from, Position to, BoardView board) { + Position midpoint = board.getDiagonalMidpointInPalace(from, to); + if (midpoint == null) { + return false; + } + + if (board.isEmptyPosition(midpoint)) { + return false; + } + + Piece bridgePiece = board.findByPosition(midpoint); + Piece targetPiece = board.findByPosition(to); + return !isCannon(bridgePiece) && !isCannon(targetPiece); + } + + + private boolean canMoveStraight(Position from, Position to, BoardView board) { int fromRow = from.getRow(); int fromCol = from.getCol(); int toRow = to.getRow(); int toCol = to.getCol(); - if (!isStraightLine(fromRow, fromCol, toRow, toCol)) { - return false; - } - int jumpedPieceCount = countPiecesBetween(board, fromRow, fromCol, toRow, toCol); if (jumpedPieceCount != 1) { return false; @@ -25,14 +48,12 @@ public boolean canMove(Position from, Position to, BoardView board) { Piece bridgePiece = findBridgePiece(board, fromRow, fromCol, toRow, toCol); Piece targetPiece = board.findByPosition(to); - if (isCannon(bridgePiece) || isCannon(targetPiece)) { - return false; - } - return true; + return !isCannon(bridgePiece) && !isCannon(targetPiece); } - private boolean isStraightLine(int fromRow, int fromCol, int toRow, int toCol) { - return fromRow == toRow || fromCol == toCol; + + private boolean isStraightLine(Position from, Position to) { + return from.getRow() == to.getRow() || from.getCol() == to.getCol(); } private int countPiecesBetween(BoardView board, int fromRow, int fromCol, int toRow, int toCol) { diff --git a/src/main/java/janggi/domain/mouveRule/KingMoveRule.java b/src/main/java/janggi/domain/mouveRule/KingMoveRule.java new file mode 100644 index 0000000000..bbd6f2434a --- /dev/null +++ b/src/main/java/janggi/domain/mouveRule/KingMoveRule.java @@ -0,0 +1,21 @@ +package janggi.domain.mouveRule; + +import janggi.domain.BoardView; +import janggi.domain.vo.Position; + +public class KingMoveRule implements MoveRule { + + @Override + public boolean canMove(Position from, Position to, BoardView board) { + if (!board.isInsidePalace(from) || !board.isInsidePalace(to)) { + return false; + } + return isStraightOneStep(from, to) || board.canMoveDiagonallyInPalace(from, to); + } + + private boolean isStraightOneStep(Position from, Position to) { + int rowDis = Math.abs(to.getRow() - from.getRow()); + int colDis = Math.abs(to.getCol() - from.getCol()); + return (rowDis + colDis) == 1; + } +} diff --git a/src/main/java/janggi/domain/mouveRule/OneStepMoveRule.java b/src/main/java/janggi/domain/mouveRule/OneStepMoveRule.java deleted file mode 100644 index 338b2b1fe2..0000000000 --- a/src/main/java/janggi/domain/mouveRule/OneStepMoveRule.java +++ /dev/null @@ -1,14 +0,0 @@ -package janggi.domain.mouveRule; - - -import janggi.domain.BoardView; -import janggi.domain.vo.Position; - -public class OneStepMoveRule implements MoveRule { - @Override - public boolean canMove(Position from, Position to, BoardView board) { - int rowDis = Math.abs(to.getRow() - from.getRow()); - int colDis = Math.abs(to.getCol() - from.getCol()); - return rowDis <= 1 && colDis <= 1; - } -} diff --git a/src/main/java/janggi/domain/mouveRule/SoldierMoveRule.java b/src/main/java/janggi/domain/mouveRule/SoldierMoveRule.java index 7d934f51b4..372bb49d96 100644 --- a/src/main/java/janggi/domain/mouveRule/SoldierMoveRule.java +++ b/src/main/java/janggi/domain/mouveRule/SoldierMoveRule.java @@ -16,7 +16,12 @@ public SoldierMoveRule(Team team) { public boolean canMove(Position from, Position to, BoardView board) { int rowDis = to.getRow() - from.getRow(); int colDis = to.getCol() - from.getCol(); - return isForward(rowDis, colDis) || isSideStep(rowDis, colDis); + + if (isForward(rowDis, colDis) || isSideStep(rowDis, colDis)) { + return true; + } + + return isForwardDiagonalInPalace(from, to, rowDis, board); } // 한이 위쪽배치임 -> 행증가가 전진 @@ -30,4 +35,12 @@ private boolean isSideStep(int rowDis, int colDis) { return rowDis == 0 && Math.abs(colDis) == 1; } + private boolean isForwardDiagonalInPalace(Position from, Position to, int rowDis, BoardView board) { + if (!board.canMoveDiagonallyInPalace(from, to)) { + return false; + } + int forwardDirection = (team == Team.CHO) ? -1 : 1; + return rowDis == forwardDirection; + } + } diff --git a/src/main/java/janggi/domain/mouveRule/TankMoveRule.java b/src/main/java/janggi/domain/mouveRule/TankMoveRule.java index 775f5c44d7..930be8d691 100644 --- a/src/main/java/janggi/domain/mouveRule/TankMoveRule.java +++ b/src/main/java/janggi/domain/mouveRule/TankMoveRule.java @@ -6,30 +6,41 @@ public class TankMoveRule implements MoveRule { @Override public boolean canMove(Position from, Position to, BoardView board) { - int fromRow = from.getRow(); - int fromCol = from.getCol(); - int toRow = to.getRow(); - int toCol = to.getCol(); - - if (!isStraightLine(fromRow, fromCol, toRow, toCol)) { - return false; + if (isStraightLine(from, to)) { + return isStraightPathClear(board, from, to); + } + if (board.isDiagonalInPalace(from, to)) { + return isDiagonalPathClear(board, from, to); } + return false; + } - return isPathClear(board, fromRow, fromCol, toRow, toCol); + private boolean isDiagonalPathClear(BoardView board, Position from, Position to) { + Position midpoint = board.getDiagonalMidpointInPalace(from, to); + if (midpoint == null) { + return true; + } + return board.isEmptyPosition(midpoint); } - private boolean isStraightLine(int fromRow, int fromCol, int toRow, int toCol) { - return fromRow == toRow || fromCol == toCol; + private boolean isStraightLine(Position from, Position to) { + return from.getRow() == to.getRow() || from.getCol() == to.getCol(); } - private boolean isPathClear(BoardView board, int fromRow, int fromCol, int toRow, int toCol) { + + private boolean isStraightPathClear(BoardView board, Position from, Position to) { + int fromRow = from.getRow(); + int fromCol = from.getCol(); + int toRow = to.getRow(); + int toCol = to.getCol(); + if (fromRow == toRow) { return isHorizontalPathClear(board, fromRow, fromCol, toCol); } - return isVerticalPathClear(board, fromCol, fromRow, toRow); } + private boolean isHorizontalPathClear(BoardView board, int row, int fromCol, int toCol) { int start = Math.min(fromCol, toCol); int end = Math.max(fromCol, toCol); diff --git a/src/main/java/janggi/domain/piece/Advisor.java b/src/main/java/janggi/domain/piece/Advisor.java index d341ec5427..86a36dd859 100644 --- a/src/main/java/janggi/domain/piece/Advisor.java +++ b/src/main/java/janggi/domain/piece/Advisor.java @@ -1,7 +1,7 @@ package janggi.domain.piece; +import janggi.domain.mouveRule.KingMoveRule; import janggi.domain.mouveRule.MoveRule; -import janggi.domain.mouveRule.OneStepMoveRule; public class Advisor extends Piece { @@ -16,6 +16,12 @@ public String toString() { @Override public MoveRule moveRule() { - return new OneStepMoveRule(); + return new KingMoveRule(); } + + @Override + public int score() { + return 3; + } + } diff --git a/src/main/java/janggi/domain/piece/Cannon.java b/src/main/java/janggi/domain/piece/Cannon.java index 165b786055..88a68df497 100644 --- a/src/main/java/janggi/domain/piece/Cannon.java +++ b/src/main/java/janggi/domain/piece/Cannon.java @@ -18,4 +18,9 @@ public String toString() { public MoveRule moveRule() { return new CannonMoveRule(); } + + @Override + public int score() { + return 7; + } } diff --git a/src/main/java/janggi/domain/piece/Elephant.java b/src/main/java/janggi/domain/piece/Elephant.java index 9b96c64cae..1f93ae80ec 100644 --- a/src/main/java/janggi/domain/piece/Elephant.java +++ b/src/main/java/janggi/domain/piece/Elephant.java @@ -18,4 +18,9 @@ public String toString() { public MoveRule moveRule() { return new ElephantMoveRule(); } + + @Override + public int score() { + return 3; + } } diff --git a/src/main/java/janggi/domain/piece/EmptyPosition.java b/src/main/java/janggi/domain/piece/EmptyPosition.java index f3451cfac5..a5406b3c98 100644 --- a/src/main/java/janggi/domain/piece/EmptyPosition.java +++ b/src/main/java/janggi/domain/piece/EmptyPosition.java @@ -22,4 +22,14 @@ public String toString() { public MoveRule moveRule() { return null; } + + @Override + public int score() { + return 0; + } + + @Override + public String display() { + return " "; + } } diff --git a/src/main/java/janggi/domain/piece/Horse.java b/src/main/java/janggi/domain/piece/Horse.java index 950462be01..4fbc30e561 100644 --- a/src/main/java/janggi/domain/piece/Horse.java +++ b/src/main/java/janggi/domain/piece/Horse.java @@ -19,4 +19,9 @@ public MoveRule moveRule() { return new HorseMoveRule(); } + @Override + public int score() { + return 5; + } + } diff --git a/src/main/java/janggi/domain/piece/King.java b/src/main/java/janggi/domain/piece/King.java index 4e9350290d..ae5c11a71a 100644 --- a/src/main/java/janggi/domain/piece/King.java +++ b/src/main/java/janggi/domain/piece/King.java @@ -1,7 +1,7 @@ package janggi.domain.piece; +import janggi.domain.mouveRule.KingMoveRule; import janggi.domain.mouveRule.MoveRule; -import janggi.domain.mouveRule.OneStepMoveRule; public class King extends Piece { @@ -16,6 +16,11 @@ public String toString() { @Override public MoveRule moveRule() { - return new OneStepMoveRule(); + return new KingMoveRule(); + } + + @Override + public int score() { + return 0; } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 1400428381..98772c2ad3 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -29,7 +29,14 @@ public boolean canMove(Position from, Position to, BoardView board) { protected abstract MoveRule moveRule(); + public String display() { + return team.findPrefix() + toString(); + } + + @Override public abstract String toString(); + public abstract int score(); + } diff --git a/src/main/java/janggi/domain/piece/PieceType.java b/src/main/java/janggi/domain/piece/PieceType.java new file mode 100644 index 0000000000..290f59c48b --- /dev/null +++ b/src/main/java/janggi/domain/piece/PieceType.java @@ -0,0 +1,61 @@ +package janggi.domain.piece; + +public enum PieceType { + TANK, + HORSE, + ELEPHANT, + ADVISOR, + KING, + CANNON, + SOLDIER; + + public static PieceType from(Piece piece) { + if (piece instanceof Tank) { + return TANK; + } + if (piece instanceof Horse) { + return HORSE; + } + if (piece instanceof Elephant) { + return ELEPHANT; + } + if (piece instanceof Advisor) { + return ADVISOR; + } + if (piece instanceof King) { + return KING; + } + if (piece instanceof Cannon) { + return CANNON; + } + if (piece instanceof Soldier) { + return SOLDIER; + } + throw new IllegalArgumentException("알 수 없는 기물 타입입니다."); + } + + public Piece createPiece(Team team) { + if (this == TANK) { + return new Tank(team); + } + if (this == HORSE) { + return new Horse(team); + } + if (this == ELEPHANT) { + return new Elephant(team); + } + if (this == ADVISOR) { + return new Advisor(team); + } + if (this == KING) { + return new King(team); + } + if (this == CANNON) { + return new Cannon(team); + } + if (this == SOLDIER) { + return new Soldier(team); + } + throw new IllegalArgumentException("알 수 없는 기물 타입입니다."); + } +} diff --git a/src/main/java/janggi/domain/piece/Soldier.java b/src/main/java/janggi/domain/piece/Soldier.java index fc8d384d87..9d9428cd30 100644 --- a/src/main/java/janggi/domain/piece/Soldier.java +++ b/src/main/java/janggi/domain/piece/Soldier.java @@ -18,4 +18,9 @@ public String toString() { public MoveRule moveRule() { return new SoldierMoveRule(findTeam()); } + + @Override + public int score() { + return 2; + } } diff --git a/src/main/java/janggi/domain/piece/Tank.java b/src/main/java/janggi/domain/piece/Tank.java index 66248de249..7186cfe68d 100644 --- a/src/main/java/janggi/domain/piece/Tank.java +++ b/src/main/java/janggi/domain/piece/Tank.java @@ -18,4 +18,9 @@ public String toString() { public MoveRule moveRule() { return new TankMoveRule(); } + + @Override + public int score() { + return 13; + } } diff --git a/src/main/java/janggi/domain/piece/Team.java b/src/main/java/janggi/domain/piece/Team.java index 3e51bf138b..08240a9417 100644 --- a/src/main/java/janggi/domain/piece/Team.java +++ b/src/main/java/janggi/domain/piece/Team.java @@ -1,13 +1,20 @@ package janggi.domain.piece; public enum Team { - CHO("초"), - HAN("한"), - OTHER("빈"); + CHO("초", "C"), + HAN("한", "H"), + OTHER("빈", " "); private final String name; + private final String prefix; - Team(String name) { + Team(String name, String prefix) { this.name = name; + this.prefix = prefix; + } + + public String findPrefix() { + return prefix; } } + diff --git a/src/main/java/janggi/infrastructure/DBConnectionManager.java b/src/main/java/janggi/infrastructure/DBConnectionManager.java new file mode 100644 index 0000000000..c5173f4964 --- /dev/null +++ b/src/main/java/janggi/infrastructure/DBConnectionManager.java @@ -0,0 +1,18 @@ +package janggi.infrastructure; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class DBConnectionManager { + + private static final String URL = "jdbc:sqlite:janggi.db"; + + public static Connection getConnection() { + try { + return DriverManager.getConnection(URL); + } catch (SQLException e) { + throw new RuntimeException("DB 연결에 실패했습니다.", e); + } + } +} diff --git a/src/main/java/janggi/infrastructure/DBInitializer.java b/src/main/java/janggi/infrastructure/DBInitializer.java new file mode 100644 index 0000000000..d7d6a35d8d --- /dev/null +++ b/src/main/java/janggi/infrastructure/DBInitializer.java @@ -0,0 +1,43 @@ +package janggi.infrastructure; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +public class DBInitializer { + + public static void initialize() { + try (Connection connection = DBConnectionManager.getConnection(); + Statement statement = connection.createStatement()) { + + statement.execute(createGameTable()); + statement.execute(createPieceTable()); + + } catch (SQLException e) { + throw new RuntimeException("테이블 초기화에 실패했습니다.", e); + } + } + + private static String createGameTable() { + return "CREATE TABLE IF NOT EXISTS game (" + + "game_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "current_turn TEXT NOT NULL, " + + "game_status TEXT NOT NULL, " + + "winner TEXT, " + + "created_at TEXT DEFAULT (datetime('now', 'localtime')), " + + "updated_at TEXT DEFAULT (datetime('now', 'localtime'))" + + ")"; + } + + private static String createPieceTable() { + return "CREATE TABLE IF NOT EXISTS piece (" + + "piece_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "game_id INTEGER NOT NULL, " + + "row_pos INTEGER NOT NULL, " + + "col_pos INTEGER NOT NULL, " + + "piece_type TEXT NOT NULL, " + + "team TEXT NOT NULL, " + + "FOREIGN KEY (game_id) REFERENCES game(game_id)" + + ")"; + } +} diff --git a/src/main/java/janggi/repository/GameRepository.java b/src/main/java/janggi/repository/GameRepository.java new file mode 100644 index 0000000000..06d470a168 --- /dev/null +++ b/src/main/java/janggi/repository/GameRepository.java @@ -0,0 +1,134 @@ +package janggi.repository; + +import janggi.domain.JanggiGame; +import janggi.domain.piece.Team; +import janggi.infrastructure.DBConnectionManager; +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; + +public class GameRepository { + + public Long save(JanggiGame game) { + String sql = "INSERT INTO game (current_turn, game_status, winner) VALUES (?, ?, ?)"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + pstmt.setString(1, game.findCurrentTeam().name()); + pstmt.setString(2, toGameStatus(game)); + pstmt.setString(3, toWinnerString(game)); + pstmt.executeUpdate(); + + ResultSet rs = pstmt.getGeneratedKeys(); + if (rs.next()) { + return rs.getLong(1); + } + throw new SQLException("게임 ID 생성에 실패했습니다."); + + } catch (SQLException e) { + throw new RuntimeException("게임 저장에 실패했습니다.", e); + } + } + + public List findPlayingGames() { + String sql = "SELECT game_id, current_turn, game_status, winner FROM game WHERE game_status = 'PLAYING'"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + ResultSet rs = pstmt.executeQuery(); + List games = new ArrayList<>(); + + while (rs.next()) { + games.add(toJanggiGame(rs)); + } + return games; + + } catch (SQLException e) { + throw new RuntimeException("진행 중인 게임 조회에 실패했습니다.", e); + } + } + + public void updateTurn(JanggiGame game) { + String sql = "UPDATE game SET current_turn = ?, updated_at = datetime('now', 'localtime') WHERE game_id = ?"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setString(1, game.findCurrentTeam().name()); + pstmt.setLong(2, game.findGameId()); + pstmt.executeUpdate(); + + } catch (SQLException e) { + throw new RuntimeException("턴 갱신에 실패했습니다.", e); + } + } + + public void updateFinished(JanggiGame game) { + String sql = "UPDATE game SET game_status = 'FINISHED', winner = ?, updated_at = datetime('now', 'localtime') WHERE game_id = ?"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setString(1, toWinnerString(game)); + pstmt.setLong(2, game.findGameId()); + pstmt.executeUpdate(); + + } catch (SQLException e) { + throw new RuntimeException("게임 종료 갱신에 실패했습니다.", e); + } + } + + private JanggiGame toJanggiGame(ResultSet rs) throws SQLException { + Long gameId = rs.getLong("game_id"); + Team currentTurn = Team.valueOf(rs.getString("current_turn")); + String status = rs.getString("game_status"); + String winnerStr = rs.getString("winner"); + + boolean isFinished = "FINISHED".equals(status); + Team winner = (winnerStr != null) ? Team.valueOf(winnerStr) : null; + + return new JanggiGame(gameId, currentTurn, isFinished, winner); + } + + private String toGameStatus(JanggiGame game) { + if (game.isFinished()) { + return "FINISHED"; + } + return "PLAYING"; + } + + private String toWinnerString(JanggiGame game) { + if (game.findWinner() == null) { + return null; + } + return game.findWinner().name(); + } + + public JanggiGame findById(Long gameId) { // PR + String sql = "SELECT game_id, current_turn, game_status, winner FROM game WHERE game_id = ?"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setLong(1, gameId); + ResultSet rs = pstmt.executeQuery(); + + if (!rs.next()) { + throw new IllegalArgumentException("존재하지 않는 게임입니다. id=" + gameId); + } + + return toJanggiGame(rs); + + } catch (SQLException e) { + throw new RuntimeException("게임 조회에 실패했습니다.", e); + } + } + + +} diff --git a/src/main/java/janggi/repository/PieceRepository.java b/src/main/java/janggi/repository/PieceRepository.java new file mode 100644 index 0000000000..567b4066c3 --- /dev/null +++ b/src/main/java/janggi/repository/PieceRepository.java @@ -0,0 +1,103 @@ +package janggi.repository; + +import janggi.domain.Board; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceType; +import janggi.domain.piece.Team; +import janggi.domain.vo.Position; +import janggi.infrastructure.DBConnectionManager; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class PieceRepository { + + public void saveAll(Long gameId, Board board) { + String sql = "INSERT INTO piece (game_id, row_pos, col_pos, piece_type, team) VALUES (?, ?, ?, ?, ?)"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + Map pieces = board.findAllPieces(); + for (Map.Entry entry : pieces.entrySet()) { + Position position = entry.getKey(); + Piece piece = entry.getValue(); + + pstmt.setLong(1, gameId); + pstmt.setInt(2, position.getRow()); + pstmt.setInt(3, position.getCol()); + pstmt.setString(4, PieceType.from(piece).name()); + pstmt.setString(5, piece.findTeam().name()); + pstmt.executeUpdate(); + } + } catch (SQLException e) { + throw new RuntimeException("기물 저장에 실패했습니다.", e); + } + } + + public Board findByGameId(Long gameId) { + String sql = "SELECT row_pos, col_pos, piece_type, team FROM piece WHERE game_id = ?"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setLong(1, gameId); + ResultSet rs = pstmt.executeQuery(); + + Map pieces = new HashMap<>(); + while (rs.next()) { + Position position = new Position(rs.getInt("row_pos"), rs.getInt("col_pos")); + PieceType pieceType = PieceType.valueOf(rs.getString("piece_type")); + Team team = Team.valueOf(rs.getString("team")); + Piece piece = pieceType.createPiece(team); + pieces.put(position, piece); + } + return Board.of(pieces); + + } catch (SQLException e) { + throw new RuntimeException("기물 조회에 실패했습니다.", e); + } + } + + public void movePiece(Long gameId, Position from, Position to) { + deleteByPosition(gameId, to); + updatePosition(gameId, from, to); + } + + private void deleteByPosition(Long gameId, Position position) { + String sql = "DELETE FROM piece WHERE game_id = ? AND row_pos = ? AND col_pos = ?"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setLong(1, gameId); + pstmt.setInt(2, position.getRow()); + pstmt.setInt(3, position.getCol()); + pstmt.executeUpdate(); + + } catch (SQLException e) { + throw new RuntimeException("기물 삭제에 실패했습니다.", e); + } + } + + private void updatePosition(Long gameId, Position from, Position to) { + String sql = "UPDATE piece SET row_pos = ?, col_pos = ? WHERE game_id = ? AND row_pos = ? AND col_pos = ?"; + + try (Connection conn = DBConnectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setInt(1, to.getRow()); + pstmt.setInt(2, to.getCol()); + pstmt.setLong(3, gameId); + pstmt.setInt(4, from.getRow()); + pstmt.setInt(5, from.getCol()); + pstmt.executeUpdate(); + + } catch (SQLException e) { + throw new RuntimeException("기물 이동 갱신에 실패했습니다.", e); + } + } +} diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index 7a47eccf40..fc90fed0ae 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -1,7 +1,9 @@ package janggi.view; +import janggi.domain.JanggiGame; import janggi.domain.dto.MoveCommand; import java.util.Arrays; +import java.util.List; import java.util.Scanner; public class InputView { @@ -20,4 +22,17 @@ public MoveCommand readMovePositions() { return MoveCommand.from(numbers); } + public int readGameChoice(List playingGames) { + System.out.println("진행 중인 게임이 있습니다."); + for (int i = 0; i < playingGames.size(); i++) { + JanggiGame game = playingGames.get(i); + System.out.println((i + 1) + ". 게임 " + game.findGameId() + + " (현재 턴: " + game.findCurrentTeam() + ")"); + } + System.out.println((playingGames.size() + 1) + ". 새 게임 시작"); + System.out.println("번호를 입력해 주세요."); + + return Integer.parseInt(scanner.nextLine()); + } + } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java new file mode 100644 index 0000000000..85939cf4f5 --- /dev/null +++ b/src/main/java/janggi/view/OutputView.java @@ -0,0 +1,58 @@ +package janggi.view; + +import janggi.domain.BoardView; +import janggi.domain.piece.Team; +import janggi.domain.vo.Position; + +public class OutputView { + private static final int ROW_SIZE = 10; + private static final int COL_SIZE = 9; + + public void printGameStart(Long gameId) { + System.out.println("게임 " + gameId + "을(를) 시작합니다."); + } + + public void printResume(Long gameId, Team currentTurn) { + System.out.println("게임 " + gameId + "을(를) 이어서 진행합니다. 현재 턴: " + currentTurn); + } + + public void printCurrentTurn(Team team) { + System.out.println(team + "의 차례입니다."); + } + + public void printGameEnd(Team winner) { + System.out.println("게임이 종료되었습니다. 승자: " + winner); + } + + public void printScore(Team team, int score) { + System.out.println(team + " 점수: " + score); + } + + public void printBoard(BoardView board) { + System.out.println(); + printColumnHeader(); + for (int row = 0; row < ROW_SIZE; row++) { + printRow(board, row); + } + System.out.println(); + } + + private void printColumnHeader() { + StringBuilder sb = new StringBuilder(" "); + for (int col = 0; col < COL_SIZE; col++) { + sb.append(" ").append(col).append(" "); + } + System.out.println(sb); + } + + private void printRow(BoardView board, int row) { + StringBuilder sb = new StringBuilder(); + sb.append(row).append(" "); + for (int col = 0; col < COL_SIZE; col++) { + String display = board.findByPosition(new Position(row, col)).display(); + sb.append("[").append(display).append("]"); + } + System.out.println(sb); + } + +} diff --git a/src/test/java/janggi/domain/BoardTest.java b/src/test/java/janggi/domain/BoardTest.java index bb074562b5..f632696339 100644 --- a/src/test/java/janggi/domain/BoardTest.java +++ b/src/test/java/janggi/domain/BoardTest.java @@ -3,10 +3,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; +import janggi.domain.piece.Cannon; import janggi.domain.piece.King; import janggi.domain.piece.Piece; +import janggi.domain.piece.Soldier; +import janggi.domain.piece.Tank; import janggi.domain.piece.Team; import janggi.domain.vo.Position; +import java.util.Map; import org.junit.jupiter.api.Test; public class BoardTest { @@ -30,4 +34,44 @@ public class BoardTest { assertTrue(board.isEmptyPosition(position)); } + @Test + void 이동시_잡힌_기물을_반환한다() { + Board board = Board.of(Map.of( + new Position(0, 0), new Tank(Team.HAN), + new Position(0, 3), new Soldier(Team.CHO) + )); + + Piece captured = board.move( + new Position(0, 0), new Position(0, 3), Team.HAN); + + assertThat(captured).isInstanceOf(Soldier.class); + assertThat(captured.findTeam()).isEqualTo(Team.CHO); + } + + @Test + void 빈칸으로_이동시_빈칸을_반환한다() { + Board board = Board.of(Map.of( + new Position(0, 0), new Tank(Team.HAN) + )); + + Piece captured = board.move( + new Position(0, 0), new Position(0, 3), Team.HAN); + + assertThat(captured.isEmpty()).isTrue(); + } + + //점수 계산 + @Test + void 특정_진영의_점수를_계산한다() { + // 한나라: 차(13) + 졸(2) = 15 + Board board = Board.of(Map.of( + new Position(0, 0), new Tank(Team.HAN), + new Position(3, 4), new Soldier(Team.HAN), + new Position(9, 0), new Cannon(Team.CHO) + )); + + assertThat(board.calculateScore(Team.HAN)).isEqualTo(15); + assertThat(board.calculateScore(Team.CHO)).isEqualTo(7); + } + } diff --git a/src/test/java/janggi/domain/JanggiGameTest.java b/src/test/java/janggi/domain/JanggiGameTest.java new file mode 100644 index 0000000000..4af1621fc5 --- /dev/null +++ b/src/test/java/janggi/domain/JanggiGameTest.java @@ -0,0 +1,67 @@ +package janggi.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.piece.King; +import janggi.domain.piece.Soldier; +import janggi.domain.piece.Team; +import org.junit.jupiter.api.Test; + +class JanggiGameTest { + + @Test + void 게임_시작시_초나라_턴이다() { + JanggiGame game = new JanggiGame(); + assertThat(game.findCurrentTeam()).isEqualTo(Team.CHO); + } + + @Test + void 턴_변경시_한나라로_바뀐다() { + JanggiGame game = new JanggiGame(); + game.changeTurn(); + assertThat(game.findCurrentTeam()).isEqualTo(Team.HAN); + } + + @Test + void 왕이_잡히면_게임이_종료된다() { + JanggiGame game = new JanggiGame(); + King capturedKing = new King(Team.HAN); + + game.processCaptured(capturedKing); + + assertThat(game.isFinished()).isTrue(); + } + + @Test + void 왕이_잡히면_상대_진영이_승자다() { + JanggiGame game = new JanggiGame(); + King capturedKing = new King(Team.HAN); + + game.processCaptured(capturedKing); + + assertThat(game.findWinner()).isEqualTo(Team.CHO); + } + + // PR + @Test + void 일반_기물이_잡혀도_게임은_계속된다() { + JanggiGame game = new JanggiGame(); + Soldier capturedSoldier = new Soldier(Team.HAN); + + game.processCaptured(capturedSoldier); + + assertThat(game.isFinished()).isFalse(); + } + + + @Test + void DB에서_복원한_게임_상태가_유지된다() { + JanggiGame game = new JanggiGame(1L, Team.HAN, false, null); + + assertThat(game.findGameId()).isEqualTo(1L); + assertThat(game.findCurrentTeam()).isEqualTo(Team.HAN); + assertThat(game.isFinished()).isFalse(); + assertThat(game.findWinner()).isNull(); + } + +} diff --git a/src/test/java/janggi/domain/PalaceTest.java b/src/test/java/janggi/domain/PalaceTest.java new file mode 100644 index 0000000000..15174f0f7d --- /dev/null +++ b/src/test/java/janggi/domain/PalaceTest.java @@ -0,0 +1,102 @@ +package janggi.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.vo.Position; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class PalaceTest { + + @ParameterizedTest + @CsvSource({ + "0, 3", "0, 4", "0, 5", + "1, 3", "1, 4", "1, 5", + "2, 3", "2, 4", "2, 5", + "7, 3", "7, 4", "7, 5", + "8, 3", "8, 4", "8, 5", + "9, 3", "9, 4", "9, 5" + }) + void 궁성_내부_좌표를_판별한다(int row, int col) { + assertThat(Palace.isInsidePalace(new Position(row, col))).isTrue(); + } + + @ParameterizedTest + @CsvSource({ + "0, 0", "3, 4", "5, 5", "6, 4" + }) + void 궁성_외부_좌표를_판별한다(int row, int col) { + assertThat(Palace.isInsidePalace(new Position(row, col))).isFalse(); + } + + @Test + void 한_궁성_중앙에서_꼭짓점으로_대각선_이동_가능() { + assertThat(Palace.canMoveDiagonally( + new Position(1, 4), new Position(0, 3))).isTrue(); + } + + @Test + void 한_궁성_꼭짓점에서_중앙으로_대각선_이동_가능() { + assertThat(Palace.canMoveDiagonally( + new Position(2, 5), new Position(1, 4))).isTrue(); + } + + @Test + void 초_궁성_중앙에서_꼭짓점으로_대각선_이동_가능() { + assertThat(Palace.canMoveDiagonally( + new Position(8, 4), new Position(7, 3))).isTrue(); + } + + @Test + void 궁성_내부지만_대각선_선이_아닌_곳은_불가() { + assertThat(Palace.canMoveDiagonally( + new Position(0, 3), new Position(1, 3))).isFalse(); + } + + @Test + void 대각선_2칸_이동_불가() { + assertThat(Palace.canMoveDiagonally( + new Position(0, 3), new Position(2, 5))).isFalse(); + } + + @Test + void 궁성_외부에서는_대각선_이동_불가() { + assertThat(Palace.canMoveDiagonally( + new Position(4, 4), new Position(5, 5))).isFalse(); + } + + // 2칸 대각선 판별 차, 포 전용 - 한나라, 초나라 구분 없이 시작 + @Test + void 궁성_꼭짓점에서_반대_꼭짓점으로_2칸_대각선_이동_판별() { + assertThat(Palace.isDiagonalInPalace( + new Position(0, 3), new Position(2, 5))).isTrue(); + assertThat(Palace.isDiagonalInPalace( + new Position(0, 5), new Position(2, 3))).isTrue(); + assertThat(Palace.isDiagonalInPalace( + new Position(7, 3), new Position(9, 5))).isTrue(); + } + + @Test + void 궁성_1칸_대각선도_판별_가능() { + assertThat(Palace.isDiagonalInPalace( + new Position(1, 4), new Position(0, 3))).isTrue(); + } + + + @Test + void 궁성_2칸_대각선의_중간_경유지는_궁성_중앙() { + Position midpoint = Palace.getDiagonalMidpoint( + new Position(0, 3), new Position(2, 5)); + assertThat(midpoint).isEqualTo(new Position(1, 4)); + } + + + @Test + void 궁성_1칸_대각선은_중간_경유지_없음() { + Position midpoint = Palace.getDiagonalMidpoint( + new Position(1, 4), new Position(0, 3)); + assertThat(midpoint).isNull(); + } + +} diff --git a/src/test/java/janggi/domain/mouveRule/AdvisorMoveRuleTest.java b/src/test/java/janggi/domain/mouveRule/AdvisorMoveRuleTest.java new file mode 100644 index 0000000000..42b0fca95a --- /dev/null +++ b/src/test/java/janggi/domain/mouveRule/AdvisorMoveRuleTest.java @@ -0,0 +1,188 @@ +package janggi.domain.mouveRule; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Board; +import janggi.domain.vo.Position; +import org.junit.jupiter.api.Test; + +class AdvisorMoveRuleTest { + private final Board board = Board.empty(); + private final MoveRule moveRule = new AdvisorMoveRule(); + + //한나라 + @Test + void 한_궁성_중앙은_상하좌우_모두_이동_가능() { + Position center = new Position(1, 4); + + assertThat(moveRule.canMove(center, new Position(0, 4), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(2, 4), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(1, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(1, 5), board)).isTrue(); + } + + @Test + void 한_궁성_위_변은_위로는_이동_불가() { + Position pos = new Position(0, 4); + + assertThat(moveRule.canMove(pos, new Position(1, 4), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(0, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(0, 5), board)).isTrue(); + } + + @Test + void 한_궁성_왼쪽_변은_왼쪽으로는_이동_불가() { + Position pos = new Position(1, 3); + + assertThat(moveRule.canMove(pos, new Position(1, 2), board)).isFalse(); + assertThat(moveRule.canMove(pos, new Position(0, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(2, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(1, 4), board)).isTrue(); + } + + @Test + void 한_궁성_꼭짓점은_두_방향만_이동_가능() { + Position pos = new Position(0, 3); + + assertThat(moveRule.canMove(pos, new Position(0, 2), board)).isFalse(); + assertThat(moveRule.canMove(pos, new Position(1, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(0, 4), board)).isTrue(); + } + + + //초나라 + @Test + void 초_궁성_중앙은_상하좌우_모두_이동_가능() { + Position center = new Position(8, 4); + + assertThat(moveRule.canMove(center, new Position(7, 4), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(9, 4), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(8, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(8, 5), board)).isTrue(); + } + + @Test + void 초_궁성_아래_변은_아래로는_이동_불가() { + Position pos = new Position(9, 4); + + assertThat(moveRule.canMove(pos, new Position(8, 4), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(9, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(9, 5), board)).isTrue(); + } + + @Test + void 초_궁성_왼쪽_변은_왼쪽으로는_이동_불가() { + Position pos = new Position(8, 3); + + assertThat(moveRule.canMove(pos, new Position(8, 2), board)).isFalse(); + assertThat(moveRule.canMove(pos, new Position(7, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(9, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(8, 4), board)).isTrue(); + } + + @Test + void 초_궁성_꼭짓점은_두_방향만_이동_가능() { + Position pos = new Position(9, 3); + + assertThat(moveRule.canMove(pos, new Position(9, 2), board)).isFalse(); + assertThat(moveRule.canMove(pos, new Position(8, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(9, 4), board)).isTrue(); + } + + // === 궁성 내 대각선 이동 === + @Test + void 한_궁성_중앙에서_각_꼭짓점으로_대각선_이동_가능() { + Position center = new Position(1, 4); + + assertThat(moveRule.canMove(center, new Position(0, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(0, 5), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(2, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(2, 5), board)).isTrue(); + } + + @Test + void 초_궁성_중앙에서_각_꼭짓점으로_대각선_이동_가능() { + Position center = new Position(8, 4); + + assertThat(moveRule.canMove(center, new Position(7, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(7, 5), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(9, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(9, 5), board)).isTrue(); + } + + @Test + void 한_궁성_꼭짓점에서_중앙으로_대각선_이동_가능() { + assertThat(moveRule.canMove( + new Position(0, 3), new Position(1, 4), board)).isTrue(); + assertThat(moveRule.canMove( + new Position(2, 5), new Position(1, 4), board)).isTrue(); + } + + @Test + void 초_궁성_꼭짓점에서_중앙으로_대각선_이동_가능() { + assertThat(moveRule.canMove( + new Position(7, 3), new Position(8, 4), board)).isTrue(); + assertThat(moveRule.canMove( + new Position(9, 5), new Position(8, 4), board)).isTrue(); + } + + // === 궁성 밖으로 나가는 이동 불가 === + + @Test + void 한_궁성에서_궁성_밖으로_나가는_이동은_불가하다() { + assertThat(moveRule.canMove( + new Position(0, 3), new Position(0, 2), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(2, 4), new Position(3, 4), board)).isFalse(); + } + + @Test + void 초_궁성에서_궁성_밖으로_나가는_이동은_불가하다() { + assertThat(moveRule.canMove( + new Position(9, 3), new Position(9, 2), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(7, 4), new Position(6, 4), board)).isFalse(); + } + + // === 대각선 선이 아닌 곳에서 대각선 이동 불가 === + + @Test + void 한_궁성_내부라도_대각선_선이_아닌_이동은_불가하다() { + assertThat(moveRule.canMove( + new Position(0, 4), new Position(1, 5), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(1, 3), new Position(2, 4), board)).isFalse(); + } + + @Test + void 초_궁성_내부라도_대각선_선이_아닌_이동은_불가하다() { + assertThat(moveRule.canMove( + new Position(7, 4), new Position(8, 5), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(8, 3), new Position(9, 4), board)).isFalse(); + } + + // === 2칸 이상 이동 불가 === + @Test + void 한_궁성에서는_대각선_두_칸_이상_이동할_수_없다() { + assertThat(moveRule.canMove( + new Position(0, 3), new Position(2, 5), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(0, 5), new Position(2, 3), board)).isFalse(); + } + + @Test + void 초_궁성에서는_대각선_두_칸_이상_이동할_수_없다() { + assertThat(moveRule.canMove( + new Position(7, 3), new Position(9, 5), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(7, 5), new Position(9, 3), board)).isFalse(); + } +} + diff --git a/src/test/java/janggi/domain/mouveRule/CannonMoveRuleTest.java b/src/test/java/janggi/domain/mouveRule/CannonMoveRuleTest.java index 3b0fcd5f19..ee8d84664c 100644 --- a/src/test/java/janggi/domain/mouveRule/CannonMoveRuleTest.java +++ b/src/test/java/janggi/domain/mouveRule/CannonMoveRuleTest.java @@ -57,4 +57,57 @@ class CannonMoveRuleTest { assertThat(moveRule.canMove(new Position(0, 0), new Position(0, 6), board)).isFalse(); } + + // 궁성 대각선 + @Test + void 궁성_대각선_2칸_이동시_중간에_기물_1개_넘으면_이동_가능() { + Board board = Board.of(Map.of( + new Position(1, 4), new Soldier(Team.HAN) + )); + assertThat(moveRule.canMove( + new Position(0, 3), new Position(2, 5), board)).isTrue(); + } + + @Test + void 초_궁성에서도_대각선_2칸_이동_가능() { + Board board = Board.of(Map.of( + new Position(8, 4), new Soldier(Team.CHO) + )); + assertThat(moveRule.canMove( + new Position(7, 3), new Position(9, 5), board)).isTrue(); + } + + @Test + // 아래 테스트들 PR + void 궁성_대각선_2칸_이동시_중간에_기물_없으면_이동_불가() { + assertThat(moveRule.canMove( + new Position(0, 3), new Position(2, 5), board)).isFalse(); + } + + @Test + void 궁성_대각선_2칸_이동시_다리가_포이면_이동_불가() { + Board board = Board.of(Map.of( + new Position(1, 4), new Cannon(Team.CHO) + )); + assertThat(moveRule.canMove( + new Position(0, 3), new Position(2, 5), board)).isFalse(); + } + + @Test + void 궁성_대각선_2칸_이동시_도착칸에_포가_있으면_이동_불가() { + Board board = Board.of(Map.of( + new Position(1, 4), new Soldier(Team.HAN), + new Position(2, 5), new Cannon(Team.CHO) + )); + assertThat(moveRule.canMove( + new Position(0, 3), new Position(2, 5), board)).isFalse(); + } + + @Test + void 궁성_대각선_1칸은_넘을_기물이_없으므로_이동_불가() { + assertThat(moveRule.canMove( + new Position(1, 4), new Position(0, 3), board)).isFalse(); + } + + } diff --git a/src/test/java/janggi/domain/mouveRule/KingMoveRuleTest.java b/src/test/java/janggi/domain/mouveRule/KingMoveRuleTest.java new file mode 100644 index 0000000000..25f11cbd94 --- /dev/null +++ b/src/test/java/janggi/domain/mouveRule/KingMoveRuleTest.java @@ -0,0 +1,188 @@ +package janggi.domain.mouveRule; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Board; +import janggi.domain.vo.Position; +import org.junit.jupiter.api.Test; + +class KingMoveRuleTest { + private final Board board = Board.empty(); + private final MoveRule moveRule = new KingMoveRule(); + + //한나라 + @Test + void 한_궁성_중앙은_상하좌우_모두_이동_가능() { + Position center = new Position(1, 4); + + assertThat(moveRule.canMove(center, new Position(0, 4), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(2, 4), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(1, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(1, 5), board)).isTrue(); + } + + @Test + void 한_궁성_위_변은_위로는_이동_불가() { + Position pos = new Position(0, 4); + + assertThat(moveRule.canMove(pos, new Position(1, 4), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(0, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(0, 5), board)).isTrue(); + } + + @Test + void 한_궁성_왼쪽_변은_왼쪽으로는_이동_불가() { + Position pos = new Position(1, 3); + + assertThat(moveRule.canMove(pos, new Position(1, 2), board)).isFalse(); + assertThat(moveRule.canMove(pos, new Position(0, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(2, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(1, 4), board)).isTrue(); + } + + @Test + void 한_궁성_꼭짓점은_두_방향만_이동_가능() { + Position pos = new Position(0, 3); + + assertThat(moveRule.canMove(pos, new Position(0, 2), board)).isFalse(); + assertThat(moveRule.canMove(pos, new Position(1, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(0, 4), board)).isTrue(); + } + + + //초나라 + @Test + void 초_궁성_중앙은_상하좌우_모두_이동_가능() { + Position center = new Position(8, 4); + + assertThat(moveRule.canMove(center, new Position(7, 4), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(9, 4), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(8, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(8, 5), board)).isTrue(); + } + + @Test + void 초_궁성_아래_변은_아래로는_이동_불가() { + Position pos = new Position(9, 4); + + assertThat(moveRule.canMove(pos, new Position(8, 4), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(9, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(9, 5), board)).isTrue(); + } + + @Test + void 초_궁성_왼쪽_변은_왼쪽으로는_이동_불가() { + Position pos = new Position(8, 3); + + assertThat(moveRule.canMove(pos, new Position(8, 2), board)).isFalse(); + assertThat(moveRule.canMove(pos, new Position(7, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(9, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(8, 4), board)).isTrue(); + } + + @Test + void 초_궁성_꼭짓점은_두_방향만_이동_가능() { + Position pos = new Position(9, 3); + + assertThat(moveRule.canMove(pos, new Position(9, 2), board)).isFalse(); + assertThat(moveRule.canMove(pos, new Position(8, 3), board)).isTrue(); + assertThat(moveRule.canMove(pos, new Position(9, 4), board)).isTrue(); + } + + // === 궁성 내 대각선 이동 === + @Test + void 한_궁성_중앙에서_각_꼭짓점으로_대각선_이동_가능() { + Position center = new Position(1, 4); + + assertThat(moveRule.canMove(center, new Position(0, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(0, 5), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(2, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(2, 5), board)).isTrue(); + } + + @Test + void 초_궁성_중앙에서_각_꼭짓점으로_대각선_이동_가능() { + Position center = new Position(8, 4); + + assertThat(moveRule.canMove(center, new Position(7, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(7, 5), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(9, 3), board)).isTrue(); + assertThat(moveRule.canMove(center, new Position(9, 5), board)).isTrue(); + } + + @Test + void 한_궁성_꼭짓점에서_중앙으로_대각선_이동_가능() { + assertThat(moveRule.canMove( + new Position(0, 3), new Position(1, 4), board)).isTrue(); + assertThat(moveRule.canMove( + new Position(2, 5), new Position(1, 4), board)).isTrue(); + } + + @Test + void 초_궁성_꼭짓점에서_중앙으로_대각선_이동_가능() { + assertThat(moveRule.canMove( + new Position(7, 3), new Position(8, 4), board)).isTrue(); + assertThat(moveRule.canMove( + new Position(9, 5), new Position(8, 4), board)).isTrue(); + } + + // === 궁성 밖으로 나가는 이동 불가 === + + @Test + void 한_궁성에서_궁성_밖으로_나가는_이동은_불가하다() { + assertThat(moveRule.canMove( + new Position(0, 3), new Position(0, 2), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(2, 4), new Position(3, 4), board)).isFalse(); + } + + @Test + void 초_궁성에서_궁성_밖으로_나가는_이동은_불가하다() { + assertThat(moveRule.canMove( + new Position(9, 3), new Position(9, 2), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(7, 4), new Position(6, 4), board)).isFalse(); + } + + // === 대각선 선이 아닌 곳에서 대각선 이동 불가 === + + @Test + void 한_궁성_내부라도_대각선_선이_아닌_이동은_불가하다() { + assertThat(moveRule.canMove( + new Position(0, 4), new Position(1, 5), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(1, 3), new Position(2, 4), board)).isFalse(); + } + + @Test + void 초_궁성_내부라도_대각선_선이_아닌_이동은_불가하다() { + assertThat(moveRule.canMove( + new Position(7, 4), new Position(8, 5), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(8, 3), new Position(9, 4), board)).isFalse(); + } + + // === 2칸 이상 이동 불가 === + @Test + void 한_궁성에서는_대각선_두_칸_이상_이동할_수_없다() { + assertThat(moveRule.canMove( + new Position(0, 3), new Position(2, 5), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(0, 5), new Position(2, 3), board)).isFalse(); + } + + @Test + void 초_궁성에서는_대각선_두_칸_이상_이동할_수_없다() { + assertThat(moveRule.canMove( + new Position(7, 3), new Position(9, 5), board)).isFalse(); + + assertThat(moveRule.canMove( + new Position(7, 5), new Position(9, 3), board)).isFalse(); + } +} + diff --git a/src/test/java/janggi/domain/mouveRule/OneStepMoveRuleTest.java b/src/test/java/janggi/domain/mouveRule/OneStepMoveRuleTest.java deleted file mode 100644 index f538598f99..0000000000 --- a/src/test/java/janggi/domain/mouveRule/OneStepMoveRuleTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package janggi.domain.mouveRule; - -import static org.assertj.core.api.Assertions.assertThat; - -import janggi.domain.Board; -import janggi.domain.vo.Position; -import org.junit.jupiter.api.Test; - -class OneStepMoveRuleTest { - - private Board board = Board.empty(); - private final MoveRule moveRule = new OneStepMoveRule(); - - @Test - void 상하좌우_1칸_이동가능() { - assertThat(moveRule.canMove(new Position(4, 4), new Position(5, 4), board)).isTrue(); - assertThat(moveRule.canMove(new Position(4, 4), new Position(3, 4), board)).isTrue(); - assertThat(moveRule.canMove(new Position(4, 4), new Position(4, 5), board)).isTrue(); - assertThat(moveRule.canMove(new Position(4, 4), new Position(4, 3), board)).isTrue(); - } - - @Test - void _2칸이상_이동못함() { - assertThat(moveRule.canMove(new Position(4, 4), new Position(6, 4), board)).isFalse(); - assertThat(moveRule.canMove(new Position(4, 4), new Position(4, 6), board)).isFalse(); - assertThat(moveRule.canMove(new Position(4, 4), new Position(6, 6), board)).isFalse(); - } -} diff --git a/src/test/java/janggi/domain/mouveRule/SoldierMoveRuleTest.java b/src/test/java/janggi/domain/mouveRule/SoldierMoveRuleTest.java index e0569350af..c6f28ed99f 100644 --- a/src/test/java/janggi/domain/mouveRule/SoldierMoveRuleTest.java +++ b/src/test/java/janggi/domain/mouveRule/SoldierMoveRuleTest.java @@ -50,5 +50,62 @@ class SoldierMoveRuleTest { } + @Test + void 초나라_졸은_한_궁성에서_전진_대각선_이동_가능() { + // 초 전진 = 행 감소 + MoveRule rule = new SoldierMoveRule(Team.CHO); + assertThat(rule.canMove( + new Position(2, 5), new Position(1, 4), board)).isTrue(); + } + + @Test + void 한나라_졸은_초_궁성에서_전진_대각선_이동_가능() { + // 한 전진 = 행 증가 + MoveRule rule = new SoldierMoveRule(Team.HAN); + assertThat(rule.canMove( + new Position(7, 3), new Position(8, 4), board)).isTrue(); + } + + @Test + void 궁성_중앙에서_꼭짓점으로_전진_대각선_가능() { + MoveRule rule = new SoldierMoveRule(Team.CHO); + // 행 감소 = 전진 + assertThat(rule.canMove( + new Position(1, 4), new Position(0, 3), board)).isTrue(); + } + + // PR + @Test + void 초나라_졸은_한_궁성에서_후진_대각선_이동_불가() { + MoveRule rule = new SoldierMoveRule(Team.CHO); + // 행 증가 대각선 = 후진 + assertThat(rule.canMove( + new Position(0, 3), new Position(1, 4), board)).isFalse(); + } + + + @Test + void 한나라_졸은_초_궁성에서_후진_대각선_이동_불가() { + MoveRule rule = new SoldierMoveRule(Team.HAN); + // 행 감소 대각선 = 후진 + assertThat(rule.canMove( + new Position(9, 5), new Position(8, 4), board)).isFalse(); + } + + @Test + void 궁성_밖에서는_대각선_이동_불가() { + MoveRule rule = new SoldierMoveRule(Team.CHO); + // 궁성 밖 대각선 + assertThat(rule.canMove( + new Position(5, 4), new Position(4, 5), board)).isFalse(); + } + + @Test + void 궁성_대각선_선이_아닌_곳에서는_대각선_불가() { + MoveRule rule = new SoldierMoveRule(Team.HAN); + // 궁성 내부지만 중앙 미경유 대각선 + assertThat(rule.canMove( + new Position(7, 4), new Position(8, 5), board)).isFalse(); + } } diff --git a/src/test/java/janggi/domain/mouveRule/TankMoveRuleTest.java b/src/test/java/janggi/domain/mouveRule/TankMoveRuleTest.java index a2ddc173be..1e05de3c6e 100644 --- a/src/test/java/janggi/domain/mouveRule/TankMoveRuleTest.java +++ b/src/test/java/janggi/domain/mouveRule/TankMoveRuleTest.java @@ -52,4 +52,40 @@ class TankMoveRuleTest { assertThat(moveRule.canMove(from, to, board)).isFalse(); } + + // 궁성 대각선 + + @Test + void 궁성_중앙에서_꼭짓점으로_대각선_1칸_이동_가능() { + from = new Position(1, 4); + to = new Position(0, 3); + assertThat(moveRule.canMove(from, to, board)).isTrue(); + } + + @Test + void 궁성_꼭짓점에서_반대_꼭짓점으로_대각선_2칸_이동_가능() { + // (0,3) → (2,5), 중간 (1,4) 비어있음 + from = new Position(0, 3); + to = new Position(2, 5); + assertThat(moveRule.canMove(from, to, board)).isTrue(); + } + + @Test + void 궁성_2칸_대각선_경로에_기물_있으면_이동_불가() { + Board board = Board.of(Map.of( + new Position(1, 4), new Soldier(Team.HAN) + )); + from = new Position(0, 3); + to = new Position(2, 5); + assertThat(moveRule.canMove(from, to, board)).isFalse(); + } + + @Test + void 궁성_밖에서는_대각선_이동_불가() { + from = new Position(4, 4); + to = new Position(5, 5); + assertThat(moveRule.canMove(from, to, board)).isFalse(); + } + + } diff --git a/src/test/java/janggi/domain/piece/PieceTest.java b/src/test/java/janggi/domain/piece/PieceTest.java new file mode 100644 index 0000000000..6ba890eca8 --- /dev/null +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -0,0 +1,56 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class PieceTest { + + @Test + void 차의_점수는_13이다() { + Piece piece = new Tank(Team.HAN); + assertThat(piece.score()).isEqualTo(13); + } + + @Test + void 포의_점수는_7이다() { + Piece piece = new Cannon(Team.HAN); + assertThat(piece.score()).isEqualTo(7); + } + + @Test + void 마의_점수는_5이다() { + Piece piece = new Horse(Team.HAN); + assertThat(piece.score()).isEqualTo(5); + } + + @Test + void 상의_점수는_3이다() { + Piece piece = new Elephant(Team.HAN); + assertThat(piece.score()).isEqualTo(3); + } + + @Test + void 사의_점수는_3이다() { + Piece piece = new Advisor(Team.HAN); + assertThat(piece.score()).isEqualTo(3); + } + + @Test + void 졸의_점수는_2이다() { + Piece piece = new Soldier(Team.HAN); + assertThat(piece.score()).isEqualTo(2); + } + + @Test + void 장의_점수는_0이다() { + Piece piece = new King(Team.HAN); + assertThat(piece.score()).isEqualTo(0); + } + + @Test + void 빈칸의_점수는_0이다() { + Piece piece = new EmptyPosition(Team.OTHER); + assertThat(piece.score()).isEqualTo(0); + } +} diff --git a/src/test/java/janggi/infrastructure/DBConnectionManagerTest.java b/src/test/java/janggi/infrastructure/DBConnectionManagerTest.java new file mode 100644 index 0000000000..8a39f045cb --- /dev/null +++ b/src/test/java/janggi/infrastructure/DBConnectionManagerTest.java @@ -0,0 +1,15 @@ +package janggi.infrastructure; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.Connection; +import org.junit.jupiter.api.Test; + +class DBConnectionManagerTest { + + @Test + void DB_연결이_정상적으로_생성된다() { + Connection connection = DBConnectionManager.getConnection(); + assertThat(connection).isNotNull(); + } +} diff --git a/src/test/java/janggi/infrastructure/DBInitializerTest.java b/src/test/java/janggi/infrastructure/DBInitializerTest.java new file mode 100644 index 0000000000..45b8996b04 --- /dev/null +++ b/src/test/java/janggi/infrastructure/DBInitializerTest.java @@ -0,0 +1,35 @@ +package janggi.infrastructure; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.junit.jupiter.api.Test; + +class DBInitializerTest { + + @Test + void 테이블이_정상적으로_생성된다() throws SQLException { + DBInitializer.initialize(); + + try (Connection connection = DBConnectionManager.getConnection(); + Statement statement = connection.createStatement()) { + + ResultSet rs = statement.executeQuery( + "SELECT name FROM sqlite_master WHERE type='table' AND name='game'"); + assertThat(rs.next()).isTrue(); + + rs = statement.executeQuery( + "SELECT name FROM sqlite_master WHERE type='table' AND name='piece'"); + assertThat(rs.next()).isTrue(); + } + } + + @Test + void 테이블_초기화를_여러번_호출해도_에러가_발생하지_않는다() { + DBInitializer.initialize(); + DBInitializer.initialize(); + } +} diff --git a/src/test/java/janggi/repository/GameRepositoryTest.java b/src/test/java/janggi/repository/GameRepositoryTest.java new file mode 100644 index 0000000000..a4249241cf --- /dev/null +++ b/src/test/java/janggi/repository/GameRepositoryTest.java @@ -0,0 +1,83 @@ +package janggi.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.JanggiGame; +import janggi.domain.piece.King; +import janggi.domain.piece.Team; +import janggi.infrastructure.DBInitializer; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GameRepositoryTest { + + private final GameRepository gameRepository = new GameRepository(); + + @BeforeEach + void setUp() { + DBInitializer.initialize(); + } + + @Test + void 새게임을_저장하고_ID로_조회한다() { + JanggiGame game = new JanggiGame(); + Long gameId = gameRepository.save(game); + + JanggiGame found = gameRepository.findById(gameId); + + assertThat(found.findGameId()).isEqualTo(gameId); + assertThat(found.findCurrentTeam()).isEqualTo(Team.CHO); + assertThat(found.isFinished()).isFalse(); + assertThat(found.findWinner()).isNull(); + } + + @Test + void 턴_변경_후_디비에반영이된다() { + JanggiGame game = new JanggiGame(); + Long gameId = gameRepository.save(game); + game.assignId(gameId); + game.changeTurn(); + + gameRepository.updateTurn(game); + + JanggiGame found = gameRepository.findById(gameId); + assertThat(found.findCurrentTeam()).isEqualTo(Team.HAN); + } + + @Test + void 게임_종료_후_갱신한다() { + JanggiGame game = new JanggiGame(); + Long gameId = gameRepository.save(game); + game.assignId(gameId); + game.processCaptured(new King(Team.HAN)); + + gameRepository.updateFinished(game); + + JanggiGame found = gameRepository.findById(gameId); + assertThat(found.isFinished()).isTrue(); + assertThat(found.findWinner()).isEqualTo(Team.CHO); + } + + @Test + void 진행_중인_게임_목록을_조회한다() { + JanggiGame game1 = new JanggiGame(); + Long gameId1 = gameRepository.save(game1); + + JanggiGame game2 = new JanggiGame(); + Long gameId2 = gameRepository.save(game2); + game2.assignId(gameId2); + game2.processCaptured(new King(Team.HAN)); + gameRepository.updateFinished(game2); + + List playingGames = gameRepository.findPlayingGames(); + + boolean containsGame1 = playingGames.stream() + .anyMatch(g -> g.findGameId().equals(gameId1)); + assertThat(containsGame1).isTrue(); + + boolean containsGame2 = playingGames.stream() + .anyMatch(g -> g.findGameId().equals(gameId2)); + assertThat(containsGame2).isFalse(); + } +} diff --git a/src/test/java/janggi/repository/PieceRepositoryTest.java b/src/test/java/janggi/repository/PieceRepositoryTest.java new file mode 100644 index 0000000000..fe1eac9a29 --- /dev/null +++ b/src/test/java/janggi/repository/PieceRepositoryTest.java @@ -0,0 +1,74 @@ +package janggi.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Board; +import janggi.domain.JanggiGame; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Soldier; +import janggi.domain.piece.Tank; +import janggi.domain.piece.Team; +import janggi.domain.vo.Position; +import janggi.infrastructure.DBInitializer; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class PieceRepositoryTest { + + private final GameRepository gameRepository = new GameRepository(); + private final PieceRepository pieceRepository = new PieceRepository(); + + @BeforeEach + void setUp() { + DBInitializer.initialize(); + } + + @Test + void 보드기물들을_저장하고_복원한다() { + Board board = new Board(); + Long gameId = gameRepository.save(new JanggiGame()); + pieceRepository.saveAll(gameId, board); + + Board restored = pieceRepository.findByGameId(gameId); + + Map pieces = restored.findAllPieces(); + assertThat(pieces).hasSize(32); + } + + @Test + void 기물이동을_DB에_반영한다() { + Board board = Board.of(Map.of( + new Position(0, 0), new Tank(Team.HAN) + )); + Long gameId = gameRepository.save(new JanggiGame()); + pieceRepository.saveAll(gameId, board); + + pieceRepository.movePiece(gameId, new Position(0, 0), new Position(0, 3)); + + Board restored = pieceRepository.findByGameId(gameId); + Map pieces = restored.findAllPieces(); + assertThat(pieces).hasSize(1); + assertThat(pieces.containsKey(new Position(0, 3))).isTrue(); + assertThat(pieces.containsKey(new Position(0, 0))).isFalse(); + } + + @Test +//PR + void 상대_기물을_잡으며_이동하면_잡힌_기물이_삭제된다() { + Board board = Board.of(Map.of( + new Position(0, 0), new Tank(Team.HAN), + new Position(0, 3), new Soldier(Team.CHO) + )); + Long gameId = gameRepository.save(new JanggiGame()); + pieceRepository.saveAll(gameId, board); + + pieceRepository.movePiece(gameId, new Position(0, 0), new Position(0, 3)); + + Board restored = pieceRepository.findByGameId(gameId); + Map pieces = restored.findAllPieces(); + assertThat(pieces).hasSize(1); + assertThat(pieces.get(new Position(0, 3))).isInstanceOf(Tank.class); + } + +}