diff --git a/README.md b/README.md index 9775dda0a..c60e03322 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,51 @@ # java-janggi 장기 미션 저장소 + +## 미션을 수행하며 반드시 고민할 것 + +### ✅ 장기의 말(駒)들은 어떻게 객체로 나누어야 하는가? + +장기가 이동할 수 있는 경로를 정의한 인터페이스를 각 기물 종류마다 구현한다. + +### ✅ 각 말의 이동 규칙을 어떻게 설계할 것인가? + +Enum으로 상,하,좌,우,대각선 4방향을 정의하고, 각 기물이 해당 Enum을 이용해서 이동 규칙을 구현한다. + +### ✅ 객체 간의 책임을 어떻게 나눌 것인가? + +- 객체가 주체성을 갖고 행동하는가? (_'객체의 결합도가 낮고 응집도가 높은가?'_) +- 객체안의 메서드들을 테스트할 수 있는지 판단 (_'메서드가 public으로 유지 될 수 있는가?'_) +- 객체안의 메서드들이 필드 멤버를 온전히 사용하고 있는지 확인 + +### ✅ 어떤 원칙을 적용해야 더 좋은 설계를 할 수 있을까? + +#### TDD 사이클이 한번 끝날때 마다 아래 항목들을 검사한다. + +- 각 객체의 책임이 적절히 할당이 되었는가? +- 메서드 시그니처가 자연스러운가? +- getter의 사용이 적절한가? (_'진짜 여기서 getter를 써야만할까?'_) + +## 기능 구현 목록 + +### 보드 + +- [ ] 특정 위치에서 이동 가능한 위치 리스트를 반환한다 +- [ ] 점수를 계산 한다 +- [ ] 예외 처리 + - [ ] 해당 위치에 기물이 없으면 예외 + +### 기물들의 위치 + +- [x] 기물들의 위치를 초기화 한다 +- [x] 특정 위치에 어떤 기물이 있는지 반환한다 + +### 기물 + +- [x] 이동 가능한 위치를 리스트로 반환 + - [x] 포 (`Cannon`) + - [x] 차 (`Chariot`) + - [x] 상 (`Elephant`) + - [x] 졸/병 (`Guard`) + - [x] 마 (`Horse`) +- [x] 대상 기물을 먹을 수 있는지 판단 diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 000000000..aa9d847b2 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,13 @@ +import game.JanggiGame; +import view.InputView; +import view.OutputView; + +public class Application { + public static void main(String[] args) { + JanggiGame janggiGame = new JanggiGame( + new InputView(), + new OutputView() + ); + janggiGame.run(); + } +} diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java new file mode 100644 index 000000000..a4e556c95 --- /dev/null +++ b/src/main/java/domain/Board.java @@ -0,0 +1,77 @@ +package domain; + +import domain.chessPiece.ChessPiece; +import domain.hurdlePolicy.HurdlePolicy; +import domain.path.Path; +import domain.position.ChessPiecePositions; +import domain.position.ChessPosition; +import domain.score.Score; +import domain.type.ChessTeam; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class Board { + + private final ChessPiecePositions chessPiecePositions; + private final Map scores = new EnumMap<>(ChessTeam.class); + + public Board(final ChessPiecePositions chessPiecePositions) { + this.chessPiecePositions = chessPiecePositions; + scores.putAll(Map.of( + ChessTeam.RED, Score.zero(), + ChessTeam.BLUE, Score.zero() + )); + } + + public boolean isExistPieceAt(ChessPosition position) { + return chessPiecePositions.existChessPieceByPosition(position); + } + + public void move(final ChessTeam currentTeam, final ChessPosition from, final ChessPosition to) { + validateTeam(currentTeam, from); + validateDestination(from, to); + if (chessPiecePositions.existChessPieceByPosition(to)) { + killTarget(currentTeam, to); + } + chessPiecePositions.move(from, to); + } + + public List getAvailableDestination(final ChessPosition position) { + ChessPiece chessPiece = chessPiecePositions.getChessPieceByPosition(position); + final List coordinatePaths = chessPiece.getCoordinatePaths(position); + final HurdlePolicy hurdlePolicy = chessPiece.getHurdlePolicy(); + return hurdlePolicy.pickDestinations(chessPiece.getTeam(), coordinatePaths, chessPiecePositions); + } + + public void validateTeam(final ChessTeam currentTeam, final ChessPosition from) { + ChessPiece chessPiece = chessPiecePositions.getChessPieceByPosition(from); + if (currentTeam != chessPiece.getTeam()) { + throw new IllegalArgumentException("상대편의 기물을 움직일 수 없습니다."); + } + } + + private void killTarget(ChessTeam currentTeam, ChessPosition to) { + ChessPiece target = chessPiecePositions.getChessPieceByPosition(to); + updateScore(currentTeam, target); + chessPiecePositions.removeChessPieceByPosition(to); + } + + private void updateScore(ChessTeam currentTeam, ChessPiece target) { + Score score = target.getScore(); + Score updatedScore = scores.get(currentTeam).add(score); + scores.put(currentTeam, updatedScore); + } + + private void validateDestination(final ChessPosition from, final ChessPosition to) { + List destinations = getAvailableDestination(from); + if (!destinations.contains(to)) { + throw new IllegalArgumentException("이동할 수 없는 경로입니다."); + } + } + + public Map getPositions() { + return chessPiecePositions.getChessPieces(); + } +} diff --git a/src/main/java/domain/chessPiece/Cannon.java b/src/main/java/domain/chessPiece/Cannon.java new file mode 100644 index 000000000..c5c9b5992 --- /dev/null +++ b/src/main/java/domain/chessPiece/Cannon.java @@ -0,0 +1,44 @@ +package domain.chessPiece; + +import domain.direction.Direction; +import domain.hurdlePolicy.CannonHurdlePolicy; +import domain.hurdlePolicy.HurdlePolicy; +import domain.position.ChessPosition; +import domain.type.ChessPieceType; +import domain.type.ChessTeam; + +import java.util.List; +import java.util.Map; + +public class Cannon extends UnlimitedMoveChessPiece { + private static final List directions = List.of( + Direction.UP, + Direction.DOWN, + Direction.LEFT, + Direction.RIGHT + ); + private final HurdlePolicy hurdlePolicy = new CannonHurdlePolicy(); + + public Cannon(final ChessTeam team) { + super(team, directions); + } + + public static Map initPieces() { + return Map.of( + new ChessPosition(2, 1), new Cannon(ChessTeam.RED), + new ChessPosition(2, 7), new Cannon(ChessTeam.RED), + new ChessPosition(7, 1), new Cannon(ChessTeam.BLUE), + new ChessPosition(7, 7), new Cannon(ChessTeam.BLUE) + ); + } + + @Override + public HurdlePolicy getHurdlePolicy() { + return hurdlePolicy; + } + + @Override + public ChessPieceType getChessPieceType() { + return ChessPieceType.CANNON; + } +} diff --git a/src/main/java/domain/chessPiece/Chariot.java b/src/main/java/domain/chessPiece/Chariot.java new file mode 100644 index 000000000..9d34b47ce --- /dev/null +++ b/src/main/java/domain/chessPiece/Chariot.java @@ -0,0 +1,45 @@ +package domain.chessPiece; + +import domain.direction.Direction; +import domain.hurdlePolicy.HurdlePolicy; +import domain.hurdlePolicy.StopAtHurdlePolicy; +import domain.position.ChessPosition; +import domain.type.ChessPieceType; +import domain.type.ChessTeam; + +import java.util.List; +import java.util.Map; + +public class Chariot extends UnlimitedMoveChessPiece { + + private static final List directions = List.of( + Direction.UP, + Direction.DOWN, + Direction.LEFT, + Direction.RIGHT + ); + private final HurdlePolicy hurdlePolicy = new StopAtHurdlePolicy(); + + public Chariot(final ChessTeam team) { + super(team, directions); + } + + public static Map initPieces() { + return Map.of( + new ChessPosition(0, 0), new Chariot(ChessTeam.RED), + new ChessPosition(0, 8), new Chariot(ChessTeam.RED), + new ChessPosition(9, 0), new Chariot(ChessTeam.BLUE), + new ChessPosition(9, 8), new Chariot(ChessTeam.BLUE) + ); + } + + @Override + public HurdlePolicy getHurdlePolicy() { + return hurdlePolicy; + } + + @Override + public ChessPieceType getChessPieceType() { + return ChessPieceType.CHARIOT; + } +} diff --git a/src/main/java/domain/chessPiece/ChessPiece.java b/src/main/java/domain/chessPiece/ChessPiece.java new file mode 100644 index 000000000..f11c4c7d0 --- /dev/null +++ b/src/main/java/domain/chessPiece/ChessPiece.java @@ -0,0 +1,20 @@ +package domain.chessPiece; + +import domain.hurdlePolicy.HurdlePolicy; +import domain.path.Path; +import domain.position.ChessPiecePositions; +import domain.score.Score; +import domain.type.ChessPieceType; +import domain.position.ChessPosition; +import domain.type.ChessTeam; + +import java.util.List; + +public interface ChessPiece { + List getDestinations(ChessPosition startPosition, ChessPiecePositions positions); + ChessPieceType getChessPieceType(); + ChessTeam getTeam(); + Score getScore(); + List getCoordinatePaths(final ChessPosition startPosition); + HurdlePolicy getHurdlePolicy(); +} diff --git a/src/main/java/domain/chessPiece/Elephant.java b/src/main/java/domain/chessPiece/Elephant.java new file mode 100644 index 000000000..3d7b47b25 --- /dev/null +++ b/src/main/java/domain/chessPiece/Elephant.java @@ -0,0 +1,49 @@ +package domain.chessPiece; + +import domain.direction.Direction; +import domain.direction.Directions; +import domain.hurdlePolicy.HurdlePolicy; +import domain.hurdlePolicy.UnpassableHurdlePolicy; +import domain.position.ChessPosition; +import domain.type.ChessPieceType; +import domain.type.ChessTeam; + +import java.util.List; +import java.util.Map; + +public class Elephant extends LimitedMoveChessPiece { + private static final List directions = List.of( + new Directions(List.of(Direction.UP, Direction.RIGHT_UP, Direction.RIGHT_UP)), + new Directions(List.of(Direction.UP, Direction.LEFT_UP, Direction.LEFT_UP)), + new Directions(List.of(Direction.LEFT, Direction.LEFT_UP, Direction.LEFT_UP)), + new Directions(List.of(Direction.LEFT, Direction.LEFT_DOWN, Direction.LEFT_DOWN)), + new Directions(List.of(Direction.RIGHT, Direction.RIGHT_UP, Direction.RIGHT_UP)), + new Directions(List.of(Direction.RIGHT, Direction.RIGHT_DOWN, Direction.RIGHT_DOWN)), + new Directions(List.of(Direction.DOWN, Direction.LEFT_DOWN, Direction.LEFT_DOWN)), + new Directions(List.of(Direction.DOWN, Direction.RIGHT_DOWN, Direction.RIGHT_DOWN)) + ); + private final HurdlePolicy hurdlePolicy = new UnpassableHurdlePolicy(); + + public Elephant(final ChessTeam team) { + super(team, directions); + } + + public static Map initPieces() { + return Map.of( + new ChessPosition(0, 2), new Elephant(ChessTeam.RED), + new ChessPosition(0, 6), new Elephant(ChessTeam.RED), + new ChessPosition(9, 2), new Elephant(ChessTeam.BLUE), + new ChessPosition(9, 6), new Elephant(ChessTeam.BLUE) + ); + } + + @Override + public HurdlePolicy getHurdlePolicy() { + return hurdlePolicy; + } + + @Override + public ChessPieceType getChessPieceType() { + return ChessPieceType.ELEPHANT; + } +} diff --git a/src/main/java/domain/chessPiece/Horse.java b/src/main/java/domain/chessPiece/Horse.java new file mode 100644 index 000000000..48eeab31d --- /dev/null +++ b/src/main/java/domain/chessPiece/Horse.java @@ -0,0 +1,49 @@ +package domain.chessPiece; + +import domain.direction.Direction; +import domain.direction.Directions; +import domain.hurdlePolicy.HurdlePolicy; +import domain.hurdlePolicy.UnpassableHurdlePolicy; +import domain.position.ChessPosition; +import domain.type.ChessPieceType; +import domain.type.ChessTeam; + +import java.util.List; +import java.util.Map; + +public class Horse extends LimitedMoveChessPiece { + private static final List directions = List.of( + new Directions(List.of(Direction.UP, Direction.RIGHT_UP)), + new Directions(List.of(Direction.UP, Direction.LEFT_UP)), + new Directions(List.of(Direction.LEFT, Direction.LEFT_UP)), + new Directions(List.of(Direction.LEFT, Direction.LEFT_DOWN)), + new Directions(List.of(Direction.RIGHT, Direction.RIGHT_UP)), + new Directions(List.of(Direction.RIGHT, Direction.RIGHT_DOWN)), + new Directions(List.of(Direction.DOWN, Direction.LEFT_DOWN)), + new Directions(List.of(Direction.DOWN, Direction.RIGHT_DOWN)) + ); + private final HurdlePolicy hurdlePolicy = new UnpassableHurdlePolicy(); + + public Horse(final ChessTeam team) { + super(team, directions); + } + + public static Map initPieces() { + return Map.of( + new ChessPosition(0, 1), new Horse(ChessTeam.RED), + new ChessPosition(0, 7), new Horse(ChessTeam.RED), + new ChessPosition(9, 1), new Horse(ChessTeam.BLUE), + new ChessPosition(9, 7), new Horse(ChessTeam.BLUE) + ); + } + + @Override + public HurdlePolicy getHurdlePolicy() { + return hurdlePolicy; + } + + @Override + public ChessPieceType getChessPieceType() { + return ChessPieceType.HORSE; + } +} diff --git a/src/main/java/domain/chessPiece/JanggiChessPiece.java b/src/main/java/domain/chessPiece/JanggiChessPiece.java new file mode 100644 index 000000000..3dc90da61 --- /dev/null +++ b/src/main/java/domain/chessPiece/JanggiChessPiece.java @@ -0,0 +1,43 @@ +package domain.chessPiece; + +import domain.hurdlePolicy.HurdlePolicy; +import domain.position.ChessPiecePositions; +import domain.position.ChessPosition; +import domain.score.Score; +import domain.type.ChessTeam; +import domain.path.Path; + +import java.util.List; + +public abstract class JanggiChessPiece implements ChessPiece { + + private final ChessTeam team; + + protected JanggiChessPiece(ChessTeam team) { + this.team = team; + } + + @Override + public final List getDestinations(ChessPosition startPosition, ChessPiecePositions positions) { + List coordinates = getCoordinatePaths(startPosition); + HurdlePolicy hurdlePolicy = getHurdlePolicy(); + return hurdlePolicy.pickDestinations(team, coordinates, positions); + } + + + @Override + public abstract List getCoordinatePaths(ChessPosition startPosition); + + @Override + public abstract HurdlePolicy getHurdlePolicy(); + + @Override + public final ChessTeam getTeam() { + return team; + } + + @Override + public final Score getScore() { + return getChessPieceType().score; + } +} diff --git a/src/main/java/domain/chessPiece/LimitedMoveChessPiece.java b/src/main/java/domain/chessPiece/LimitedMoveChessPiece.java new file mode 100644 index 000000000..d60f90497 --- /dev/null +++ b/src/main/java/domain/chessPiece/LimitedMoveChessPiece.java @@ -0,0 +1,30 @@ +package domain.chessPiece; + +import domain.direction.Directions; +import domain.path.Path; +import domain.position.ChessPosition; +import domain.type.ChessPieceType; +import domain.type.ChessTeam; + +import java.util.ArrayList; +import java.util.List; + +public abstract class LimitedMoveChessPiece extends JanggiChessPiece { + private final List directions; + + protected LimitedMoveChessPiece(ChessTeam team, List directions) { + super(team); + this.directions = directions; + } + + @Override + public final List getCoordinatePaths(ChessPosition startPosition) { + List result = new ArrayList<>(); + for (Directions direction : directions) { + if (direction.canApplyFrom(startPosition)) { + result.add(direction.getPathFrom(startPosition)); + } + } + return result; + } +} diff --git a/src/main/java/domain/chessPiece/Pawn.java b/src/main/java/domain/chessPiece/Pawn.java new file mode 100644 index 000000000..606246831 --- /dev/null +++ b/src/main/java/domain/chessPiece/Pawn.java @@ -0,0 +1,58 @@ +package domain.chessPiece; + +import domain.direction.Direction; +import domain.direction.Directions; +import domain.hurdlePolicy.HurdlePolicy; +import domain.hurdlePolicy.UnpassableHurdlePolicy; +import domain.position.ChessPosition; +import domain.type.ChessPieceType; +import domain.type.ChessTeam; + +import java.util.List; +import java.util.Map; + +public class Pawn extends LimitedMoveChessPiece { + + private static final Map> DIRECTIONS = Map.of( + ChessTeam.RED, List.of( + new Directions(List.of(Direction.LEFT)), + new Directions(List.of(Direction.RIGHT)), + new Directions(List.of(Direction.DOWN)) + ), + ChessTeam.BLUE, List.of( + new Directions(List.of(Direction.LEFT)), + new Directions(List.of(Direction.RIGHT)), + new Directions(List.of(Direction.UP)) + ) + ); + private final HurdlePolicy hurdlePolicy = new UnpassableHurdlePolicy(); + + public Pawn(ChessTeam chessTeam) { + super(chessTeam, DIRECTIONS.get(chessTeam)); + } + + public static Map initPieces() { + return Map.of( + new ChessPosition(3, 0), new Pawn(ChessTeam.RED), + new ChessPosition(3, 2), new Pawn(ChessTeam.RED), + new ChessPosition(3, 4), new Pawn(ChessTeam.RED), + new ChessPosition(3, 6), new Pawn(ChessTeam.RED), + new ChessPosition(3, 8), new Pawn(ChessTeam.RED), + new ChessPosition(6, 0), new Pawn(ChessTeam.BLUE), + new ChessPosition(6, 2), new Pawn(ChessTeam.BLUE), + new ChessPosition(6, 4), new Pawn(ChessTeam.BLUE), + new ChessPosition(6, 6), new Pawn(ChessTeam.BLUE), + new ChessPosition(6, 8), new Pawn(ChessTeam.BLUE) + ); + } + + @Override + public HurdlePolicy getHurdlePolicy() { + return hurdlePolicy; + } + + @Override + public ChessPieceType getChessPieceType() { + return ChessPieceType.PAWN; + } +} diff --git a/src/main/java/domain/chessPiece/UnlimitedMoveChessPiece.java b/src/main/java/domain/chessPiece/UnlimitedMoveChessPiece.java new file mode 100644 index 000000000..9d3d4525c --- /dev/null +++ b/src/main/java/domain/chessPiece/UnlimitedMoveChessPiece.java @@ -0,0 +1,40 @@ +package domain.chessPiece; + +import domain.direction.Direction; +import domain.path.Path; +import domain.position.ChessPosition; +import domain.type.ChessTeam; + +import java.util.ArrayList; +import java.util.List; + +public abstract class UnlimitedMoveChessPiece extends JanggiChessPiece { + private final List directions; + + protected UnlimitedMoveChessPiece(ChessTeam team, List directions) { + super(team); + this.directions = directions; + } + + @Override + public final List getCoordinatePaths(ChessPosition startPosition) { + final List paths = new ArrayList<>(); + for (Direction direction : directions) { + List boundaryPositions = getBoundaryPositions(startPosition, direction); + if (!boundaryPositions.isEmpty()) { + paths.add(new Path(boundaryPositions)); + } + } + return paths; + } + + private List getBoundaryPositions(ChessPosition startPosition, Direction direction) { + final List chessPositions = new ArrayList<>(); + ChessPosition currentPosition = startPosition; + while (currentPosition.canMove(direction)) { + currentPosition = currentPosition.move(direction); + chessPositions.add(currentPosition); + } + return chessPositions; + } +} diff --git a/src/main/java/domain/direction/Direction.java b/src/main/java/domain/direction/Direction.java new file mode 100644 index 000000000..9f5d97646 --- /dev/null +++ b/src/main/java/domain/direction/Direction.java @@ -0,0 +1,21 @@ +package domain.direction; + +public enum Direction { + UP(-1, 0), + DOWN(1, 0), + LEFT(0, -1), + RIGHT(0, 1), + LEFT_UP(-1, -1), + RIGHT_UP(-1, 1), + LEFT_DOWN(1, -1), + RIGHT_DOWN(1, 1), + ; + + public final int dr; + public final int dc; + + Direction(int dr, int dc) { + this.dr = dr; + this.dc = dc; + } +} diff --git a/src/main/java/domain/direction/Directions.java b/src/main/java/domain/direction/Directions.java new file mode 100644 index 000000000..6461ad15a --- /dev/null +++ b/src/main/java/domain/direction/Directions.java @@ -0,0 +1,42 @@ +package domain.direction; + +import domain.path.Path; +import domain.position.ChessPosition; + +import java.util.ArrayList; +import java.util.List; + +public class Directions { + private final List directions; + + public Directions(List directions) { + this.directions = directions; + } + + public boolean canApplyFrom(final ChessPosition startPosition) { + ChessPosition currentPosition = startPosition; + for (Direction direction : directions) { + if (!currentPosition.canMove(direction)) { + return false; + } + currentPosition = currentPosition.move(direction); + } + return true; + } + + public Path getPathFrom(ChessPosition currentPosition) { + List positions = new ArrayList<>(); + for (Direction direction : directions) { + validatePosition(currentPosition, direction); + currentPosition = currentPosition.move(direction); + positions.add(currentPosition); + } + return new Path(positions); + } + + private void validatePosition(ChessPosition currentPosition, Direction direction) { + if (!currentPosition.canMove(direction)) { + throw new IllegalArgumentException("이동할 수 없는 위치입니다."); + } + } +} diff --git a/src/main/java/domain/hurdlePolicy/CannonHurdlePolicy.java b/src/main/java/domain/hurdlePolicy/CannonHurdlePolicy.java new file mode 100644 index 000000000..8ae436788 --- /dev/null +++ b/src/main/java/domain/hurdlePolicy/CannonHurdlePolicy.java @@ -0,0 +1,74 @@ +package domain.hurdlePolicy; + +import domain.chessPiece.ChessPiece; +import domain.path.Path; +import domain.position.ChessPiecePositions; +import domain.position.ChessPosition; +import domain.type.ChessPieceType; +import domain.type.ChessTeam; + +import java.util.ArrayList; +import java.util.List; + +public class CannonHurdlePolicy implements HurdlePolicy { + @Override + public List pickDestinations(ChessTeam team, List coordinates, ChessPiecePositions positions) { + final List destinations = new ArrayList<>(); + for (Path path : coordinates) { + List overHurdlePaths = getOverHurdlePaths(team, path, positions); + destinations.addAll(getOverHurdleDestinations(team, overHurdlePaths, positions)); + } + return destinations; + } + + private List getOverHurdlePaths(ChessTeam team, Path path, ChessPiecePositions positions) { + List pathPositions = path.getPath(); + for (int i = 0; i < pathPositions.size(); i++) { + ChessPosition currentPosition = pathPositions.get(i); + if (isHurdle(team, currentPosition, positions)) { + return pathPositions.subList(i+1, pathPositions.size()); + } + if (isWall(currentPosition, positions)) { + return List.of(); + } + } + return List.of(); + } + + private boolean isHurdle(ChessTeam team, ChessPosition targetPosition, ChessPiecePositions positions) { + if (!positions.existChessPieceByPosition(targetPosition)) { + return false; + } + ChessPiece other = positions.getChessPieceByPosition(targetPosition); + return !isWall(targetPosition, positions) && team != other.getTeam(); + } + + private boolean isWall(ChessPosition targetPosition, ChessPiecePositions positions) { + if (!positions.existChessPieceByPosition(targetPosition)) { + return false; + } + ChessPiece other = positions.getChessPieceByPosition(targetPosition); + return other.getChessPieceType() == ChessPieceType.CANNON; + } + + private List getOverHurdleDestinations( + ChessTeam team, + List overHurdlePaths, + ChessPiecePositions positions + ) { + List result = new ArrayList<>(); + for (ChessPosition currentPosition : overHurdlePaths) { + if (canMove(team, currentPosition, positions)) { + result.add(currentPosition); + } + if (positions.existChessPieceByPosition(currentPosition)) { + return result; + } + } + return result; + } + + private boolean canMove(ChessTeam team, ChessPosition targetPosition, ChessPiecePositions positions) { + return !positions.existChessPieceByPosition(targetPosition) || isHurdle(team, targetPosition, positions); + } +} diff --git a/src/main/java/domain/hurdlePolicy/HurdlePolicy.java b/src/main/java/domain/hurdlePolicy/HurdlePolicy.java new file mode 100644 index 000000000..3d55e66c8 --- /dev/null +++ b/src/main/java/domain/hurdlePolicy/HurdlePolicy.java @@ -0,0 +1,12 @@ +package domain.hurdlePolicy; + +import domain.path.Path; +import domain.position.ChessPiecePositions; +import domain.position.ChessPosition; +import domain.type.ChessTeam; + +import java.util.List; + +public interface HurdlePolicy { + List pickDestinations(ChessTeam team, List coordinates, ChessPiecePositions positions); +} diff --git a/src/main/java/domain/hurdlePolicy/StopAtHurdlePolicy.java b/src/main/java/domain/hurdlePolicy/StopAtHurdlePolicy.java new file mode 100644 index 000000000..c16a26e70 --- /dev/null +++ b/src/main/java/domain/hurdlePolicy/StopAtHurdlePolicy.java @@ -0,0 +1,46 @@ +package domain.hurdlePolicy; + +import domain.chessPiece.ChessPiece; +import domain.path.Path; +import domain.position.ChessPiecePositions; +import domain.position.ChessPosition; +import domain.type.ChessTeam; + +import java.util.ArrayList; +import java.util.List; + +public class StopAtHurdlePolicy implements HurdlePolicy { + @Override + public List pickDestinations(ChessTeam team, List coordinates, ChessPiecePositions positions) { + List destinations = new ArrayList<>(); + for (Path path : coordinates) { + destinations.addAll(getAvailablePosition(team, positions, path)); + } + return destinations; + } + + private List getAvailablePosition( + final ChessTeam team, + final ChessPiecePositions positions, + final Path path + ) { + final List chessPositions = new ArrayList<>(); + for (ChessPosition targetPosition : path.getPath()) { + if (canMove(team, targetPosition, positions)) { + chessPositions.add(targetPosition); + } + if (positions.existChessPieceByPosition(targetPosition)) { + return chessPositions; + } + } + return chessPositions; + } + + private boolean canMove(ChessTeam team, ChessPosition targetPosition, ChessPiecePositions positions) { + if (!positions.existChessPieceByPosition(targetPosition)) { + return true; + } + ChessPiece targetPiece = positions.getChessPieceByPosition(targetPosition); + return targetPiece.getTeam() != team; + } +} diff --git a/src/main/java/domain/hurdlePolicy/UnpassableHurdlePolicy.java b/src/main/java/domain/hurdlePolicy/UnpassableHurdlePolicy.java new file mode 100644 index 000000000..5b5ce7634 --- /dev/null +++ b/src/main/java/domain/hurdlePolicy/UnpassableHurdlePolicy.java @@ -0,0 +1,39 @@ +package domain.hurdlePolicy; + +import domain.chessPiece.ChessPiece; +import domain.path.Path; +import domain.position.ChessPiecePositions; +import domain.position.ChessPosition; +import domain.type.ChessTeam; + +import java.util.List; + +public class UnpassableHurdlePolicy implements HurdlePolicy { + @Override + public List pickDestinations(ChessTeam team, List coordinates, ChessPiecePositions positions) { + return coordinates.stream() + .filter(path -> !existChessPiece(positions, path)) + .map(Path::getDestination) + .filter(destination -> isAbleToCatch(team, destination, positions)) + .toList(); + } + + private boolean existChessPiece(final ChessPiecePositions positions, final Path path) { + List pathPositions = path.getPath(); + for (int i = 0; i < pathPositions.size() - 1; i++) { + ChessPosition currentPosition = pathPositions.get(i); + if (positions.existChessPieceByPosition(currentPosition)) { + return true; + } + } + return false; + } + + private boolean isAbleToCatch(ChessTeam team, ChessPosition targetPosition, ChessPiecePositions positions) { + if (!positions.existChessPieceByPosition(targetPosition)) { + return true; + } + ChessPiece targetPiece = positions.getChessPieceByPosition(targetPosition); + return targetPiece.getTeam() != team; + } +} diff --git a/src/main/java/domain/path/Path.java b/src/main/java/domain/path/Path.java new file mode 100644 index 000000000..73c086475 --- /dev/null +++ b/src/main/java/domain/path/Path.java @@ -0,0 +1,22 @@ +package domain.path; + +import domain.position.ChessPosition; + +import java.util.Collections; +import java.util.List; + +public class Path { + private final List path; + + public Path(List path) { + this.path = path; + } + + public ChessPosition getDestination() { + return path.getLast(); + } + + public List getPath() { + return Collections.unmodifiableList(path); + } +} diff --git a/src/main/java/domain/position/ChessPiecePositions.java b/src/main/java/domain/position/ChessPiecePositions.java new file mode 100644 index 000000000..c154b919a --- /dev/null +++ b/src/main/java/domain/position/ChessPiecePositions.java @@ -0,0 +1,58 @@ +package domain.position; + +import domain.chessPiece.ChessPiece; + +import java.util.Collections; +import java.util.Map; + +public class ChessPiecePositions { + + private final Map chessPieces; + + public ChessPiecePositions(ChessPiecePositionsGenerator generator) { + this.chessPieces = generator.generate(); + } + + public boolean existChessPieceByPosition(final ChessPosition position) { + return chessPieces.containsKey(position); + } + + public ChessPiece getChessPieceByPosition(final ChessPosition position) { + validateExistPiece(position); + return chessPieces.get(position); + } + + public void move(final ChessPosition from, final ChessPosition to) { + validateExistPiece(from); + validateEmptyPosition(to); + ChessPiece target = getChessPieceByPosition(from); + removeChessPieceByPosition(from); + putChessPiece(to, target); + } + + private void validateExistPiece(final ChessPosition position) { + if (!existChessPieceByPosition(position)) { + throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); + } + } + + private void validateEmptyPosition(final ChessPosition position) { + if (existChessPieceByPosition(position)) { + throw new IllegalArgumentException("해당 위치에 이미 다른 기물이 존재합니다."); + } + } + + public void removeChessPieceByPosition(final ChessPosition position) { + validateExistPiece(position); + chessPieces.remove(position); + } + + private void putChessPiece(final ChessPosition position, final ChessPiece chessPiece) { + validateEmptyPosition(position); + chessPieces.put(position, chessPiece); + } + + public Map getChessPieces() { + return Collections.unmodifiableMap(chessPieces); + } +} diff --git a/src/main/java/domain/position/ChessPiecePositionsGenerator.java b/src/main/java/domain/position/ChessPiecePositionsGenerator.java new file mode 100644 index 000000000..47b21fb00 --- /dev/null +++ b/src/main/java/domain/position/ChessPiecePositionsGenerator.java @@ -0,0 +1,9 @@ +package domain.position; + +import domain.chessPiece.ChessPiece; + +import java.util.Map; + +public interface ChessPiecePositionsGenerator { + Map generate(); +} diff --git a/src/main/java/domain/position/ChessPosition.java b/src/main/java/domain/position/ChessPosition.java new file mode 100644 index 000000000..08d10bdac --- /dev/null +++ b/src/main/java/domain/position/ChessPosition.java @@ -0,0 +1,33 @@ +package domain.position; + +import domain.direction.Direction; + +public record ChessPosition( + int row, + int column +) { + public static final int MIN_ROW = 0; + public static final int MAX_ROW = 9; + public static final int MIN_COL = 0; + public static final int MAX_COL = 8; + + public ChessPosition { + if (!isValid(row, column)) { + throw new IllegalArgumentException( + String.format("위치는 (%d, %d) ~ (%d, %d) 값만 가능합니다.", MIN_ROW, MIN_COL, MAX_ROW, MAX_COL) + ); + } + } + + private boolean isValid(final int row, final int col) { + return row >= MIN_ROW && row <= MAX_ROW && col >= MIN_COL && col <= MAX_COL; + } + + public boolean canMove(Direction direction) { + return isValid(row + direction.dr, column + direction.dc); + } + + public ChessPosition move(Direction direction) { + return new ChessPosition(row + direction.dr, column + direction.dc); + } +} diff --git a/src/main/java/domain/position/InitialChessPiecePositionsGenerator.java b/src/main/java/domain/position/InitialChessPiecePositionsGenerator.java new file mode 100644 index 000000000..4ab80ec5e --- /dev/null +++ b/src/main/java/domain/position/InitialChessPiecePositionsGenerator.java @@ -0,0 +1,19 @@ +package domain.position; + +import domain.chessPiece.*; + +import java.util.HashMap; +import java.util.Map; + +public class InitialChessPiecePositionsGenerator implements ChessPiecePositionsGenerator { + @Override + public Map generate() { + Map chessPieces = new HashMap<>(); + chessPieces.putAll(Cannon.initPieces()); + chessPieces.putAll(Chariot.initPieces()); + chessPieces.putAll(Elephant.initPieces()); + chessPieces.putAll(Horse.initPieces()); + chessPieces.putAll(Pawn.initPieces()); + return chessPieces; + } +} diff --git a/src/main/java/domain/score/Score.java b/src/main/java/domain/score/Score.java new file mode 100644 index 000000000..9262c6f92 --- /dev/null +++ b/src/main/java/domain/score/Score.java @@ -0,0 +1,17 @@ +package domain.score; + +public record Score(int value) { + public Score { + if (value < 0) { + throw new IllegalArgumentException("점수는 음수가 될 수 없습니다."); + } + } + + public static Score zero() { + return new Score(0); + } + + public Score add(Score other) { + return new Score(value + other.value); + } +} diff --git a/src/main/java/domain/type/ChessPieceType.java b/src/main/java/domain/type/ChessPieceType.java new file mode 100644 index 000000000..94520f3a5 --- /dev/null +++ b/src/main/java/domain/type/ChessPieceType.java @@ -0,0 +1,21 @@ +package domain.type; + +import domain.score.Score; + +public enum ChessPieceType { + + CHARIOT(new Score(13)), + HORSE(new Score(5)), + ELEPHANT(new Score(3)), + CANNON(new Score(7)), + GUARD(new Score(3)), + PAWN(new Score(2)), + KING(new Score(0)) + ; + + public final Score score; + + ChessPieceType(Score score) { + this.score = score; + } +} diff --git a/src/main/java/domain/type/ChessTeam.java b/src/main/java/domain/type/ChessTeam.java new file mode 100644 index 000000000..f212aa744 --- /dev/null +++ b/src/main/java/domain/type/ChessTeam.java @@ -0,0 +1,12 @@ +package domain.type; + +public enum ChessTeam { + + RED, + BLUE, + ; + + public static ChessTeam firstTurn() { + return BLUE; + } +} diff --git a/src/main/java/game/JanggiGame.java b/src/main/java/game/JanggiGame.java new file mode 100644 index 000000000..550eb61de --- /dev/null +++ b/src/main/java/game/JanggiGame.java @@ -0,0 +1,92 @@ +package game; + +import domain.Board; +import domain.chessPiece.ChessPiece; +import domain.position.ChessPiecePositions; +import domain.position.ChessPosition; +import domain.position.InitialChessPiecePositionsGenerator; +import domain.type.ChessTeam; +import view.InputView; +import view.OutputView; + +import java.util.List; +import java.util.Map; + +public class JanggiGame { + private final Board board; + private ChessTeam currentTeam; + private final InputView inputView; + private final OutputView outputView; + + public JanggiGame(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + ChessPiecePositions positions = new ChessPiecePositions(new InitialChessPiecePositionsGenerator()); + this.board = new Board(positions); + this.currentTeam = ChessTeam.firstTurn(); + } + + public void run() { + while (true) { + try { + showBoard(); + showCurrentTeam(); + ChessPosition startPosition = getStartPosition(); + showAvailableDestinations(startPosition); + ChessPosition destinationPosition = getDestinationPosition(startPosition); + board.move(currentTeam, startPosition, destinationPosition); + switchTeam(); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } + + private void showBoard() { + Map boardPositions = board.getPositions(); + outputView.printBoard(boardPositions); + } + + private void showCurrentTeam() { + outputView.printCurrentTeam(currentTeam); + } + + private void switchTeam() { + switch (currentTeam) { + case RED -> currentTeam = ChessTeam.BLUE; + case BLUE -> currentTeam = ChessTeam.RED; + } + } + + private ChessPosition getStartPosition() { + while (true) { + ChessPosition targetPosition = inputView.readStartPosition(); + if (!board.isExistPieceAt(targetPosition)) { + outputView.printNotExistPieceAt(targetPosition); + continue; + } + board.validateTeam(currentTeam, targetPosition); + List availableDestinations = board.getAvailableDestination(targetPosition); + if (!availableDestinations.isEmpty()) { + return targetPosition; + } + outputView.printNotExistPath(); + } + } + + private void showAvailableDestinations(ChessPosition startPosition) { + List availableDestinations = board.getAvailableDestination(startPosition); + outputView.printAvailableDestinations(availableDestinations); + } + + private ChessPosition getDestinationPosition(ChessPosition startPosition) { + while (true) { + ChessPosition destinationPosition = inputView.readDestinationPosition(); + List availableDestinations = board.getAvailableDestination(startPosition); + if (availableDestinations.contains(destinationPosition)) { + return destinationPosition; + } + outputView.printInvalidDestination(destinationPosition); + } + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 000000000..0cef9b2a5 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,51 @@ +package view; + +import domain.position.ChessPosition; + +import java.util.Scanner; + +public class InputView { + private final Scanner scanner; + + public InputView() { + this.scanner = new Scanner(System.in); + } + + public ChessPosition readStartPosition() { + while (true) { + try { + System.out.println("움직이려는 기물의 행 번호를 입력해주세요."); + final int row = readNumber(); + System.out.println("움직이려는 기물의 열 번호를 입력해주세요."); + final int col = readNumber(); + return new ChessPosition(row, col); + } catch (IllegalArgumentException e) { + System.out.printf("%s 다시 입력해주세요.\n", e.getMessage()); + } + } + } + + public ChessPosition readDestinationPosition() { + while (true) { + try { + System.out.println("목적지의 행 번호를 입력해주세요."); + final int row = readNumber(); + System.out.println("목적지의 열 번호를 입력해주세요."); + final int col = readNumber(); + return new ChessPosition(row, col); + } catch (IllegalArgumentException e) { + System.out.printf("%s 다시 입력해주세요.\n", e.getMessage()); + } + } + } + + private int readNumber() { + while (true) { + try { + return Integer.parseInt(scanner.nextLine()); + } catch (NumberFormatException e) { + System.out.println("숫자 형식이 올바르지 않습니다. 다시 입력해주세요."); + } + } + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 000000000..48307fcf5 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,139 @@ +package view; + +import domain.chessPiece.ChessPiece; +import domain.position.ChessPosition; +import domain.type.ChessPieceType; +import domain.type.ChessTeam; + +import java.util.List; +import java.util.Map; + +public class OutputView { + + private static final String RED = "\u001B[31m"; + private static final String BLUE = "\u001B[34m"; + public static final String YELLOW = "\u001B[33m"; + public static final String GREEN = "\u001B[32m"; + private static final String EXIT = "\u001B[0m"; + + public void printErrorMessage(String message) { + printNewLine(); + System.out.print(YELLOW); + System.out.printf("[ERROR] %s\n", message); + System.out.print(EXIT); + printNewLine(); + } + + public void printBoard(Map boardPositions) { + printGridValue(" "); + for (int col = ChessPosition.MIN_COL; col <= ChessPosition.MAX_COL; ++col) { + printGridValue(String.valueOf(col)); + } + printNewLine(); + for (int row = ChessPosition.MIN_ROW; row <= ChessPosition.MAX_ROW; ++row) { + printGridValue(String.valueOf(row)); + for (int col = ChessPosition.MIN_COL; col <= ChessPosition.MAX_COL; ++col) { + printChessPiece(new ChessPosition(row, col), boardPositions); + } + printNewLine(); + } + printNewLine(); + } + + public void printCurrentTeam(ChessTeam currentTeam) { + String color = getTeamColor(currentTeam); + System.out.print(color); + System.out.printf("%s의 차례입니다.", getTeamText(currentTeam)); + System.out.print(EXIT); + printNewLine(); + } + + public void printNotExistPieceAt(ChessPosition position) { + System.out.print(YELLOW); + System.out.printf("(%d, %d) 위치에는 기물이 존재하지 않습니다.\n", position.row(), position.column()); + System.out.print(EXIT); + printNewLine(); + } + + public void printNotExistPath() { + System.out.print(YELLOW); + System.out.println("해당 기물은 움직일 수 없습니다."); + System.out.print(EXIT); + printNewLine(); + } + + public void printAvailableDestinations(List destinations) { + printNewLine(); + System.out.print(GREEN); + System.out.println("해당 기물이 이동 가능한 위치는 다음과 같습니다."); + String joined = String.join(", ", destinations.stream() + .map(this::getFormattedPosition) + .toList()); + System.out.println(joined); + System.out.print(EXIT); + printNewLine(); + } + + public void printInvalidDestination(ChessPosition destinationPosition) { + printNewLine(); + System.out.print(YELLOW); + System.out.printf("%s 는 이동할 수 없는 위치입니다. 이동 가능한 위치 중에서 선택해주세요.\n", getFormattedPosition(destinationPosition)); + System.out.print(EXIT); + printNewLine(); + } + + private String getFormattedPosition(ChessPosition position) { + return String.format("(%d, %d)", position.row(), position.column()); + } + + private void printGridValue(String value) { + System.out.printf("%-3s", value); + } + + private void printChessPiece(ChessPosition currentPosition, Map boardPositions) { + if (!boardPositions.containsKey(currentPosition)) { + printGridValue("_"); + return; + } + ChessPiece piece = boardPositions.get(currentPosition); + printPiece(piece); + } + + private void printPiece(ChessPiece piece) { + String symbol = getPieceSymbol(piece.getChessPieceType()); + String color = getTeamColor(piece.getTeam()); + System.out.print(color); + printGridValue(symbol); + System.out.print(EXIT); + } + + private String getTeamColor(ChessTeam team) { + return switch (team) { + case BLUE -> BLUE; + case RED -> RED; + }; + } + + private String getPieceSymbol(ChessPieceType type) { + return switch (type) { + case KING -> "왕"; + case PAWN -> "졸"; + case GUARD -> "사"; + case HORSE -> "마"; + case CANNON -> "포"; + case CHARIOT -> "차"; + case ELEPHANT -> "상"; + }; + } + + private void printNewLine() { + System.out.println(); + } + + private String getTeamText(ChessTeam team) { + return switch (team) { + case RED -> "한나라"; + case BLUE -> "초나라"; + }; + } +} diff --git a/src/test/java/domain/chessPiece/CannonTest.java b/src/test/java/domain/chessPiece/CannonTest.java new file mode 100644 index 000000000..397f5d6cd --- /dev/null +++ b/src/test/java/domain/chessPiece/CannonTest.java @@ -0,0 +1,83 @@ +package domain.chessPiece; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; + +import domain.chessPiece.Cannon; +import domain.chessPiece.ChessPiece; +import domain.chessPiece.Pawn; +import domain.position.ChessPiecePositions; +import domain.position.ChessPiecePositionsGenerator; +import domain.position.ChessPosition; +import domain.type.ChessTeam; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CannonTest { + private final ChessPosition chessPosition = new ChessPosition(7, 4); + private final Cannon cannon = new Cannon(ChessTeam.BLUE); + + private class NotExistCannonPositionsGenerator implements ChessPiecePositionsGenerator { + @Override + public Map generate() { + return Map.of( + chessPosition, cannon, + new ChessPosition(6, 4), new Pawn(ChessTeam.RED), + new ChessPosition(2, 4), new Pawn(ChessTeam.RED) + ); + } + } + + @Test + @DisplayName("상대방의 포가 존재하지 않을때 포의 이동 경로를 계산한다") + void test1() { + //given + final ChessPiecePositions piecePositions = new ChessPiecePositions(new NotExistCannonPositionsGenerator()); + final List expected = List.of( + new ChessPosition(2, 4), + new ChessPosition(3, 4), + new ChessPosition(4, 4), + new ChessPosition(5, 4) + ); + + //when + final List destinations = cannon.getDestinations(chessPosition, piecePositions); + + //then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + private class ExistCannonPositionsGenerator implements ChessPiecePositionsGenerator { + @Override + public Map generate() { + return Map.of( + chessPosition, cannon, + new ChessPosition(6, 4), new Cannon(ChessTeam.RED), + new ChessPosition(2, 4), new Pawn(ChessTeam.RED), + new ChessPosition(7,5), new Pawn(ChessTeam.RED) + ); + } + } + + @Test + @DisplayName("상대방의 포가 존재할때 포의 이동 경로를 계산한다") + void test2() { + //given + final ChessPiecePositions piecePositions = new ChessPiecePositions(new ExistCannonPositionsGenerator()); + + final List expected = List.of( + new ChessPosition(7,6), + new ChessPosition(7,7), + new ChessPosition(7,8) + ); + + //when + final List destinations = cannon.getDestinations(chessPosition, piecePositions); + + //then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + +} diff --git a/src/test/java/domain/chessPiece/ChariotTest.java b/src/test/java/domain/chessPiece/ChariotTest.java new file mode 100644 index 000000000..401241bdc --- /dev/null +++ b/src/test/java/domain/chessPiece/ChariotTest.java @@ -0,0 +1,58 @@ +package domain.chessPiece; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; + +import domain.chessPiece.Chariot; +import domain.chessPiece.ChessPiece; +import domain.chessPiece.Pawn; +import domain.position.ChessPiecePositions; +import domain.position.ChessPiecePositionsGenerator; +import domain.position.ChessPosition; +import domain.type.ChessTeam; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ChariotTest { + private final ChessPosition chariotPosition = new ChessPosition(7, 4); + private final Chariot chariot = new Chariot(ChessTeam.BLUE); + + private class FakeChessPositionsGenerator implements ChessPiecePositionsGenerator { + @Override + public Map generate() { + return Map.of( + new ChessPosition(2, 4), new Pawn(ChessTeam.RED), + new ChessPosition(7, 3), new Pawn(ChessTeam.RED), + new ChessPosition(7, 5), new Pawn(ChessTeam.RED), + new ChessPosition(7, 8), new Pawn(ChessTeam.RED) + ); + } + } + + @Test + @DisplayName("차의 이동 경로를 계산한다") + void test1() { + //given + final ChessPiecePositions piecePositions = new ChessPiecePositions(new FakeChessPositionsGenerator()); + final List expected = List.of( + new ChessPosition(2, 4), + new ChessPosition(3, 4), + new ChessPosition(4, 4), + new ChessPosition(5, 4), + new ChessPosition(6,4), + new ChessPosition(7, 3), + new ChessPosition(7,5), + new ChessPosition(8,4), + new ChessPosition(9,4) + ); + + //when + + final List destinations = chariot.getDestinations(chariotPosition, piecePositions); + + //then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/src/test/java/domain/chessPiece/ElephantTest.java b/src/test/java/domain/chessPiece/ElephantTest.java new file mode 100644 index 000000000..378abd859 --- /dev/null +++ b/src/test/java/domain/chessPiece/ElephantTest.java @@ -0,0 +1,79 @@ +package domain.chessPiece; + +import domain.chessPiece.ChessPiece; +import domain.chessPiece.Elephant; +import domain.chessPiece.Pawn; +import domain.position.ChessPiecePositions; +import domain.position.ChessPiecePositionsGenerator; +import domain.position.ChessPosition; +import domain.position.EmptyChessPiecePositionsGenerator; +import domain.type.ChessTeam; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class ElephantTest { + private final ChessPosition elephantPosition = new ChessPosition(4, 4); + private final Elephant elephant = new Elephant(ChessTeam.BLUE); + + @DisplayName("다른 기물이 없는 경우, 모든 목적지를 반환할 수 있다.") + @Test + void notExistOtherPieces() { + // given + ChessPiecePositions emptyPositions = new ChessPiecePositions(new EmptyChessPiecePositionsGenerator()); + + final List expected = List.of( + new ChessPosition(7, 6), + new ChessPosition(7, 2), + new ChessPosition(1, 6), + new ChessPosition(1, 2), + new ChessPosition(6, 7), + new ChessPosition(2, 7), + new ChessPosition(6, 1), + new ChessPosition(2, 1) + ); + + //when + final List destinations = elephant.getDestinations(elephantPosition, emptyPositions); + + //then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + private static class FakeChessPositionsGenerator implements ChessPiecePositionsGenerator { + @Override + public Map generate() { + return Map.of( + new ChessPosition(2, 5), new Pawn(ChessTeam.RED), + new ChessPosition(1, 2), new Pawn(ChessTeam.BLUE), + new ChessPosition(7, 2), new Pawn(ChessTeam.RED) + ); + } + } + + @DisplayName("다른 기물이 있는 경우, 모든 목적지를 반환할 수 있다.") + @Test + void existOtherPieces() { + // given + final ChessPiecePositions piecePositions = new ChessPiecePositions(new FakeChessPositionsGenerator()); + + final List expected = List.of( + new ChessPosition(7, 6), + new ChessPosition(7, 2), + new ChessPosition(6, 7), + new ChessPosition(2, 7), + new ChessPosition(6, 1), + new ChessPosition(2, 1) + ); + + //when + final List destinations = elephant.getDestinations(elephantPosition, piecePositions); + + //then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/src/test/java/domain/chessPiece/HorseTest.java b/src/test/java/domain/chessPiece/HorseTest.java new file mode 100644 index 000000000..c1b7bb98e --- /dev/null +++ b/src/test/java/domain/chessPiece/HorseTest.java @@ -0,0 +1,68 @@ +package domain.chessPiece; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; + +import domain.chessPiece.ChessPiece; +import domain.chessPiece.Horse; +import domain.chessPiece.Pawn; +import domain.position.ChessPiecePositions; +import domain.position.ChessPiecePositionsGenerator; +import domain.position.ChessPosition; +import domain.position.EmptyChessPiecePositionsGenerator; +import domain.type.ChessTeam; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HorseTest { + private final ChessPosition horsePosition = new ChessPosition(4, 4); + private final Horse horse = new Horse(ChessTeam.BLUE); + + @Test + @DisplayName("마의 이동 경로를 반환한다") + void test1() { + //given + ChessPiecePositions emptyPositions = new ChessPiecePositions(new EmptyChessPiecePositionsGenerator()); + final List expected = List.of( + new ChessPosition(6, 5), + new ChessPosition(6, 3), + new ChessPosition(2, 3), + new ChessPosition(2, 5), + new ChessPosition(5, 6), + new ChessPosition(3, 6), + new ChessPosition(3, 2), + new ChessPosition(5, 2)); + + //when + final List destinations = horse.getDestinations(horsePosition, emptyPositions); + + //then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + private static class ExistHurdlePositionsGenerator implements ChessPiecePositionsGenerator { + @Override + public Map generate() { + return Map.of( + new ChessPosition(3, 4), new Pawn(ChessTeam.RED), + new ChessPosition(5, 2), new Pawn(ChessTeam.BLUE) + ); + } + } + + @Test + @DisplayName("장애물이 있을때 마의 이동 경로를 계산한다") + void test2() { + //given + final ChessPiecePositions piecePositions = new ChessPiecePositions(new ExistHurdlePositionsGenerator()); + final List expected = List.of(new ChessPosition(3, 2), new ChessPosition(3, 6), new ChessPosition(6, 3), new ChessPosition(6, 5), new ChessPosition(5, 6)); + + //when + final List destinations = horse.getDestinations(horsePosition, piecePositions); + + //then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/src/test/java/domain/chessPiece/PathTest.java b/src/test/java/domain/chessPiece/PathTest.java new file mode 100644 index 000000000..75d161441 --- /dev/null +++ b/src/test/java/domain/chessPiece/PathTest.java @@ -0,0 +1,30 @@ +package domain.chessPiece; + +import domain.path.Path; +import domain.position.ChessPosition; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PathTest { + @DisplayName("목적지의 위치를 알려줄 수 있다.") + @Test + void getDestination() { + // given + ChessPosition destination = new ChessPosition(0, 1); + Path path = new Path(List.of( + new ChessPosition(0, 0), + new ChessPosition(1, 0), + destination + )); + + // when + ChessPosition chessPosition = path.getDestination(); + + // then + assertThat(chessPosition).isEqualTo(destination); + } +} diff --git a/src/test/java/domain/chessPiece/PawnTest.java b/src/test/java/domain/chessPiece/PawnTest.java new file mode 100644 index 000000000..a75898299 --- /dev/null +++ b/src/test/java/domain/chessPiece/PawnTest.java @@ -0,0 +1,35 @@ +package domain.chessPiece; + +import domain.position.ChessPiecePositions; +import domain.position.ChessPosition; +import domain.position.EmptyChessPiecePositionsGenerator; +import domain.type.ChessTeam; +import domain.chessPiece.Pawn; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PawnTest { + private final ChessPiecePositions emptyPositions = new ChessPiecePositions(new EmptyChessPiecePositionsGenerator()); + + @Test + @DisplayName("폰이 이동 가능한 경로를 반환한다") + void test1() { + //given + final ChessPosition chessPosition = new ChessPosition(0, 0); + final List expectDestinations = List.of( + new ChessPosition(1,0), + new ChessPosition(0,1) + ); + + //when + final Pawn pawn = new Pawn(ChessTeam.RED); + final List destinations = pawn.getDestinations(chessPosition, emptyPositions); + + //then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expectDestinations); + } +} diff --git a/src/test/java/domain/position/ChessPositionTest.java b/src/test/java/domain/position/ChessPositionTest.java new file mode 100644 index 000000000..23376e619 --- /dev/null +++ b/src/test/java/domain/position/ChessPositionTest.java @@ -0,0 +1,22 @@ +package domain.position; + +import domain.position.ChessPosition; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ChessPositionTest { + @DisplayName("(0, 0) ~ (9, 8) 이외의 위치는 생성할 수 없다.") + @ParameterizedTest + @CsvSource({"-1, 0", "0, -1", "9, 9", "10, 8"}) + void outOfBoundPosition(final int row, final int col) { + // given + + // when & then + assertThatThrownBy(() -> { + new ChessPosition(row, col); + }).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/domain/position/EmptyChessPiecePositionsGenerator.java b/src/test/java/domain/position/EmptyChessPiecePositionsGenerator.java new file mode 100644 index 000000000..033c9e189 --- /dev/null +++ b/src/test/java/domain/position/EmptyChessPiecePositionsGenerator.java @@ -0,0 +1,12 @@ +package domain.position; + +import domain.chessPiece.ChessPiece; + +import java.util.Map; + +public class EmptyChessPiecePositionsGenerator implements ChessPiecePositionsGenerator { + @Override + public Map generate() { + return Map.of(); + } +} diff --git a/src/test/java/domain/position/InitialChessPiecePositionsGeneratorTest.java b/src/test/java/domain/position/InitialChessPiecePositionsGeneratorTest.java new file mode 100644 index 000000000..a377e4cfe --- /dev/null +++ b/src/test/java/domain/position/InitialChessPiecePositionsGeneratorTest.java @@ -0,0 +1,37 @@ +package domain.position; + +import domain.chessPiece.ChessPiece; +import domain.type.ChessPieceType; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class InitialChessPiecePositionsGeneratorTest { + @ParameterizedTest(name = "기물들의 위치를 초기화 한다 - {0}") + @MethodSource + void initializePositions(String name, final ChessPosition position, final ChessPieceType chessPieceType) { + // given + InitialChessPiecePositionsGenerator generator = new InitialChessPiecePositionsGenerator(); + + // when + Map generated = generator.generate(); + + // then + assertThat(generated.get(position).getChessPieceType()).isEqualTo(chessPieceType); + } + + private static Stream initializePositions() { + return Stream.of( + Arguments.of("RED Chariot", new ChessPosition(0,0), ChessPieceType.CHARIOT), + Arguments.of("RED Horse", new ChessPosition(0,1), ChessPieceType.HORSE), + Arguments.of("RED Elephant", new ChessPosition(0,2), ChessPieceType.ELEPHANT), + Arguments.of("RED Cannon", new ChessPosition(2,1), ChessPieceType.CANNON), + Arguments.of("RED Pawn", new ChessPosition(3,0), ChessPieceType.PAWN) + ); + } +}