diff --git a/README.md b/README.md index 9775dda0ae..ea123ed478 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,47 @@ # java-janggi -장기 미션 저장소 +# 구현 기능 + +- 보드 초기화 + - 각 플레이어에게 장기판 차림 방법을 입력 받는다. + - 먼저 플레이하는 초나라부터 명령 번호를 입력하여 판차림 방법을 결정한다. + - 1, 2, 3, 4 이외의 숫자가 들어오는 경우 예외 처리한다. + - 선택할 수 있는 판차림은 다음과 같다. + 1. 왼상 차림 + 2. 오른상 차림 + 3. 안상 차림 + 4. 바깥상 차림 + - 입력한 장기판 차림을 기반으로 보드를 세팅한다. + - 보드 출력 + - 빈 공간은 온점 전각 문자를 활용한다. + - 팀 구분은 색상(빨강, 파랑)으로 구분한다. + - 기물은 각 기물에 대응하는 한나라의 한자로 표기한다. + +- 기물 이동 구현 + - 움직일 기물의 좌표를 플레이어에게 입력받는다. + - 다음의 경우 다시 입력받는다. + - `row`, `column` 형식이 아닌 경우 + - 플레이어의 팀이 아닌 기물의 좌표를 입력한 경우 + - 입력한 기물이 이동 가능한 위치가 없는 경우 + - 입력받은 위치의 기물이 이동 가능한 위치를 출력한다. + - 이동 가능한 위치는 초록색으로 표현된다. + - 플레이어에게 이동 가능한 위치 중 하나를 입력받는다. + - 다음의 경우 다시 입력받는다. + - `row`, `column` 형식이 아닌 경우 + - 이동 가능한 위치가 아닌 경우 + - 입력받은 위치로 기물을 이동시킨다. + - 각 기물의 이동 규칙은 다음과 같다. + - 차 (車): 가로/세로 직선 이동가능, 거리 제한 없음, 중간에 기물이 있으면 못 지나감 + - 마 (馬): 1칸 직선 + 1칸 대각선, 가는 길이 막히면 이동 불가 + - 상 (象): 1칸 직선 + 2칸 대각선, 가는 길이 막히면 이동 불가 + - 사 (士): 모든 방향으로 1칸 이동 가능 + - 장 (將): 모든 방향으로 1칸 이동 가능 + - 포 (砲): 반드시 하나를 넘어 전후좌우로 이동이 가능하고 포는 서로 넘거나 잡을 수 없다. + - 졸 (卒): 앞, 왼쪽, 오른쪽 1칸씩 이동 가능 + +- 게임 루프 로직 + - 처음엔 한나라(Red Team)로 시작한다. + - 각 플레이어가 움직일 기물의 위치를 입력한다. + - 기물 이동 구현의 규칙과 같다. + - 한나라 -> 초나라 -> 한나라... 반복된다. + - 종료 조건은 어느 한 팀의 장 기물이 잡히면 게임은 종료된다. \ No newline at end of file diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java new file mode 100644 index 0000000000..18b64c6564 --- /dev/null +++ b/src/main/java/janggi/Application.java @@ -0,0 +1,11 @@ +package janggi; + +import janggi.controller.JanggiController; + +public class Application { + + public static void main(String[] args) { + final JanggiController janggiController = new JanggiController(); + janggiController.run(); + } +} diff --git a/src/main/java/janggi/controller/JanggiController.java b/src/main/java/janggi/controller/JanggiController.java new file mode 100644 index 0000000000..41f9e47ba2 --- /dev/null +++ b/src/main/java/janggi/controller/JanggiController.java @@ -0,0 +1,103 @@ +package janggi.controller; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.board.BoardGenerator; +import janggi.domain.board.setup.ElephantFormation; +import janggi.domain.board.setup.SetupStrategy; +import janggi.domain.team.BlueTeam; +import janggi.domain.team.RedTeam; +import janggi.domain.team.Team; +import janggi.domain.team.TeamType; +import janggi.domain.team.TurnManager; +import janggi.dto.BoardDto; +import janggi.utils.RetryExecutor; +import janggi.view.InputView; +import janggi.view.OutputView; +import java.util.List; + +public class JanggiController { + + public JanggiController() { + } + + public void run() { + Board board = BoardGenerator.generate(setupRedTeam(), setupBlueTeam()); + TurnManager turnManager = new TurnManager(); + while (board.hasGeneral(turnManager.currentTeamType())) { + playTurn(board, turnManager); + } + turnManager.changeTurn(); + OutputView.printGameOverMessage(turnManager.currentTeamTypeToString()); + } + + private Team setupRedTeam() { + OutputView.printSetupGuide(TeamType.RED); + final SetupStrategy setupStrategyCommand = RetryExecutor.retry(this::readSetupCommand); + final ElephantFormation elephantFormation = setupStrategyCommand.toPolicy(); + return new RedTeam(elephantFormation); + } + + private Team setupBlueTeam() { + OutputView.printSetupGuide(TeamType.BLUE); + final SetupStrategy setupStrategyCommand = RetryExecutor.retry(this::readSetupCommand); + final ElephantFormation elephantFormation = setupStrategyCommand.toPolicy(); + return new BlueTeam(elephantFormation); + } + + private SetupStrategy readSetupCommand() { + int inputCommand = InputView.readSetupCommand(); + return SetupStrategy.from(inputCommand); + } + + private void playTurn(Board board, TurnManager turnManager) { + OutputView.printBoard(BoardDto.from(board), turnManager.currentTeamTypeToString()); + Position from = findFromPosition(board, turnManager); + List movable = board.calculateMovablePositions(from); + OutputView.printBoardWithMovable(BoardDto.from(board), movable); + Position to = RetryExecutor.retry(() -> inputToPosition(movable)); + board.movePiece(from, to); + turnManager.changeTurn(); + } + + private Position findFromPosition(Board board, TurnManager turnManager) { + while (true) { + Position from = RetryExecutor.retry(() -> inputFromPosition(board, turnManager)); + List movable = board.calculateMovablePositions(from); + if (!movable.isEmpty()) { + return from; + } + OutputView.printErrorMessage("이동 가능한 위치가 없습니다. 다른 기물을 선택하세요."); + } + } + + private Position inputFromPosition(Board board, TurnManager turnManager) { + while (true) { + try { + OutputView.printInputFromPosition(); + Position from = InputView.readPosition(); + if (!board.isSameTeamType(from, turnManager.currentTeamType())) { + throw new IllegalArgumentException("자신의 기물을 선택하세요."); + } + return from; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + } + } + } + + private Position inputToPosition(List movable) { + while (true) { + try { + OutputView.printInputToPosition(); + Position to = InputView.readPosition(); + if (!movable.contains(to)) { + throw new IllegalArgumentException("이동 불가능한 위치입니다."); + } + return to; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + } + } + } +} diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java new file mode 100644 index 0000000000..60e35e6a29 --- /dev/null +++ b/src/main/java/janggi/domain/Position.java @@ -0,0 +1,87 @@ +package janggi.domain; + +import janggi.domain.movement.Direction; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +public final class Position { + + public static final int MAXIMUM_ROW = 10; + public static final int MAXIMUM_COLUMN = 9; + public static final int MINIMUM_ROW = 1; + public static final int MINIMUM_COLUMN = 1; + private static final int ROW_FLIP_VALUE = 11; + private static final Map> CACHE; + + static { + CACHE = new LinkedHashMap<>(); + for (int row = MINIMUM_ROW; row <= MAXIMUM_ROW; row++) { + CACHE.put(row, new LinkedHashMap<>()); + } + } + + private final int row; + private final int column; + + private Position(final int row, final int column) { + this.row = row; + this.column = column; + } + + public static Position valueOf(final int row, final int column) { + validateRowRange(row); + validateColumnRange(column); + final Map secondaryMap = CACHE.get(row); + if (!secondaryMap.containsKey(column)) { + secondaryMap.put(column, new Position(row, column)); + } + return secondaryMap.get(column); + } + + private static void validateRowRange(final int row) { + if (row < MINIMUM_ROW || row > MAXIMUM_ROW) { + throw new IllegalArgumentException(String.format("행 입력은 %d~%d을 입력해야 합니다.", MINIMUM_ROW, MAXIMUM_ROW)); + } + } + + private static void validateColumnRange(final int column) { + if (column < MINIMUM_COLUMN || column > MAXIMUM_COLUMN) { + throw new IllegalArgumentException(String.format("열 입력은 %d~%d을 입력해야 합니다.", MINIMUM_COLUMN, MAXIMUM_COLUMN)); + } + } + + public Position flipAroundMiddleRow() { + return Position.valueOf(ROW_FLIP_VALUE - row, column); + } + + public boolean checkNextBound(final int distance, final Direction direction) { + final int nextRow = row + direction.getRowDirection() * distance; + final int nextColumn = column + direction.getColumnDirection() * distance; + + return nextRow >= MINIMUM_ROW && nextRow <= MAXIMUM_ROW && nextColumn >= MINIMUM_COLUMN + && nextColumn <= MAXIMUM_COLUMN; + } + + public Position calculateNext(final int distance, final Direction direction) { + final int nextRow = row + direction.getRowDirection() * distance; + final int nextColumn = column + direction.getColumnDirection() * distance; + + return Position.valueOf(Math.clamp(nextRow, MINIMUM_ROW, MAXIMUM_ROW), + Math.clamp(nextColumn, MINIMUM_COLUMN, MAXIMUM_COLUMN)); + } + + @Override + public boolean equals(final Object object) { + if (object == null || getClass() != object.getClass()) { + return false; + } + final Position position = (Position) object; + return row == position.row && column == position.column; + } + + @Override + public int hashCode() { + return Objects.hash(row, column); + } +} diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java new file mode 100644 index 0000000000..3f32e52acc --- /dev/null +++ b/src/main/java/janggi/domain/board/Board.java @@ -0,0 +1,61 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Piece; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Board implements BoardMediator { + + private final Map positionPieceMap; + + public Board(final Map positionPieceMap) { + this.positionPieceMap = new LinkedHashMap<>(positionPieceMap); + } + + public void movePiece(final Position from, final Position to) { + positionPieceMap.put(to, positionPieceMap.remove(from)); + } + + public List calculateMovablePositions(Position from) { + return findPieceByPosition(from).calculateMovablePositions(from, this); + } + + @Override + public boolean hasPieceAt(final Position position) { + return hasPieceIn(position); + } + + @Override + public boolean hasGeneral(TeamType teamType) { + return positionPieceMap.values().stream() + .anyMatch(piece -> piece.isGeneral() && piece.isSameTeamType(teamType)); + } + + @Override + public boolean isCannon(Position position) { + return findPieceByPosition(position).isCannon(); + } + + @Override + public boolean isSameTeamType(Position position, TeamType teamType) { + return findPieceByPosition(position).isSameTeamType(teamType); + } + + public Map getPositionPieceMapForDTO() { + return Map.copyOf(positionPieceMap); + } + + private boolean hasPieceIn(final Position position) { + return positionPieceMap.containsKey(position); + } + + private Piece findPieceByPosition(final Position position) { + if (hasPieceIn(position)) { + return positionPieceMap.get(position); + } + throw new IllegalArgumentException("요청된 위치에는 기물이 존재하지 않습니다."); + } +} diff --git a/src/main/java/janggi/domain/board/BoardGenerator.java b/src/main/java/janggi/domain/board/BoardGenerator.java new file mode 100644 index 0000000000..1440bc4b86 --- /dev/null +++ b/src/main/java/janggi/domain/board/BoardGenerator.java @@ -0,0 +1,20 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Piece; +import janggi.domain.team.Team; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class BoardGenerator { + + private BoardGenerator() { + } + + public static Board generate(final Team redTeam, final Team blueTeam) { + final Map positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.putAll(redTeam.generatePieces()); + positionPieceMap.putAll(blueTeam.generatePieces()); + return new Board(positionPieceMap); + } +} diff --git a/src/main/java/janggi/domain/board/BoardMediator.java b/src/main/java/janggi/domain/board/BoardMediator.java new file mode 100644 index 0000000000..759adc1168 --- /dev/null +++ b/src/main/java/janggi/domain/board/BoardMediator.java @@ -0,0 +1,14 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.team.TeamType; + +public interface BoardMediator { + boolean hasPieceAt(Position position); + + boolean hasGeneral(TeamType teamType); + + boolean isCannon(Position position); + + boolean isSameTeamType(Position position, TeamType teamType); +} diff --git a/src/main/java/janggi/domain/board/setup/ElephantFormation.java b/src/main/java/janggi/domain/board/setup/ElephantFormation.java new file mode 100644 index 0000000000..a7fb58e62a --- /dev/null +++ b/src/main/java/janggi/domain/board/setup/ElephantFormation.java @@ -0,0 +1,30 @@ +package janggi.domain.board.setup; + +import janggi.domain.Position; +import janggi.domain.piece.PieceType; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public abstract class ElephantFormation { + static final Map COMMON_BOARD_MAP; + + static { + Map boardMap = new LinkedHashMap<>(); + boardMap.put(Position.valueOf(1, 1), PieceType.CHARIOT); + boardMap.put(Position.valueOf(1, 4), PieceType.GUARD); + boardMap.put(Position.valueOf(1, 6), PieceType.GUARD); + boardMap.put(Position.valueOf(1, 9), PieceType.CHARIOT); + boardMap.put(Position.valueOf(2, 5), PieceType.GENERAL); + boardMap.put(Position.valueOf(3, 2), PieceType.CANNON); + boardMap.put(Position.valueOf(3, 8), PieceType.CANNON); + boardMap.put(Position.valueOf(4, 1), PieceType.SOLDIER); + boardMap.put(Position.valueOf(4, 3), PieceType.SOLDIER); + boardMap.put(Position.valueOf(4, 5), PieceType.SOLDIER); + boardMap.put(Position.valueOf(4, 7), PieceType.SOLDIER); + boardMap.put(Position.valueOf(4, 9), PieceType.SOLDIER); + COMMON_BOARD_MAP = Collections.unmodifiableMap(boardMap); + } + + public abstract Map offerBoardMap(); +} \ No newline at end of file diff --git a/src/main/java/janggi/domain/board/setup/InnerElephantElephantFormation.java b/src/main/java/janggi/domain/board/setup/InnerElephantElephantFormation.java new file mode 100644 index 0000000000..c4ffaaa61b --- /dev/null +++ b/src/main/java/janggi/domain/board/setup/InnerElephantElephantFormation.java @@ -0,0 +1,19 @@ +package janggi.domain.board.setup; + +import janggi.domain.Position; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; + +public class InnerElephantElephantFormation extends ElephantFormation { + + @Override + public Map offerBoardMap() { + final Map boardMap = new LinkedHashMap<>(COMMON_BOARD_MAP); + boardMap.put(Position.valueOf(1, 2), PieceType.HORSE); + boardMap.put(Position.valueOf(1, 3), PieceType.ELEPHANT); + boardMap.put(Position.valueOf(1, 7), PieceType.ELEPHANT); + boardMap.put(Position.valueOf(1, 8), PieceType.HORSE); + return Map.copyOf(boardMap); + } +} diff --git a/src/main/java/janggi/domain/board/setup/LeftElephantElephantFormation.java b/src/main/java/janggi/domain/board/setup/LeftElephantElephantFormation.java new file mode 100644 index 0000000000..6852ebcd74 --- /dev/null +++ b/src/main/java/janggi/domain/board/setup/LeftElephantElephantFormation.java @@ -0,0 +1,18 @@ +package janggi.domain.board.setup; + +import janggi.domain.Position; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; + +public class LeftElephantElephantFormation extends ElephantFormation { + @Override + public Map offerBoardMap() { + final Map boardMap = new LinkedHashMap<>(COMMON_BOARD_MAP); + boardMap.put(Position.valueOf(1, 2), PieceType.ELEPHANT); + boardMap.put(Position.valueOf(1, 3), PieceType.HORSE); + boardMap.put(Position.valueOf(1, 7), PieceType.ELEPHANT); + boardMap.put(Position.valueOf(1, 8), PieceType.HORSE); + return Map.copyOf(boardMap); + } +} diff --git a/src/main/java/janggi/domain/board/setup/OuterElephantElephantFormation.java b/src/main/java/janggi/domain/board/setup/OuterElephantElephantFormation.java new file mode 100644 index 0000000000..81b7d7cf8c --- /dev/null +++ b/src/main/java/janggi/domain/board/setup/OuterElephantElephantFormation.java @@ -0,0 +1,18 @@ +package janggi.domain.board.setup; + +import janggi.domain.Position; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; + +public class OuterElephantElephantFormation extends ElephantFormation { + @Override + public Map offerBoardMap() { + final Map boardMap = new LinkedHashMap<>(COMMON_BOARD_MAP); + boardMap.put(Position.valueOf(1, 2), PieceType.ELEPHANT); + boardMap.put(Position.valueOf(1, 3), PieceType.HORSE); + boardMap.put(Position.valueOf(1, 7), PieceType.HORSE); + boardMap.put(Position.valueOf(1, 8), PieceType.ELEPHANT); + return Map.copyOf(boardMap); + } +} diff --git a/src/main/java/janggi/domain/board/setup/RightElephantElephantFormation.java b/src/main/java/janggi/domain/board/setup/RightElephantElephantFormation.java new file mode 100644 index 0000000000..9edc17164b --- /dev/null +++ b/src/main/java/janggi/domain/board/setup/RightElephantElephantFormation.java @@ -0,0 +1,18 @@ +package janggi.domain.board.setup; + +import janggi.domain.Position; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; + +public class RightElephantElephantFormation extends ElephantFormation { + @Override + public Map offerBoardMap() { + final Map boardMap = new LinkedHashMap<>(COMMON_BOARD_MAP); + boardMap.put(Position.valueOf(1, 2), PieceType.HORSE); + boardMap.put(Position.valueOf(1, 3), PieceType.ELEPHANT); + boardMap.put(Position.valueOf(1, 7), PieceType.HORSE); + boardMap.put(Position.valueOf(1, 8), PieceType.ELEPHANT); + return Map.copyOf(boardMap); + } +} diff --git a/src/main/java/janggi/domain/board/setup/SetupStrategy.java b/src/main/java/janggi/domain/board/setup/SetupStrategy.java new file mode 100644 index 0000000000..1104d15854 --- /dev/null +++ b/src/main/java/janggi/domain/board/setup/SetupStrategy.java @@ -0,0 +1,36 @@ +package janggi.domain.board.setup; + +import java.util.function.Supplier; + +public enum SetupStrategy { + + INNER_ELEPHANT(1, InnerElephantElephantFormation::new), + OUTER_ELEPHANT(2, OuterElephantElephantFormation::new), + LEFT_ELEPHANT(3, LeftElephantElephantFormation::new), + RIGHT_ELEPHANT(4, RightElephantElephantFormation::new); + + private static final int MIN_SETUP_COMMAND = 1; + private static final int MAX_SETUP_COMMAND = 4; + + private final int setupTypeNumber; + private final Supplier policySupplier; + + SetupStrategy(final int setupTypeNumber, final Supplier policySupplier) { + this.setupTypeNumber = setupTypeNumber; + this.policySupplier = policySupplier; + } + + public static SetupStrategy from(final int setupTypeNumber) { + for (SetupStrategy setup : SetupStrategy.values()) { + if (setup.setupTypeNumber == setupTypeNumber) { + return setup; + } + } + throw new IllegalArgumentException("입력은 " + MIN_SETUP_COMMAND + "에서 " + MAX_SETUP_COMMAND + "까지의 정수 값이어야 합니다."); + } + + + public ElephantFormation toPolicy() { + return policySupplier.get(); + } +} diff --git a/src/main/java/janggi/domain/movement/CannonMoveRule.java b/src/main/java/janggi/domain/movement/CannonMoveRule.java new file mode 100644 index 0000000000..3392ebecfc --- /dev/null +++ b/src/main/java/janggi/domain/movement/CannonMoveRule.java @@ -0,0 +1,44 @@ +package janggi.domain.movement; + +import static janggi.domain.Position.MAXIMUM_ROW; + +import janggi.domain.Position; +import janggi.domain.board.BoardMediator; +import janggi.domain.team.TeamType; +import java.util.List; + +public class CannonMoveRule implements MoveRule { + + private final Movement movement; + + public CannonMoveRule(final Direction direction) { + this.movement = new Movement(MAXIMUM_ROW, direction); + } + + @Override + public List execute(Position from, final TeamType teamType, final BoardMediator boardMediator) { + from = movement.findFirstOccupiedPositionOrMax(from, boardMediator); // 포다리 찾기 + if (isInvalidBridge(from, boardMediator)) { + return List.of(); + } + return removeCannonFromPositions( + movement.calculateTraces(from, teamType, boardMediator), boardMediator); + } + + // 포다리가 안되는 경우 검증(빈 공간인지 or 포다리가 포 인지) + private boolean isInvalidBridge(final Position bridge, final BoardMediator boardMediator) { + return !boardMediator.hasPieceAt(bridge) || boardMediator.isCannon(bridge); + } + + private List removeCannonFromPositions(final List movablePositions, + final BoardMediator boardMediator) { + return movablePositions.stream() + .filter(position -> !isCannonPosition(position, boardMediator)) + .toList(); + } + + private boolean isCannonPosition(final Position position, final BoardMediator boardMediator) { + return boardMediator.hasPieceAt(position) && boardMediator.isCannon(position); + } + +} diff --git a/src/main/java/janggi/domain/movement/Direction.java b/src/main/java/janggi/domain/movement/Direction.java new file mode 100644 index 0000000000..16d1e61d11 --- /dev/null +++ b/src/main/java/janggi/domain/movement/Direction.java @@ -0,0 +1,28 @@ +package janggi.domain.movement; + +public final class Direction { + + public static final Direction UP = new Direction(-1, 0); + public static final Direction DOWN = new Direction(1, 0); + public static final Direction LEFT = new Direction(0, -1); + public static final Direction RIGHT = new Direction(0, 1); + public static final Direction UP_RIGHT = new Direction(-1, 1); + public static final Direction UP_LEFT = new Direction(-1, -1); + public static final Direction DOWN_RIGHT = new Direction(1, 1); + public static final Direction DOWN_LEFT = new Direction(1, -1); + private final int rowDirection; + private final int columnDirection; + + private Direction(final int rowDirection, final int columnDirection) { + this.rowDirection = rowDirection; + this.columnDirection = columnDirection; + } + + public int getRowDirection() { + return rowDirection; + } + + public int getColumnDirection() { + return columnDirection; + } +} diff --git a/src/main/java/janggi/domain/movement/MoveRule.java b/src/main/java/janggi/domain/movement/MoveRule.java new file mode 100644 index 0000000000..cee4999639 --- /dev/null +++ b/src/main/java/janggi/domain/movement/MoveRule.java @@ -0,0 +1,10 @@ +package janggi.domain.movement; + +import janggi.domain.Position; +import janggi.domain.board.BoardMediator; +import janggi.domain.team.TeamType; +import java.util.List; + +public interface MoveRule { + List execute(Position from, TeamType teamType, BoardMediator boardMediator); +} diff --git a/src/main/java/janggi/domain/movement/Movement.java b/src/main/java/janggi/domain/movement/Movement.java new file mode 100644 index 0000000000..3bca4ff2f6 --- /dev/null +++ b/src/main/java/janggi/domain/movement/Movement.java @@ -0,0 +1,97 @@ +package janggi.domain.movement; + +import janggi.domain.Position; +import janggi.domain.board.BoardMediator; +import janggi.domain.team.TeamType; +import java.util.ArrayList; +import java.util.List; + +public class Movement { + + private static final int ONE_STEP = 1; + private final int maxDistance; + private final Direction direction; + + public Movement(int maxDistance, Direction direction) { + this.maxDistance = maxDistance; + this.direction = direction; + } + + public boolean canMove(final Position from) { + return from.checkNextBound(maxDistance, direction); + } + + // 마지막 movement에서만 사용, 1칸 이동으로 고정 + public boolean hasReachablePosition(Position from, final TeamType teamType, final BoardMediator boardMediator) { + Position to = calculateNextPosition(from, ONE_STEP); + return !hasPieceAt(to, boardMediator) || !boardMediator.isSameTeamType(to, teamType); + } + + // 이동 경로 중 막히는지, 1칸 이동으로 고정 + public boolean isBlocked(final Position from, final BoardMediator boardMediator) { + final Position to = calculateNextPosition(from, ONE_STEP); + return hasPieceAt(to, boardMediator); + } + + // 1칸 움직였을 때, 갈 수 있는 위치 반환 + public Position calculateDestination(final Position from, final TeamType teamType, + final BoardMediator boardMediator) { + final Position to = calculateNextPosition(from, ONE_STEP); + if (!hasPieceAt(to, boardMediator)) { + return to; + } + if (boardMediator.isSameTeamType(to, teamType)) { + return from; + } + return to; + } + + // 처음 만나는 기물 위치 반환, 만약 끝까지 가도 없으면 그 끝 위치 반환 + public Position findFirstOccupiedPositionOrMax(final Position from, + final BoardMediator boardMediator) { + for (int distance = 1; distance <= maxDistance; distance++) { + final Position to = calculateNextPosition(from, distance); + if (hasPieceAt(to, boardMediator)) { + return to; + } + } + return calculateNextPosition(from, maxDistance); + } + + // 이동 가능한 경로의 자취 위치 리스트를 반환한다. + // 경로에 장애물을 만나면 그때까지의 리스트를 반환하고, 적을 만난다면 적의 좌표를 포함하여 반환한다. + public List calculateTraces(final Position from, final TeamType teamType, + final BoardMediator boardMediator) { + final List traces = new ArrayList<>(); + for (int distance = 1; distance <= maxDistance; distance++) { + if (!from.checkNextBound(distance, direction)) { + break; + } + Position to = calculateNextPosition(from, distance); + if (processPosition(to, traces, teamType, boardMediator)) { + return traces; + } + } + return traces; + } + + private boolean processPosition(final Position to, final List traces, TeamType teamType, + BoardMediator boardMediator) { + if (!hasPieceAt(to, boardMediator)) { + traces.add(to); + return false; + } + if (!boardMediator.isSameTeamType(to, teamType)) { + traces.add(to); + } + return true; + } + + private Position calculateNextPosition(final Position from, final int distance) { + return from.calculateNext(distance, direction); + } + + private boolean hasPieceAt(final Position position, final BoardMediator boardMediator) { + return boardMediator.hasPieceAt(position); + } +} diff --git a/src/main/java/janggi/domain/movement/SlidingMoveRule.java b/src/main/java/janggi/domain/movement/SlidingMoveRule.java new file mode 100644 index 0000000000..c5c44d1dfa --- /dev/null +++ b/src/main/java/janggi/domain/movement/SlidingMoveRule.java @@ -0,0 +1,20 @@ +package janggi.domain.movement; + +import janggi.domain.Position; +import janggi.domain.board.BoardMediator; +import janggi.domain.team.TeamType; +import java.util.List; + +public class SlidingMoveRule implements MoveRule { + + private final Movement movementOrder; + + public SlidingMoveRule(Movement movementOrder) { + this.movementOrder = movementOrder; + } + + @Override + public List execute(Position from, final TeamType teamType, final BoardMediator boardMediator) { + return movementOrder.calculateTraces(from, teamType, boardMediator); + } +} diff --git a/src/main/java/janggi/domain/movement/StepMoveRule.java b/src/main/java/janggi/domain/movement/StepMoveRule.java new file mode 100644 index 0000000000..45b3da255f --- /dev/null +++ b/src/main/java/janggi/domain/movement/StepMoveRule.java @@ -0,0 +1,51 @@ +package janggi.domain.movement; + +import janggi.domain.Position; +import janggi.domain.board.BoardMediator; +import janggi.domain.team.TeamType; +import java.util.List; + +public class StepMoveRule implements MoveRule { + + private final List movementOrder; + + public StepMoveRule(final List movementOrder) { + this.movementOrder = movementOrder; + } + + public static StepMoveRule elephantShape(Direction straight, Direction diagonal) { + return new StepMoveRule(List.of( + new Movement(1, straight), + new Movement(1, diagonal), + new Movement(1, diagonal) + )); + } + + public static StepMoveRule horseShape(Direction straight, Direction diagonal) { + return new StepMoveRule(List.of( + new Movement(1, straight), + new Movement(1, diagonal) + )); + } + + @Override + public List execute(Position from, final TeamType teamType, final BoardMediator boardMediator) { + for (int index = 0; index < movementOrder.size() - 1; index++) { + final Movement movement = movementOrder.get(index); + if (!movement.canMove(from) || movement.isBlocked(from, boardMediator)) { + return List.of(); + } + from = movement.calculateDestination(from, teamType, boardMediator); + } + return findLastPosition(from, teamType, boardMediator); + } + + private List findLastPosition(final Position from, TeamType teamType, final BoardMediator boardMediator) { + final Movement lastMovement = movementOrder.getLast(); + if (!lastMovement.canMove(from) || !lastMovement.hasReachablePosition(from, teamType, boardMediator)) { + return List.of(); + } + final Position destination = lastMovement.findFirstOccupiedPositionOrMax(from, boardMediator); + return List.of(destination); + } +} diff --git a/src/main/java/janggi/domain/piece/AbstractPiece.java b/src/main/java/janggi/domain/piece/AbstractPiece.java new file mode 100644 index 0000000000..99f44dc53c --- /dev/null +++ b/src/main/java/janggi/domain/piece/AbstractPiece.java @@ -0,0 +1,38 @@ +package janggi.domain.piece; + +import janggi.domain.Position; +import janggi.domain.board.BoardMediator; +import janggi.domain.team.TeamType; +import java.util.List; + +public abstract class AbstractPiece implements Piece { + protected final TeamType teamType; + + protected AbstractPiece(TeamType teamType) { + this.teamType = teamType; + } + + protected abstract PieceType getPieceType(); + + protected abstract PieceAction getPieceAction(); + + @Override + public final boolean isSameTeamType(final TeamType teamType) { + return this.teamType == teamType; + } + + @Override + public final List calculateMovablePositions(final Position from, final BoardMediator boardMediator) { + return getPieceAction().calculateMovablePositions(from, this.teamType, boardMediator); + } + + @Override + public final TeamType getTeamTypeForDTO() { + return this.teamType; + } + + @Override + public final PieceType getPieceTypeForDTO() { + return getPieceType(); + } +} diff --git a/src/main/java/janggi/domain/piece/Cannon.java b/src/main/java/janggi/domain/piece/Cannon.java new file mode 100644 index 0000000000..31745c5c2c --- /dev/null +++ b/src/main/java/janggi/domain/piece/Cannon.java @@ -0,0 +1,40 @@ +package janggi.domain.piece; + +import janggi.domain.movement.CannonMoveRule; +import janggi.domain.movement.Direction; +import janggi.domain.movement.MoveRule; +import janggi.domain.team.TeamType; +import java.util.List; + +public class Cannon extends AbstractPiece { + private static final PieceType PIECE_TYPE = PieceType.CANNON; + private static final PieceAction PIECE_ACTION; + + static { + final List movementStrategies = List.of( + new CannonMoveRule(Direction.UP), + new CannonMoveRule(Direction.DOWN), + new CannonMoveRule(Direction.RIGHT), + new CannonMoveRule(Direction.LEFT)); + PIECE_ACTION = new PieceAction(movementStrategies); + } + + public Cannon(TeamType teamType) { + super(teamType); + } + + @Override + public boolean isCannon() { + return true; + } + + @Override + protected PieceType getPieceType() { + return PIECE_TYPE; + } + + @Override + protected PieceAction getPieceAction() { + return PIECE_ACTION; + } +} diff --git a/src/main/java/janggi/domain/piece/Chariot.java b/src/main/java/janggi/domain/piece/Chariot.java new file mode 100644 index 0000000000..62d59cd292 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Chariot.java @@ -0,0 +1,39 @@ +package janggi.domain.piece; + +import static janggi.domain.Position.MAXIMUM_COLUMN; +import static janggi.domain.Position.MAXIMUM_ROW; + +import janggi.domain.movement.Direction; +import janggi.domain.movement.MoveRule; +import janggi.domain.movement.Movement; +import janggi.domain.movement.SlidingMoveRule; +import janggi.domain.team.TeamType; +import java.util.List; + +public class Chariot extends AbstractPiece { + private static final PieceType PIECE_TYPE = PieceType.CHARIOT; + private static final PieceAction PIECE_ACTION; + + static { + final List movementStrategies = List.of( + new SlidingMoveRule(new Movement(MAXIMUM_ROW, Direction.UP)), + new SlidingMoveRule(new Movement(MAXIMUM_ROW, Direction.DOWN)), + new SlidingMoveRule(new Movement(MAXIMUM_COLUMN, Direction.RIGHT)), + new SlidingMoveRule(new Movement(MAXIMUM_COLUMN, Direction.LEFT))); + PIECE_ACTION = new PieceAction(movementStrategies); + } + + public Chariot(TeamType teamType) { + super(teamType); + } + + @Override + protected PieceType getPieceType() { + return PIECE_TYPE; + } + + @Override + protected PieceAction getPieceAction() { + return PIECE_ACTION; + } +} diff --git a/src/main/java/janggi/domain/piece/Elephant.java b/src/main/java/janggi/domain/piece/Elephant.java new file mode 100644 index 0000000000..ec1c9f8c41 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Elephant.java @@ -0,0 +1,39 @@ +package janggi.domain.piece; + +import janggi.domain.movement.Direction; +import janggi.domain.movement.MoveRule; +import janggi.domain.movement.StepMoveRule; +import janggi.domain.team.TeamType; +import java.util.List; + +public class Elephant extends AbstractPiece { + private static final PieceType PIECE_TYPE = PieceType.ELEPHANT; + private static final PieceAction PIECE_ACTION; + + static { + final List movementStrategies = List.of( + StepMoveRule.elephantShape(Direction.UP, Direction.UP_LEFT), + StepMoveRule.elephantShape(Direction.UP, Direction.UP_RIGHT), + StepMoveRule.elephantShape(Direction.RIGHT, Direction.UP_RIGHT), + StepMoveRule.elephantShape(Direction.RIGHT, Direction.DOWN_RIGHT), + StepMoveRule.elephantShape(Direction.DOWN, Direction.DOWN_LEFT), + StepMoveRule.elephantShape(Direction.DOWN, Direction.DOWN_RIGHT), + StepMoveRule.elephantShape(Direction.LEFT, Direction.DOWN_LEFT), + StepMoveRule.elephantShape(Direction.LEFT, Direction.UP_LEFT)); + PIECE_ACTION = new PieceAction(movementStrategies); + } + + public Elephant(TeamType teamType) { + super(teamType); + } + + @Override + protected PieceType getPieceType() { + return PIECE_TYPE; + } + + @Override + protected PieceAction getPieceAction() { + return PIECE_ACTION; + } +} \ No newline at end of file diff --git a/src/main/java/janggi/domain/piece/General.java b/src/main/java/janggi/domain/piece/General.java new file mode 100644 index 0000000000..6a29048af1 --- /dev/null +++ b/src/main/java/janggi/domain/piece/General.java @@ -0,0 +1,45 @@ +package janggi.domain.piece; + +import janggi.domain.movement.Direction; +import janggi.domain.movement.MoveRule; +import janggi.domain.movement.Movement; +import janggi.domain.movement.SlidingMoveRule; +import janggi.domain.team.TeamType; +import java.util.List; + +public class General extends AbstractPiece { + private static final PieceType PIECE_TYPE = PieceType.GENERAL; + private static final PieceAction PIECE_ACTION; + + static { + final List movementStrategies = List.of( + new SlidingMoveRule(new Movement(1, Direction.UP_LEFT)), + new SlidingMoveRule(new Movement(1, Direction.UP)), + new SlidingMoveRule(new Movement(1, Direction.UP_RIGHT)), + new SlidingMoveRule(new Movement(1, Direction.LEFT)), + new SlidingMoveRule(new Movement(1, Direction.RIGHT)), + new SlidingMoveRule(new Movement(1, Direction.DOWN_LEFT)), + new SlidingMoveRule(new Movement(1, Direction.DOWN)), + new SlidingMoveRule(new Movement(1, Direction.DOWN_RIGHT))); + PIECE_ACTION = new PieceAction(movementStrategies); + } + + public General(TeamType teamType) { + super(teamType); + } + + @Override + public boolean isGeneral() { + return true; + } + + @Override + protected PieceType getPieceType() { + return PIECE_TYPE; + } + + @Override + protected PieceAction getPieceAction() { + return PIECE_ACTION; + } +} \ No newline at end of file diff --git a/src/main/java/janggi/domain/piece/Guard.java b/src/main/java/janggi/domain/piece/Guard.java new file mode 100644 index 0000000000..eac8f1a004 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Guard.java @@ -0,0 +1,40 @@ +package janggi.domain.piece; + +import janggi.domain.movement.Direction; +import janggi.domain.movement.MoveRule; +import janggi.domain.movement.Movement; +import janggi.domain.movement.SlidingMoveRule; +import janggi.domain.team.TeamType; +import java.util.List; + +public class Guard extends AbstractPiece { + private static final PieceType PIECE_TYPE = PieceType.GUARD; + private static final PieceAction PIECE_ACTION; + + static { + final List movementStrategies = List.of( + new SlidingMoveRule(new Movement(1, Direction.UP_LEFT)), + new SlidingMoveRule(new Movement(1, Direction.UP)), + new SlidingMoveRule(new Movement(1, Direction.UP_RIGHT)), + new SlidingMoveRule(new Movement(1, Direction.LEFT)), + new SlidingMoveRule(new Movement(1, Direction.RIGHT)), + new SlidingMoveRule(new Movement(1, Direction.DOWN_LEFT)), + new SlidingMoveRule(new Movement(1, Direction.DOWN)), + new SlidingMoveRule(new Movement(1, Direction.DOWN_RIGHT))); + PIECE_ACTION = new PieceAction(movementStrategies); + } + + public Guard(TeamType teamType) { + super(teamType); + } + + @Override + protected PieceType getPieceType() { + return PIECE_TYPE; + } + + @Override + protected PieceAction getPieceAction() { + return PIECE_ACTION; + } +} \ No newline at end of file diff --git a/src/main/java/janggi/domain/piece/Horse.java b/src/main/java/janggi/domain/piece/Horse.java new file mode 100644 index 0000000000..79acd43886 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Horse.java @@ -0,0 +1,39 @@ +package janggi.domain.piece; + +import janggi.domain.movement.Direction; +import janggi.domain.movement.MoveRule; +import janggi.domain.movement.StepMoveRule; +import janggi.domain.team.TeamType; +import java.util.List; + +public class Horse extends AbstractPiece { + private static final PieceType PIECE_TYPE = PieceType.HORSE; + private static final PieceAction PIECE_ACTION; + + static { + final List movementStrategies = List.of( + StepMoveRule.horseShape(Direction.UP, Direction.UP_LEFT), + StepMoveRule.horseShape(Direction.UP, Direction.UP_RIGHT), + StepMoveRule.horseShape(Direction.RIGHT, Direction.UP_RIGHT), + StepMoveRule.horseShape(Direction.RIGHT, Direction.DOWN_RIGHT), + StepMoveRule.horseShape(Direction.DOWN, Direction.DOWN_LEFT), + StepMoveRule.horseShape(Direction.DOWN, Direction.DOWN_RIGHT), + StepMoveRule.horseShape(Direction.LEFT, Direction.DOWN_LEFT), + StepMoveRule.horseShape(Direction.LEFT, Direction.UP_LEFT)); + PIECE_ACTION = new PieceAction(movementStrategies); + } + + public Horse(TeamType teamType) { + super(teamType); + } + + @Override + protected PieceType getPieceType() { + return PIECE_TYPE; + } + + @Override + protected PieceAction getPieceAction() { + return PIECE_ACTION; + } +} \ No newline at end of file diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java new file mode 100644 index 0000000000..9e080d258e --- /dev/null +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -0,0 +1,24 @@ +package janggi.domain.piece; + +import janggi.domain.Position; +import janggi.domain.board.BoardMediator; +import janggi.domain.team.TeamType; +import java.util.List; + +public interface Piece { + boolean isSameTeamType(TeamType teamType); + + List calculateMovablePositions(Position from, BoardMediator boardMediator); + + default boolean isGeneral() { + return false; + } + + default boolean isCannon() { + return false; + } + + TeamType getTeamTypeForDTO(); + + PieceType getPieceTypeForDTO(); +} diff --git a/src/main/java/janggi/domain/piece/PieceAction.java b/src/main/java/janggi/domain/piece/PieceAction.java new file mode 100644 index 0000000000..47d5590e59 --- /dev/null +++ b/src/main/java/janggi/domain/piece/PieceAction.java @@ -0,0 +1,24 @@ +package janggi.domain.piece; + +import janggi.domain.Position; +import janggi.domain.board.BoardMediator; +import janggi.domain.movement.MoveRule; +import janggi.domain.team.TeamType; +import java.util.Collection; +import java.util.List; + +public class PieceAction { + private final List movementStrategies; + + public PieceAction(List movementStrategies) { + this.movementStrategies = List.copyOf(movementStrategies); + } + + public List calculateMovablePositions(final Position from, final TeamType teamType, + final BoardMediator boardMediator) { + return movementStrategies.stream() + .map(rule -> rule.execute(from, teamType, boardMediator)) + .flatMap(Collection::stream) + .toList(); + } +} 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..75cc2780b8 --- /dev/null +++ b/src/main/java/janggi/domain/piece/PieceType.java @@ -0,0 +1,26 @@ +package janggi.domain.piece; + +import janggi.domain.team.TeamType; +import java.util.function.Function; + +public enum PieceType { + + GENERAL(General::new), + GUARD(Guard::new), + HORSE(Horse::new), + ELEPHANT(Elephant::new), + CHARIOT(Chariot::new), + CANNON(Cannon::new), + SOLDIER(Soldier::new); + + private final Function function; + + PieceType(Function function) { + this.function = function; + } + + public Piece toPiece(final TeamType teamType) { + return function.apply(teamType); + } + +} diff --git a/src/main/java/janggi/domain/piece/Soldier.java b/src/main/java/janggi/domain/piece/Soldier.java new file mode 100644 index 0000000000..91ef785816 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Soldier.java @@ -0,0 +1,44 @@ +package janggi.domain.piece; + +import janggi.domain.movement.Direction; +import janggi.domain.movement.MoveRule; +import janggi.domain.movement.Movement; +import janggi.domain.movement.SlidingMoveRule; +import janggi.domain.team.TeamType; +import java.util.List; + +public class Soldier extends AbstractPiece { + private static final PieceType PIECE_TYPE = PieceType.SOLDIER; + private static final PieceAction RED_PIECE_ACTION; + private static final PieceAction BLUE_PIECE_ACTION; + + static { + final List redMovementStrategies = List.of( + new SlidingMoveRule(new Movement(1, Direction.LEFT)), + new SlidingMoveRule(new Movement(1, Direction.RIGHT)), + new SlidingMoveRule(new Movement(1, Direction.DOWN))); + final List blueMovementStrategies = List.of( + new SlidingMoveRule(new Movement(1, Direction.LEFT)), + new SlidingMoveRule(new Movement(1, Direction.RIGHT)), + new SlidingMoveRule(new Movement(1, Direction.UP))); + RED_PIECE_ACTION = new PieceAction(redMovementStrategies); + BLUE_PIECE_ACTION = new PieceAction(blueMovementStrategies); + } + + public Soldier(TeamType teamType) { + super(teamType); + } + + @Override + protected PieceType getPieceType() { + return PIECE_TYPE; + } + + @Override + protected PieceAction getPieceAction() { + if (this.teamType == TeamType.RED) { + return RED_PIECE_ACTION; + } + return BLUE_PIECE_ACTION; + } +} \ No newline at end of file diff --git a/src/main/java/janggi/domain/team/BlueTeam.java b/src/main/java/janggi/domain/team/BlueTeam.java new file mode 100644 index 0000000000..c8131e2214 --- /dev/null +++ b/src/main/java/janggi/domain/team/BlueTeam.java @@ -0,0 +1,33 @@ +package janggi.domain.team; + +import janggi.domain.Position; +import janggi.domain.board.setup.ElephantFormation; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; + +public class BlueTeam implements Team { + + private static final TeamType TEAM_TYPE = TeamType.BLUE; + private final ElephantFormation elephantFormation; + + public BlueTeam(final ElephantFormation elephantFormation) { + this.elephantFormation = elephantFormation; + } + + @Override + public Map generatePieces() { + final Map positionPieceMap = new LinkedHashMap<>(); + final Map positionPieceTypeMap = elephantFormation.offerBoardMap(); + positionPieceTypeMap.forEach((position, pieceType) -> + positionPieceMap.put(position.flipAroundMiddleRow(), pieceType.toPiece(TEAM_TYPE))); + + return positionPieceMap; + } + + @Override + public String getName() { + return TEAM_TYPE.getName(); + } +} diff --git a/src/main/java/janggi/domain/team/RedTeam.java b/src/main/java/janggi/domain/team/RedTeam.java new file mode 100644 index 0000000000..fa7412884d --- /dev/null +++ b/src/main/java/janggi/domain/team/RedTeam.java @@ -0,0 +1,34 @@ +package janggi.domain.team; + +import janggi.domain.Position; +import janggi.domain.board.setup.ElephantFormation; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; + +public class RedTeam implements Team { + + private static final TeamType TEAM_TYPE = TeamType.RED; + private final ElephantFormation elephantFormation; + + + public RedTeam(final ElephantFormation elephantFormation) { + this.elephantFormation = elephantFormation; + } + + @Override + public Map generatePieces() { + final Map positionPieceMap = new LinkedHashMap<>(); + final Map positionPieceTypeMap = elephantFormation.offerBoardMap(); + positionPieceTypeMap.forEach((position, pieceType) -> + positionPieceMap.put(position, pieceType.toPiece(TEAM_TYPE))); + + return positionPieceMap; + } + + @Override + public String getName() { + return TEAM_TYPE.getName(); + } +} diff --git a/src/main/java/janggi/domain/team/Team.java b/src/main/java/janggi/domain/team/Team.java new file mode 100644 index 0000000000..d8bda186ed --- /dev/null +++ b/src/main/java/janggi/domain/team/Team.java @@ -0,0 +1,11 @@ +package janggi.domain.team; + +import janggi.domain.Position; +import janggi.domain.piece.Piece; +import java.util.Map; + +public interface Team { + Map generatePieces(); + + String getName(); +} diff --git a/src/main/java/janggi/domain/team/TeamType.java b/src/main/java/janggi/domain/team/TeamType.java new file mode 100644 index 0000000000..90d4099153 --- /dev/null +++ b/src/main/java/janggi/domain/team/TeamType.java @@ -0,0 +1,23 @@ +package janggi.domain.team; + +public enum TeamType { + RED("한나라"), + BLUE("초나라"); + + private final String name; + + TeamType(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public TeamType nextTeamType() { + if (this == RED) { + return BLUE; + } + return RED; + } +} diff --git a/src/main/java/janggi/domain/team/TurnManager.java b/src/main/java/janggi/domain/team/TurnManager.java new file mode 100644 index 0000000000..477f46dd65 --- /dev/null +++ b/src/main/java/janggi/domain/team/TurnManager.java @@ -0,0 +1,25 @@ +package janggi.domain.team; + +public class TurnManager { + private TeamType currentTurnTeamType; + + public TurnManager() { + currentTurnTeamType = TeamType.RED; + } + + public void changeTurn() { + this.currentTurnTeamType = currentTurnTeamType.nextTeamType(); + } + + public boolean isCurrentTurnOf(TeamType teamType) { + return this.currentTurnTeamType == teamType; + } + + public TeamType currentTeamType() { + return currentTurnTeamType; + } + + public String currentTeamTypeToString() { + return currentTurnTeamType.getName(); + } +} diff --git a/src/main/java/janggi/dto/BoardDto.java b/src/main/java/janggi/dto/BoardDto.java new file mode 100644 index 0000000000..937866c13f --- /dev/null +++ b/src/main/java/janggi/dto/BoardDto.java @@ -0,0 +1,18 @@ +package janggi.dto; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.piece.Piece; +import java.util.LinkedHashMap; +import java.util.Map; + +public record BoardDto(Map pieceMap) { + + public static BoardDto from(final Board board) { + final Map positionPieceMap = board.getPositionPieceMapForDTO(); + final Map pieceDtoMap = new LinkedHashMap<>(); + positionPieceMap.forEach((position, piece) -> + pieceDtoMap.put(position, new PieceDto(piece.getPieceTypeForDTO(), piece.getTeamTypeForDTO()))); + return new BoardDto(Map.copyOf(pieceDtoMap)); + } +} diff --git a/src/main/java/janggi/dto/PieceDto.java b/src/main/java/janggi/dto/PieceDto.java new file mode 100644 index 0000000000..f8a7d2ac65 --- /dev/null +++ b/src/main/java/janggi/dto/PieceDto.java @@ -0,0 +1,7 @@ +package janggi.dto; + +import janggi.domain.piece.PieceType; +import janggi.domain.team.TeamType; + +public record PieceDto(PieceType pieceType, TeamType teamType) { +} \ No newline at end of file diff --git a/src/main/java/janggi/utils/Parser.java b/src/main/java/janggi/utils/Parser.java new file mode 100644 index 0000000000..8c58d29c5b --- /dev/null +++ b/src/main/java/janggi/utils/Parser.java @@ -0,0 +1,15 @@ +package janggi.utils; + +public final class Parser { + + private Parser() { + } + + public static int parseInteger(final String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("정수만 입력할 수 있습니다."); + } + } +} diff --git a/src/main/java/janggi/utils/RetryExecutor.java b/src/main/java/janggi/utils/RetryExecutor.java new file mode 100644 index 0000000000..c819a4e66c --- /dev/null +++ b/src/main/java/janggi/utils/RetryExecutor.java @@ -0,0 +1,18 @@ +package janggi.utils; + +import janggi.view.OutputView; +import java.util.function.Supplier; + +public final class RetryExecutor { + private RetryExecutor() { + } + + public static T retry(final Supplier supplier) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + return retry(supplier); + } + } +} diff --git a/src/main/java/janggi/view/ConsoleColor.java b/src/main/java/janggi/view/ConsoleColor.java new file mode 100644 index 0000000000..983c3551e5 --- /dev/null +++ b/src/main/java/janggi/view/ConsoleColor.java @@ -0,0 +1,24 @@ +package janggi.view; + +public final class ConsoleColor { + + private static final String RED = "\u001B[31m"; + private static final String BLUE = "\u001B[34m"; + private static final String GREEN_BACKGROUND = "\u001B[42m"; + private static final String EXIT = "\u001B[0m"; + + private ConsoleColor() { + } + + public static String red(final String string) { + return RED + string + EXIT; + } + + public static String blue(final String string) { + return BLUE + string + EXIT; + } + + public static String getGreenBackground(final String string) { + return GREEN_BACKGROUND + string + EXIT; + } +} diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java new file mode 100644 index 0000000000..8d05b62cd0 --- /dev/null +++ b/src/main/java/janggi/view/InputView.java @@ -0,0 +1,41 @@ +package janggi.view; + +import static janggi.domain.Position.MAXIMUM_COLUMN; +import static janggi.domain.Position.MAXIMUM_ROW; +import static janggi.domain.Position.MINIMUM_COLUMN; +import static janggi.domain.Position.MINIMUM_ROW; + +import janggi.domain.Position; +import janggi.utils.Parser; +import janggi.view.reader.Console; + +public final class InputView { + private InputView() { + } + + public static int readSetupCommand() { + return Parser.parseInteger(Console.readLine()); + } + + public static Position readPosition() { + String input = Console.readLine(); + String[] parts = input.split(","); + int inputRow = Parser.parseInteger(parts[0].trim()); + validateRowRange(inputRow); + int inputColumn = Parser.parseInteger(parts[1].trim()); + validateColumnRange(inputColumn); + return Position.valueOf(inputRow, inputColumn); + } + + private static void validateRowRange(final int row) { + if (row < MINIMUM_ROW || row > MAXIMUM_ROW) { + throw new IllegalArgumentException("행 입력은 1~10을 입력해야 합니다."); + } + } + + private static void validateColumnRange(final int column) { + if (column < MINIMUM_COLUMN || column > MAXIMUM_COLUMN) { + throw new IllegalArgumentException("열 입력은 1~9을 입력해야 합니다."); + } + } +} diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java new file mode 100644 index 0000000000..3a033b3478 --- /dev/null +++ b/src/main/java/janggi/view/OutputView.java @@ -0,0 +1,129 @@ +package janggi.view; + +import janggi.domain.Position; +import janggi.domain.piece.PieceType; +import janggi.domain.team.TeamType; +import janggi.dto.BoardDto; +import janggi.dto.PieceDto; +import java.util.List; +import java.util.Map; + +public final class OutputView { + + private static final String ERROR_PREFIX = "[ERROR]: "; + private static final String EMPTY_SPACE = "*"; + private static final String DELIMITER = " "; + private static final String[] INDEX_LABELS = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}; + private static final Map> CHINESE_MAP; + private static final List ELEPHANT_FORMATION_DESCRIPTIONS = List.of( + "1. 안상 차림", + "2. 바깥상 차림", + "3. 왼상 차림", + "4. 오른상 차림" + ); + + static { + CHINESE_MAP = Map.of( + TeamType.RED, Map.of( + PieceType.GENERAL, "漢", + PieceType.GUARD, "士", + PieceType.CHARIOT, "車", + PieceType.CANNON, "包", + PieceType.HORSE, "馬", + PieceType.ELEPHANT, "象", + PieceType.SOLDIER, "兵" + ), + TeamType.BLUE, Map.of( + PieceType.GENERAL, "楚", + PieceType.GUARD, "士", + PieceType.CHARIOT, "車", + PieceType.CANNON, "包", + PieceType.HORSE, "馬", + PieceType.ELEPHANT, "象", + PieceType.SOLDIER, "卒" + )); + } + + private OutputView() { + } + + public static void printSetupGuide(TeamType teamType) { + System.out.println(teamType.getName() + "의 차림법을 입력해주세요."); + for (final String description : ELEPHANT_FORMATION_DESCRIPTIONS) { + System.out.println(description); + } + } + + public static void printErrorMessage(String message) { + System.out.println(ERROR_PREFIX + message); + } + + public static void printBoard(final BoardDto boardDto, String currentTeamType) { + System.out.println(currentTeamType + "의 차례입니다."); + printBoardWithMovable(boardDto, List.of()); + } + + public static void printBoardWithMovable(final BoardDto boardDto, final List movablePositions) { + System.out.println(generateColumnIndex()); + for (int row = Position.MINIMUM_ROW; row <= Position.MAXIMUM_ROW; row++) { + System.out.println(composeRowStatus(row, boardDto.pieceMap(), movablePositions)); + } + } + + public static void printInputFromPosition() { + System.out.println("움직이고 싶은 기물의 위치를 n,n 형태로 입력해주세요."); + } + + public static void printInputToPosition() { + System.out.println("이동하고 싶은 위치를 n,n 형태로 입력해주세요."); + } + + public static void printGameOverMessage(String teamType) { + System.out.println(teamType + "의 승리로 게임이 종료되었습니다."); + } + + private static String generateColumnIndex() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(DELIMITER).append(DELIMITER); + for (int column = Position.MINIMUM_COLUMN; column <= Position.MAXIMUM_COLUMN; column++) { + stringBuilder.append(DELIMITER).append(INDEX_LABELS[column - 1]); + } + return stringBuilder.toString(); + } + + private static String composeRowStatus(final int row, final Map pieceMap, + final List movablePositions) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(DELIMITER).append(INDEX_LABELS[row - 1]); + for (int column = Position.MINIMUM_COLUMN; column <= Position.MAXIMUM_COLUMN; column++) { + Position position = Position.valueOf(row, column); + stringBuilder.append(DELIMITER).append(getPositionDisplay(position, pieceMap, movablePositions)); + } + return stringBuilder.toString(); + } + + private static String getPositionDisplay(final Position position, final Map pieceMap, + final List movablePositions) { + String cellText = toCellText(position, pieceMap); + if (movablePositions.contains(position)) { + return ConsoleColor.getGreenBackground(cellText); + } + return cellText; + } + + private static String toCellText(final Position position, final Map pieceMap) { + PieceDto pieceDto = pieceMap.get(position); + if (pieceDto == null) { + return EMPTY_SPACE; + } + return applyTeamColor(pieceDto); + } + + private static String applyTeamColor(final PieceDto pieceDto) { + String chinese = CHINESE_MAP.get(pieceDto.teamType()).get(pieceDto.pieceType()); + if (pieceDto.teamType() == TeamType.RED) { + return ConsoleColor.red(chinese); + } + return ConsoleColor.blue(chinese); + } +} diff --git a/src/main/java/janggi/view/reader/Console.java b/src/main/java/janggi/view/reader/Console.java new file mode 100644 index 0000000000..1e6ec5ce39 --- /dev/null +++ b/src/main/java/janggi/view/reader/Console.java @@ -0,0 +1,17 @@ +package janggi.view.reader; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class Console { + private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in)); + + public static String readLine() { + try { + return READER.readLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java new file mode 100644 index 0000000000..e72a5dbc64 --- /dev/null +++ b/src/test/java/janggi/domain/PositionTest.java @@ -0,0 +1,45 @@ +package janggi.domain; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class PositionTest { + + @Nested + @DisplayName("생성 테스트") + class Constructor { + + @Test + @DisplayName("정상 테스트") + void success() { + int row = 1; + int column = 1; + + assertDoesNotThrow(() -> Position.valueOf(row, column)); + } + + @Test + @DisplayName("행 값이 범위를 벗어난 경우") + void failure_1() { + int wrongRow = 11; + int column = 1; + + assertThatIllegalArgumentException() + .isThrownBy(() -> Position.valueOf(wrongRow, column)); + } + + @Test + @DisplayName("열 값이 범위를 벗어난 경우") + void failure_2() { + int wrongRow = 1; + int column = 10; + + assertThatIllegalArgumentException() + .isThrownBy(() -> Position.valueOf(wrongRow, column)); + } + } +} \ No newline at end of file diff --git a/src/test/java/janggi/domain/board/BoardGeneratorTest.java b/src/test/java/janggi/domain/board/BoardGeneratorTest.java new file mode 100644 index 0000000000..7288aa6528 --- /dev/null +++ b/src/test/java/janggi/domain/board/BoardGeneratorTest.java @@ -0,0 +1,30 @@ +package janggi.domain.board; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import janggi.domain.board.setup.InnerElephantElephantFormation; +import janggi.domain.team.BlueTeam; +import janggi.domain.team.RedTeam; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class BoardGeneratorTest { + + @Test + @DisplayName("보드판 기물 정상 테스트") + void success() { + // given + RedTeam redTeam = new RedTeam(new InnerElephantElephantFormation()); + BlueTeam blueTeam = new BlueTeam(new InnerElephantElephantFormation()); + final int expected = 32; + + // when + Board board = BoardGenerator.generate(redTeam, blueTeam); + + // then + assertThat(board).extracting("positionPieceMap") + .asInstanceOf(InstanceOfAssertFactories.MAP) + .hasSize(expected); + } +} diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java new file mode 100644 index 0000000000..8a13e76ff6 --- /dev/null +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -0,0 +1,90 @@ +package janggi.domain.board; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import janggi.domain.Position; +import janggi.domain.piece.Cannon; +import janggi.domain.piece.General; +import janggi.domain.piece.Piece; +import janggi.domain.team.TeamType; +import janggi.domain.team.TurnManager; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class BoardTest { + + @Test + @DisplayName("현재 차례 플레이어의 장이 살아있다") + void generalIsTwo() { + Map positionPieceMap; + Position position1 = Position.valueOf(9, 5); + TurnManager turnManager = new TurnManager(); + positionPieceMap = Map.of(position1, new General(TeamType.RED)); + Board board = new Board(positionPieceMap); + boolean expected = true; + + boolean actual = board.hasGeneral(turnManager.currentTeamType()); + + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("현재 차례 플레이어의 장이 없다") + void generalIsOne() { + Map positionPieceMap; + Position position1 = Position.valueOf(9, 5); + TurnManager turnManager = new TurnManager(); + positionPieceMap = Map.of(position1, new General(TeamType.BLUE)); + Board board = new Board(positionPieceMap); + boolean expected = false; + + boolean actual = board.hasGeneral(turnManager.currentTeamType()); + + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("빈 칸의 기물을 이동하려고 한 경우") + void failure() { + Map positionPieceMap = new LinkedHashMap<>(); + Board board = new Board(positionPieceMap); + Position position = Position.valueOf(9, 5); + + assertThatIllegalArgumentException() + .isThrownBy(() -> board.calculateMovablePositions(position)); + } + + @Nested + @DisplayName("빈칸 여부 테스트") + class isBlank { + + @Test + @DisplayName("빈칸인 경우") + void success_1() { + Board board = new Board(new LinkedHashMap<>()); + Position position = Position.valueOf(1, 1); + boolean expected = false; + + boolean actual = board.hasPieceAt(position); + + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("빈칸이 아닌 경우") + void success_2() { + Position position = Position.valueOf(1, 1); + Map positionPieceMap = Map.of(position, new Cannon(TeamType.RED)); + Board board = new Board(positionPieceMap); + boolean expected = true; + + boolean actual = board.hasPieceAt(position); + + assertThat(actual).isEqualTo(expected); + } + } +} diff --git a/src/test/java/janggi/domain/fixture/SetupPolicyTestFixture.java b/src/test/java/janggi/domain/fixture/SetupPolicyTestFixture.java new file mode 100644 index 0000000000..a000605c42 --- /dev/null +++ b/src/test/java/janggi/domain/fixture/SetupPolicyTestFixture.java @@ -0,0 +1,26 @@ +package janggi.domain.fixture; + +import janggi.domain.Position; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; + +public class SetupPolicyTestFixture { + + public static Map 상_마를_제외한_기물_배치_정보_제공() { + Map map = new LinkedHashMap<>(); + map.put(Position.valueOf(1, 1), PieceType.CHARIOT); + map.put(Position.valueOf(1, 4), PieceType.GUARD); + map.put(Position.valueOf(1, 6), PieceType.GUARD); + map.put(Position.valueOf(1, 9), PieceType.CHARIOT); + map.put(Position.valueOf(2, 5), PieceType.GENERAL); + map.put(Position.valueOf(3, 2), PieceType.CANNON); + map.put(Position.valueOf(3, 8), PieceType.CANNON); + map.put(Position.valueOf(4, 1), PieceType.SOLDIER); + map.put(Position.valueOf(4, 3), PieceType.SOLDIER); + map.put(Position.valueOf(4, 5), PieceType.SOLDIER); + map.put(Position.valueOf(4, 7), PieceType.SOLDIER); + map.put(Position.valueOf(4, 9), PieceType.SOLDIER); + return map; + } +} diff --git a/src/test/java/janggi/domain/movement/CannonMoveRuleTest.java b/src/test/java/janggi/domain/movement/CannonMoveRuleTest.java new file mode 100644 index 0000000000..8c620ed169 --- /dev/null +++ b/src/test/java/janggi/domain/movement/CannonMoveRuleTest.java @@ -0,0 +1,143 @@ +package janggi.domain.movement; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.piece.Cannon; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Soldier; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class CannonMoveRuleTest { + + + @Nested + @DisplayName("이동 가능한 목적지 계산 테스트") + class Execute { + + @Test + @DisplayName("포는 포를 잡을 수 없다") + void execute_1() { + // given + Map positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(Position.valueOf(6, 2), new Cannon(TeamType.BLUE)); + positionPieceMap.put(Position.valueOf(6, 4), new Soldier(TeamType.RED)); + positionPieceMap.put(Position.valueOf(6, 7), new Cannon(TeamType.RED)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(6, 2); + MoveRule moveRuleOfCannon = new CannonMoveRule(Direction.RIGHT); + + // when + List actual = moveRuleOfCannon.execute(from, TeamType.BLUE, board); + + // then + List expected = List.of(Position.valueOf(6, 5), Position.valueOf(6, 6)); + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("포는 포를 제외한 기물을 잡을 수 있다") + void execute_2() { + // given + Map positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(Position.valueOf(6, 2), new Cannon(TeamType.BLUE)); + positionPieceMap.put(Position.valueOf(6, 4), new Soldier(TeamType.RED)); + positionPieceMap.put(Position.valueOf(6, 7), new Soldier(TeamType.RED)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(6, 2); + MoveRule moveRuleOfCannon = new CannonMoveRule(Direction.RIGHT); + + // when + List actual = moveRuleOfCannon.execute(from, TeamType.BLUE, board); + + // then + List expected = List.of(Position.valueOf(6, 5), Position.valueOf(6, 6), Position.valueOf(6, 7)); + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("포는 포를 뛰어넘을 수 없다") + void execute_3() { + // given + Map positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(Position.valueOf(6, 2), new Cannon(TeamType.BLUE)); + positionPieceMap.put(Position.valueOf(6, 4), new Cannon(TeamType.RED)); + positionPieceMap.put(Position.valueOf(6, 7), new Soldier(TeamType.RED)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(6, 2); + MoveRule moveRuleOfCannon = new CannonMoveRule(Direction.RIGHT); + + // when + List actual = moveRuleOfCannon.execute(from, TeamType.BLUE, board); + + // then + List expected = List.of(); + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("포는 뛰어넘을 수 있는 기물이 없다면 움직일 수 없다") + void execute_4() { + // given + Map positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(Position.valueOf(6, 2), new Cannon(TeamType.BLUE)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(6, 2); + MoveRule moveRuleOfCannon = new CannonMoveRule(Direction.RIGHT); + + // when + List actual = moveRuleOfCannon.execute(from, TeamType.BLUE, board); + + // then + List expected = List.of(); + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("포는 반드시 기물을 하나 뛰어넘은 후 장애물을 만나기 전까지 이동할 수 있다") + void execute_5() { + // given + Map positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(Position.valueOf(6, 2), new Cannon(TeamType.BLUE)); + positionPieceMap.put(Position.valueOf(6, 4), new Soldier(TeamType.RED)); + positionPieceMap.put(Position.valueOf(6, 7), new Soldier(TeamType.BLUE)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(6, 2); + MoveRule moveRuleOfCannon = new CannonMoveRule(Direction.RIGHT); + + // when + List actual = moveRuleOfCannon.execute(from, TeamType.BLUE, board); + + // then + List expected = List.of(Position.valueOf(6, 5), Position.valueOf(6, 6)); + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("포는 반드시 기물을 하나 뛰어넘은 후 장기판의 경계까지 이동할 수 있다") + void execute_6() { + // given + Map positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(Position.valueOf(6, 2), new Cannon(TeamType.BLUE)); + positionPieceMap.put(Position.valueOf(6, 4), new Soldier(TeamType.RED)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(6, 2); + MoveRule moveRuleOfCannon = new CannonMoveRule(Direction.RIGHT); + + // when + List actual = moveRuleOfCannon.execute(from, TeamType.BLUE, board); + + // then + List expected = List.of(Position.valueOf(6, 5), Position.valueOf(6, 6), Position.valueOf(6, 7), + Position.valueOf(6, 8), Position.valueOf(6, 9)); + assertThat(actual).isEqualTo(expected); + } + } +} diff --git a/src/test/java/janggi/domain/movement/MovementTest.java b/src/test/java/janggi/domain/movement/MovementTest.java new file mode 100644 index 0000000000..083532c651 --- /dev/null +++ b/src/test/java/janggi/domain/movement/MovementTest.java @@ -0,0 +1,278 @@ +package janggi.domain.movement; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.piece.Chariot; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Soldier; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class MovementTest { + @Nested + @DisplayName("잡기 여부 판정 테스트") + class CanKill { + + private static Position from; + private static Piece me; + private static Map positionPieceMap; + + @BeforeEach + void setUp() { + from = Position.valueOf(5, 3); + me = new Soldier(TeamType.RED); + positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(from, me); + } + + @Test + @DisplayName("대상이 적군인 경우") + void success_1() { + // given + positionPieceMap.put(Position.valueOf(5, 4), new Soldier(TeamType.BLUE)); + Board board = new Board(positionPieceMap); + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(1, direction); + boolean expected = true; + + // when + boolean actual = Movement.hasReachablePosition(from, TeamType.RED, board); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("대상이 아군인 경우") + void success_2() { + // given + positionPieceMap.put(Position.valueOf(5, 4), new Soldier(TeamType.RED)); + Board board = new Board(positionPieceMap); + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(1, direction); + boolean expected = false; + + // when + boolean actual = Movement.hasReachablePosition(from, TeamType.RED, board); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("대상이 없는 경우") + void success_3() { + // given + Board board = new Board(positionPieceMap); + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(1, direction); + boolean expected = true; + + // when + boolean actual = Movement.hasReachablePosition(from, TeamType.RED, board); + + // then + assertThat(actual).isEqualTo(expected); + } + } + + @Nested + @DisplayName("장애물 여부 판정 테스트") + class IsBlocked { + + private static Position from; + private static Piece me; + private static Map positionPieceMap; + + @BeforeEach + void setUp() { + from = Position.valueOf(5, 3); + me = new Soldier(TeamType.RED); + positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(from, me); + } + + @Test + @DisplayName("기물이 없는 경우") + void success_1() { + // given + Board board = new Board(positionPieceMap); + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(1, direction); + boolean expected = false; + + // when + boolean actual = Movement.isBlocked(from, board); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("기물이 있는 경우") + void success_2() { + // given + positionPieceMap.put(Position.valueOf(5, 4), new Soldier(TeamType.BLUE)); + Board board = new Board(positionPieceMap); + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(1, direction); + boolean expected = true; + + // when + boolean actual = Movement.isBlocked(from, board); + + // then + assertThat(actual).isEqualTo(expected); + } + } + + @Nested + @DisplayName("목적지 계산 테스트") + class CalculateDestination { + + private static Map positionPieceMap; + + @BeforeEach + void setUp() { + positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(Position.valueOf(5, 3), new Chariot(TeamType.RED)); + } + + @Test + @DisplayName("경로에 아군이 있는 경우") + void success_1() { + // given + positionPieceMap.put(Position.valueOf(5, 4), new Soldier(TeamType.RED)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(5, 3); + int maxDistance = 1; + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(maxDistance, direction); + Position expected = Position.valueOf(5, 3); + + // when + Position actual = Movement.calculateDestination(from, TeamType.RED, board); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("경로에 적군이 있는 경우") + void success_2() { + // given + positionPieceMap.put(Position.valueOf(5, 4), new Soldier(TeamType.BLUE)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(5, 3); + int maxDistance = 1; + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(maxDistance, direction); + Position expected = Position.valueOf(5, 4); + + // when + Position actual = Movement.calculateDestination(from, TeamType.RED, board); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("경로에 기물이 없는 경우") + void success_3() { + // given + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(5, 3); + int maxDistance = 1; + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(maxDistance, direction); + Position expected = Position.valueOf(5, 4); + + // when + Position actual = Movement.calculateDestination(from, TeamType.RED, board); + + // then + assertThat(actual).isEqualTo(expected); + } + + } + + @Nested + @DisplayName("경로 자취 계산 테스트") + class CalculateTraces { + + private static Map positionPieceMap; + + @BeforeEach + void setUp() { + positionPieceMap = new LinkedHashMap<>(); + positionPieceMap.put(Position.valueOf(5, 3), new Chariot(TeamType.RED)); + } + + @Test + @DisplayName("경로에 아군이 있는 경우") + void success_1() { + // given + positionPieceMap.put(Position.valueOf(5, 6), new Soldier(TeamType.RED)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(5, 3); + int maxDistance = 4; + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(maxDistance, direction); + List expected = List.of(Position.valueOf(5, 4), Position.valueOf(5, 5)); + + // when + List actual = Movement.calculateTraces(from, TeamType.RED, + board); + + // then + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("경로에 적군이 있는 경우") + void success_2() { + // given + positionPieceMap.put(Position.valueOf(5, 6), new Soldier(TeamType.BLUE)); + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(5, 3); + int maxDistance = 4; + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(maxDistance, direction); + List expected = List.of(Position.valueOf(5, 4), Position.valueOf(5, 5), + Position.valueOf(5, 6)); + + // when + List actual = Movement.calculateTraces(from, TeamType.RED, board); + + // then + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("경로에 기물이 없는 경우") + void success_3() { + // given + Board board = new Board(positionPieceMap); + Position from = Position.valueOf(5, 3); + int maxDistance = 4; + Direction direction = Direction.RIGHT; + Movement Movement = new Movement(maxDistance, direction); + List expected = List.of(Position.valueOf(5, 4), Position.valueOf(5, 5), + Position.valueOf(5, 6), Position.valueOf(5, 7)); + + // when + List actual = Movement.calculateTraces(from, TeamType.RED, board); + + // then + assertThat(actual).hasSameElementsAs(expected); + } + } +} \ No newline at end of file diff --git a/src/test/java/janggi/domain/movement/SlidingMoveRuleTest.java b/src/test/java/janggi/domain/movement/SlidingMoveRuleTest.java new file mode 100644 index 0000000000..045bc04339 --- /dev/null +++ b/src/test/java/janggi/domain/movement/SlidingMoveRuleTest.java @@ -0,0 +1,109 @@ +package janggi.domain.movement; + +import static janggi.domain.Position.MAXIMUM_ROW; +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.piece.Chariot; +import janggi.domain.piece.Elephant; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Soldier; +import janggi.domain.team.TeamType; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class SlidingMoveRuleTest { + + @Test + @DisplayName("적이 있는 곳 까지 이동할 수 있다") + public void success1() { + // given + Map positionPieceMap = Map.of( + Position.valueOf(5, 3), new Chariot(TeamType.RED), + Position.valueOf(8, 3), new Elephant(TeamType.BLUE) + ); + Board board = new Board(positionPieceMap); + Direction direction = Direction.DOWN; + MoveRule moveRuleWithTraces = new SlidingMoveRule(new Movement(MAXIMUM_ROW, direction)); + Position from = Position.valueOf(5, 3); + + // when + List actual = moveRuleWithTraces.execute(from, TeamType.RED, board); + + // then + List expected = List.of( + Position.valueOf(6, 3), + Position.valueOf(7, 3), + Position.valueOf(8, 3) + ); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("아군이 있는 곳 까지 이동할 수 있다") + public void success2() { + // given + Map positionPieceMap = Map.of( + Position.valueOf(5, 3), new Chariot(TeamType.RED), + Position.valueOf(8, 3), new Soldier(TeamType.RED) + ); + Board board = new Board(positionPieceMap); + Direction direction = Direction.DOWN; + MoveRule moveRuleWithTraces = new SlidingMoveRule(new Movement(MAXIMUM_ROW, direction)); + Position from = Position.valueOf(5, 3); + + // when + List actual = moveRuleWithTraces.execute(from, TeamType.RED, board); + + // then + List expected = List.of( + Position.valueOf(6, 3), + Position.valueOf(7, 3) + ); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("장기판 경계 까지 이동할 수 있다") + public void success3() { + // given + Map positionPieceMap = Map.of(Position.valueOf(5, 3), new Chariot(TeamType.RED)); + Board board = new Board(positionPieceMap); + Direction direction = Direction.UP; + MoveRule moveRuleWithTraces = new SlidingMoveRule(new Movement(MAXIMUM_ROW, direction)); + Position from = Position.valueOf(5, 3); + + // when + List actual = moveRuleWithTraces.execute(from, TeamType.RED, board); + + // then + List expected = List.of( + Position.valueOf(1, 3), + Position.valueOf(2, 3), + Position.valueOf(3, 3), + Position.valueOf(4, 3) + ); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("장기판 경계에선 더 이동할 수 없다") + public void success4() { + // given + Map positionPieceMap = Map.of(Position.valueOf(1, 3), new Chariot(TeamType.RED)); + Board board = new Board(positionPieceMap); + Direction direction = Direction.UP; + MoveRule moveRuleWithTraces = new SlidingMoveRule(new Movement(MAXIMUM_ROW, direction)); + Position from = Position.valueOf(1, 3); + + // when + List actual = moveRuleWithTraces.execute(from, TeamType.RED, board); + + // then + List expected = List.of(); + assertThat(actual).hasSameElementsAs(expected); + } +} diff --git a/src/test/java/janggi/domain/movement/StepMoveRuleTest.java b/src/test/java/janggi/domain/movement/StepMoveRuleTest.java new file mode 100644 index 0000000000..0935bd4eb0 --- /dev/null +++ b/src/test/java/janggi/domain/movement/StepMoveRuleTest.java @@ -0,0 +1,167 @@ +package janggi.domain.movement; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.piece.Elephant; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Soldier; +import janggi.domain.team.TeamType; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class StepMoveRuleTest { + + @Test + @DisplayName("경로와 목적지에 장애물이 없다") + void execute_1() { + // given + Map positionPieceMap = Map.of( + Position.valueOf(5, 6), new Elephant(TeamType.RED) + ); + Board board = new Board(positionPieceMap); + List movementOrder = List.of( + new Movement(1, Direction.RIGHT), + new Movement(1, Direction.UP_RIGHT), + new Movement(1, Direction.UP_RIGHT) + ); + MoveRule stepMoveRule = new StepMoveRule(movementOrder); + Position from = Position.valueOf(5, 6); + + // when + List actual = stepMoveRule.execute(from, TeamType.RED, board); + + // then + List expected = List.of(Position.valueOf(3, 9)); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("경로에 장애물이 없지만 목적지에 장애물이 있다") + void execute_2() { + // given + Map positionPieceMap = Map.of( + Position.valueOf(5, 3), new Elephant(TeamType.RED), + Position.valueOf(3, 6), new Soldier(TeamType.RED) + ); + Board board = new Board(positionPieceMap); + List movementOrder = List.of( + new Movement(1, Direction.RIGHT), + new Movement(1, Direction.UP_RIGHT), + new Movement(1, Direction.UP_RIGHT) + ); + MoveRule stepMoveRule = new StepMoveRule(movementOrder); + Position from = Position.valueOf(5, 3); + + // when + List actual = stepMoveRule.execute(from, TeamType.RED, board); + + // then + List expected = List.of(); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("경로에 장애물이 없고 목적지에 적 기물이 있다") + void execute_3() { + // given + Map positionPieceMap = Map.of( + Position.valueOf(5, 3), new Elephant(TeamType.RED), + Position.valueOf(3, 6), new Soldier(TeamType.BLUE) + ); + Board board = new Board(positionPieceMap); + List movementOrder = List.of( + new Movement(1, Direction.RIGHT), + new Movement(1, Direction.UP_RIGHT), + new Movement(1, Direction.UP_RIGHT) + ); + MoveRule stepMoveRule = new StepMoveRule(movementOrder); + Position from = Position.valueOf(5, 3); + + // when + List actual = stepMoveRule.execute(from, TeamType.RED, board); + + // then + List expected = List.of(Position.valueOf(3, 6)); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("경로에 장애물이 있고 목적지에 적 기물이 있다") + void execute_4() { + // given + Map positionPieceMap = Map.of( + Position.valueOf(5, 3), new Elephant(TeamType.RED), + Position.valueOf(5, 4), new Soldier(TeamType.BLUE), + Position.valueOf(6, 3), new Soldier(TeamType.BLUE) + ); + Board board = new Board(positionPieceMap); + List movementOrder = List.of( + new Movement(1, Direction.RIGHT), + new Movement(1, Direction.UP_RIGHT), + new Movement(1, Direction.UP_RIGHT) + ); + MoveRule stepMoveRule = new StepMoveRule(movementOrder); + Position from = Position.valueOf(5, 3); + + // when + List actual = stepMoveRule.execute(from, TeamType.RED, board); + + // then + List expected = List.of(); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("경로에 장애물이 있고 목적지에 적 기물이 있다") + void execute_5() { + // given + Map positionPieceMap = Map.of( + Position.valueOf(5, 3), new Elephant(TeamType.RED), + Position.valueOf(4, 5), new Soldier(TeamType.BLUE), + Position.valueOf(6, 3), new Soldier(TeamType.BLUE) + ); + Board board = new Board(positionPieceMap); + List movementOrder = List.of( + new Movement(1, Direction.RIGHT), + new Movement(1, Direction.UP_RIGHT), + new Movement(1, Direction.UP_RIGHT) + ); + MoveRule stepMoveRule = new StepMoveRule(movementOrder); + Position from = Position.valueOf(5, 3); + + // when + List actual = stepMoveRule.execute(from, TeamType.RED, board); + + // then + List expected = List.of(); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("보드판의 경계를 나가게 되어 목적지로 이동할 수 없다") + void execute_6() { + // given + Map positionPieceMap = Map.of( + Position.valueOf(5, 7), new Elephant(TeamType.RED) + ); + Board board = new Board(positionPieceMap); + List movementOrder = List.of( + new Movement(1, Direction.RIGHT), + new Movement(1, Direction.UP_RIGHT), + new Movement(1, Direction.UP_RIGHT) + ); + MoveRule stepMoveRule = new StepMoveRule(movementOrder); + Position from = Position.valueOf(5, 7); + + // when + List actual = stepMoveRule.execute(from, TeamType.RED, board); + + // then + List expected = List.of(); + assertThat(actual).hasSameElementsAs(expected); + } +} diff --git a/src/test/java/janggi/domain/piece/BlueSoldierTest.java b/src/test/java/janggi/domain/piece/BlueSoldierTest.java new file mode 100644 index 0000000000..88a16e1529 --- /dev/null +++ b/src/test/java/janggi/domain/piece/BlueSoldierTest.java @@ -0,0 +1,98 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class BlueSoldierTest { + + @Nested + @DisplayName("Blue Soldier 이동 가능한 위치 계산 테스트") + class CalculateMovablePositions { + + static Piece blueSoldier; + static Piece enemy1; + static Piece enemy2; + static Piece ally1; + static Piece ally2; + static Map positionPieceMap; + + @BeforeEach + void setUp() { + blueSoldier = new Soldier(TeamType.BLUE); + enemy1 = new Soldier(TeamType.RED); + enemy2 = new Soldier(TeamType.RED); + ally1 = new Soldier(TeamType.BLUE); + ally2 = new Soldier(TeamType.BLUE); + positionPieceMap = new LinkedHashMap<>(); + } + + @Test + @DisplayName("청졸은 앞, 좌, 우로 이동할 수 있고 적이 있으면 잡을 수 있다.") + void test1() { + positionPieceMap.put(Position.valueOf(6, 4), blueSoldier); + positionPieceMap.put(Position.valueOf(5, 4), enemy1); + positionPieceMap.put(Position.valueOf(6, 3), enemy2); + + List expected = List.of( + Position.valueOf(5, 4), + Position.valueOf(6, 3), + Position.valueOf(6, 5) + ); + + Board board = new Board(positionPieceMap); + + List actual = blueSoldier.calculateMovablePositions( + Position.valueOf(6, 4), board + ); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("청졸은 아군이 있는 위치로 이동할 수 없다.") + void test2() { + positionPieceMap.put(Position.valueOf(6, 4), blueSoldier); + positionPieceMap.put(Position.valueOf(5, 4), ally1); + positionPieceMap.put(Position.valueOf(6, 5), ally2); + + List expected = List.of( + Position.valueOf(6, 3) + ); + + Board board = new Board(positionPieceMap); + + List actual = blueSoldier.calculateMovablePositions( + Position.valueOf(6, 4), board + ); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("청졸은 장기판 밖으로 이동할 수 없다.") + void test3() { + positionPieceMap.put(Position.valueOf(1, 1), blueSoldier); + positionPieceMap.put(Position.valueOf(1, 2), ally1); + + List expected = List.of(); + + Board board = new Board(positionPieceMap); + + List actual = blueSoldier.calculateMovablePositions( + Position.valueOf(1, 1), board + ); + + assertThat(actual).hasSameElementsAs(expected); + } + } +} \ No newline at end of file diff --git a/src/test/java/janggi/domain/piece/CannonTest.java b/src/test/java/janggi/domain/piece/CannonTest.java new file mode 100644 index 0000000000..7ecd764f12 --- /dev/null +++ b/src/test/java/janggi/domain/piece/CannonTest.java @@ -0,0 +1,95 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.team.TeamType; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class CannonTest { + + @Nested + @DisplayName("이동 가능한 위치 계산 테스트") + class CalculateMovablePositions { + + static Piece cannon; + static Piece allySoldier; + static Piece allyCannon; + static Piece enemySoldier; + static Piece enemyCannon; + static Map positionPieceMap; + + @BeforeEach + void setUp() { + cannon = new Cannon(TeamType.RED); + enemySoldier = new Soldier(TeamType.BLUE); + enemyCannon = new Cannon(TeamType.BLUE); + allySoldier = new Soldier(TeamType.BLUE); + allyCannon = new Cannon(TeamType.RED); + } + + @Test + @DisplayName("포는 기물을 뛰어 넘어서 이동할 수 있다.") + void success_1() { + positionPieceMap = Map.of( + Position.valueOf(6, 7), cannon, + Position.valueOf(6, 5), enemySoldier, + Position.valueOf(7, 7), allySoldier); + Board board = new Board(positionPieceMap); + List expected = List.of( + Position.valueOf(6, 1), + Position.valueOf(6, 2), + Position.valueOf(6, 3), + Position.valueOf(6, 4), + Position.valueOf(8, 7), + Position.valueOf(9, 7), + Position.valueOf(10, 7)); + + List actual = cannon.calculateMovablePositions(Position.valueOf(6, 7), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("포는 포를 넘을 수 없다.") + void success_2() { + positionPieceMap = Map.of( + Position.valueOf(6, 7), cannon, + Position.valueOf(4, 7), enemySoldier, + Position.valueOf(6, 5), enemyCannon, + Position.valueOf(9, 7), allyCannon); + Board board = new Board(positionPieceMap); + List expected = List.of( + Position.valueOf(1, 7), + Position.valueOf(2, 7), + Position.valueOf(3, 7)); + + List actual = cannon.calculateMovablePositions(Position.valueOf(6, 7), + board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("포는 포를 잡을 수 없다.") + void success_3() { + positionPieceMap = Map.of( + Position.valueOf(6, 7), cannon, + Position.valueOf(6, 3), enemyCannon, + Position.valueOf(6, 4), allySoldier); + Board board = new Board(positionPieceMap); + List expected = List.of(); + + List actual = cannon.calculateMovablePositions(Position.valueOf(6, 7), + board); + + assertThat(actual).hasSameElementsAs(expected); + } + } +} diff --git a/src/test/java/janggi/domain/piece/ChariotTest.java b/src/test/java/janggi/domain/piece/ChariotTest.java new file mode 100644 index 0000000000..f4bcdb8b73 --- /dev/null +++ b/src/test/java/janggi/domain/piece/ChariotTest.java @@ -0,0 +1,100 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.team.TeamType; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class ChariotTest { + + @Nested + @DisplayName("이동 가능한 위치 계산 테스트") + class CalculateMovablePositions { + + static Piece chariot; + static Piece enemy1; + static Piece enemy2; + static Piece enemy3; + static Piece enemy4; + static Piece ally1; + static Piece ally2; + static Piece ally3; + static Piece ally4; + static Map positionPieceMap; + static List candidatePositions; + + @BeforeEach + void setUp() { + chariot = new Chariot(TeamType.RED); + enemy1 = new Soldier(TeamType.BLUE); + enemy3 = new Soldier(TeamType.BLUE); + enemy4 = new Soldier(TeamType.BLUE); + enemy2 = new Soldier(TeamType.BLUE); + ally1 = new Soldier(TeamType.RED); + ally2 = new Soldier(TeamType.RED); + ally3 = new Soldier(TeamType.RED); + ally4 = new Soldier(TeamType.RED); + positionPieceMap = new LinkedHashMap(); + candidatePositions = initCanididatePositions(); + } + + @Test + @DisplayName("차는 적군을 뛰어넘어 갈 수 없다.") + void test1() { + positionPieceMap.put(Position.valueOf(5, 3), chariot); + positionPieceMap.put(Position.valueOf(5, 1), enemy1); + positionPieceMap.put(Position.valueOf(3, 3), enemy2); + positionPieceMap.put(Position.valueOf(5, 6), enemy3); + positionPieceMap.put(Position.valueOf(7, 3), enemy4); + List expected = List.of(Position.valueOf(3, 3), Position.valueOf(4, 3), Position.valueOf(5, 1), + Position.valueOf(5, 2), Position.valueOf(5, 4), + Position.valueOf(5, 5), Position.valueOf(5, 6), + Position.valueOf(6, 3), Position.valueOf(7, 3)); + + Board board = new Board(positionPieceMap); + List actual = chariot.calculateMovablePositions(Position.valueOf(5, 3), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("차는 아군을 뛰어넘어 갈 수 없다.") + void test2() { + positionPieceMap.put(Position.valueOf(5, 3), chariot); + positionPieceMap.put(Position.valueOf(5, 1), ally1); + positionPieceMap.put(Position.valueOf(3, 3), ally2); + positionPieceMap.put(Position.valueOf(5, 6), ally3); + positionPieceMap.put(Position.valueOf(7, 3), ally4); + List expected = List.of(Position.valueOf(4, 3), + Position.valueOf(5, 2), Position.valueOf(5, 4), + Position.valueOf(5, 5), Position.valueOf(6, 3)); + + Board board = new Board(positionPieceMap); + List actual = chariot.calculateMovablePositions(Position.valueOf(5, 3), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + List initCanididatePositions() { + List candidatePositions = new ArrayList<>(); + for (int row = 1; row <= 10; row++) { + candidatePositions.add(Position.valueOf(row, 3)); + } + for (int column = 1; column <= 9; column++) { + candidatePositions.add(Position.valueOf(5, column)); + } + candidatePositions.remove(Position.valueOf(5, 3)); + candidatePositions.remove(Position.valueOf(5, 3)); + return candidatePositions; + } + } +} diff --git a/src/test/java/janggi/domain/piece/ElephantTest.java b/src/test/java/janggi/domain/piece/ElephantTest.java new file mode 100644 index 0000000000..9b6aea2a13 --- /dev/null +++ b/src/test/java/janggi/domain/piece/ElephantTest.java @@ -0,0 +1,108 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class ElephantTest { + + @Nested + @DisplayName("이동 가능한 위치 계산 테스트") + class CalculateMovablePositions { + + static Piece elephant; + static Piece enemy1; + static Piece enemy2; + static Piece enemy3; + static Piece enemy4; + static Piece ally1; + static Piece ally2; + static Piece ally3; + static Piece ally4; + static Map positionPieceMap; + + @BeforeEach + void setUp() { + elephant = new Elephant(TeamType.RED); + enemy1 = new Soldier(TeamType.BLUE); + enemy3 = new Soldier(TeamType.BLUE); + enemy4 = new Soldier(TeamType.BLUE); + enemy2 = new Soldier(TeamType.BLUE); + ally1 = new Soldier(TeamType.RED); + ally2 = new Soldier(TeamType.RED); + ally3 = new Soldier(TeamType.RED); + ally4 = new Soldier(TeamType.RED); + positionPieceMap = new LinkedHashMap(); + } + + @Test + @DisplayName("상은 기물을 뛰어넘을 수 없다.") + void test1() { + positionPieceMap.put(Position.valueOf(6, 4), elephant); + positionPieceMap.put(Position.valueOf(4, 1), ally1); + positionPieceMap.put(Position.valueOf(5, 4), enemy1); + positionPieceMap.put(Position.valueOf(7, 4), ally2); + positionPieceMap.put(Position.valueOf(5, 6), ally3); + positionPieceMap.put(Position.valueOf(8, 1), ally4); + positionPieceMap.put(Position.valueOf(7, 6), enemy2); + List expected = List.of(); + + Board board = new Board(positionPieceMap); + List actual = elephant.calculateMovablePositions(Position.valueOf(6, 4), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("상은 직선 한 칸, 대각선 두 칸을 가서 기물을 잡을 수 있다.") + void test2() { + positionPieceMap.put(Position.valueOf(6, 4), elephant); + positionPieceMap.put(Position.valueOf(3, 2), enemy1); + positionPieceMap.put(Position.valueOf(4, 7), enemy2); + List expected = List.of(Position.valueOf(3, 2), Position.valueOf(3, 6), Position.valueOf(4, 1), + Position.valueOf(4, 7), Position.valueOf(8, 1), Position.valueOf(8, 7), Position.valueOf(9, 2), + Position.valueOf(9, 6)); + + Board board = new Board(positionPieceMap); + List actual = elephant.calculateMovablePositions(Position.valueOf(6, 4), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("상은 장기판 밖으로 이동할 수 없다.") + void test3() { + positionPieceMap.put(Position.valueOf(5, 2), elephant); + List expected = List.of(Position.valueOf(3, 5), Position.valueOf(2, 4), Position.valueOf(7, 5), + Position.valueOf(8, 4)); + + Board board = new Board(positionPieceMap); + List actual = elephant.calculateMovablePositions(Position.valueOf(5, 2), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("상은 장기판 밖으로 이동할 수 없다.") + void test4() { + positionPieceMap.put(Position.valueOf(1, 1), elephant); + positionPieceMap.put(Position.valueOf(1, 2), ally1); + positionPieceMap.put(Position.valueOf(2, 1), ally3); + List expected = List.of(); + + Board board = new Board(positionPieceMap); + List actual = elephant.calculateMovablePositions(Position.valueOf(1, 1), board); + + assertThat(actual).hasSameElementsAs(expected); + } + } +} diff --git a/src/test/java/janggi/domain/piece/GeneralTest.java b/src/test/java/janggi/domain/piece/GeneralTest.java new file mode 100644 index 0000000000..0c87a85ba6 --- /dev/null +++ b/src/test/java/janggi/domain/piece/GeneralTest.java @@ -0,0 +1,100 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class GeneralTest { + + @Nested + @DisplayName("이동 가능한 위치 계산 테스트") + class CalculateMovablePositions { + + static Piece general; + static Piece enemy1; + static Piece enemy2; + static Piece enemy3; + static Piece enemy4; + static Piece ally1; + static Piece ally2; + static Piece ally3; + static Piece ally4; + static Map positionPieceMap; + + @BeforeEach + void setUp() { + general = new General(TeamType.RED); + enemy1 = new Soldier(TeamType.BLUE); + enemy3 = new Soldier(TeamType.BLUE); + enemy4 = new Soldier(TeamType.BLUE); + enemy2 = new Soldier(TeamType.BLUE); + ally1 = new Soldier(TeamType.RED); + ally2 = new Soldier(TeamType.RED); + ally3 = new Soldier(TeamType.RED); + ally4 = new Soldier(TeamType.RED); + positionPieceMap = new LinkedHashMap(); + } + + @Test + @DisplayName("장은 기물을 뛰어넘을 수 없다.") + void test1() { + positionPieceMap.put(Position.valueOf(6, 4), general); + positionPieceMap.put(Position.valueOf(5, 3), ally1); + positionPieceMap.put(Position.valueOf(5, 4), ally2); + positionPieceMap.put(Position.valueOf(5, 5), ally3); + positionPieceMap.put(Position.valueOf(6, 3), ally4); + positionPieceMap.put(Position.valueOf(6, 5), enemy1); + positionPieceMap.put(Position.valueOf(7, 3), enemy2); + positionPieceMap.put(Position.valueOf(7, 4), enemy3); + positionPieceMap.put(Position.valueOf(7, 5), enemy4); + + List expected = List.of(Position.valueOf(6, 5), Position.valueOf(7, 3), + Position.valueOf(7, 4), Position.valueOf(7, 5)); + + Board board = new Board(positionPieceMap); + List actual = general.calculateMovablePositions(Position.valueOf(6, 4), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("장은 모든 방향 중 한 칸을 가서 기물을 잡을 수 있다.") + void test2() { + positionPieceMap.put(Position.valueOf(6, 4), general); + positionPieceMap.put(Position.valueOf(6, 5), enemy1); + positionPieceMap.put(Position.valueOf(7, 3), enemy2); + List expected = List.of(Position.valueOf(5, 3), Position.valueOf(5, 4), + Position.valueOf(5, 5), Position.valueOf(6, 3), Position.valueOf(6, 5), Position.valueOf(7, 3), + Position.valueOf(7, 4), Position.valueOf(7, 5)); + + Board board = new Board(positionPieceMap); + List actual = general.calculateMovablePositions(Position.valueOf(6, 4), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("장은 장기판 밖으로 이동할 수 없다.") + void test3() { + positionPieceMap.put(Position.valueOf(1, 1), general); + positionPieceMap.put(Position.valueOf(1, 2), ally1); + positionPieceMap.put(Position.valueOf(2, 1), ally2); + positionPieceMap.put(Position.valueOf(2, 2), ally3); + List expected = List.of(); + + Board board = new Board(positionPieceMap); + List actual = general.calculateMovablePositions(Position.valueOf(1, 1), board); + + assertThat(actual).hasSameElementsAs(expected); + } + } +} diff --git a/src/test/java/janggi/domain/piece/GuardTest.java b/src/test/java/janggi/domain/piece/GuardTest.java new file mode 100644 index 0000000000..16d6af1692 --- /dev/null +++ b/src/test/java/janggi/domain/piece/GuardTest.java @@ -0,0 +1,103 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.board.BoardMediator; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class GuardTest { + + @Nested + @DisplayName("이동 가능한 위치 계산 테스트") + class CalculateMovablePositions { + + static Piece guard; + static Piece enemy1; + static Piece enemy2; + static Piece enemy3; + static Piece enemy4; + static Piece ally1; + static Piece ally2; + static Piece ally3; + static Piece ally4; + static Map positionPieceMap; + + @BeforeEach + void setUp() { + guard = new Guard(TeamType.RED); + enemy1 = new Soldier(TeamType.BLUE); + enemy3 = new Soldier(TeamType.BLUE); + enemy4 = new Soldier(TeamType.BLUE); + enemy2 = new Soldier(TeamType.BLUE); + ally1 = new Soldier(TeamType.RED); + ally2 = new Soldier(TeamType.RED); + ally3 = new Soldier(TeamType.RED); + ally4 = new Soldier(TeamType.RED); + positionPieceMap = new LinkedHashMap(); + } + + @Test + @DisplayName("사는 기물을 뛰어넘을 수 없다.") + void test1() { + positionPieceMap.put(Position.valueOf(6, 4), guard); + positionPieceMap.put(Position.valueOf(5, 3), ally1); + positionPieceMap.put(Position.valueOf(5, 4), ally2); + positionPieceMap.put(Position.valueOf(5, 5), ally3); + positionPieceMap.put(Position.valueOf(6, 3), ally4); + positionPieceMap.put(Position.valueOf(6, 5), enemy1); + positionPieceMap.put(Position.valueOf(7, 3), enemy2); + positionPieceMap.put(Position.valueOf(7, 4), enemy3); + positionPieceMap.put(Position.valueOf(7, 5), enemy4); + + List expected = List.of(Position.valueOf(6, 5), Position.valueOf(7, 3), + Position.valueOf(7, 4), Position.valueOf(7, 5)); + + Board board = new Board(positionPieceMap); + BoardMediator boardMediator = board; + List actual = guard.calculateMovablePositions(Position.valueOf(6, 4), + boardMediator); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("사는 모든 방향 중 한 칸을 가서 기물을 잡을 수 있다.") + void test2() { + positionPieceMap.put(Position.valueOf(6, 4), guard); + positionPieceMap.put(Position.valueOf(6, 5), enemy1); + positionPieceMap.put(Position.valueOf(7, 3), enemy2); + List expected = List.of(Position.valueOf(5, 3), Position.valueOf(5, 4), + Position.valueOf(5, 5), Position.valueOf(6, 3), Position.valueOf(6, 5), Position.valueOf(7, 3), + Position.valueOf(7, 4), Position.valueOf(7, 5)); + + Board board = new Board(positionPieceMap); + List actual = guard.calculateMovablePositions(Position.valueOf(6, 4), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("사는 장기판 밖으로 이동할 수 없다.") + void test3() { + positionPieceMap.put(Position.valueOf(1, 1), guard); + positionPieceMap.put(Position.valueOf(1, 2), ally1); + positionPieceMap.put(Position.valueOf(2, 1), ally2); + positionPieceMap.put(Position.valueOf(2, 2), ally3); + List expected = List.of(); + + Board board = new Board(positionPieceMap); + List actual = guard.calculateMovablePositions(Position.valueOf(1, 1), board); + + assertThat(actual).hasSameElementsAs(expected); + } + } +} diff --git a/src/test/java/janggi/domain/piece/HorseTest.java b/src/test/java/janggi/domain/piece/HorseTest.java new file mode 100644 index 0000000000..3707b640a0 --- /dev/null +++ b/src/test/java/janggi/domain/piece/HorseTest.java @@ -0,0 +1,94 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class HorseTest { + + @Nested + @DisplayName("이동 가능한 위치 계산 테스트") + class CalculateMovablePositions { + + static Piece horse; + static Piece enemy1; + static Piece enemy2; + static Piece enemy3; + static Piece enemy4; + static Piece ally1; + static Piece ally2; + static Piece ally3; + static Piece ally4; + static Map positionPieceMap; + + @BeforeEach + void setUp() { + horse = new Horse(TeamType.RED); + enemy1 = new Soldier(TeamType.BLUE); + enemy3 = new Soldier(TeamType.BLUE); + enemy4 = new Soldier(TeamType.BLUE); + enemy2 = new Soldier(TeamType.BLUE); + ally1 = new Soldier(TeamType.RED); + ally2 = new Soldier(TeamType.RED); + ally3 = new Soldier(TeamType.RED); + ally4 = new Soldier(TeamType.RED); + positionPieceMap = new LinkedHashMap(); + } + + @Test + @DisplayName("마는 기물을 뛰어넘을 수 없다.") + void test1() { + positionPieceMap.put(Position.valueOf(6, 4), horse); + positionPieceMap.put(Position.valueOf(6, 3), ally1); + positionPieceMap.put(Position.valueOf(5, 4), enemy1); + positionPieceMap.put(Position.valueOf(7, 4), ally2); + positionPieceMap.put(Position.valueOf(6, 5), ally3); + List expected = List.of(); + + Board board = new Board(positionPieceMap); + List actual = horse.calculateMovablePositions(Position.valueOf(6, 4), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("마는 직선 한 칸, 대각선 한 칸을 가서 기물을 잡을 수 있다.") + void test2() { + positionPieceMap.put(Position.valueOf(6, 4), horse); + positionPieceMap.put(Position.valueOf(5, 6), enemy1); + positionPieceMap.put(Position.valueOf(7, 2), enemy2); + List expected = List.of(Position.valueOf(4, 3), Position.valueOf(4, 5), + Position.valueOf(5, 6), Position.valueOf(7, 6), + Position.valueOf(8, 3), Position.valueOf(8, 5), Position.valueOf(7, 2), + Position.valueOf(5, 2)); + + Board board = new Board(positionPieceMap); + List actual = horse.calculateMovablePositions(Position.valueOf(6, 4), board); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("마는 장기판 밖으로 이동할 수 없다.") + void test3() { + positionPieceMap.put(Position.valueOf(1, 1), horse); + positionPieceMap.put(Position.valueOf(1, 2), ally1); + positionPieceMap.put(Position.valueOf(2, 1), ally3); + List expected = List.of(); + + Board board = new Board(positionPieceMap); + List actual = horse.calculateMovablePositions(Position.valueOf(1, 1), board); + + assertThat(actual).hasSameElementsAs(expected); + } + } +} 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..353bae8c3b --- /dev/null +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -0,0 +1,42 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import janggi.domain.team.TeamType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class PieceTest { + + + @Nested + @DisplayName("특정 팀 기물 여부 판정 테스트") + class belongsToTeam { + + @Test + @DisplayName("특정 팀에 해당하는 경우") + void success_1() { + TeamType teamType = TeamType.RED; + Piece cannon = new Cannon(teamType); + boolean expected = true; + + boolean actual = cannon.isSameTeamType(teamType); + + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("특정 팀에 해당하지 않는 경우") + void success_2() { + TeamType teamType = TeamType.RED; + TeamType otherTeamType = TeamType.BLUE; + Piece cannon = new Cannon(otherTeamType); + boolean expected = false; + + boolean actual = cannon.isSameTeamType(teamType); + + assertThat(actual).isEqualTo(expected); + } + } +} diff --git a/src/test/java/janggi/domain/piece/RedSoldierTest.java b/src/test/java/janggi/domain/piece/RedSoldierTest.java new file mode 100644 index 0000000000..ff4e0b7462 --- /dev/null +++ b/src/test/java/janggi/domain/piece/RedSoldierTest.java @@ -0,0 +1,98 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.team.TeamType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class RedSoldierTest { + + @Nested + @DisplayName("Red Soldier 이동 가능한 위치 계산 테스트") + class CalculateMovablePositions { + + static Piece redSoldier; + static Piece enemy1; + static Piece enemy2; + static Piece ally1; + static Piece ally2; + static Map positionPieceMap; + + @BeforeEach + void setUp() { + redSoldier = new Soldier(TeamType.RED); + enemy1 = new Soldier(TeamType.BLUE); + enemy2 = new Soldier(TeamType.BLUE); + ally1 = new Soldier(TeamType.RED); + ally2 = new Soldier(TeamType.RED); + positionPieceMap = new LinkedHashMap<>(); + } + + @Test + @DisplayName("홍졸은 앞, 좌, 우로 이동할 수 있고 적이 있으면 잡을 수 있다.") + void test1() { + positionPieceMap.put(Position.valueOf(6, 4), redSoldier); + positionPieceMap.put(Position.valueOf(7, 4), enemy1); + positionPieceMap.put(Position.valueOf(6, 3), enemy2); + + List expected = List.of( + Position.valueOf(7, 4), + Position.valueOf(6, 3), + Position.valueOf(6, 5) + ); + + Board board = new Board(positionPieceMap); + + List actual = redSoldier.calculateMovablePositions( + Position.valueOf(6, 4), board + ); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("홍졸은 아군이 있는 위치로 이동할 수 없다.") + void test2() { + positionPieceMap.put(Position.valueOf(6, 4), redSoldier); + positionPieceMap.put(Position.valueOf(7, 4), ally1); + positionPieceMap.put(Position.valueOf(6, 5), ally2); + + List expected = List.of( + Position.valueOf(6, 3) + ); + + Board board = new Board(positionPieceMap); + + List actual = redSoldier.calculateMovablePositions( + Position.valueOf(6, 4), board + ); + + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + @DisplayName("홍졸은 장기판 밖으로 이동할 수 없다.") + void test3() { + positionPieceMap.put(Position.valueOf(10, 1), redSoldier); + positionPieceMap.put(Position.valueOf(10, 2), ally1); + + List expected = List.of(); + + Board board = new Board(positionPieceMap); + + List actual = redSoldier.calculateMovablePositions( + Position.valueOf(10, 1), board + ); + + assertThat(actual).hasSameElementsAs(expected); + } + } +} \ No newline at end of file diff --git a/src/test/java/janggi/domain/setup/InnerElephantElephantFormationTest.java b/src/test/java/janggi/domain/setup/InnerElephantElephantFormationTest.java new file mode 100644 index 0000000000..4d43e7bf5c --- /dev/null +++ b/src/test/java/janggi/domain/setup/InnerElephantElephantFormationTest.java @@ -0,0 +1,32 @@ +package janggi.domain.setup; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.setup.InnerElephantElephantFormation; +import janggi.domain.fixture.SetupPolicyTestFixture; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class InnerElephantElephantFormationTest { + + @Test + @DisplayName("안상 차림 테스트") + void setUp() { + InnerElephantElephantFormation policy = new InnerElephantElephantFormation(); + Map expected = + new LinkedHashMap<>(SetupPolicyTestFixture.상_마를_제외한_기물_배치_정보_제공()); + expected.put(Position.valueOf(1, 2), PieceType.HORSE); + expected.put(Position.valueOf(1, 3), PieceType.ELEPHANT); + expected.put(Position.valueOf(1, 7), PieceType.ELEPHANT); + expected.put(Position.valueOf(1, 8), PieceType.HORSE); + + Map actual = policy.offerBoardMap(); + + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } +} diff --git a/src/test/java/janggi/domain/setup/LeftElephantElephantFormationTest.java b/src/test/java/janggi/domain/setup/LeftElephantElephantFormationTest.java new file mode 100644 index 0000000000..42686856f1 --- /dev/null +++ b/src/test/java/janggi/domain/setup/LeftElephantElephantFormationTest.java @@ -0,0 +1,32 @@ +package janggi.domain.setup; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.setup.LeftElephantElephantFormation; +import janggi.domain.fixture.SetupPolicyTestFixture; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class LeftElephantElephantFormationTest { + + @Test + @DisplayName("왼상 차림 테스트") + void setUp() { + LeftElephantElephantFormation policy = new LeftElephantElephantFormation(); + Map expected = + new LinkedHashMap<>(SetupPolicyTestFixture.상_마를_제외한_기물_배치_정보_제공()); + expected.put(Position.valueOf(1, 2), PieceType.ELEPHANT); + expected.put(Position.valueOf(1, 3), PieceType.HORSE); + expected.put(Position.valueOf(1, 7), PieceType.ELEPHANT); + expected.put(Position.valueOf(1, 8), PieceType.HORSE); + + Map actual = policy.offerBoardMap(); + + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/janggi/domain/setup/OuterElephantElephantFormationTest.java b/src/test/java/janggi/domain/setup/OuterElephantElephantFormationTest.java new file mode 100644 index 0000000000..c3765b5b70 --- /dev/null +++ b/src/test/java/janggi/domain/setup/OuterElephantElephantFormationTest.java @@ -0,0 +1,32 @@ +package janggi.domain.setup; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.setup.OuterElephantElephantFormation; +import janggi.domain.fixture.SetupPolicyTestFixture; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class OuterElephantElephantFormationTest { + + @Test + @DisplayName("바깥상 차림 테스트") + void setUp() { + OuterElephantElephantFormation policy = new OuterElephantElephantFormation(); + Map expected = + new LinkedHashMap<>(SetupPolicyTestFixture.상_마를_제외한_기물_배치_정보_제공()); + expected.put(Position.valueOf(1, 2), PieceType.ELEPHANT); + expected.put(Position.valueOf(1, 3), PieceType.HORSE); + expected.put(Position.valueOf(1, 7), PieceType.HORSE); + expected.put(Position.valueOf(1, 8), PieceType.ELEPHANT); + + Map actual = policy.offerBoardMap(); + + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } +} diff --git a/src/test/java/janggi/domain/setup/RightElephantElephantFormationTest.java b/src/test/java/janggi/domain/setup/RightElephantElephantFormationTest.java new file mode 100644 index 0000000000..4390f1a02a --- /dev/null +++ b/src/test/java/janggi/domain/setup/RightElephantElephantFormationTest.java @@ -0,0 +1,32 @@ +package janggi.domain.setup; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import janggi.domain.Position; +import janggi.domain.board.setup.RightElephantElephantFormation; +import janggi.domain.fixture.SetupPolicyTestFixture; +import janggi.domain.piece.PieceType; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RightElephantElephantFormationTest { + + @Test + @DisplayName("오른상 차림 테스트") + void setUp() { + RightElephantElephantFormation policy = new RightElephantElephantFormation(); + Map expected = + new LinkedHashMap<>(SetupPolicyTestFixture.상_마를_제외한_기물_배치_정보_제공()); + expected.put(Position.valueOf(1, 2), PieceType.HORSE); + expected.put(Position.valueOf(1, 3), PieceType.ELEPHANT); + expected.put(Position.valueOf(1, 7), PieceType.HORSE); + expected.put(Position.valueOf(1, 8), PieceType.ELEPHANT); + + Map actual = policy.offerBoardMap(); + + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/janggi/domain/team/TurnManagerTest.java b/src/test/java/janggi/domain/team/TurnManagerTest.java new file mode 100644 index 0000000000..1172404f20 --- /dev/null +++ b/src/test/java/janggi/domain/team/TurnManagerTest.java @@ -0,0 +1,41 @@ +package janggi.domain.team; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class TurnManagerTest { + private TurnManager turnManager; + + @BeforeEach + void setUp() { + turnManager = new TurnManager(); + } + + @Nested + @DisplayName("턴 변경 테스트") + class ChangeTurn { + @Test + @DisplayName("처음 턴을 변경하면 초깃값(RED)에서 BLUE로 바뀐다") + void changeTurn_ToBlue() { + // when + turnManager.changeTurn(); + + // then + assertThat(turnManager.isCurrentTurnOf(TeamType.BLUE)).isTrue(); + } + + @Test + @DisplayName("한번 더 변경하면 BLUE에서 RED로 바뀐다") + void changeTurn_BackToRed() { + // when + turnManager.changeTurn(); + + // then + assertThat(turnManager.isCurrentTurnOf(TeamType.BLUE)).isTrue(); + } + } +} diff --git a/src/test/java/janggi/utils/ParserTest.java b/src/test/java/janggi/utils/ParserTest.java new file mode 100644 index 0000000000..be2460429e --- /dev/null +++ b/src/test/java/janggi/utils/ParserTest.java @@ -0,0 +1,38 @@ +package janggi.utils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class ParserTest { + + @Nested + @DisplayName("정수 파싱 테스트") + class ParseInteger { + + @Test + @DisplayName("정상 테스트") + void success1() { + // given + String input = "10"; + int expected = Integer.valueOf(input); + + // when + int actual = Parser.parseInteger(input); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("정수가 아닌 값을 변환하는 경우 테스트") + void failure1() { + String input = "test"; + assertThatIllegalArgumentException() + .isThrownBy(() -> Parser.parseInteger(input)); + } + } +}