diff --git a/README.md b/README.md index 9775dda0a..75930495f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,71 @@ # java-janggi 장기 미션 저장소 + +## 기능 구현 + +### 장기(장기판), Janggi + +- [x] 생성 시 장기판을 초기화한다. +- [x] 장기판의 위치를 선택하면, 해당 장기말이 움직일 수 있는 모든 경로를 반환한다. +- [x] 장기말을 장기판의 위치를 이용해 선택하고, 도착지를 지정해 해당 장기말을 움직일 수 있다. + - [x] 도착지가 규칙에 벗어나면 예외를 발생시킨다. + - [x] 도착지에 적군이 있다면 잡는다. + +### 장기말(기물), Unit + +- [x] 해당 기물이 위치로부터 갈 수 있는 모든 위치를 반환한다. + - [x] 반환하는 경로는 다른 기물의 위치를 고려하지 않는다. + - [x] 장기판 내의 좌표인지는 고려한다. + +### 장기말 이동 규칙, UnitRule + +- [x] 각 장기말의 이동 규칙을 알고있다. + +### 장기판 내의 위치, Position + +- [x] 장기판 내의 위치를 관리한다. +- [x] 범위를 벗어나서 위치를 생성하려하면 예외를 발생시킨다. + +### 출력 형식 + +``` +* | 00 01 02 03 04 05 06 07 08 +-------------------------------- +0 | CH EL HR GD .. GD EL HR CH +1 | .. .. .. .. GN .. .. .. .. +2 | .. CN .. .. .. .. .. CN .. +3 | SD .. SD .. SD .. SD .. SD +4 | .. .. .. .. .. .. .. .. .. +5 | .. .. .. .. .. .. .. .. .. +6 | SD .. SD .. SD .. SD .. SD +7 | .. CN .. .. .. .. .. CN .. +8 | .. .. .. .. GN .. .. .. .. +9 | CH EL HR GD .. GD EL HR CH + +기물 별 대표 문자 +궁(General) : GN +졸, 병(Soldiers) : SD +사(Guards) : GD +마(Horses) : HR +차(Chariots) : CH +포(Cannons) : CN +상(Elephants) : EL +``` + +## 1단계 블랙잭 피드백 + +- [x] 장기 네이밍 변경하기 [장기 기획서](https://en.wikipedia.org/wiki/Janggi) +- [x] `Team#isFront()` 메서드 삭제 +- [x] `JanggiTest` 불필요한 줄바꿈 제거 +- [x] `Position#isHorizontal()` -> `isHorizontalOrVertical()` 메서드 네임 변경 +- [x] `ChariotUnitRule#calculateAllRoute()` 에서 `@Override` 애너테이션 붙이기 +- [x] `CannonUnitRule#calculateEndPoint()` 접근제한자 `private`으로 변경 +- [x] `OutputView`에서 if문에 도달하지 않는 경우 예외 발생시키기 +- [x] `Janggi` 외부에서 주입하기 + - [ ] 주입하는 위치 고민 +- [x] `Janggi#changeTeam()`구현 로직 `Team` 객체에 메시지를 보내는 방향으로 +- [x] `Unit` -> 동등성 정의 로직 삭제 +- [x] `Unit` 객체 인스턴스 변수 3개를 2개로 줄이기 +- [x] 에외 메시지 작성 +- [ ] `DefaultUnitPosition`에서 기본 좌표 처리에 대한 고민 diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/java/JanggiApplication.java b/src/main/java/JanggiApplication.java new file mode 100644 index 000000000..688b4b4d8 --- /dev/null +++ b/src/main/java/JanggiApplication.java @@ -0,0 +1,20 @@ +import domain.Game; +import view.InputView; +import view.OutputView; + +public class JanggiApplication { + + public static void main(String[] args) { + final InputView inputView = new InputView(); + final OutputView outputView = new OutputView(); + final Game game = new Game(inputView, outputView); + + while (!game.isEnd()) { + try { + game.doTurn(); + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + } + } + } +} diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java new file mode 100644 index 000000000..8342a591b --- /dev/null +++ b/src/main/java/domain/Game.java @@ -0,0 +1,69 @@ +package domain; + +import domain.position.DefaultUnitPosition; +import domain.position.Position; +import domain.position.Route; +import domain.unit.Team; +import domain.unit.Unit; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import view.InputView; +import view.OutputView; + +public class Game { + + private final Janggi janggi; + private final InputView inputView; + private final OutputView outputView; + + public Game(InputView inputView, OutputView outputView) { + this.janggi = createJanggi(); + this.inputView = inputView; + this.outputView = outputView; + } + + private Janggi createJanggi() { + Map hanUnits = settingUnits(Team.HAN); + Map choUnits = settingUnits(Team.CHO); + return Janggi.of(hanUnits, choUnits, Team.CHO); + } + + private Map settingUnits(Team team) { + Map units = new HashMap<>(); + for (DefaultUnitPosition value : DefaultUnitPosition.values()) { + units.putAll(DefaultUnitPosition.createDefaultUnits(value, team)); + } + return units; + } + + public void doTurn() { + outputView.printJanggiUnits(janggi.getUnits()); + Position pick = parsePosition(inputView.readUnitPosition(janggi.getTurn())); + + List routes = janggi.findMovableRoutesFrom(pick); + outputView.printAvailableRoute(pick, routes); + + Position destination = parsePosition(inputView.readDestinationPosition(janggi.getTurn())); + + janggi.doTurn(pick, destination); + outputView.printJanggiUnits(janggi.getUnits()); + } + + public boolean isEnd() { + return janggi.isOneOfTeamNonExist(); + } + + private List parseInteger(String rawPosition) { + return Arrays.stream(rawPosition.split(",")) + .map(String::trim) + .map(Integer::parseInt) + .toList(); + } + + private Position parsePosition(String rawPosition) { + List positionValue = parseInteger(rawPosition); + return Position.of(positionValue.get(0), positionValue.get(1)); + } +} diff --git a/src/main/java/domain/Janggi.java b/src/main/java/domain/Janggi.java new file mode 100644 index 000000000..a800a911b --- /dev/null +++ b/src/main/java/domain/Janggi.java @@ -0,0 +1,164 @@ +package domain; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.Team; +import domain.unit.Unit; +import domain.unit.UnitType; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Janggi { + + public static final String EMPTY_POINT_EXCEPTION = "해당 위치에 기물이 존재하지 않습니다."; + public static final String PICK_OPPOSITE_UNIT_EXCEPTION = "상대팀 말은 고를 수 없습니다."; + public static final String CANNOT_MOVE_EXCEPTION = "이동할 수 없는 도착지입니다."; + + private final Map units; + private Team turn; + + private Janggi(Map units, Team turn) { + this.units = new HashMap<>(units); + this.turn = turn; + } + + public static Janggi of(Map hanUnits, Map choUnits, Team turn) { + Map units = new HashMap<>(); + units.putAll(hanUnits); + units.putAll(choUnits); + return new Janggi(units, turn); + } + + public void doTurn(Position pick, Position destination) { + if (!canMove(pick, destination)) { + throw new IllegalArgumentException(CANNOT_MOVE_EXCEPTION); + } + Unit pickedUnit = units.get(pick); + Unit destinationUnit = units.get(destination); + if (destinationUnit != null && destinationUnit.getTeam() == turn.getOpposite()) { + units.remove(destination); + } + units.remove(pick); + units.put(destination, pickedUnit); + switchTurn(); + } + + private boolean canMove(Position pick, Position destination) { + List movableRoutes = findMovableRoutesFrom(pick); + return movableRoutes.stream() + .map(route -> route.searchDestination(pick)) + .anyMatch(position -> position.equals(destination)); + } + + private void switchTurn() { + turn = turn.getOpposite(); + } + + public List findMovableRoutesFrom(Position pick) { + if (isEmptyPosition(pick)) { + throw new IllegalArgumentException(EMPTY_POINT_EXCEPTION); + } + Unit pickedUnit = units.get(pick); + if (pickedUnit.getTeam() != turn) { + throw new IllegalArgumentException(PICK_OPPOSITE_UNIT_EXCEPTION); + } + + List totalRoutes = pickedUnit.calculateRoutes(pick); + totalRoutes = filterRoutesByUnitType(pickedUnit, pick, totalRoutes); + if (pickedUnit.getType() == UnitType.CANNON) { + return totalRoutes; + } + return filterBlockedRoutes(pick, totalRoutes); + } + + private List filterRoutesByUnitType(Unit pickedUnit, Position pick, List totalRoutes) { + UnitType type = pickedUnit.getType(); + if (type == UnitType.CANNON) { + return totalRoutes.stream() + .filter(route -> canCannonJump(pick, route)) + .toList(); + } + if (type == UnitType.SOLDIER) { + return filterSoldierMoves(pick, pickedUnit, totalRoutes); + } + return totalRoutes; + } + + private List filterSoldierMoves(Position pick, Unit pickedUnit, List totalRoutes) { + if (pickedUnit.getTeam() == Team.HAN) { + return totalRoutes.stream() + .filter(route -> route.getPositions().getFirst().getY() >= pick.getY()) + .toList(); + } + return totalRoutes.stream() + .filter(route -> route.getPositions().getFirst().getY() <= pick.getY()) + .toList(); + } + + private boolean canCannonJump(Position current, Route route) { + Position endPoint = route.searchDestination(current); + if (!isEmptyPosition(endPoint)) { + Unit endUnit = units.get(endPoint); + if (endUnit.getType() == UnitType.CANNON) { + return false; + } + } + + int count = 0; + for (Position position : route.getPositionsExceptDestination(current)) { + if (isEmptyPosition(position)) { + continue; + } + Unit unit = units.get(position); + if (unit.getType() == UnitType.CANNON) { + return false; + } + count++; + } + return (count == 1); + } + + private List filterBlockedRoutes(Position pick, List routes) { + return routes.stream() + .filter(route -> isClearRoute(pick, route)) + .filter(route -> isClearDestination(pick, route)) + .toList(); + } + + private boolean isClearRoute(Position pick, Route route) { + return route.getPositionsExceptDestination(pick).stream() + .allMatch(this::isEmptyPosition); + } + + private boolean isClearDestination(Position pick, Route route) { + Position endPosition = route.searchDestination(pick); + if (isEmptyPosition(endPosition)) { + return true; + } + Unit endPointUnit = units.get(endPosition); + return endPointUnit.getTeam() != this.turn; + } + + private boolean isEmptyPosition(Position position) { + return !units.containsKey(position); + } + + public boolean isOneOfTeamNonExist() { + long hanUnitCount = units.values().stream(). + filter(unit -> unit.getTeam() == Team.HAN) + .count(); + long choUnitCount = units.values().stream(). + filter(unit -> unit.getTeam() == Team.CHO) + .count(); + return (hanUnitCount == 0 || choUnitCount == 0); + } + + public Team getTurn() { + return turn; + } + + public Map getUnits() { + return units; + } +} diff --git a/src/main/java/domain/position/DefaultUnitPosition.java b/src/main/java/domain/position/DefaultUnitPosition.java new file mode 100644 index 000000000..b746936c2 --- /dev/null +++ b/src/main/java/domain/position/DefaultUnitPosition.java @@ -0,0 +1,51 @@ +package domain.position; + +import domain.unit.Team; +import domain.unit.Unit; +import domain.unit.rule.CannonUnitRule; +import domain.unit.rule.ChariotUnitRule; +import domain.unit.rule.ElephantUnitRule; +import domain.unit.rule.HorseUnitRule; +import domain.unit.rule.NoneUnitRule; +import domain.unit.rule.SoldierUnitRule; +import domain.unit.rule.UnitRule; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public enum DefaultUnitPosition { + + GENERAL(1, 8, List.of(4), NoneUnitRule::new), + GUARD(0, 9, List.of(3, 5), NoneUnitRule::new), + CHARIOT(0, 9, List.of(0, 8), ChariotUnitRule::new), + CANNON(2, 7, List.of(1, 7), CannonUnitRule::new), + SOLDIER(3, 6, List.of(0, 2, 4, 6, 8), SoldierUnitRule::new), + HORSE(0, 9, List.of(2, 7), HorseUnitRule::new), + ELEPHANT(0, 9, List.of(1, 6), ElephantUnitRule::new), + ; + + private final int hanY; + private final int choY; + private final List xPositions; + private final Supplier rule; + + DefaultUnitPosition(int hanY, int choY, List xPositions, Supplier rule) { + this.hanY = hanY; + this.choY = choY; + this.xPositions = xPositions; + this.rule = rule; + } + + public static Map createDefaultUnits(DefaultUnitPosition position, Team team) { + if (team == Team.CHO) { + return position.xPositions.stream() + .map(x -> Position.of(x, position.choY)) + .collect(Collectors.toMap(pos -> pos, pos -> Unit.of(team, position.rule.get()) + )); + } + return position.xPositions.stream() + .map(x -> Position.of(x, position.hanY)) + .collect(Collectors.toMap(pos -> pos, pos -> Unit.of(team, position.rule.get()))); + } +} diff --git a/src/main/java/domain/position/Point.java b/src/main/java/domain/position/Point.java new file mode 100644 index 000000000..476275c28 --- /dev/null +++ b/src/main/java/domain/position/Point.java @@ -0,0 +1,24 @@ +package domain.position; + +public class Point { + + private final int x; + private final int y; + + private Point(int x, int y) { + this.x = x; + this.y = y; + } + + public static Point of(int x, int y) { + return new Point(x, y); + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } +} \ No newline at end of file diff --git a/src/main/java/domain/position/Position.java b/src/main/java/domain/position/Position.java new file mode 100644 index 000000000..e2d736e9a --- /dev/null +++ b/src/main/java/domain/position/Position.java @@ -0,0 +1,75 @@ +package domain.position; + +import java.util.Objects; + +public class Position { + + public static final String INVALID_POSITION_EXCEPTION = "유효하지 않은 장기판 위치입니다."; + public static final int X_MAX = 8; + public static final int Y_MAX = 9; + + private final int x; + private final int y; + + private Position(int x, int y) { + validate(x, y); + this.x = x; + this.y = y; + } + + public static Position of(int x, int y) { + return new Position(x, y); + } + + public static Position from(Point point) { + return new Position(point.getX(), point.getY()); + } + + private void validate(int x, int y) { + if (x < 0 || x > X_MAX) { + throw new IllegalArgumentException(INVALID_POSITION_EXCEPTION); + } + if (y < 0 || y > Y_MAX) { + throw new IllegalArgumentException(INVALID_POSITION_EXCEPTION); + } + } + + public static boolean isCanBePosition(Point point) { + if (point.getX() < 0 || point.getX() > X_MAX) { + return false; + } + return !(point.getY() < 0 || point.getY() > Y_MAX); + } + + public double calculateDistance(Position other) { + int xDifference = Math.abs(this.getX() - other.getX()); + int yDifference = Math.abs(this.getY() - other.getY()); + return Math.sqrt(Math.pow(xDifference, 2) + Math.pow(yDifference, 2)); + } + + public boolean isHorizontalOrVertical(Position opposite) { + return (this.x == opposite.x || this.y == opposite.y); + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) { + return false; + } + Position position = (Position) object; + return x == position.x && y == position.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } +} diff --git a/src/main/java/domain/position/Route.java b/src/main/java/domain/position/Route.java new file mode 100644 index 000000000..35efcdc7f --- /dev/null +++ b/src/main/java/domain/position/Route.java @@ -0,0 +1,35 @@ +package domain.position; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class Route { + + private final List positions; + + private Route(List positions) { + this.positions = positions; + } + + public static Route of(List positions) { + return new Route(positions); + } + + public Position searchDestination(Position position) { + return positions.stream() + .max(Comparator.comparingDouble(pos -> pos.calculateDistance(position))) + .orElseThrow(() -> new IllegalArgumentException("비어있는 경로입니다.")); + } + + public List getPositionsExceptDestination(Position position) { + Position endPoint = searchDestination(position); + return positions.stream() + .filter(pos -> !pos.equals(endPoint)) + .toList(); + } + + public List getPositions() { + return Collections.unmodifiableList(positions); + } +} diff --git a/src/main/java/domain/unit/Direction.java b/src/main/java/domain/unit/Direction.java new file mode 100644 index 000000000..5e1f6c4c5 --- /dev/null +++ b/src/main/java/domain/unit/Direction.java @@ -0,0 +1,52 @@ +package domain.unit; + +import java.util.List; + +public enum Direction { + + UPPER(0, 1), + UPPER_RIGHT(1, 1), + RIGHT(1, 0), + UNDER_RIGHT(1, -1), + UNDER(0, -1), + UNDER_LEFT(-1, -1), + LEFT(-1, 0), + UPPER_LEFT(-1, 1), + NONE(0, 0), + ; + + private final int x; + private final int y; + + Direction(int x, int y) { + this.x = x; + this.y = y; + } + + public List getNext() { + if (this == NONE) { + return List.of(UPPER, UNDER, LEFT, RIGHT); + } + if (this == UPPER) { + return List.of(UPPER_LEFT, UPPER_RIGHT); + } + if (this == UNDER) { + return List.of(UNDER_LEFT, UNDER_RIGHT); + } + if (this == LEFT) { + return List.of(UPPER_LEFT, UNDER_LEFT); + } + if (this == RIGHT) { + return List.of(UPPER_RIGHT, UNDER_RIGHT); + } + return List.of(this); + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } +} diff --git a/src/main/java/domain/unit/Team.java b/src/main/java/domain/unit/Team.java new file mode 100644 index 000000000..916887ac8 --- /dev/null +++ b/src/main/java/domain/unit/Team.java @@ -0,0 +1,15 @@ +package domain.unit; + +public enum Team { + + HAN, + CHO, + ; + + public Team getOpposite() { + if (this == HAN) { + return CHO; + } + return HAN; + } +} diff --git a/src/main/java/domain/unit/Unit.java b/src/main/java/domain/unit/Unit.java new file mode 100644 index 000000000..2e84861ac --- /dev/null +++ b/src/main/java/domain/unit/Unit.java @@ -0,0 +1,34 @@ +package domain.unit; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.rule.UnitRule; +import java.util.List; + +public class Unit { + + private final Team team; + private final UnitRule unitRule; + + + public Unit(Team team, UnitRule unitRule) { + this.team = team; + this.unitRule = unitRule; + } + + public static Unit of(Team team, UnitRule unitRule) { + return new Unit(team, unitRule); + } + + public List calculateRoutes(Position position) { + return unitRule.calculateAllRoute(position); + } + + public UnitType getType() { + return this.unitRule.getType(); + } + + public Team getTeam() { + return team; + } +} diff --git a/src/main/java/domain/unit/UnitType.java b/src/main/java/domain/unit/UnitType.java new file mode 100644 index 000000000..aaf9a602b --- /dev/null +++ b/src/main/java/domain/unit/UnitType.java @@ -0,0 +1,14 @@ +package domain.unit; + +public enum UnitType { + + GENERAL, + GUARD, + CHARIOT, + HORSE, + ELEPHANT, + CANNON, + SOLDIER, + NONE, + ; +} diff --git a/src/main/java/domain/unit/rule/CannonUnitRule.java b/src/main/java/domain/unit/rule/CannonUnitRule.java new file mode 100644 index 000000000..660b16015 --- /dev/null +++ b/src/main/java/domain/unit/rule/CannonUnitRule.java @@ -0,0 +1,69 @@ +package domain.unit.rule; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.UnitType; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class CannonUnitRule implements UnitRule { + + @Override + public List calculateAllRoute(Position start) { + List routes = new ArrayList<>(); + List positions = calculateEndPoints(start); + for (Position end : positions) { + Route route = calculateRoute(start, end); + if (route.getPositions().size() == 1) { + continue; + } + routes.add(route); + } + return routes; + } + + private List calculateEndPoints(Position start) { + int x = start.getX(); + int y = start.getY(); + List xPositions = IntStream.range(0, Position.X_MAX + 1) + .filter(element -> element != x) + .mapToObj(element -> Position.of(element, y)) + .toList(); + List yPositions = IntStream.range(0, Position.Y_MAX + 1) + .filter(element -> element != y) + .mapToObj(element -> Position.of(x, element)) + .toList(); + return Stream.concat(xPositions.stream(), yPositions.stream()) + .toList(); + } + + public Route calculateRoute(Position start, Position end) { + int startX = start.getX(); + int startY = start.getY(); + + int endX = end.getX(); + int endY = end.getY(); + + if (startX == endX) { + int maxY = Integer.max(startY, endY); + int minY = Integer.min(startY, endY); + return Route.of(IntStream.range(minY, maxY + 1) + .filter(y -> startY != y) + .mapToObj(y -> Position.of(startX, y)) + .toList()); + } + int maxX = Integer.max(startX, endX); + int minX = Integer.min(startX, endX); + return Route.of(IntStream.range(minX, maxX + 1) + .filter(x -> startX != x) + .mapToObj(x -> Position.of(x, startY)) + .toList()); + } + + @Override + public UnitType getType() { + return UnitType.CANNON; + } +} diff --git a/src/main/java/domain/unit/rule/ChariotUnitRule.java b/src/main/java/domain/unit/rule/ChariotUnitRule.java new file mode 100644 index 000000000..1fbdce1b1 --- /dev/null +++ b/src/main/java/domain/unit/rule/ChariotUnitRule.java @@ -0,0 +1,64 @@ +package domain.unit.rule; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.UnitType; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class ChariotUnitRule implements UnitRule { + + @Override + public List calculateAllRoute(Position start) { + List routes = new ArrayList<>(); + List positions = calculateEndPoints(start); + for (Position end : positions) { + routes.add(calculateRoute(start, end)); + } + return routes; + } + + public List calculateEndPoints(Position start) { + int x = start.getX(); + int y = start.getY(); + List xPositions = IntStream.range(0, Position.X_MAX + 1) + .filter(element -> element != x) + .mapToObj(element -> Position.of(element, y)) + .toList(); + List yPositions = IntStream.range(0, Position.Y_MAX + 1) + .filter(element -> element != y) + .mapToObj(element -> Position.of(x, element)) + .toList(); + return Stream.concat(xPositions.stream(), yPositions.stream()) + .toList(); + } + + public Route calculateRoute(Position start, Position end) { + int startX = start.getX(); + int startY = start.getY(); + + int endX = end.getX(); + int endY = end.getY(); + + if (startX == endX) { + int maxY = Integer.max(startY, endY); + int minY = Integer.min(startY, endY); + return Route.of(IntStream.range(minY, maxY + 1) + .filter(y -> startY != y) + .mapToObj(y -> Position.of(startX, y)) + .toList()); + } + int maxX = Integer.max(startX, endX); + int minX = Integer.min(startX, endX); + return Route.of(IntStream.range(minX, maxX + 1) + .filter(x -> startX != x) + .mapToObj(x -> Position.of(x, startY)) + .toList()); + } + + public UnitType getType() { + return UnitType.CHARIOT; + } +} diff --git a/src/main/java/domain/unit/rule/ElephantUnitRule.java b/src/main/java/domain/unit/rule/ElephantUnitRule.java new file mode 100644 index 000000000..f3bbd3ae8 --- /dev/null +++ b/src/main/java/domain/unit/rule/ElephantUnitRule.java @@ -0,0 +1,39 @@ +package domain.unit.rule; + +import domain.position.Point; +import domain.position.Position; +import domain.position.Route; +import domain.unit.Direction; +import domain.unit.UnitType; +import java.util.ArrayList; +import java.util.List; + +public class ElephantUnitRule implements UnitRule { + + @Override + public List calculateAllRoute(Position start) { + List routes = new ArrayList<>(); + dfs(0, Direction.NONE, new ArrayList<>(), Point.of(start.getX(), start.getY()), routes); + return routes; + } + + private void dfs(int depth, Direction before, List route, Point prevPoint, List routes) { + if (depth == 3) { + if (route.stream().allMatch(Position::isCanBePosition)) { + routes.add(Route.of(route.stream().map(Position::from).toList())); + } + return; + } + for (Direction direction : before.getNext()) { + Point next = Point.of(prevPoint.getX() + direction.getX(), prevPoint.getY() + direction.getY()); + route.add(next); + dfs(depth + 1, direction, route, next, routes); + route.remove(next); + } + } + + @Override + public UnitType getType() { + return UnitType.ELEPHANT; + } +} diff --git a/src/main/java/domain/unit/rule/HorseUnitRule.java b/src/main/java/domain/unit/rule/HorseUnitRule.java new file mode 100644 index 000000000..887f1f3cb --- /dev/null +++ b/src/main/java/domain/unit/rule/HorseUnitRule.java @@ -0,0 +1,39 @@ +package domain.unit.rule; + +import domain.position.Point; +import domain.position.Position; +import domain.position.Route; +import domain.unit.Direction; +import domain.unit.UnitType; +import java.util.ArrayList; +import java.util.List; + +public class HorseUnitRule implements UnitRule { + + @Override + public List calculateAllRoute(Position start) { + List routes = new ArrayList<>(); + dfs(0, Direction.NONE, new ArrayList<>(), Point.of(start.getX(), start.getY()), routes); + return routes; + } + + private void dfs(int depth, Direction before, List route, Point prevPoint, List routes) { + if (depth == 2) { + if (route.stream().allMatch(Position::isCanBePosition)) { + routes.add(Route.of(route.stream().map(Position::from).toList())); + } + return; + } + for (Direction direction : before.getNext()) { + Point next = Point.of(prevPoint.getX() + direction.getX(), prevPoint.getY() + direction.getY()); + route.add(next); + dfs(depth + 1, direction, route, next, routes); + route.remove(next); + } + } + + @Override + public UnitType getType() { + return UnitType.HORSE; + } +} diff --git a/src/main/java/domain/unit/rule/NoneUnitRule.java b/src/main/java/domain/unit/rule/NoneUnitRule.java new file mode 100644 index 000000000..689cfa903 --- /dev/null +++ b/src/main/java/domain/unit/rule/NoneUnitRule.java @@ -0,0 +1,19 @@ +package domain.unit.rule; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.UnitType; +import java.util.List; + +public class NoneUnitRule implements UnitRule { + + @Override + public List calculateAllRoute(Position start) { + return List.of(); + } + + @Override + public UnitType getType() { + return UnitType.NONE; + } +} diff --git a/src/main/java/domain/unit/rule/SoldierUnitRule.java b/src/main/java/domain/unit/rule/SoldierUnitRule.java new file mode 100644 index 000000000..31d70e7ac --- /dev/null +++ b/src/main/java/domain/unit/rule/SoldierUnitRule.java @@ -0,0 +1,34 @@ +package domain.unit.rule; + +import domain.position.Point; +import domain.position.Position; +import domain.position.Route; +import domain.unit.UnitType; +import java.util.ArrayList; +import java.util.List; + +public class SoldierUnitRule implements UnitRule { + + @Override + public List calculateAllRoute(Position start) { + List routes = new ArrayList<>(); + + List dx = List.of(0, 1, 0, -1); + List dy = List.of(1, 0, -1, 0); + + int x = start.getX(); + int y = start.getY(); + for (int i = 0; i < dx.size(); i++) { + List route = List.of(Point.of(x + dx.get(i), y + dy.get(i))); + if (route.stream().allMatch(Position::isCanBePosition)) { + routes.add(Route.of(route.stream().map(Position::from).toList())); + } + } + return routes; + } + + @Override + public UnitType getType() { + return UnitType.SOLDIER; + } +} diff --git a/src/main/java/domain/unit/rule/UnitRule.java b/src/main/java/domain/unit/rule/UnitRule.java new file mode 100644 index 000000000..0fc00bf42 --- /dev/null +++ b/src/main/java/domain/unit/rule/UnitRule.java @@ -0,0 +1,13 @@ +package domain.unit.rule; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.UnitType; +import java.util.List; + +public interface UnitRule { + + List calculateAllRoute(Position start); + + UnitType getType(); +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 000000000..b49306adc --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,29 @@ +package view; + +import domain.unit.Team; +import java.util.Scanner; + +public class InputView { + + private final Scanner scanner = new Scanner(System.in); + + public String readUnitPosition(Team team) { + System.out.println(teamToName(team) + ":: 이동할 장기의 위치를 선택해 주세요."); + return scanner.nextLine(); + } + + public String readDestinationPosition(Team team) { + System.out.println("도착할 위치를 선택해 주세요."); + return scanner.nextLine(); + } + + private String teamToName(Team team) { + if (team == Team.HAN) { + return "한나라"; + } + if (team == Team.CHO) { + return "초나라"; + } + throw new IllegalStateException("예기치 못한 예외가 발생하였습니다."); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 000000000..8d8ad2e3d --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,79 @@ +package view; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.Team; +import domain.unit.Unit; +import domain.unit.UnitType; +import java.util.List; +import java.util.Map; + +public class OutputView { + + private static final String ANSI_RESET = "\u001B[0m"; + private static final String ANSI_RED = "\u001B[31m"; + private static final String ANSI_GREEN = "\u001B[32m"; + + public void printJanggiUnits(Map units) { + System.out.println("\n* | 0 1 2 3 4 5 6 7 8 "); + System.out.println("--------------------------------"); + + for (int y = 0; y <= Position.Y_MAX; y++) { + System.out.print(y + " | "); + + for (int x = 0; x <= Position.X_MAX; x++) { + Position position = Position.of(x, y); + Unit unit = units.get(position); + if (unit != null) { + String unitType = typeToName(unit.getType()); + if (unit.getTeam() == Team.CHO) { + System.out.print(ANSI_GREEN + unitType + ANSI_RESET + " "); + continue; + } + System.out.print(ANSI_RED + unitType + ANSI_RESET + " "); + continue; + } + System.out.print(".. "); + } + System.out.println(); + } + System.out.println(); + } + + public void printAvailableRoute(Position current, List routes) { + System.out.println("\n이동 가능한 위치"); + for (Route route : routes) { + Position endPoint = route.searchDestination(current); + System.out.printf("x = %d, y = %d\n" + , endPoint.getX(), endPoint.getY()); + } + } + + private String typeToName(UnitType unitType) { + if (unitType == UnitType.GENERAL) { + return "GN"; + } + if (unitType == UnitType.GUARD) { + return "GD"; + } + if (unitType == UnitType.CHARIOT) { + return "CH"; + } + if (unitType == UnitType.HORSE) { + return "HR"; + } + if (unitType == UnitType.ELEPHANT) { + return "EL"; + } + if (unitType == UnitType.CANNON) { + return "CN"; + } + if (unitType == UnitType.SOLDIER) { + return "SD"; + } + if (unitType == UnitType.NONE) { + return ".."; + } + throw new IllegalStateException("예기치 못한 예외가 발생하였습니다."); + } +} diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/java/domain/JanggiTest.java b/src/test/java/domain/JanggiTest.java new file mode 100644 index 000000000..bd114d44a --- /dev/null +++ b/src/test/java/domain/JanggiTest.java @@ -0,0 +1,218 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.Team; +import domain.unit.Unit; +import domain.unit.rule.CannonUnitRule; +import domain.unit.rule.ChariotUnitRule; +import domain.unit.rule.ElephantUnitRule; +import domain.unit.rule.HorseUnitRule; +import domain.unit.rule.SoldierUnitRule; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class JanggiTest { + @Test + @DisplayName("기물(상)의 타기물을 고려한 이동경로를 구한다") + void test1() { + // given + Unit target = Unit.of(Team.CHO, new ElephantUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target, + Position.of(4, 4), Unit.of(Team.CHO, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, Map.of(), Team.CHO); + + // when + List routes = janggi.findMovableRoutesFrom(targetPosition); + + // then + assertThat(routes).hasSize(6); + } + + @Test + @DisplayName("기물(마)의 타기물을 고려한 이동경로를 구한다") + void test2() { + // given + Unit target = Unit.of(Team.CHO, new HorseUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target, + Position.of(4, 4), Unit.of(Team.CHO, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, Map.of(), Team.CHO); + + // when + List routes = janggi.findMovableRoutesFrom(targetPosition); + + // then + assertThat(routes).hasSize(6); + } + + @Test + @DisplayName("기물(차)의 타기물을 고려한 이동경로를 구한다") + void test3() { + // given + Unit target = Unit.of(Team.CHO, new ChariotUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target, + Position.of(4, 4), Unit.of(Team.CHO, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, Map.of(), Team.CHO); + + // when + List routes = janggi.findMovableRoutesFrom(targetPosition); + + // then + assertThat(routes).hasSize(12); + } + + @Test + @DisplayName("기물(포)의 타기물을 고려한 이동경로를 구한다") + void test4() { + // given + Unit target = Unit.of(Team.CHO, new CannonUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target, + Position.of(4, 4), Unit.of(Team.CHO, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, Map.of(), Team.CHO); + + // when + List routes = janggi.findMovableRoutesFrom(targetPosition); + + // then + assertThat(routes).hasSize(4); + } + + @Test + @DisplayName("기물(졸)의 타기물을 고려한 이동경로를 구한다") + void test5() { + // given + Unit target = Unit.of(Team.CHO, new SoldierUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target, + Position.of(4, 4), Unit.of(Team.CHO, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, Map.of(), Team.CHO); + + // when + List routes = janggi.findMovableRoutesFrom(targetPosition); + + // then + assertThat(routes).hasSize(2); + } + + @Test + @DisplayName("기물을 이동할 때 도착지가 비어있는 곳이라면 이동에 성공한다") + void test6() { + // given + Unit target = Unit.of(Team.CHO, new SoldierUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target, + Position.of(4, 4), Unit.of(Team.CHO, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, Map.of(), Team.CHO); + + // when + Position destination = Position.of(3, 5); + janggi.doTurn(targetPosition, destination); + + // then + assertThat(janggi.getUnits().get(destination)).isEqualTo(target); + assertThat(janggi.getUnits().get(targetPosition)).isNull(); + } + + @Test + @DisplayName("기물을 이동할 때 도착지가 적군이라면 이동에 성공하고 적군을 삭제한다") + void test7() { + // given + Unit target = Unit.of(Team.CHO, new SoldierUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target + ); + Map oppositeUnits = Map.of( + Position.of(4, 4), Unit.of(Team.HAN, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, oppositeUnits, Team.CHO); + + // when + Position destination = Position.of(4, 4); + janggi.doTurn(targetPosition, destination); + + // then + assertThat(janggi.getUnits()).hasSize(1); + assertThat(janggi.getUnits().get(destination)).isEqualTo(target); + } + + @Test + @DisplayName("기물을 이동할 때 적군을 선택하면 예외가 발생한다.") + void test8() { + // given + Unit target = Unit.of(Team.CHO, new SoldierUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target + ); + Position oppositePosition = Position.of(4, 4); + Map oppositeUnits = Map.of( + oppositePosition, Unit.of(Team.HAN, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, oppositeUnits, Team.CHO); + + // when & then + assertThatThrownBy(() -> janggi.doTurn(oppositePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(Janggi.PICK_OPPOSITE_UNIT_EXCEPTION); + } + + @Test + @DisplayName("기물을 이동할 때 빈 곳을 선택하면 예외가 발생한다.") + void test9() { + // given + Unit target = Unit.of(Team.CHO, new SoldierUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target + ); + Position oppositePosition = Position.of(4, 4); + Map oppositeUnits = Map.of( + oppositePosition, Unit.of(Team.HAN, new SoldierUnitRule()) + ); + Janggi janggi = Janggi.of(units, oppositeUnits, Team.CHO); + + // when & then + assertThatThrownBy(() -> janggi.doTurn(Position.of(0, 0), oppositePosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(Janggi.EMPTY_POINT_EXCEPTION); + } + + @Test + @DisplayName("기물을 이동할 때 기물의 규칙에 맞지 않는 도착지를 선택하면 예외가 발생한다.") + void test10() { + // given + Unit target = Unit.of(Team.CHO, new SoldierUnitRule()); + Position targetPosition = Position.of(4, 5); + Map units = Map.of( + targetPosition, target + ); + Janggi janggi = Janggi.of(units, Map.of(), Team.CHO); + + // when & then + assertThatThrownBy(() -> janggi.doTurn(targetPosition, Position.of(0, 0))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(Janggi.CANNOT_MOVE_EXCEPTION); + } +} diff --git a/src/test/java/domain/position/PositionTest.java b/src/test/java/domain/position/PositionTest.java new file mode 100644 index 000000000..38cb52df8 --- /dev/null +++ b/src/test/java/domain/position/PositionTest.java @@ -0,0 +1,79 @@ +package domain.position; + +import static domain.position.Position.INVALID_POSITION_EXCEPTION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PositionTest { + + @ValueSource(strings = {"0,-1", "-1,0", "9,0", "0,10"}) + @ParameterizedTest + @DisplayName("좌표 범위를 넘으면 예외를 발생시킨다") + void test1(String str) { + // given + String[] split = str.split(","); + int x = Integer.parseInt(split[0]); + int y = Integer.parseInt(split[1]); + + // when & then + assertThatThrownBy(() -> Position.of(x, y)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_POSITION_EXCEPTION); + } + + @Test + @DisplayName("두 위치 사이의 거리를 반환한다") + void test2() { + // given + Position current = Position.of(0, 0); + + // when + double distance = current.calculateDistance(Position.of(3, 4)); + + // then + assertThat(distance).isEqualTo(5); + } + + @ValueSource(strings = {"0,2", "2,0", "2,9", "8,2"}) + @ParameterizedTest + @DisplayName("수평 혹은 수직 직선에 점이 위치해 있으면 참을 반환한다") + void test3(String str) { + // given + String[] split = str.split(","); + int x = Integer.parseInt(split[0]); + int y = Integer.parseInt(split[1]); + Position position = Position.of(x, y); + + Position opposite = Position.of(2, 2); + + // when + boolean b = position.isHorizontalOrVertical(opposite); + + // then + assertThat(b).isTrue(); + } + + @ValueSource(strings = {"3,3", "1,1", "3,1", "1,3"}) + @ParameterizedTest + @DisplayName("수평 혹은 수직 직선에 점이 위치해있지 않으면 거짓을 반환한다") + void test4(String str) { + // given + String[] split = str.split(","); + int x = Integer.parseInt(split[0]); + int y = Integer.parseInt(split[1]); + Position position = Position.of(x, y); + + Position opposite = Position.of(2, 2); + + // when + boolean b = position.isHorizontalOrVertical(opposite); + + // then + assertThat(b).isFalse(); + } +} diff --git a/src/test/java/domain/position/RouteTest.java b/src/test/java/domain/position/RouteTest.java new file mode 100644 index 000000000..20c63bd29 --- /dev/null +++ b/src/test/java/domain/position/RouteTest.java @@ -0,0 +1,51 @@ +package domain.position; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RouteTest { + + @Test + @DisplayName("경로의 도착지를 반환한다.") + void test1() { + // given + List positions = List.of( + Position.of(0, 0), + Position.of(0, 1), + Position.of(0, 2), + Position.of(0, 3)); + Route route = Route.of(positions); + + // when + Position destination = route.searchDestination(Position.of(0, 4)); + + // then + assertThat(destination).isEqualTo(Position.of(0, 0)); + } + + @Test + @DisplayName("경로의 도착지를 제외한 위치들을 반환한다.") + void test2() { + // given + List positions = List.of( + Position.of(0, 0), + Position.of(0, 1), + Position.of(0, 2), + Position.of(0, 3)); + Route route = Route.of(positions); + + // when + List exceptDestination = route.getPositionsExceptDestination(Position.of(0, 4)); + + // then + assertThat(exceptDestination).hasSize(3) + .contains( + Position.of(0, 1), + Position.of(0, 2), + Position.of(0, 3) + ); + } +} diff --git a/src/test/java/domain/unit/UnitTest.java b/src/test/java/domain/unit/UnitTest.java new file mode 100644 index 000000000..51c4059a5 --- /dev/null +++ b/src/test/java/domain/unit/UnitTest.java @@ -0,0 +1,91 @@ +package domain.unit; + +import static org.assertj.core.api.Assertions.assertThat; + +import domain.position.Position; +import domain.position.Route; +import domain.unit.rule.CannonUnitRule; +import domain.unit.rule.ChariotUnitRule; +import domain.unit.rule.ElephantUnitRule; +import domain.unit.rule.HorseUnitRule; +import domain.unit.rule.SoldierUnitRule; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class UnitTest { + @Test + @DisplayName("기물(상)의 이동 가능한 모든 경로를 반환한다") + void test1() { + // given + Unit unit = Unit.of(Team.CHO, new ElephantUnitRule()); + + // when + List routesOfCenter = unit.calculateRoutes(Position.of(4, 5)); + List routesOfCorner = unit.calculateRoutes(Position.of(0, 0)); + + // then + assertThat(routesOfCenter).hasSize(8); + assertThat(routesOfCorner).hasSize(2); + } + + @Test + @DisplayName("기물(마)의 이동 가능한 모든 경로를 반환한다") + void test2() { + // given + Unit unit = Unit.of(Team.CHO, new HorseUnitRule()); + + // when + List routesOfCenter = unit.calculateRoutes(Position.of(4, 5)); + List routesOfCorner = unit.calculateRoutes(Position.of(0, 0)); + + // then + assertThat(routesOfCenter).hasSize(8); + assertThat(routesOfCorner).hasSize(2); + } + + @Test + @DisplayName("기물(차)의 이동 가능한 모든 경로를 반환한다") + void test3() { + // given + Unit unit = Unit.of(Team.CHO, new ChariotUnitRule()); + + // when + List routesOfCenter = unit.calculateRoutes(Position.of(4, 5)); + List routesOfCorner = unit.calculateRoutes(Position.of(0, 0)); + + // then + assertThat(routesOfCenter).hasSize(17); + assertThat(routesOfCorner).hasSize(17); + } + + @Test + @DisplayName("기물(포)의 이동 가능한 모든 경로를 반환한다") + void test4() { + // given + Unit unit = Unit.of(Team.CHO, new CannonUnitRule()); + + // when + List routesOfCenter = unit.calculateRoutes(Position.of(4, 5)); + List routesOfCorner = unit.calculateRoutes(Position.of(0, 0)); + + // then + assertThat(routesOfCenter).hasSize(13); + assertThat(routesOfCorner).hasSize(15); + } + + @Test + @DisplayName("기물(졸)의 이동 가능한 모든 경로를 반환한다") + void test5() { + // given + Unit unit = Unit.of(Team.CHO, new SoldierUnitRule()); + + // when + List routesOfCenter = unit.calculateRoutes(Position.of(4, 5)); + List routesOfCorner = unit.calculateRoutes(Position.of(0, 0)); + + // then + assertThat(routesOfCenter).hasSize(4); + assertThat(routesOfCorner).hasSize(2); + } +} diff --git a/src/test/java/domain/unit/rule/ChariotUnitRuleTest.java b/src/test/java/domain/unit/rule/ChariotUnitRuleTest.java new file mode 100644 index 000000000..38e550cb9 --- /dev/null +++ b/src/test/java/domain/unit/rule/ChariotUnitRuleTest.java @@ -0,0 +1,41 @@ +package domain.unit.rule; + +import static org.assertj.core.api.Assertions.assertThat; + +import domain.position.Position; +import domain.position.Route; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ChariotUnitRuleTest { + @Test + @DisplayName("차가 갈 수 있는 엔드포인트를 모두 반환한다") + void test1() { + // given + Position p = Position.of(0, 0); + ChariotUnitRule rule = new ChariotUnitRule(); + + // when + List endPositions = rule.calculateEndPoints(p); + + // then + assertThat(endPositions) + .hasSize(17); + } + + @Test + @DisplayName("시작점과 끝점의 경로를 반환한다") + void test2() { + // given + Position start = Position.of(0, 0); + Position end = Position.of(0, 5); + ChariotUnitRule rule = new ChariotUnitRule(); + + // when + Route path = rule.calculateRoute(start, end); + + // then + assertThat(path.getPositions()).hasSize(5); + } +} diff --git a/src/test/java/domain/unit/rule/ElephantUnitRuleTest.java b/src/test/java/domain/unit/rule/ElephantUnitRuleTest.java new file mode 100644 index 000000000..ffcfe376f --- /dev/null +++ b/src/test/java/domain/unit/rule/ElephantUnitRuleTest.java @@ -0,0 +1,25 @@ +package domain.unit.rule; + +import static org.assertj.core.api.Assertions.assertThat; + +import domain.position.Position; +import domain.position.Route; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ElephantUnitRuleTest { + + @Test + @DisplayName("코끼리가 이동 가능한 루트를 계산한다.") + void test2() { + // given + ElephantUnitRule elephantUnitRule = new ElephantUnitRule(); + + // when + List routes = elephantUnitRule.calculateAllRoute(Position.of(1, 9)); + + // then + assertThat(routes).hasSize(2); + } +} \ No newline at end of file diff --git a/src/test/java/domain/unit/rule/HorseUnitRuleTest.java b/src/test/java/domain/unit/rule/HorseUnitRuleTest.java new file mode 100644 index 000000000..624d91256 --- /dev/null +++ b/src/test/java/domain/unit/rule/HorseUnitRuleTest.java @@ -0,0 +1,26 @@ +package domain.unit.rule; + + +import static org.assertj.core.api.Assertions.assertThat; + +import domain.position.Position; +import domain.position.Route; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HorseUnitRuleTest { + + @Test + @DisplayName("말") + void test1() { + // given + HorseUnitRule horseUnitRule = new HorseUnitRule(); + + // when + List routes = horseUnitRule.calculateAllRoute(Position.of(2, 9)); + + // then + assertThat(routes).hasSize(4); + } +}