diff --git a/README.md b/README.md
index 9775dda0ae..491ba28db4 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,157 @@
# java-janggi
-장기 미션 저장소
+# 기능 명세서
+
+---
+
+## 기능
+
+### 게임 초기화
+
+- [x] 한나라가 위, 초나라를 아래로 위치시켜 보드를 초기화한다.
+
+### 게임 진행
+
+- [x] 초나라 선, 한나라 후로 게임이 반복적으로 진행된다.
+- [x] 입력을 받은 좌표와 기물 규칙에 따라 기물을 이동시킨다.
+- [ ] 궁을 잡는 수를 두면 장군이다.
+- [ ] 장군을 피하는 수를 두면 멍군이다.
+- [ ] 빅장일 때 게임을 종료하기 싫으면 빅장을 깨뜨리는 수를 둘 수 있다.
+
+### 기물 이동 규칙
+
+| **기물** | **기본 이동 방식** | **궁성(3x3) 내 특수 규칙** | **특징 (구현 시 고려사항)** |
+| --- |----------------------------------| --- |----------------------------------------------------------|
+| **궁(將/楚)** | 상하좌우 1칸 | 대각선 선을 따라 1칸 이동 가능 | 궁성 밖으로 절대 나갈 수 없음 |
+| **사(士)** | 상하좌우 1칸 | 대각선 선을 따라 1칸 이동 가능 | 궁성 밖으로 절대 나갈 수 없음 |
+| **차(車)** | 상하좌우 직선으로 거리 제한 없이 이동 | 대각선 선을 따라 이동 가능 | 경로에 다른 기물이 있으면 못 지나감 |
+| **포(包)** | 상하좌우로 다른 기물 하나를 뛰어넘어 거리 제한 없이 이동 | 대각선 선을 따라 기물 하나를 넘어서 이동 | 포끼리는 서로 넘을 수 없음
포끼리는 잡을 수 없음
넘을 기물이 반드시 1개 있어야 함 |
+| **마(馬)** | 상하좌우 직선 1칸 + 이동한 방향으로 대각선 1칸 | 해당 없음 | 멱(길목)이 막혀 있으면 못 감 |
+| **상(象)** | 상하좌우 직선 1칸 + 이동한 방향으로 대각선 2칸 | 해당 없음 | 멱(길목)이 막혀 있으면 못 감 |
+| **졸/병(卒/兵)** | 앞 또는 옆으로 1칸 | 적군 궁성 내에서 대각선 앞으로 1칸 이동 가능 | 진영에 따라 '앞'의 방향이 다름
뒤로는 못감 |
+
+### 게임 종료
+
+- [ ] 외통수이면 승리한다.
+- [ ] 빅장이면 점수를 계산해 승패를 결정한다.
+- [ ] 한나라에 1.5점을 더해서 점수를 계산한다.
+
+## 입력
+
+- [x] 초나라가 기물을 선택하고 행마를 입력한다.
+
+ ```java
+ 초나라 턴입니다. 이동할 기물의 좌표와 도착할 좌표를 입력하세요. (예 : (1, 2), (3, 3))
+ ```
+
+- [x] 한나라가 기물을 선택하고 행마를 입력한다.
+
+ ```java
+ 한나라 턴입니다. 이동할 기물의 좌표와 도착할 좌표를 입력하세요. (예 : (1, 2), (3, 3))
+ ```
+
+
+## 출력
+
+- [x] 게임 시작 멘트를 출력한다.
+
+ ```java
+ 게임을 시작하겠습니다.
+ ```
+
+- [x] 보드판을 출력한다.
+
+ ```java
+ 0 1 2 3 4 5 6 7 8
+
+ 0 [車]-------[馬]-------[象]-------[士]-------[ ]-------[士]-------[象]-------[馬]-------[車]
+ ㅣ ㅣ ㅣ ㅣ \ ㅣ / ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ \ ㅣ / ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ \ ㅣ / ㅣ ㅣ ㅣ ㅣ
+ 1 [ ]-------[ ]-------[ ]-------[ ]-------[漢]-------[ ]-------[ ]-------[ ]-------[ ]
+ ㅣ ㅣ ㅣ ㅣ / ㅣ \ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ / ㅣ \ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ / ㅣ \ ㅣ ㅣ ㅣ ㅣ
+ 2 [ ]-------[包]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[包]-------[ ]
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ 3 [兵]-------[ ]-------[兵]-------[ ]-------[兵]-------[ ]-------[兵]-------[ ]-------[兵]
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ 4 [ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ 5 [ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ 6 [卒]-------[ ]-------[卒]-------[ ]-------[卒]-------[ ]-------[卒]-------[ ]-------[卒]
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ ㅣ
+ 7 [ ]-------[包]-------[ ]-------[ ]-------[ ]-------[ ]-------[ ]-------[包]-------[ ]
+ ㅣ ㅣ ㅣ ㅣ \ ㅣ / ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ \ ㅣ / ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ \ ㅣ / ㅣ ㅣ ㅣ ㅣ
+ 8 [ ]-------[ ]-------[ ]-------[ ]-------[楚]-------[ ]-------[ ]-------[ ]-------[ ]
+ ㅣ ㅣ ㅣ ㅣ / ㅣ \ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ / ㅣ \ ㅣ ㅣ ㅣ ㅣ
+ ㅣ ㅣ ㅣ ㅣ / ㅣ \ ㅣ ㅣ ㅣ ㅣ
+ 9 [車]-------[馬]-------[象]-------[士]-------[ ]-------[士]-------[象]-------[馬]-------[車]
+ ```
+
+- [ ] 장군이면 장군을 출력한다.
+
+ ```java
+ 장군입니다! 기물을 움직여 궁을 살리세요.
+ ```
+
+- [ ] 외통이면 게임 종료를 출력한다.
+
+ ```java
+ 외통입니다. 초나라/한나라의 승리입니다.
+ ```
+
+- [ ] 빅장이면 상대방이 선택할 수 있게 조건을 출력한다.
+
+ ```java
+ 빅장입니다. 게임을 종료하기 싫으면 왕 앞에 기물을 놔두세요!
+ ```
+
+- [ ] 빅장으로 게임이 종료되면 점수를 출력한다.
+
+ ```java
+ 빅장입니다.
+
+ 초나라 점수 :
+ 한나라 점수 :
+
+ 결과 : 초나라/한나라의 승리입니다.
+ ```
+
+
+## 유효성
+
+### Position
+
+- [x] 장기판 범위를 벗어난 경우
+ ```java
+ [ERROR] 장기판 범위를 벗어난 좌표입니다.
+ ```
+
+### Piece
+
+- [x] 기물이 이동할 수 없을 경우
+ ```
+ [ERROR] 유효하지 않는 행마입니다.
+ ```
+
+### Board
+
+- [x] 출발지에 이동할 기물이 없는 경우
+ ```
+ [ERROR] 출발지에 이동할 기물이 없습니다.
+ ```
diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java
new file mode 100644
index 0000000000..c5d0e612b3
--- /dev/null
+++ b/src/main/java/janggi/Application.java
@@ -0,0 +1,16 @@
+package janggi;
+
+import janggi.controller.Controller;
+import janggi.view.InputView;
+import janggi.view.OutputView;
+
+public class Application {
+ public static void main(String[] args) {
+
+ InputView inputView = new InputView();
+ OutputView outputView = new OutputView();
+ Controller controller = new Controller(inputView, outputView);
+
+ controller.run();
+ }
+}
diff --git a/src/main/java/janggi/controller/Controller.java b/src/main/java/janggi/controller/Controller.java
new file mode 100644
index 0000000000..b0f88f98ec
--- /dev/null
+++ b/src/main/java/janggi/controller/Controller.java
@@ -0,0 +1,53 @@
+package janggi.controller;
+
+import janggi.domain.Board;
+import janggi.domain.BoardFactory;
+import janggi.domain.Column;
+import janggi.domain.Position;
+import janggi.domain.Row;
+import janggi.domain.Team;
+import janggi.dto.BoardDto;
+import janggi.exception.BusinessException;
+import janggi.view.InputView;
+import janggi.view.OutputView;
+
+import java.util.List;
+
+public class Controller {
+ private final InputView inputView;
+ private final OutputView outputView;
+
+ public Controller(InputView inputView, OutputView outputView) {
+ this.inputView = inputView;
+ this.outputView = outputView;
+ }
+
+ public void run() {
+ outputView.printStartMessage();
+ Board board = new Board(BoardFactory.generate());
+ outputView.printBoard(BoardDto.from(board));
+
+ outputView.printStartMessage();
+
+ Team currentTeam = Team.CHO;
+
+ for (int i = 0; i < 10; i++) {
+ while (true) {
+ try {
+ List positions = inputView.playTurn(currentTeam.getTeam());
+ Position from = Position.of(Row.of(positions.get(0)), Column.of(positions.get(1)));
+ Position to = Position.of(Row.of(positions.get(2)), Column.of(positions.get(3)));
+
+ board.move(from, to);
+ outputView.printBoard(BoardDto.from(board));
+
+ currentTeam = currentTeam.switchTeam();
+ break;
+
+ } catch (BusinessException e) {
+ outputView.printErrorMessage(e.getMessage());
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/janggi/domain/Board.java b/src/main/java/janggi/domain/Board.java
new file mode 100644
index 0000000000..365373604f
--- /dev/null
+++ b/src/main/java/janggi/domain/Board.java
@@ -0,0 +1,40 @@
+package janggi.domain;
+
+import janggi.exception.EmptyPositionException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Board implements BoardState{
+ private final Map board;
+
+ public Board(Map initialPieces) {
+ this.board = new HashMap<>(initialPieces);
+ }
+
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return board.containsKey(position);
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return board.get(position);
+ }
+
+ public void move(Position from, Position to) {
+ Piece movingPiece = board.get(from);
+
+ if (movingPiece == null) {
+ throw new EmptyPositionException();
+ }
+
+ movingPiece.verifyMove(from, to, this);
+ board.remove(from);
+ board.put(to, movingPiece);
+ }
+
+ public Map getBoard() {
+ return board;
+ }
+}
diff --git a/src/main/java/janggi/domain/BoardFactory.java b/src/main/java/janggi/domain/BoardFactory.java
new file mode 100644
index 0000000000..df2dad5f79
--- /dev/null
+++ b/src/main/java/janggi/domain/BoardFactory.java
@@ -0,0 +1,106 @@
+package janggi.domain;
+
+import janggi.domain.movestorage.ChaMoveStorage;
+import janggi.domain.movestorage.GungAndSaMoveStorage;
+import janggi.domain.movestorage.JolMoveStorage;
+import janggi.domain.movestorage.MaMoveStorage;
+import janggi.domain.movestorage.PoMoveStorage;
+import janggi.domain.movestorage.SangMoveStorage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class BoardFactory {
+ public static Map generate() {
+ Map board = new HashMap<>();
+
+ SetHanPieces(board);
+ SetChoPieces(board);
+
+ return board;
+ }
+
+ private static void SetHanPieces(Map board) {
+ // 차
+ board.put(Position.of(Row.of(0), Column.of(0)),
+ new Piece(new ChaMoveStorage(), Team.HAN, 13, "車"));
+ board.put(Position.of(Row.of(8), Column.of(0)),
+ new Piece(new ChaMoveStorage(), Team.HAN, 13, "車"));
+ // 상
+ board.put(Position.of(Row.of(1), Column.of(0)),
+ new Piece(new SangMoveStorage(), Team.HAN, 3, "象"));
+ board.put(Position.of(Row.of(6), Column.of(0)),
+ new Piece(new SangMoveStorage(), Team.HAN, 3, "象"));
+ // 마
+ board.put(Position.of(Row.of(2), Column.of(0)),
+ new Piece(new MaMoveStorage(), Team.HAN, 5, "馬"));
+ board.put(Position.of(Row.of(7), Column.of(0)),
+ new Piece(new MaMoveStorage(), Team.HAN, 5, "馬"));
+ // 사
+ board.put(Position.of(Row.of(3), Column.of(0)),
+ new Piece(new GungAndSaMoveStorage(), Team.HAN, 3, "士"));
+ board.put(Position.of(Row.of(5), Column.of(0)),
+ new Piece(new GungAndSaMoveStorage(), Team.HAN, 3, "士"));
+ // 궁
+ board.put(Position.of(Row.of(4), Column.of(1)),
+ new Piece(new GungAndSaMoveStorage(), Team.HAN, 0, "漢"));
+ // 포
+ board.put(Position.of(Row.of(1), Column.of(2)),
+ new Piece(new PoMoveStorage(), Team.HAN, 7, "包"));
+ board.put(Position.of(Row.of(7), Column.of(2)),
+ new Piece(new PoMoveStorage(), Team.HAN, 7, "包"));
+ // 졸
+ board.put(Position.of(Row.of(0), Column.of(3)),
+ new Piece(new JolMoveStorage(), Team.HAN, 2, "兵"));
+ board.put(Position.of(Row.of(2), Column.of(3)),
+ new Piece(new JolMoveStorage(), Team.HAN, 2, "兵"));
+ board.put(Position.of(Row.of(4), Column.of(3)),
+ new Piece(new JolMoveStorage(), Team.HAN, 2, "兵"));
+ board.put(Position.of(Row.of(6), Column.of(3)),
+ new Piece(new JolMoveStorage(), Team.HAN, 2, "兵"));
+ board.put(Position.of(Row.of(8), Column.of(3)),
+ new Piece(new JolMoveStorage(), Team.HAN, 2, "兵"));
+ }
+
+ private static void SetChoPieces(Map board) {
+ // 차
+ board.put(Position.of(Row.of(0), Column.of(9)),
+ new Piece(new ChaMoveStorage(), Team.CHO, 13, "車"));
+ board.put(Position.of(Row.of(8), Column.of(9)),
+ new Piece(new ChaMoveStorage(), Team.CHO, 13, "車"));
+ // 상
+ board.put(Position.of(Row.of(1), Column.of(9)),
+ new Piece(new SangMoveStorage(), Team.CHO, 3, "象"));
+ board.put(Position.of(Row.of(6), Column.of(9)),
+ new Piece(new SangMoveStorage(), Team.CHO, 3, "象"));
+ // 마
+ board.put(Position.of(Row.of(2), Column.of(9)),
+ new Piece(new MaMoveStorage(), Team.CHO, 5, "馬"));
+ board.put(Position.of(Row.of(7), Column.of(9)),
+ new Piece(new MaMoveStorage(), Team.CHO, 5, "馬"));
+ // 사
+ board.put(Position.of(Row.of(3), Column.of(9)),
+ new Piece(new GungAndSaMoveStorage(), Team.CHO, 3, "士"));
+ board.put(Position.of(Row.of(5), Column.of(9)),
+ new Piece(new GungAndSaMoveStorage(), Team.CHO, 3, "士"));
+ // 궁
+ board.put(Position.of(Row.of(4), Column.of(8)),
+ new Piece(new GungAndSaMoveStorage(), Team.CHO, 0, "楚"));
+ // 포
+ board.put(Position.of(Row.of(1), Column.of(7)),
+ new Piece(new PoMoveStorage(), Team.CHO, 7, "包"));
+ board.put(Position.of(Row.of(7), Column.of(7)),
+ new Piece(new PoMoveStorage(), Team.CHO, 7, "包"));
+ // 졸
+ board.put(Position.of(Row.of(0), Column.of(6)),
+ new Piece(new JolMoveStorage(), Team.CHO, 2, "卒"));
+ board.put(Position.of(Row.of(2), Column.of(6)),
+ new Piece(new JolMoveStorage(), Team.CHO, 2, "卒"));
+ board.put(Position.of(Row.of(4), Column.of(6)),
+ new Piece(new JolMoveStorage(), Team.CHO, 2, "卒"));
+ board.put(Position.of(Row.of(6), Column.of(6)),
+ new Piece(new JolMoveStorage(), Team.CHO, 2, "卒"));
+ board.put(Position.of(Row.of(8), Column.of(6)),
+ new Piece(new JolMoveStorage(), Team.CHO, 2, "卒"));
+ }
+}
diff --git a/src/main/java/janggi/domain/BoardState.java b/src/main/java/janggi/domain/BoardState.java
new file mode 100644
index 0000000000..ca1eb7fe5f
--- /dev/null
+++ b/src/main/java/janggi/domain/BoardState.java
@@ -0,0 +1,6 @@
+package janggi.domain;
+
+public interface BoardState { //인터페이스명 (readable)
+ boolean hasPieceAt(Position position);
+ Piece getPieceAt(Position position);
+}
diff --git a/src/main/java/janggi/domain/Column.java b/src/main/java/janggi/domain/Column.java
new file mode 100644
index 0000000000..57e58543e4
--- /dev/null
+++ b/src/main/java/janggi/domain/Column.java
@@ -0,0 +1,54 @@
+package janggi.domain;
+
+import janggi.exception.ColumnOutOfRangeException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class Column {
+ private static final int MIN = 0;
+ private static final int MAX = 9;
+ private static final Map CACHE = new HashMap<>(); // 직접 실험해서 성능 차이 확인
+
+ static {
+ for (int i = MIN; i <= MAX; i++) {
+ CACHE.put(i, new Column(i));
+ }
+ }
+
+ private final int value;
+
+ private Column(int value) {
+ this.value = value;
+ }
+ public static Column of(int value) {
+ if (!CACHE.containsKey(value)) {
+ throw new ColumnOutOfRangeException();
+ }
+ return CACHE.get(value);
+ }
+
+ public static Collection values() {
+ return CACHE.values();
+ }
+
+ public int getColumn() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Column column = (Column) o;
+ return this.value == column.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+}
diff --git a/src/main/java/janggi/domain/Piece.java b/src/main/java/janggi/domain/Piece.java
new file mode 100644
index 0000000000..65f1287da3
--- /dev/null
+++ b/src/main/java/janggi/domain/Piece.java
@@ -0,0 +1,56 @@
+package janggi.domain;
+
+import janggi.domain.movestorage.MoveStorage;
+import janggi.exception.InvalidMoveException;
+
+import java.util.Objects;
+
+public class Piece {
+ private final MoveStorage moveStorage;
+ private final Team team;
+ private final int score;
+ private final String name;
+
+ public Piece(MoveStorage moveStorage, Team team, int score, String name) {
+ this.moveStorage = moveStorage;
+ this.team = team;
+ this.score = score;
+ this.name = name;
+ }
+
+ public void verifyMove(Position from, Position to, BoardState boardState) {
+ if (!moveStorage.canMove(from, to, boardState)) {
+ throw new InvalidMoveException();
+ }
+
+ Piece pieceTo = boardState.getPieceAt(to);
+
+ if (pieceTo != null && isSameTeam(pieceTo)) {
+ throw new InvalidMoveException();
+ }
+ }
+
+ private boolean isSameTeam(Piece pieceTo) {
+ return team == pieceTo.getTeam();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Team getTeam() {
+ return team;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ Piece piece = (Piece) o;
+ return score == piece.score && Objects.equals(moveStorage, piece.moveStorage) && team == piece.team && Objects.equals(name, piece.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(moveStorage, team, score, name);
+ }
+}
diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java
new file mode 100644
index 0000000000..017f97613f
--- /dev/null
+++ b/src/main/java/janggi/domain/Position.java
@@ -0,0 +1,55 @@
+package janggi.domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class Position {
+ private static final Map CACHE = new HashMap<>();
+
+ static {
+ for (Row row : Row.values()) {
+ for (Column column : Column.values()) {
+ CACHE.put(toKey(row, column), new Position(row, column));
+ }
+ }
+ }
+
+ private final Row x;
+ private final Column y;
+
+ private Position(Row x, Column y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public static Position of(Row x, Column y) {
+ String key = toKey(x, y);
+ return CACHE.get(key);
+ }
+
+ private static String toKey(Row x, Column y) {
+ return x.getRow() + "," + y.getColumn();
+ }
+
+ public List getPosition() { // Position을 활용
+ List position = new ArrayList<>();
+ position.add(x.getRow()); // 캡슐화 깨짐
+ position.add(y.getColumn());
+ return position;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ Position position = (Position) o;
+ return Objects.equals(x, position.x) && Objects.equals(y, position.y);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(x, y);
+ }
+}
diff --git a/src/main/java/janggi/domain/Row.java b/src/main/java/janggi/domain/Row.java
new file mode 100644
index 0000000000..fc8c790360
--- /dev/null
+++ b/src/main/java/janggi/domain/Row.java
@@ -0,0 +1,54 @@
+package janggi.domain;
+
+import janggi.exception.RowOutOfRangeException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class Row {
+ private static final int MIN = 0;
+ private static final int MAX = 8;
+ private static final Map CACHE = new HashMap<>();
+
+ static {
+ for (int i = MIN; i <= MAX; i++) {
+ CACHE.put(i, new Row(i));
+ }
+ }
+
+ private final int value;
+
+ private Row(int value) {
+ this.value = value;
+ }
+ public static Row of(int value) {
+ if (!CACHE.containsKey(value)) {
+ throw new RowOutOfRangeException();
+ }
+ return CACHE.get(value);
+ }
+
+ public static Collection values() {
+ return CACHE.values();
+ }
+
+ public int getRow() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Row row = (Row) o;
+ return this.value == row.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+}
diff --git a/src/main/java/janggi/domain/Team.java b/src/main/java/janggi/domain/Team.java
new file mode 100644
index 0000000000..bb288031a2
--- /dev/null
+++ b/src/main/java/janggi/domain/Team.java
@@ -0,0 +1,23 @@
+package janggi.domain;
+
+public enum Team {
+ CHO("초"),
+ HAN("한");
+
+ private final String name;
+
+ Team(String name) {
+ this.name = name;
+ }
+
+ public Team switchTeam() {
+ if (this == Team.CHO) {
+ return Team.HAN;
+ }
+ return Team.CHO;
+ }
+
+ public String getTeam() {
+ return name;
+ }
+}
diff --git a/src/main/java/janggi/domain/movestorage/ChaMoveStorage.java b/src/main/java/janggi/domain/movestorage/ChaMoveStorage.java
new file mode 100644
index 0000000000..584565738f
--- /dev/null
+++ b/src/main/java/janggi/domain/movestorage/ChaMoveStorage.java
@@ -0,0 +1,51 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Position;
+import janggi.domain.Row;
+
+import java.util.List;
+
+public class ChaMoveStorage implements MoveStorage {
+
+ @Override
+ public boolean canMove(Position from, Position to, BoardState boardState) {
+ List fromPosition = from.getPosition();
+ List toPosition = to.getPosition();
+
+ int fromX = fromPosition.getFirst();
+ int fromY = fromPosition.getLast();
+ int toX = toPosition.getFirst();
+ int toY = toPosition.getLast();
+
+ if (fromX != toX && fromY != toY) {
+ return false;
+ }
+
+ if (fromX == toX) {
+ int start = Math.min(fromY, toY) + 1;
+ int end = Math.max(fromY, toY);
+
+ for (int i = start; i < end; i++) {
+ Position position = Position.of(Row.of(fromX), Column.of(i));
+ if (boardState.hasPieceAt(position)) {
+ return false;
+ }
+ }
+ }
+
+ if (fromY == toY) {
+ int start = Math.min(fromX, toX) + 1;
+ int end = Math.max(fromX, toX);
+
+ for (int i = start; i < end; i++) {
+ Position position = Position.of(Row.of(i), Column.of(fromY));
+ if (boardState.hasPieceAt(position)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/janggi/domain/movestorage/GungAndSaMoveStorage.java b/src/main/java/janggi/domain/movestorage/GungAndSaMoveStorage.java
new file mode 100644
index 0000000000..19bf9891a7
--- /dev/null
+++ b/src/main/java/janggi/domain/movestorage/GungAndSaMoveStorage.java
@@ -0,0 +1,52 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Position;
+import janggi.domain.Team;
+
+import java.util.List;
+
+public class GungAndSaMoveStorage implements MoveStorage{
+ @Override
+ public boolean canMove(Position from, Position to, BoardState boardState) {
+ List fromPosition = from.getPosition();
+ List toPosition = to.getPosition();
+
+ int fromX = fromPosition.getFirst();
+ int fromY = fromPosition.getLast();
+ int toX = toPosition.getFirst();
+ int toY = toPosition.getLast();
+
+ if (!(3 <= fromX && fromX <= 5) || !(3 <= toX && toX <= 5)) {
+ return false;
+ }
+
+ Team currentTeam = boardState.getPieceAt(from).getTeam();
+
+ if (currentTeam == Team.HAN) {
+ if (!(0 <= fromY && fromY <= 2) || !(0 <= toY && toY <= 2)) {
+ return false;
+ }
+ }
+
+ if (currentTeam == Team.CHO) {
+ if (!(7 <= fromY && fromY <= 9) || !(7 <= toY && toY <= 9)) {
+ return false;
+ }
+ }
+
+ if (fromX != toX && fromY != toY) {
+ return false;
+ }
+
+ if (fromY == toY && Math.abs(fromX - toX) != 1) {
+ return false;
+ }
+
+ if (fromX == toX && Math.abs(fromY - toY) != 1) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/janggi/domain/movestorage/JolMoveStorage.java b/src/main/java/janggi/domain/movestorage/JolMoveStorage.java
new file mode 100644
index 0000000000..fd7ecd09ac
--- /dev/null
+++ b/src/main/java/janggi/domain/movestorage/JolMoveStorage.java
@@ -0,0 +1,44 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Position;
+import janggi.domain.Team;
+
+import java.util.List;
+
+public class JolMoveStorage implements MoveStorage{
+ private static final int HAN_FORWARD = 1;
+ private static final int CHO_FORWARD = -1;
+ public static final int NEXT_TO = 1;
+
+ @Override
+ public boolean canMove(Position from, Position to, BoardState boardState) {
+ List fromPosition = from.getPosition();
+ List toPosition = to.getPosition();
+
+ int fromX = fromPosition.getFirst();
+ int fromY = fromPosition.getLast();
+ int toX = toPosition.getFirst();
+ int toY = toPosition.getLast();
+
+ if (Math.abs(fromX - toX) == NEXT_TO && fromY == toY) {
+ return true;
+ }
+
+ Team currentTeam = boardState.getPieceAt(from).getTeam();
+
+ if (currentTeam == Team.HAN) {
+ if (toY - fromY == HAN_FORWARD && fromX == toX) {
+ return true;
+ }
+ }
+
+ if (currentTeam == Team.CHO) {
+ if (toY - fromY == CHO_FORWARD && fromX == toX) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/janggi/domain/movestorage/MaMoveStorage.java b/src/main/java/janggi/domain/movestorage/MaMoveStorage.java
new file mode 100644
index 0000000000..fca865b3e0
--- /dev/null
+++ b/src/main/java/janggi/domain/movestorage/MaMoveStorage.java
@@ -0,0 +1,62 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Position;
+import janggi.domain.Row;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MaMoveStorage implements MoveStorage{
+ private static final int FORWARD = 2;
+ private static final int DIAGONAL = 1;
+
+ private static final int PATH_STEP_1 = 1;
+ private static final int PATH_STEP_0 = 0;
+
+ @Override
+ public boolean canMove(Position from, Position to, BoardState boardState) {
+ List fromPosition = from.getPosition(); //Row, Col을 사용하지 않고, int를 사용하고 있음
+ List toPosition = to.getPosition();
+
+ int fromX = fromPosition.getFirst();
+ int fromY = fromPosition.getLast();
+ int toX = toPosition.getFirst();
+ int toY = toPosition.getLast();
+
+ int diffX = toX - fromX;
+ int diffY = toY - fromY;
+
+ boolean isMaMove = (Math.abs(diffX) == FORWARD && Math.abs(diffY) == DIAGONAL) ||
+ (Math.abs(diffX) == DIAGONAL && Math.abs(diffY) == FORWARD);
+
+ if (!isMaMove) {
+ return false;
+ }
+
+ List movementPathPositions = new ArrayList<>();
+
+ int signX = Integer.signum(diffX);
+ int signY = Integer.signum(diffY);
+
+ if (Math.abs(diffX) == FORWARD) {
+ int step1X = fromX + (signX * PATH_STEP_1);
+ int step1Y = fromY + (signY * PATH_STEP_0);
+ movementPathPositions.add(Position.of(Row.of(step1X), Column.of(step1Y)));
+ }
+
+ if (Math.abs(diffY) == FORWARD) {
+ int step1X = fromX + (signX * PATH_STEP_0);
+ int step1Y = fromY + (signY * PATH_STEP_1);
+ movementPathPositions.add(Position.of(Row.of(step1X), Column.of(step1Y)));
+ }
+
+ for (Position position : movementPathPositions) {
+ if (boardState.hasPieceAt(position)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/janggi/domain/movestorage/MoveStorage.java b/src/main/java/janggi/domain/movestorage/MoveStorage.java
new file mode 100644
index 0000000000..7068b24ff6
--- /dev/null
+++ b/src/main/java/janggi/domain/movestorage/MoveStorage.java
@@ -0,0 +1,8 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Position;
+
+public interface MoveStorage {
+ boolean canMove(Position from, Position to, BoardState boardState);
+}
diff --git a/src/main/java/janggi/domain/movestorage/PoMoveStorage.java b/src/main/java/janggi/domain/movestorage/PoMoveStorage.java
new file mode 100644
index 0000000000..dcce09a47e
--- /dev/null
+++ b/src/main/java/janggi/domain/movestorage/PoMoveStorage.java
@@ -0,0 +1,73 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Piece;
+import janggi.domain.Position;
+import janggi.domain.Row;
+
+import java.util.List;
+
+public class PoMoveStorage implements MoveStorage {
+
+ @Override
+ public boolean canMove(Position from, Position to, BoardState boardState) {
+ List fromPosition = from.getPosition();
+ List toPosition = to.getPosition();
+
+ int fromX = fromPosition.getFirst();
+ int fromY = fromPosition.getLast();
+ int toX = toPosition.getFirst();
+ int toY = toPosition.getLast();
+
+ if (fromX != toX && fromY != toY) {
+ return false;
+ }
+
+ int jumpCount = 0;
+
+ if (fromX == toX) {
+ int start = Math.min(fromY, toY) + 1;
+ int end = Math.max(fromY, toY);
+
+ for (int i = start; i < end; i++) {
+ Position position = Position.of(Row.of(fromX), Column.of(i));
+ if (boardState.hasPieceAt(position)) {
+ Piece jumpPiece = boardState.getPieceAt(position);
+ if (jumpPiece.getName().equals("包")) {
+ return false;
+ }
+ jumpCount++;
+ }
+ }
+ }
+
+ if (fromY == toY) {
+ int start = Math.min(fromX, toX) + 1;
+ int end = Math.max(fromX, toX);
+
+ for (int i = start; i < end; i++) {
+ Position position = Position.of(Row.of(i), Column.of(fromY));
+ if (boardState.hasPieceAt(position)) {
+ Piece jumpPiece = boardState.getPieceAt(position);
+ if (jumpPiece.getName().equals("包")) {
+ return false;
+ }
+ jumpCount++;
+ }
+ }
+ }
+
+ if (jumpCount != 1) {
+ return false;
+ }
+
+ if (boardState.hasPieceAt(to)) {
+ Piece targetPiece = boardState.getPieceAt(to);
+ if (targetPiece.getName().equals("包")) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/janggi/domain/movestorage/SangMoveStorage.java b/src/main/java/janggi/domain/movestorage/SangMoveStorage.java
new file mode 100644
index 0000000000..63454f7f96
--- /dev/null
+++ b/src/main/java/janggi/domain/movestorage/SangMoveStorage.java
@@ -0,0 +1,71 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Position;
+import janggi.domain.Row;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SangMoveStorage implements MoveStorage{
+ private static final int FORWARD = 3;
+ private static final int DIAGONAL = 2;
+
+ private static final int PATH_STEP_2 = 2;
+ private static final int PATH_STEP_1 = 1;
+ private static final int PATH_STEP_0 = 0;
+
+ @Override
+ public boolean canMove(Position from, Position to, BoardState boardState) {
+ List fromPosition = from.getPosition();
+ List toPosition = to.getPosition();
+
+ int fromX = fromPosition.getFirst();
+ int fromY = fromPosition.getLast();
+ int toX = toPosition.getFirst();
+ int toY = toPosition.getLast();
+
+ int diffX = toX - fromX;
+ int diffY = toY - fromY;
+
+ boolean isSangMove = (Math.abs(diffX) == FORWARD && Math.abs(diffY) == DIAGONAL) ||
+ (Math.abs(diffX) == DIAGONAL && Math.abs(diffY) == FORWARD);
+
+ if (!isSangMove) {
+ return false;
+ }
+
+ List movementPathPositions = new ArrayList<>();
+
+ int signX = Integer.signum(diffX);
+ int signY = Integer.signum(diffY);
+
+ if (Math.abs(diffX) == FORWARD) {
+ int step1X = fromX + (signX * PATH_STEP_1);
+ int step1Y = fromY + (signY * PATH_STEP_0);
+ movementPathPositions.add(Position.of(Row.of(step1X), Column.of(step1Y)));
+
+ int step2X = fromX + (signX * PATH_STEP_2);
+ int step2Y = fromY + (signY * PATH_STEP_1);
+ movementPathPositions.add(Position.of(Row.of(step2X), Column.of(step2Y)));
+ }
+
+ if (Math.abs(diffY) == FORWARD) {
+ int step1X = fromX + (signX * PATH_STEP_0);
+ int step1Y = fromY + (signY * PATH_STEP_1);
+ movementPathPositions.add(Position.of(Row.of(step1X), Column.of(step1Y)));
+
+ int step2X = fromX + (signX * PATH_STEP_1);
+ int step2Y = fromY + (signY * PATH_STEP_2);
+ movementPathPositions.add(Position.of(Row.of(step2X), Column.of(step2Y)));
+ }
+
+ for (Position position : movementPathPositions) {
+ if (boardState.hasPieceAt(position)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/janggi/dto/BoardDto.java b/src/main/java/janggi/dto/BoardDto.java
new file mode 100644
index 0000000000..16ea8573c4
--- /dev/null
+++ b/src/main/java/janggi/dto/BoardDto.java
@@ -0,0 +1,31 @@
+package janggi.dto;
+
+import janggi.domain.Board;
+import janggi.domain.Piece;
+import janggi.domain.Position;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BoardDto {
+ private final Map, String> pieces;
+
+ private BoardDto(Map, String> pieces) {
+ this.pieces = pieces;
+ }
+
+ public static BoardDto from(Board board) {
+ Map, String> result = new HashMap<>();
+ Map pieces = board.getBoard();
+
+ for (Position position : pieces.keySet()) {
+ result.put(position.getPosition(), pieces.get(position).getName());
+ }
+ return new BoardDto(result);
+ }
+
+ public Map, String> getPieces() {
+ return pieces;
+ }
+}
diff --git a/src/main/java/janggi/exception/BusinessException.java b/src/main/java/janggi/exception/BusinessException.java
new file mode 100644
index 0000000000..5c412dff5d
--- /dev/null
+++ b/src/main/java/janggi/exception/BusinessException.java
@@ -0,0 +1,7 @@
+package janggi.exception;
+
+public class BusinessException extends RuntimeException {
+ public BusinessException(String message) {
+ super("[ERROR] " + message);
+ }
+}
diff --git a/src/main/java/janggi/exception/ColumnOutOfRangeException.java b/src/main/java/janggi/exception/ColumnOutOfRangeException.java
new file mode 100644
index 0000000000..6e5449bb10
--- /dev/null
+++ b/src/main/java/janggi/exception/ColumnOutOfRangeException.java
@@ -0,0 +1,7 @@
+package janggi.exception;
+
+public class ColumnOutOfRangeException extends BusinessException {
+ public ColumnOutOfRangeException() {
+ super("장기판의 y좌표 범위를 벗어났습니다.");
+ }
+}
diff --git a/src/main/java/janggi/exception/EmptyPositionException.java b/src/main/java/janggi/exception/EmptyPositionException.java
new file mode 100644
index 0000000000..bc8c38bc56
--- /dev/null
+++ b/src/main/java/janggi/exception/EmptyPositionException.java
@@ -0,0 +1,7 @@
+package janggi.exception;
+
+public class EmptyPositionException extends BusinessException {
+ public EmptyPositionException() {
+ super("출발지에 이동할 기물이 없습니다.");
+ }
+}
diff --git a/src/main/java/janggi/exception/InvalidMoveException.java b/src/main/java/janggi/exception/InvalidMoveException.java
new file mode 100644
index 0000000000..e281901cb4
--- /dev/null
+++ b/src/main/java/janggi/exception/InvalidMoveException.java
@@ -0,0 +1,7 @@
+package janggi.exception;
+
+public class InvalidMoveException extends BusinessException {
+ public InvalidMoveException() {
+ super("유효하지 않는 행마입니다.");
+ }
+}
diff --git a/src/main/java/janggi/exception/RowOutOfRangeException.java b/src/main/java/janggi/exception/RowOutOfRangeException.java
new file mode 100644
index 0000000000..b9a7b411f6
--- /dev/null
+++ b/src/main/java/janggi/exception/RowOutOfRangeException.java
@@ -0,0 +1,7 @@
+package janggi.exception;
+
+public class RowOutOfRangeException extends BusinessException {
+ public RowOutOfRangeException() {
+ super("장기판의 x좌표 범위를 벗어났습니다.");
+ }
+}
diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java
new file mode 100644
index 0000000000..3e9968c887
--- /dev/null
+++ b/src/main/java/janggi/view/InputView.java
@@ -0,0 +1,34 @@
+package janggi.view;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+
+public class InputView {
+ private final Scanner sc;
+
+ public InputView() {
+ this.sc = new Scanner(System.in);
+ }
+
+ public List playTurn(String team) {
+ System.out.println(team + "나라 턴입니다.");
+
+ List inputFrom = readCoordinates("이동할 기물의 좌표를 입력하세요. (예 : 1, 2)");
+ List inputTo = readCoordinates("도착할 좌표를 입력하세요. (예 : 1, 3)");
+
+ List result = new ArrayList<>(inputFrom);
+ result.addAll(inputTo);
+
+ return result;
+ }
+
+ private List readCoordinates(String message) {
+ System.out.println(message);
+ return Arrays.stream(sc.nextLine().split(","))
+ .map(String::trim)
+ .map(Integer::parseInt)
+ .toList();
+ }
+}
diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java
new file mode 100644
index 0000000000..b13dca1ce8
--- /dev/null
+++ b/src/main/java/janggi/view/OutputView.java
@@ -0,0 +1,134 @@
+package janggi.view;
+
+import janggi.dto.BoardDto;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class OutputView {
+
+ private static final int BOARD_WIDTH = 9;
+ private static final int BOARD_HEIGHT = 10;
+ private static final int NODE_CHARS = 10;
+ private static final int SPACER_LINES = 3;
+ private static final String EMPTY_NODE = "[ ]";
+
+ private static final String ANSI_RED = "\u001B[31m";
+ private static final String ANSI_GREEN = "\u001B[32m";
+ private static final String ANSI_RESET = "\u001B[0m";
+
+ public void printStartMessage() {
+ System.out.println("게임을 시작하겠습니다.");
+ System.out.println();
+ }
+
+ public void printErrorMessage(String errorMessage) {
+ System.out.println(errorMessage);
+ }
+
+ public void printBoard(BoardDto boardDto) {
+ String[][] boardView = generateBoardView(boardDto);
+
+ printXCoordinates();
+ for (int y = 0; y < BOARD_HEIGHT; y++) {
+ printNodeRow(boardView, y);
+ if (y < BOARD_HEIGHT - 1) {
+ printSpacerRows(y);
+ }
+ }
+ System.out.println();
+ }
+
+ private String[][] generateBoardView(BoardDto boardDto) {
+ String[][] boardView = new String[BOARD_HEIGHT][BOARD_WIDTH];
+
+ for (int y = 0; y < BOARD_HEIGHT; y++) {
+ Arrays.fill(boardView[y], EMPTY_NODE);
+ }
+
+ Map, String> pieces = boardDto.getPieces();
+ for (Map.Entry, String> entry : pieces.entrySet()) {
+ List position = entry.getKey();
+ int x = position.get(0);
+ int y = position.get(1);
+ String pieceName = entry.getValue();
+
+ String colorCode = determineFactionColor(pieceName, y);
+
+ String formattedPiece = String.format("[%s%s%s]", colorCode, pieceName, ANSI_RESET);
+ boardView[y][x] = formattedPiece;
+ }
+
+ return boardView;
+ }
+
+ private String determineFactionColor(String pieceName, int y) {
+ if (pieceName.equals("漢") || pieceName.equals("兵")) {
+ return ANSI_RED;
+ }
+ if (pieceName.equals("楚") || pieceName.equals("卒")) {
+ return ANSI_GREEN;
+ }
+
+ if (y <= 4) {
+ return ANSI_RED;
+ }
+ return ANSI_GREEN;
+ }
+
+ private void printXCoordinates() {
+ StringBuilder xAxis = new StringBuilder(" ");
+ for (int x = 0; x < BOARD_WIDTH; x++) {
+ char fullWidthNum = (char) ('\uFF10' + x);
+ xAxis.append(" ").append(fullWidthNum).append(" ");
+ }
+ System.out.println(xAxis.toString());
+ System.out.println();
+ }
+
+ private void printNodeRow(String[][] boardView, int y) {
+ StringBuilder row = new StringBuilder();
+ row.append(y).append(" ");
+ for (int x = 0; x < BOARD_WIDTH; x++) {
+ row.append(boardView[y][x]);
+ if (x < BOARD_WIDTH - 1) {
+ row.append("-------");
+ }
+ }
+ System.out.println(row.toString());
+ }
+
+ private void printSpacerRows(int y) {
+ int[] diagonalOffsets = {2, 5, 8};
+
+ for (int spacerIndex = 0; spacerIndex < SPACER_LINES; spacerIndex++) {
+ char[] spacer = new char[BOARD_WIDTH * NODE_CHARS];
+ Arrays.fill(spacer, ' ');
+
+ for (int x = 0; x < BOARD_WIDTH; x++) {
+ spacer[x * NODE_CHARS + 1] = 'ㅣ';
+ }
+
+ int offset = diagonalOffsets[spacerIndex];
+ addPalaceDiagonals(spacer, y, offset);
+
+ System.out.print(" ");
+ System.out.println(new String(spacer));
+ }
+ }
+
+ private void addPalaceDiagonals(char[] spacer, int y, int offset) {
+ int x3Center = 3 * NODE_CHARS + 1;
+ int x4Center = 4 * NODE_CHARS + 1;
+ int x5Center = 5 * NODE_CHARS + 1;
+
+ if (y == 0 || y == 7) {
+ spacer[x3Center + offset] = '\\';
+ spacer[x5Center - offset] = '/';
+ } else if (y == 1 || y == 8) {
+ spacer[x4Center - offset] = '/';
+ spacer[x4Center + offset] = '\\';
+ }
+ }
+}
diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/test/java/janggi/domain/BoardTest.java b/src/test/java/janggi/domain/BoardTest.java
new file mode 100644
index 0000000000..05c6046826
--- /dev/null
+++ b/src/test/java/janggi/domain/BoardTest.java
@@ -0,0 +1,68 @@
+package janggi.domain;
+
+import janggi.domain.movestorage.JolMoveStorage;
+import janggi.exception.EmptyPositionException;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class BoardTest {
+ @Test
+ void 보드는_특정_위치에_기물이_존재하는지_확인할_수_있다() {
+ // give
+ Position position = Position.of(Row.of(0), Column.of(7));
+ Piece piece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+ Board board = new Board(Map.of(position, piece));
+
+ // when & then
+ assertThat(board.hasPieceAt(position)).isTrue();
+ }
+
+ @Test
+ void 보드는_지정된_좌표의_기물_정보를_알려준다() {
+ // give
+ Position position = Position.of(Row.of(0), Column.of(7));
+ Piece piece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+ Board board = new Board(Map.of(position, piece));
+
+ // when & then
+ assertThat(board.getPieceAt(position)).isEqualTo(piece);
+ }
+
+ @Test
+ void 기물은_허용된_규칙에_따라_새로운_위치로_이동한다() {
+ // give
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(1));
+
+ Position position = Position.of(Row.of(0), Column.of(0));
+ Piece piece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Board board = new Board(Map.of(position, piece));
+
+ // when
+ board.move(from, to);
+
+ // then
+ assertThat(board.getPieceAt(to)).isEqualTo(piece);
+ assertThat(board.hasPieceAt(from)).isFalse();
+ }
+
+ @Test
+ void 기물이_존재하지_않는_곳에서는_이동을_시작할_수_없다() {
+ // give
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(1));
+
+ Position position = Position.of(Row.of(0), Column.of(7));
+ Piece piece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Board board = new Board(Map.of(position, piece));
+
+ // when & then
+ assertThatThrownBy(() -> board.move(from, to)).isInstanceOf(EmptyPositionException.class);
+ }
+}
diff --git a/src/test/java/janggi/domain/ColumnTest.java b/src/test/java/janggi/domain/ColumnTest.java
new file mode 100644
index 0000000000..f59872ca4d
--- /dev/null
+++ b/src/test/java/janggi/domain/ColumnTest.java
@@ -0,0 +1,30 @@
+package janggi.domain;
+
+
+import janggi.exception.ColumnOutOfRangeException;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class ColumnTest {
+ @Test
+ void 장기판의_y좌표_범위를_벗어나면_예외가_발생한다() {
+ Assertions.assertThatThrownBy(() -> Column.of(10)).isInstanceOf(ColumnOutOfRangeException.class);
+ Assertions.assertThatThrownBy(() -> Column.of(-1)).isInstanceOf(ColumnOutOfRangeException.class);
+ }
+
+ @Test
+ void 장기판의_y좌표_범위_내라면_예외가_발생하지_않는다() {
+ Column column1 = Column.of(0);
+ Column column2 = Column.of(9);
+
+ Assertions.assertThat(column1.getColumn()).isEqualTo(0);
+ Assertions.assertThat(column2.getColumn()).isEqualTo(9);
+ }
+
+ @Test
+ void 동등성_테스트() {
+ Column column = Column.of(1);
+
+ Assertions.assertThat(column).isEqualTo(Column.of(1));
+ }
+}
diff --git a/src/test/java/janggi/domain/PieceTest.java b/src/test/java/janggi/domain/PieceTest.java
new file mode 100644
index 0000000000..63ca45c852
--- /dev/null
+++ b/src/test/java/janggi/domain/PieceTest.java
@@ -0,0 +1,73 @@
+package janggi.domain;
+
+import janggi.domain.movestorage.JolMoveStorage;
+import janggi.domain.movestorage.MoveStorage;
+import janggi.exception.InvalidMoveException;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class PieceTest {
+ private static class ObstaclFakeBoard implements BoardState {
+ private final Map obstacles;
+
+ public ObstaclFakeBoard(Map obstacles) {
+ this.obstacles = new HashMap<>(obstacles);
+ }
+
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return obstacles.containsKey(position);
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return obstacles.get(position);
+ }
+ }
+
+ @Test
+ void 다른_진영의_기물을_잡았을_경우_예외가_발생하지_않는다() {
+ // given
+ MoveStorage moveStorage = new JolMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(3));
+ Position to = Position.of(Row.of(4), Column.of(4));
+
+ Piece pieceHan = new Piece(new JolMoveStorage(), Team.HAN, 9, "兵");
+ Piece pieceCho = new Piece(new JolMoveStorage(), Team.CHO, 9, "卒");
+
+ Map fakeBoard = new HashMap<>();
+ fakeBoard.put(from, pieceHan);
+ fakeBoard.put(to, pieceCho);
+
+ BoardState boardState = new ObstaclFakeBoard(fakeBoard);
+
+ // when & then
+ assertThatCode(() -> pieceHan.verifyMove(from, to, boardState))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void 같은_진영의_기물을_잡았을_경우_예외가_발생한다() {
+ // given
+ Position from = Position.of(Row.of(4), Column.of(3));
+ Position to = Position.of(Row.of(4), Column.of(4));
+
+ Piece pieceHan1 = new Piece(new JolMoveStorage(), Team.HAN, 2, "兵");
+ Piece pieceHan2 = new Piece(new JolMoveStorage(), Team.HAN, 2, "兵");
+
+ Map fakeBoard = new HashMap<>();
+ fakeBoard.put(from, pieceHan1);
+ fakeBoard.put(to, pieceHan2);
+
+ BoardState boardState = new ObstaclFakeBoard(fakeBoard);
+
+ // when & then
+ assertThatThrownBy(() -> pieceHan1.verifyMove(from, to, boardState))
+ .isInstanceOf(InvalidMoveException.class);
+ }
+}
diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java
new file mode 100644
index 0000000000..bf56f7a987
--- /dev/null
+++ b/src/test/java/janggi/domain/PositionTest.java
@@ -0,0 +1,24 @@
+package janggi.domain;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+class PositionTest {
+
+ @Test
+ void 장기판에_모든_좌표가_생성된다() {
+ // when & then
+ for (int x = 0; x < 9; x++) {
+ for(int y = 0; y < 10; y++) {
+ Position position1 = Position.of(Row.of(x), Column.of(y));
+ Position position2 = Position.of(Row.of(x), Column.of(y));
+
+ assertThat(position1)
+ .as("x=%d, y=%d 에서 캐싱 실패", x, y)
+ .isSameAs(position2);
+ }
+ }
+ }
+}
diff --git a/src/test/java/janggi/domain/RowTest.java b/src/test/java/janggi/domain/RowTest.java
new file mode 100644
index 0000000000..0c8c3dfa3a
--- /dev/null
+++ b/src/test/java/janggi/domain/RowTest.java
@@ -0,0 +1,29 @@
+package janggi.domain;
+
+import janggi.exception.RowOutOfRangeException;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class RowTest {
+ @Test
+ void 장기판의_x좌표_범위를_벗어나면_예외가_발생한다() {
+ Assertions.assertThatThrownBy(() -> Row.of(9)).isInstanceOf(RowOutOfRangeException.class);
+ Assertions.assertThatThrownBy(() -> Row.of(-1)).isInstanceOf(RowOutOfRangeException.class);
+ }
+
+ @Test
+ void 장기판의_x좌표_범위_내라면_예외가_발생하지_않는다() {
+ Row row1 = Row.of(0);
+ Row row2 = Row.of(8);
+
+ Assertions.assertThat(row1.getRow()).isEqualTo(0);
+ Assertions.assertThat(row2.getRow()).isEqualTo(8);
+ }
+
+ @Test
+ void 동등성_테스트() {
+ Row row = Row.of(1);
+
+ Assertions.assertThat(row).isEqualTo(Row.of(1));
+ }
+}
diff --git a/src/test/java/janggi/domain/movestorage/ChaMoveStorageTest.java b/src/test/java/janggi/domain/movestorage/ChaMoveStorageTest.java
new file mode 100644
index 0000000000..be75c1103f
--- /dev/null
+++ b/src/test/java/janggi/domain/movestorage/ChaMoveStorageTest.java
@@ -0,0 +1,149 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Piece;
+import janggi.domain.Position;
+import janggi.domain.Row;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ChaMoveStorageTest {
+ private static class FakeBoard implements BoardState {
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return false;
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return null;
+ }
+ }
+
+ private static class ObstaclFakeBoard implements BoardState {
+ private final List obstacles;
+
+ public ObstaclFakeBoard(List obstacles) {
+ this.obstacles = new ArrayList<>(obstacles);
+ }
+
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return obstacles.contains(position);
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return null;
+ }
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_높고_멱이_없다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(3));
+ BoardState boardState = new FakeBoard();
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_낮고_멱이_없다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(0), Column.of(3));
+ Position to = Position.of(Row.of(0), Column.of(0));
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_높고_멱이_없다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(3), Column.of(0));
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_낮고_멱이_없다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(0));
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 차가_아예_갈_수_없는_행마면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(1), Column.of(3));
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_높으면서_멱이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(3));
+ Position obstacl = Position.of(Row.of(0), Column.of(2));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstacl));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_낮으면서_멱이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(0), Column.of(3));
+ Position to = Position.of(Row.of(0), Column.of(0));
+ Position obstacl = Position.of(Row.of(0), Column.of(1));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstacl));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_높으면서_멱이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(3), Column.of(0));
+ Position obstacl = Position.of(Row.of(2), Column.of(0));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstacl));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_낮으면서_멱이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new ChaMoveStorage();
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(0));
+ Position obstacl = Position.of(Row.of(1), Column.of(0));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstacl));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+}
diff --git a/src/test/java/janggi/domain/movestorage/GungAndSaMoveStorageTest.java b/src/test/java/janggi/domain/movestorage/GungAndSaMoveStorageTest.java
new file mode 100644
index 0000000000..a946c0704c
--- /dev/null
+++ b/src/test/java/janggi/domain/movestorage/GungAndSaMoveStorageTest.java
@@ -0,0 +1,250 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Piece;
+import janggi.domain.Position;
+import janggi.domain.Row;
+import janggi.domain.Team;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class GungAndSaMoveStorageTest {
+ private static class ObstaclFakeBoard implements BoardState {
+ private final Map obstacles;
+
+ public ObstaclFakeBoard(Map obstacles) {
+ this.obstacles = new HashMap<>(obstacles);
+ }
+
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return obstacles.containsKey(position);
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return obstacles.get(position);
+ }
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성_내부에서_위로_한_칸_이동할_수_있다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(8));
+ Position to = Position.of(Row.of(4), Column.of(7));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성_내부에서_아래로_한_칸_이동할_수_있다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(8));
+ Position to = Position.of(Row.of(4), Column.of(9));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성_내부에서_왼쪽으로_한_칸_이동할_수_있다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(8));
+ Position to = Position.of(Row.of(3), Column.of(8));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성_내부에서_오른쪽으로_한_칸_이동할_수_있다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(8));
+ Position to = Position.of(Row.of(5), Column.of(8));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성의_좌측_상단_끝에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(3), Column.of(7));
+ Position to = Position.of(Row.of(2), Column.of(7));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성의_좌측_중단에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(3), Column.of(8));
+ Position to = Position.of(Row.of(2), Column.of(8));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성의_좌측_하단_끝에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(3), Column.of(9));
+ Position to = Position.of(Row.of(2), Column.of(9));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성의_우측_상단_끝에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(5), Column.of(7));
+ Position to = Position.of(Row.of(6), Column.of(7));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성의_우측_중단에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(5), Column.of(8));
+ Position to = Position.of(Row.of(6), Column.of(8));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성의_우측_하단_끝에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(5), Column.of(9));
+ Position to = Position.of(Row.of(6), Column.of(9));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 초나라_왕과_사는_궁성_제일_앞줄에서_앞으로_한_칸_더_전진하여_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(7));
+ Position to = Position.of(Row.of(4), Column.of(6));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.CHO, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 한나라_왕과_사는_궁성의_좌측_상단_끝에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(2), Column.of(0));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.HAN, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 한나라_왕과_사는_궁성의_좌측_중단에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(3), Column.of(1));
+ Position to = Position.of(Row.of(2), Column.of(1));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.HAN, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 한나라_왕과_사는_궁성의_좌측_하단_끝에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(3), Column.of(2));
+ Position to = Position.of(Row.of(2), Column.of(2));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.HAN, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 한나라_왕과_사는_궁성의_우측_상단_끝에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(5), Column.of(0));
+ Position to = Position.of(Row.of(6), Column.of(0));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.HAN, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 한나라_왕과_사는_궁성의_우측_중단에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(5), Column.of(1));
+ Position to = Position.of(Row.of(6), Column.of(1));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.HAN, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 한나라_왕과_사는_궁성의_우측_하단_끝에서_궁성_밖으로_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(5), Column.of(2));
+ Position to = Position.of(Row.of(6), Column.of(2));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.HAN, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 한나라_왕과_사는_궁성_제일_앞줄에서_앞으로_한_칸_더_전진하여_이탈하는_경우_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new GungAndSaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(2));
+ Position to = Position.of(Row.of(4), Column.of(3));
+ Piece piece = new Piece(new GungAndSaMoveStorage(), Team.HAN, 9, "士");
+ BoardState boardState = new ObstaclFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+}
diff --git a/src/test/java/janggi/domain/movestorage/JolMoveStorageTest.java b/src/test/java/janggi/domain/movestorage/JolMoveStorageTest.java
new file mode 100644
index 0000000000..d2801ee6db
--- /dev/null
+++ b/src/test/java/janggi/domain/movestorage/JolMoveStorageTest.java
@@ -0,0 +1,130 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Piece;
+import janggi.domain.Position;
+import janggi.domain.Row;
+import janggi.domain.Team;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class JolMoveStorageTest {
+ private static class FakeBoard implements BoardState {
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return false;
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return null;
+ }
+ }
+
+ private static class JolFakeBoard implements BoardState {
+ private final Map obstacles;
+
+ public JolFakeBoard(Map obstacles) {
+ this.obstacles = new HashMap<>(obstacles);
+ }
+
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return obstacles.containsKey(position);
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return obstacles.get(position);
+ }
+ }
+
+ @Test
+ void 초나라_졸은_위로_한_칸_전진할_수_있다() {
+ // given
+ MoveStorage moveStorage = new JolMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(6));
+ Position to = Position.of(Row.of(4), Column.of(5));
+ Piece piece = new Piece(new JolMoveStorage(), Team.CHO, 2, "卒");
+ BoardState boardState = new JolFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 초나라_졸은_아래로_한_칸_후퇴할_수_없다() {
+ // given
+ MoveStorage moveStorage = new JolMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(6));
+ Position to = Position.of(Row.of(4), Column.of(7));
+ Piece piece = new Piece(new JolMoveStorage(), Team.CHO, 2, "卒");
+ BoardState boardState = new JolFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 한나라_졸은_아래로_한_칸_전진할_수_있다() {
+ // given
+ MoveStorage moveStorage = new JolMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(3));
+ Position to = Position.of(Row.of(4), Column.of(4));
+ Piece piece = new Piece(new JolMoveStorage(), Team.HAN, 2, "兵");
+ BoardState boardState = new JolFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 한나라_졸은_위로_한_칸_후퇴할_수_없다() {
+ // given
+ MoveStorage moveStorage = new JolMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(3));
+ Position to = Position.of(Row.of(4), Column.of(2));
+ Piece piece = new Piece(new JolMoveStorage(), Team.HAN, 2, "兵");
+ BoardState boardState = new JolFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 졸은_왼쪽으로_이동할_수_있다() {
+ // given
+ MoveStorage moveStorage = new JolMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(3));
+ Position to = Position.of(Row.of(3), Column.of(3));
+ Piece piece = new Piece(new JolMoveStorage(), Team.CHO, 2, "卒");
+ BoardState boardState = new JolFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 졸은_오른쪽으로_이동할_수_있다() {
+ // given
+ MoveStorage moveStorage = new JolMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(3));
+ Position to = Position.of(Row.of(5), Column.of(3));
+ Piece piece = new Piece(new JolMoveStorage(), Team.CHO, 2, "卒");
+ BoardState boardState = new JolFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 이동할_수_없는_행마면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new JolMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(3));
+ Position to = Position.of(Row.of(5), Column.of(5));
+ Piece piece = new Piece(new JolMoveStorage(), Team.CHO, 2, "卒");
+ BoardState boardState = new JolFakeBoard(Map.of(from, piece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+}
diff --git a/src/test/java/janggi/domain/movestorage/MaMoveStorageTest.java b/src/test/java/janggi/domain/movestorage/MaMoveStorageTest.java
new file mode 100644
index 0000000000..1fae8c852c
--- /dev/null
+++ b/src/test/java/janggi/domain/movestorage/MaMoveStorageTest.java
@@ -0,0 +1,169 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Piece;
+import janggi.domain.Position;
+import janggi.domain.Row;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class MaMoveStorageTest {
+ private static class FakeBoard implements BoardState {
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return false;
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return null;
+ }
+ }
+
+ private static class ObstaclFakeBoard implements BoardState {
+ private final List obstacles;
+
+ public ObstaclFakeBoard(List obstacles) {
+ this.obstacles = new ArrayList<>(obstacles);
+ }
+
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return obstacles.contains(position);
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return null;
+ }
+ }
+
+ @Test
+ void 행마_성공_테스트() {
+ // given
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(6), Column.of(3));
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 마가_갈_수_없는_좌표가_들어오면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(6), Column.of(4));
+ BoardState boardState = new FakeBoard();
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 마가_위로_2칸_왼쪽으로_1칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(3), Column.of(2));
+
+ Position obstaclPosition = Position.of(Row.of(4), Column.of(3));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 마가_위로_2칸_오른쪽으로_1칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(5), Column.of(2));
+
+ Position obstaclPosition = Position.of(Row.of(4), Column.of(3));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 마가_오른쪽으로_2칸_위로_1칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(6), Column.of(3));
+
+ Position obstaclPosition = Position.of(Row.of(5), Column.of(4));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 마가_오른쪽으로_2칸_아래로_1칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(6), Column.of(5));
+
+ Position obstaclPosition = Position.of(Row.of(5), Column.of(4));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 마가_아래로_2칸_오른쪽으로_1칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(5), Column.of(6));
+
+ Position obstaclPosition = Position.of(Row.of(4), Column.of(5));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 마가_아래로_2칸_왼쪽으로_1칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(3), Column.of(6));
+
+ Position obstaclPosition = Position.of(Row.of(4), Column.of(5));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 마가_왼쪽으로_2칸_아래로_1칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(2), Column.of(5));
+
+ Position obstaclPosition = Position.of(Row.of(3), Column.of(4));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 마가_왼쪽으로_2칸_위로_1칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ MoveStorage moveStorage = new MaMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(2), Column.of(3));
+
+ Position obstaclPosition = Position.of(Row.of(3), Column.of(4));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+}
diff --git a/src/test/java/janggi/domain/movestorage/PoMoveStorageTest.java b/src/test/java/janggi/domain/movestorage/PoMoveStorageTest.java
new file mode 100644
index 0000000000..e5edca1792
--- /dev/null
+++ b/src/test/java/janggi/domain/movestorage/PoMoveStorageTest.java
@@ -0,0 +1,511 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Piece;
+import janggi.domain.Position;
+import janggi.domain.Row;
+import janggi.domain.Team;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class PoMoveStorageTest {
+
+ private static class FakeBoard implements BoardState {
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return false;
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return null;
+ }
+ }
+
+ private static class ObstaclFakeBoard implements BoardState {
+ private final Map obstacles;
+
+ public ObstaclFakeBoard(Map obstacles) {
+ this.obstacles = new HashMap<>(obstacles);
+ }
+
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return obstacles.containsKey(position);
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return obstacles.get(position);
+ }
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_높고_중간에_포가_아닌_기물이_한_개_있고_도착지점_기물이_포가_아니다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(3));
+
+ Position obstaclePosition = Position.of(Row.of(0), Column.of(1));
+ Piece obstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_낮고_중간에_포가_아닌_기물이_한_개_있고_도착지점_기물이_포가_아니다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(3));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(0), Column.of(1));
+ Piece obstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_높고_중간에_포가_아닌_기물이_한_개_있고_도착지점_기물이_포가_아니다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(3), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(2), Column.of(0));
+ Piece obstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_낮고_중간에_포가_아닌_기물이_한_개_있고_도착지점_기물이_포가_아니다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(2), Column.of(0));
+ Piece obstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 포가_아예_갈_수_없는_행마면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(1), Column.of(3));
+
+ Position obstaclePosition = Position.of(Row.of(0), Column.of(1));
+ Piece obstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_높고_중간에_기물이_없으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(3));
+
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_낮고_중간에_기물이_없으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(3));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_높고_중간에_기물이_없으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(3), Column.of(0));
+
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_낮고_중간에_기물이_없으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_높고_도착지점_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(3));
+
+ Position obstaclePosition = Position.of(Row.of(0), Column.of(3));
+ Piece obstaclePiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_낮고_도착지점_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(3));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(0), Column.of(0));
+ Piece obstaclePiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_높고_도착지점_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(3), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(3), Column.of(0));
+ Piece obstaclePiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_낮고_도착지점_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(0), Column.of(0));
+ Piece obstaclePiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_높고_중간_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(3));
+
+ Position obstaclePosition = Position.of(Row.of(0), Column.of(2));
+ Piece obstaclePiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_낮고_중간_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(3));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(0), Column.of(2));
+ Piece obstaclePiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_높고_중간_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(3), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(2), Column.of(0));
+ Piece obstaclePiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_낮고_중간_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position obstaclePosition = Position.of(Row.of(2), Column.of(0));
+ Piece obstaclePiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ BoardState boardState = new ObstaclFakeBoard(Map.of(obstaclePosition, obstaclePiece));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_높고_중간에_포가_아닌_기물이_한_개_있고_도착지점_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(3));
+
+ Position pathObstaclePosition = Position.of(Row.of(0), Column.of(2));
+ Piece pathObstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Position targetPosition = Position.of(Row.of(0), Column.of(3));
+ Piece targetPiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ Map initialPieces = Map.of(
+ pathObstaclePosition, pathObstaclePiece,
+ targetPosition, targetPiece
+ );
+
+ BoardState boardState = new ObstaclFakeBoard(initialPieces);
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_낮고_중간에_포가_아닌_기물이_한_개_있고_도착지점_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(3));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position pathObstaclePosition = Position.of(Row.of(0), Column.of(2));
+ Piece pathObstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Position targetPosition = Position.of(Row.of(0), Column.of(0));
+ Piece targetPiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ Map initialPieces = Map.of(
+ pathObstaclePosition, pathObstaclePiece,
+ targetPosition, targetPiece
+ );
+
+ BoardState boardState = new ObstaclFakeBoard(initialPieces);
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_높고_중간에_포가_아닌_기물이_한_개_있고_도착지점_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(3), Column.of(0));
+
+ Position pathObstaclePosition = Position.of(Row.of(2), Column.of(0));
+ Piece pathObstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Position targetPosition = Position.of(Row.of(3), Column.of(0));
+ Piece targetPiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ Map initialPieces = Map.of(
+ pathObstaclePosition, pathObstaclePiece,
+ targetPosition, targetPiece
+ );
+
+ BoardState boardState = new ObstaclFakeBoard(initialPieces);
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_s낮고_중간에_포가_아닌_기물이_한_개_있고_도착지점_기물이_포면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position pathObstaclePosition = Position.of(Row.of(2), Column.of(0));
+ Piece pathObstaclePiece = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Position targetPosition = Position.of(Row.of(0), Column.of(0));
+ Piece targetPiece = new Piece(new PoMoveStorage(), Team.HAN, 7, "包");
+
+ Map initialPieces = Map.of(
+ pathObstaclePosition, pathObstaclePiece,
+ targetPosition, targetPiece
+ );
+
+ BoardState boardState = new ObstaclFakeBoard(initialPieces);
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_높고_중간에_기물이_2개_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(3));
+
+ Position pathObstaclePosition1 = Position.of(Row.of(0), Column.of(1));
+ Piece pathObstaclePiece1 = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Position pathObstaclePosition2 = Position.of(Row.of(0), Column.of(2));
+ Piece pathObstaclePiece2 = new Piece(new MaMoveStorage(), Team.HAN, 5, "馬");
+
+ Map initialPieces = Map.of(
+ pathObstaclePosition1, pathObstaclePiece1,
+ pathObstaclePosition2, pathObstaclePiece2
+ );
+
+ BoardState boardState = new ObstaclFakeBoard(initialPieces);
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_X축은_같고_Y축은_도착지점이_더_낮고_중간에_기물이_2개_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(3));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position pathObstaclePosition1 = Position.of(Row.of(0), Column.of(1));
+ Piece pathObstaclePiece1 = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Position pathObstaclePosition2 = Position.of(Row.of(0), Column.of(2));
+ Piece pathObstaclePiece2 = new Piece(new MaMoveStorage(), Team.HAN, 5, "馬");
+
+ Map initialPieces = Map.of(
+ pathObstaclePosition1, pathObstaclePiece1,
+ pathObstaclePosition2, pathObstaclePiece2
+ );
+
+ BoardState boardState = new ObstaclFakeBoard(initialPieces);
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_높고_중간에_기물이_2개_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(0), Column.of(0));
+ Position to = Position.of(Row.of(3), Column.of(0));
+
+ Position pathObstaclePosition1 = Position.of(Row.of(1), Column.of(0));
+ Piece pathObstaclePiece1 = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Position pathObstaclePosition2 = Position.of(Row.of(2), Column.of(0));
+ Piece pathObstaclePiece2 = new Piece(new MaMoveStorage(), Team.HAN, 5, "馬");
+
+ Map initialPieces = Map.of(
+ pathObstaclePosition1, pathObstaclePiece1,
+ pathObstaclePosition2, pathObstaclePiece2
+ );
+
+ BoardState boardState = new ObstaclFakeBoard(initialPieces);
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 출발지점과_도착지점이_Y축은_같고_X축은_도착지점이_더_낮고_중간에_기물이_2개_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new PoMoveStorage();
+
+ Position from = Position.of(Row.of(3), Column.of(0));
+ Position to = Position.of(Row.of(0), Column.of(0));
+
+ Position pathObstaclePosition1 = Position.of(Row.of(1), Column.of(0));
+ Piece pathObstaclePiece1 = new Piece(new JolMoveStorage(), Team.HAN, 2, "卒");
+
+ Position pathObstaclePosition2 = Position.of(Row.of(2), Column.of(0));
+ Piece pathObstaclePiece2 = new Piece(new MaMoveStorage(), Team.HAN, 5, "馬");
+
+ Map initialPieces = Map.of(
+ pathObstaclePosition1, pathObstaclePiece1,
+ pathObstaclePosition2, pathObstaclePiece2
+ );
+
+ BoardState boardState = new ObstaclFakeBoard(initialPieces);
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+}
diff --git a/src/test/java/janggi/domain/movestorage/SangMoveStorageTest.java b/src/test/java/janggi/domain/movestorage/SangMoveStorageTest.java
new file mode 100644
index 0000000000..bd29fe661c
--- /dev/null
+++ b/src/test/java/janggi/domain/movestorage/SangMoveStorageTest.java
@@ -0,0 +1,170 @@
+package janggi.domain.movestorage;
+
+import janggi.domain.BoardState;
+import janggi.domain.Column;
+import janggi.domain.Piece;
+import janggi.domain.Position;
+import janggi.domain.Row;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class SangMoveStorageTest {
+
+ private static class FakeBoard implements BoardState {
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return false;
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return null;
+ }
+ }
+
+ private static class ObstaclFakeBoard implements BoardState {
+ private final List obstacles;
+
+ public ObstaclFakeBoard(List obstacles) {
+ this.obstacles = new ArrayList<>(obstacles);
+ }
+
+ @Override
+ public boolean hasPieceAt(Position position) {
+ return obstacles.contains(position);
+ }
+
+ @Override
+ public Piece getPieceAt(Position position) {
+ return null;
+ }
+ }
+
+ @Test
+ void 행마_성공_테스트() {
+ // given
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(7), Column.of(2));
+ BoardState boardState = new FakeBoard();
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isTrue();
+ }
+
+ @Test
+ void 상이_갈_수_없는_좌표가_들어오면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(7), Column.of(3));
+ BoardState boardState = new FakeBoard();
+
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 상이_위로_3칸_왼쪽으로_2칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(2), Column.of(1));
+
+ Position obstaclPosition = Position.of(Row.of(3), Column.of(2));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 상이_위로_3칸_오른쪽으로_2칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(6), Column.of(1));
+
+ Position obstaclPosition = Position.of(Row.of(5), Column.of(2));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 상이_오른쪽으로_3칸_위로_2칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(7), Column.of(2));
+
+ Position obstaclPosition = Position.of(Row.of(6), Column.of(3));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 상이_오른쪽으로_3칸_아래로_2칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(7), Column.of(6));
+
+ Position obstaclPosition = Position.of(Row.of(6), Column.of(5));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 상이_아래로_3칸_오른쪽으로_2칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(6), Column.of(7));
+
+ Position obstaclPosition = Position.of(Row.of(5), Column.of(6));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 상이_아래로_3칸_왼쪽으로_2칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ // given
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(2), Column.of(7));
+
+ Position obstaclPosition = Position.of(Row.of(3), Column.of(6));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 상이_왼쪽으로_3칸_아래로_2칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(1), Column.of(6));
+
+ Position obstaclPosition = Position.of(Row.of(2), Column.of(5));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+
+ @Test
+ void 상이_왼쪽으로_3칸_위로_2칸_이동하려_할_때_경로에_기물이_있으면_실패를_반환한다() {
+ MoveStorage moveStorage = new SangMoveStorage();
+ Position from = Position.of(Row.of(4), Column.of(4));
+ Position to = Position.of(Row.of(1), Column.of(2));
+
+ Position obstaclPosition = Position.of(Row.of(2), Column.of(3));
+ BoardState boardState = new ObstaclFakeBoard(List.of(obstaclPosition));
+ // when & then
+ assertThat(moveStorage.canMove(from, to, boardState)).isFalse();
+ }
+}