From 1e2353fc17def6b8ae71a4c038eed34de3b4d2a1 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Tue, 24 Mar 2026 20:51:50 +0900 Subject: [PATCH 01/68] =?UTF-8?q?docs(README):=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 192 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9775dda0ae..e6f1f85c6d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,194 @@ # java-janggi -장기 미션 저장소 +> 장기 미션 저장소 + +페어 : [스타크](https://github.com/MODUGGAGI), [러키](https://github.com/Jiihyun) + +--- + +## 👥 페어 프로그래밍 규칙 + +네비게이터, 드라이버 역할 교환은 **15분**에 한번씩 + +| **네비게이터** | **드라이버** | +|:---------------------------:|:------------------------------:| +| 드라이버에게 지시와 동시에 서로의 대화 내용 기록 | 자신이 작성하려는 코드의 의도를 항상 명확히 말로 전달 | + +--- + +## 🀄️ 구현 기능 목록 + +## ✅ 입력 + +- [ ] 게임 시작 시 상차림 입력 받기 + - [ ] 지정된 명령어 이외의 문자열 및 숫자 값은 `IllegalArgumentException`을 발생시킨다. + +- [ ] 플레이어의 선택 기물 좌표 입력 + - [ ] 공백일 경우 `IllegalArgumentException`을 발생시킨다. + - [ ] 숫자가 아닐 경우 `IllegalArgumentException`을 발생시킨다. + - [ ] 구분자가 `,`가 아닐 경우 `IllegalArgumentException`을 발생시킨다. + +- [ ] 플레이어의 목표 좌표 입력 + - [ ] 공백일 경우 `IllegalArgumentException`을 발생시킨다. + - [ ] 숫자가 아닐 경우 `IllegalArgumentException`을 발생시킨다. + - [ ] 구분자가 `,`가 아닐 경우 `IllegalArgumentException`을 발생시킨다. + +## ✅ 출력 + +- [ ] 장기판 출력 + - 목표 좌표 입력 후 +- [ ] 현재 차례인 나 라 출력 + - 『한』, 『초』 + +## ✅ 비지니스 기능 + +### 보드 초기화 + +- [ ] 『한』 상차림 +- [ ] 『초』 상차림 + +```text +- 좌측 상단을 0행 0열 이라 가정 + +장*1개 +시작 위치 +- 한나라: 1행 4열 +- 초나라: 8행 4열 + +사*2개 +시작 위치 +- 한나라: 0행 3열, 0행 5열 +- 초나라: 9행 3열, 9행 5열 + +차*2개 +시작 위치 +- 한나라: 0행 0열, 0행 8열 +- 초나라: 9행 0열, 9행 8열 + +포*2개 +시작 위치 +- 한나라: 2행 1열, 2행 7열 +- 초나라: 7행 1열, 7행 7열 + +마*2개 +시작 위치 +- 한나라: 0행 2열, 0행 7열 +- 초나라: 9행 2열, 9행 7열 + +상*2개 +시작 위치 +- 한나라: 0행 1열, 0행 6열 +- 초나라: 9행 1열, 9행 6열 + +졸*5개 +시작 위치 +- 한나라: 3행 0열, 3행 2열, 3행 4열, 3행 6열, 3행 8열 +- 초나라: 6행 0열, 6행 2열, 6행 4열, 6행 6열, 6행 8열 +``` + +### 기물 이동 + +- [ ] 이동 시키려는 기물의 좌표를 확인한다. + - [ ] 좌표가 장기판 범위 벗어난 좌표일 경우 `IllegalArgumentException`을 발생시킨다. + - [ ] 좌표에 기물이 존재 하지 않을 경우 `IllegalArgumentException`을 발생시킨다. + - [ ] 좌표의 기물이 자신의 진영이 아닐 경우 `IllegalArgumentException`을 발생시킨다. + +- [ ] 기물을 목적지 좌표로 이동한다. + - [ ] 목적지 좌표가 장기판 범위 벗어난 좌표일 경우 `IllegalArgumentException`을 발생시킨다. + - [ ] 기물 규칙 및 제약에 맞게 이동한다. + - [차(車)] + - [ ] 상하좌우로 장애물을 만날 때까지 칸 수 제한 없이 이동한다. + - [ ] 이동하려는 경로 중간에 다른 기물이 **있**다면 `IllegalArgumentException`을 발생시킨다. + + - [포(包)] + - [ ] 상하좌우로 이동하되, 반드시 중간에 다른 기물을 하나 뛰어넘어야 합니다. + - [ ] 이동하려는 경로 중간에 다른 기물이 **없**다면 `IllegalArgumentException`을 발생시킨다. + - [ ] 넘으려는 기물이 포(包) 일 경우 `IllegalArgumentException`을 발생시킨다. + - [ ] 목적지 좌표의 기물이 포(包)일 경우 `IllegalArgumentException`을 발생시킨다. + + - [마(馬)] + - [ ] 직선 1칸 + 대각선 1칸(日) 이동한다. + - [ ] 목적지 좌표(직선 1칸 또는 대각선 1칸)에 다른 기물이 있으면 `IllegalArgumentException`을 발생시킨다. + + - [상(象)] + - [ ] 직선 1칸 + 대각선 2칸(用) 이동한다. + - [ ] 목적지 좌표(직선 1칸 또는 대각선 1칸)에 다른 기물이 있으면 `IllegalArgumentException`을 발생시킨다. + + - [궁(楚/漢), 사(士)] + - [ ] 상하좌우 직선 1칸 이동한다. + + - [졸/병(卒/兵)] + - [ ] 상좌우 직선 1칸 이동한다. 후진은 불가 주의 + +--- + +## 💻 실행 결과 + +```text +1. 상마상마 +2. 마상마상 +3. 마상상마 +4. 상마마상 +한나라의 상차림을 선택해주세요. +1 + +1. 상마상마 +2. 마상마상 +3. 마상상마 +4. 상마마상 +초나라의 상차림을 선택해주세요. +3 + +[장기판] + 0 1 2 3 4 5 6 7 8 +0 車 馬 象 士 . 士 象 馬 車 +1 . . . . 楚 . . . . +2 . 包 . . . . . 包 . +3 卒 . 卒 . 卒 . 卒 . 卒 +4 . . . . . . . . . +5 . . . . . . . . . +6 兵 . 兵 . 兵 . 兵 . 兵 +7 . 包 . . . . . 包 . +8 . . . . 漢 . . . . +9 車 象 馬 士 . 士 象 馬 車 + +초나라 차례 입니다. +공격할 기물의 좌표를 입력해주세요. +3,0 + +이동 시킬 목적지 좌표를 입력해주세요. +4,0 + +[장기판] + 0 1 2 3 4 5 6 7 8 +0 車 馬 象 士 . 士 象 馬 車 +1 . . . . 楚 . . . . +2 . 包 . . . . . 包 . +3 . . 卒 . 卒 . 卒 . 卒 +4 卒 . . . . . . . . +5 . . . . . . . . . +6 兵 . 兵 . 兵 . 兵 . 兵 +7 . 包 . . . . . 包 . +8 . . . . 漢 . . . . +9 車 象 馬 士 . 士 象 馬 車 + +한나라 차례 입니다. +공격할 기물의 좌표를 입력해주세요. +9,0 + +이동 시킬 목적지 좌표를 입력해주세요. +8,0 + +[장기판] + 0 1 2 3 4 5 6 7 8 +0 車 馬 象 士 . 士 象 馬 車 +1 . . . . 楚 . . . . +2 . 包 . . . . . 包 . +3 . . 卒 . 卒 . 卒 . 卒 +4 卒 . . . . . . . . +5 . . . . . . . . . +6 兵 . 兵 . 兵 . 兵 . 兵 +7 . 包 . . . . . 包 . +8 車 . . . 漢 . . . . +9 . 象 馬 士 . 士 象 馬 車 +``` From 153cf041902cf766f04977c8233879ad7fceb31f Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Tue, 24 Mar 2026 21:57:04 +0900 Subject: [PATCH 02/68] =?UTF-8?q?feat:=20Position=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=20=ED=96=89,=EC=97=B4=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Position 객체 생성 시 행 범위를 벗어난 경우, 열 범위를 벗어난 경우를 검증하는 예외 테스트를 작성 및 로직을 구현했습니다. --- src/main/java/janggi/domain/Position.java | 27 +++++++++++++++ src/test/java/janggi/domain/PositionTest.java | 33 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/main/java/janggi/domain/Position.java create mode 100644 src/test/java/janggi/domain/PositionTest.java diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java new file mode 100644 index 0000000000..b1f83ae4a0 --- /dev/null +++ b/src/main/java/janggi/domain/Position.java @@ -0,0 +1,27 @@ +package janggi.domain; + +public class Position { + + private final int row; + private final int column; + + public Position(int row, int column) { + validateRow(row); + validateColumn(column); + + this.row = row; + this.column = column; + } + + private void validateRow(int row) { + if (row < 0 || 9 < row) { + throw new IllegalArgumentException("[ERROR] 행은 0행 이상 9행 이하여야 합니다."); + } + } + + private void validateColumn(int column) { + if (column < 0 || 8 < column) { + throw new IllegalArgumentException("[ERROR] 열은 0열 이상 8열 이하여야 합니다."); + } + } +} diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java new file mode 100644 index 0000000000..ee8f712c90 --- /dev/null +++ b/src/test/java/janggi/domain/PositionTest.java @@ -0,0 +1,33 @@ +package janggi.domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class PositionTest { + + @DisplayName("포지션의 행이 0~9행이 아닐 경우 예외가 발생한다.") + @ParameterizedTest + @CsvSource(value = { + "-1,8", + "10,1", + }) + void 포지션_행_예외_테스트(int row, int col) { + Assertions.assertThatThrownBy(() -> new Position(row, col)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 행은 0행 이상 9행 이하여야 합니다."); + } + + @DisplayName("포지션의 열이 0~8열이 아닐 경우 예외가 발생한다.") + @ParameterizedTest + @CsvSource(value = { + "0,9", + "0,-1", + }) + void 포지션_열_예외_테스트(int row, int col) { + Assertions.assertThatThrownBy(() -> new Position(row, col)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 열은 0열 이상 8열 이하여야 합니다."); + } +} From 12fb008efa40c86aa3d7709f182299e03a85465f Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 11:50:58 +0900 Subject: [PATCH 03/68] =?UTF-8?q?feat:=20=ED=96=89=EA=B3=BC=20=EC=97=B4=20?= =?UTF-8?q?=EC=B0=A8=EC=9D=B4=20=EA=B3=84=EC=82=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 행과 열 차이 계산 테스트 추가 --- src/main/java/janggi/domain/Position.java | 8 +++++ src/test/java/janggi/domain/PositionTest.java | 32 ++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index b1f83ae4a0..ad5d977190 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -24,4 +24,12 @@ private void validateColumn(int column) { throw new IllegalArgumentException("[ERROR] 열은 0열 이상 8열 이하여야 합니다."); } } + + public int calculateRowDistance(Position other) { + return row - other.row; + } + + public int calculateColumnDistance(Position other) { + return column - other.column; + } } diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java index ee8f712c90..431099ff55 100644 --- a/src/test/java/janggi/domain/PositionTest.java +++ b/src/test/java/janggi/domain/PositionTest.java @@ -1,33 +1,55 @@ package janggi.domain; +import static org.assertj.core.api.Assertions.assertThat; + import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; public class PositionTest { - @DisplayName("포지션의 행이 0~9행이 아닐 경우 예외가 발생한다.") @ParameterizedTest @CsvSource(value = { "-1,8", "10,1", }) - void 포지션_행_예외_테스트(int row, int col) { + void 포지션의_행이_0부터_9행까지가_아닐_경우_예외가_발생한다(int row, int col) { Assertions.assertThatThrownBy(() -> new Position(row, col)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 행은 0행 이상 9행 이하여야 합니다."); } - @DisplayName("포지션의 열이 0~8열이 아닐 경우 예외가 발생한다.") @ParameterizedTest @CsvSource(value = { "0,9", "0,-1", }) - void 포지션_열_예외_테스트(int row, int col) { + void 포지션의_열이_0부터_8열까지가_아닐_경우_예외가_발생한다(int row, int col) { Assertions.assertThatThrownBy(() -> new Position(row, col)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 열은 0열 이상 8열 이하여야 합니다."); } + + @Test + void 행의_차이를_계산한다() { + //given + Position from = new Position(5, 3); + Position to = new Position(3, 3); + //when + int result = from.calculateRowDistance(to); + //then + assertThat(result).isEqualTo(2); + } + + @Test + void 열의_차이를_계산한다() { + //given + Position from = new Position(5, 3); + Position to = new Position(5, 0); + //when + int result = from.calculateColumnDistance(to); + //then + assertThat(result).isEqualTo(3); + } } From a6b1208ca37bdc45b4b91c1bb52808e1e7eb3221 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 11:52:08 +0900 Subject: [PATCH 04/68] =?UTF-8?q?feat:=20=EB=B3=91=EC=9D=98=20=ED=96=89?= =?UTF-8?q?=EB=A7=88=EB=B2=95=20=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상좌우만 가능 (후진 구현x) - 행마법 테스트 추가 --- src/test/java/janggi/domain/Camp.java | 5 ++ src/test/java/janggi/domain/MoveStrategy.java | 8 +++ src/test/java/janggi/domain/Piece.java | 21 ++++++++ src/test/java/janggi/domain/PieceTest.java | 50 +++++++++++++++++++ .../java/janggi/domain/SoldierStrategy.java | 17 +++++++ src/test/java/janggi/domain/Type.java | 22 ++++++++ 6 files changed, 123 insertions(+) create mode 100644 src/test/java/janggi/domain/Camp.java create mode 100644 src/test/java/janggi/domain/MoveStrategy.java create mode 100644 src/test/java/janggi/domain/Piece.java create mode 100644 src/test/java/janggi/domain/PieceTest.java create mode 100644 src/test/java/janggi/domain/SoldierStrategy.java create mode 100644 src/test/java/janggi/domain/Type.java diff --git a/src/test/java/janggi/domain/Camp.java b/src/test/java/janggi/domain/Camp.java new file mode 100644 index 0000000000..5b99c40145 --- /dev/null +++ b/src/test/java/janggi/domain/Camp.java @@ -0,0 +1,5 @@ +package janggi.domain; + +public enum Camp { + HAN, CHO +} diff --git a/src/test/java/janggi/domain/MoveStrategy.java b/src/test/java/janggi/domain/MoveStrategy.java new file mode 100644 index 0000000000..34e52d300f --- /dev/null +++ b/src/test/java/janggi/domain/MoveStrategy.java @@ -0,0 +1,8 @@ +package janggi.domain; + +import java.util.List; + +public interface MoveStrategy { + + List findPath(Position from, Position to); +} diff --git a/src/test/java/janggi/domain/Piece.java b/src/test/java/janggi/domain/Piece.java new file mode 100644 index 0000000000..357648973f --- /dev/null +++ b/src/test/java/janggi/domain/Piece.java @@ -0,0 +1,21 @@ +package janggi.domain; + +import java.util.List; + +public class Piece { + + private final Type type; + private final Camp camp; + private final MoveStrategy moveStrategy; + + public Piece(Type type, Camp camp, MoveStrategy moveStrategy) { + this.type = type; + this.camp = camp; + this.moveStrategy = moveStrategy; + } + + public boolean canMove(Position from, Position to) { + List path = moveStrategy.findPath(from, to); + return true; + } +} diff --git a/src/test/java/janggi/domain/PieceTest.java b/src/test/java/janggi/domain/PieceTest.java new file mode 100644 index 0000000000..038f08e7e6 --- /dev/null +++ b/src/test/java/janggi/domain/PieceTest.java @@ -0,0 +1,50 @@ +package janggi.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class PieceTest { + + public static Stream successMovePositions() { + return Stream.of( + Arguments.of(new Position(6, 0), new Position(5, 0)), + Arguments.of(new Position(5, 1), new Position(4, 1)), + Arguments.of(new Position(6, 0), new Position(6, 1)), + Arguments.of(new Position(6, 1), new Position(6, 0)) + ); + } + + @ParameterizedTest + @MethodSource("successMovePositions") + void 병은_1칸_이동_여부를_확인한다(Position from, Position to) { + //given + Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); + //when + boolean result = piece.canMove(from, to); + //then + assertTrue(result); + } + + public static Stream exceptionMovePositions() { + return Stream.of( + Arguments.of(new Position(6, 0), new Position(5, 1)), + Arguments.of(new Position(6, 0), new Position(6, 2)), + Arguments.of(new Position(3, 2), new Position(2, 3)) + ); + } + + @ParameterizedTest + @MethodSource("exceptionMovePositions") + void 병은_1칸_이동이_아니면_예외가_발생한다(Position from, Position to) { + Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); + + assertThatThrownBy(() -> piece.canMove(from, to)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } +} diff --git a/src/test/java/janggi/domain/SoldierStrategy.java b/src/test/java/janggi/domain/SoldierStrategy.java new file mode 100644 index 0000000000..575c2ea127 --- /dev/null +++ b/src/test/java/janggi/domain/SoldierStrategy.java @@ -0,0 +1,17 @@ +package janggi.domain; + +import java.util.List; + +public class SoldierStrategy implements MoveStrategy { + + @Override + public List findPath(Position from, Position to) { + int rowDiff = Math.abs(from.calculateRowDistance(to)); + int colDiff = Math.abs(from.calculateColumnDistance(to)); + + if (rowDiff + colDiff != 1) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + return List.of(to); + } +} diff --git a/src/test/java/janggi/domain/Type.java b/src/test/java/janggi/domain/Type.java new file mode 100644 index 0000000000..de63e17843 --- /dev/null +++ b/src/test/java/janggi/domain/Type.java @@ -0,0 +1,22 @@ +package janggi.domain; + +public enum Type { + + GENERAL("將"), + CHARIOT("車"), + HORSE("馬"), + CANNON("包"), + GUARD("士"), + ELEPHANT("象"), + SOLDIER("卒"); + + private final String hanja; + + Type(String hanja) { + this.hanja = hanja; + } + + public String getHanja() { + return hanja; + } +} From a590747c352bca302650217b1884ee35c4a9d5fd Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 15:07:28 +0900 Subject: [PATCH 05/68] =?UTF-8?q?refactor:=20=EC=86=8C=EC=8A=A4=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/janggi/domain/piece}/Camp.java | 2 +- src/main/java/janggi/domain/piece/Piece.java | 38 +++++++++++++++++++ .../java/janggi/domain/piece}/Type.java | 7 +--- .../domain/piece/strategy}/MoveStrategy.java | 3 +- .../piece/strategy}/SoldierStrategy.java | 3 +- src/test/java/janggi/domain/Piece.java | 21 ---------- src/test/java/janggi/domain/PieceTest.java | 4 ++ 7 files changed, 48 insertions(+), 30 deletions(-) rename src/{test/java/janggi/domain => main/java/janggi/domain/piece}/Camp.java (54%) create mode 100644 src/main/java/janggi/domain/piece/Piece.java rename src/{test/java/janggi/domain => main/java/janggi/domain/piece}/Type.java (74%) rename src/{test/java/janggi/domain => main/java/janggi/domain/piece/strategy}/MoveStrategy.java (62%) rename src/{test/java/janggi/domain => main/java/janggi/domain/piece/strategy}/SoldierStrategy.java (87%) delete mode 100644 src/test/java/janggi/domain/Piece.java diff --git a/src/test/java/janggi/domain/Camp.java b/src/main/java/janggi/domain/piece/Camp.java similarity index 54% rename from src/test/java/janggi/domain/Camp.java rename to src/main/java/janggi/domain/piece/Camp.java index 5b99c40145..0dd5b4e496 100644 --- a/src/test/java/janggi/domain/Camp.java +++ b/src/main/java/janggi/domain/piece/Camp.java @@ -1,4 +1,4 @@ -package janggi.domain; +package janggi.domain.piece; public enum Camp { HAN, CHO diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java new file mode 100644 index 0000000000..910b4472df --- /dev/null +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -0,0 +1,38 @@ +package janggi.domain.piece; + +import janggi.domain.Position; +import janggi.domain.piece.strategy.MoveStrategy; +import java.util.List; +import java.util.Objects; + +public class Piece { + + private final Type type; + private final Camp camp; + private final MoveStrategy moveStrategy; + + public Piece(Type type, Camp camp, MoveStrategy moveStrategy) { + this.type = type; + this.camp = camp; + this.moveStrategy = moveStrategy; + } + + public boolean canMove(Position from, Position to) { + List path = moveStrategy.findPath(from, to); + return true; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Piece piece = (Piece) o; + return type == piece.type && camp == piece.camp && Objects.equals(moveStrategy, piece.moveStrategy); + } + + @Override + public int hashCode() { + return Objects.hash(type, camp, moveStrategy); + } +} diff --git a/src/test/java/janggi/domain/Type.java b/src/main/java/janggi/domain/piece/Type.java similarity index 74% rename from src/test/java/janggi/domain/Type.java rename to src/main/java/janggi/domain/piece/Type.java index de63e17843..5dde8624c9 100644 --- a/src/test/java/janggi/domain/Type.java +++ b/src/main/java/janggi/domain/piece/Type.java @@ -1,7 +1,6 @@ -package janggi.domain; +package janggi.domain.piece; public enum Type { - GENERAL("將"), CHARIOT("車"), HORSE("馬"), @@ -15,8 +14,4 @@ public enum Type { Type(String hanja) { this.hanja = hanja; } - - public String getHanja() { - return hanja; - } } diff --git a/src/test/java/janggi/domain/MoveStrategy.java b/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java similarity index 62% rename from src/test/java/janggi/domain/MoveStrategy.java rename to src/main/java/janggi/domain/piece/strategy/MoveStrategy.java index 34e52d300f..d76d766f5a 100644 --- a/src/test/java/janggi/domain/MoveStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java @@ -1,5 +1,6 @@ -package janggi.domain; +package janggi.domain.piece.strategy; +import janggi.domain.Position; import java.util.List; public interface MoveStrategy { diff --git a/src/test/java/janggi/domain/SoldierStrategy.java b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java similarity index 87% rename from src/test/java/janggi/domain/SoldierStrategy.java rename to src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java index 575c2ea127..20865e4d94 100644 --- a/src/test/java/janggi/domain/SoldierStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java @@ -1,5 +1,6 @@ -package janggi.domain; +package janggi.domain.piece.strategy; +import janggi.domain.Position; import java.util.List; public class SoldierStrategy implements MoveStrategy { diff --git a/src/test/java/janggi/domain/Piece.java b/src/test/java/janggi/domain/Piece.java deleted file mode 100644 index 357648973f..0000000000 --- a/src/test/java/janggi/domain/Piece.java +++ /dev/null @@ -1,21 +0,0 @@ -package janggi.domain; - -import java.util.List; - -public class Piece { - - private final Type type; - private final Camp camp; - private final MoveStrategy moveStrategy; - - public Piece(Type type, Camp camp, MoveStrategy moveStrategy) { - this.type = type; - this.camp = camp; - this.moveStrategy = moveStrategy; - } - - public boolean canMove(Position from, Position to) { - List path = moveStrategy.findPath(from, to); - return true; - } -} diff --git a/src/test/java/janggi/domain/PieceTest.java b/src/test/java/janggi/domain/PieceTest.java index 038f08e7e6..2f15db58a5 100644 --- a/src/test/java/janggi/domain/PieceTest.java +++ b/src/test/java/janggi/domain/PieceTest.java @@ -3,6 +3,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertTrue; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Type; +import janggi.domain.piece.strategy.SoldierStrategy; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; From 85cedc84ba8aee7f29cc5c9e1b986e045ca752b4 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 15:08:38 +0900 Subject: [PATCH 06/68] =?UTF-8?q?feat:=20=EB=B3=B4=EB=93=9C=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 보드 초기화 테스트 추가 - 초기화 전략 구현 --- .../janggi/domain/board/BoardInitializer.java | 10 +++ .../domain/board/InitialPiecePlacement.java | 68 +++++++++++++++++++ .../board/StandardBoardInitializer.java | 13 ++++ .../janggi/domain/BoardInitializerTest.java | 68 +++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 src/main/java/janggi/domain/board/BoardInitializer.java create mode 100644 src/main/java/janggi/domain/board/InitialPiecePlacement.java create mode 100644 src/main/java/janggi/domain/board/StandardBoardInitializer.java create mode 100644 src/test/java/janggi/domain/BoardInitializerTest.java diff --git a/src/main/java/janggi/domain/board/BoardInitializer.java b/src/main/java/janggi/domain/board/BoardInitializer.java new file mode 100644 index 0000000000..16da5717d7 --- /dev/null +++ b/src/main/java/janggi/domain/board/BoardInitializer.java @@ -0,0 +1,10 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Piece; +import java.util.Map; + +public interface BoardInitializer { + + Map initialize(); +} diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java new file mode 100644 index 0000000000..abc45db657 --- /dev/null +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -0,0 +1,68 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Type; +import janggi.domain.piece.strategy.MoveStrategy; +import java.util.HashMap; +import java.util.Map; + +public enum InitialPiecePlacement { + CHO_SOLDIER_1(3, 0, Camp.CHO, Type.SOLDIER, null), + CHO_SOLDIER_2(3, 2, Camp.CHO, Type.SOLDIER, null), + CHO_SOLDIER_3(3, 4, Camp.CHO, Type.SOLDIER, null), + CHO_SOLDIER_4(3, 6, Camp.CHO, Type.SOLDIER, null), + CHO_SOLDIER_5(3, 8, Camp.CHO, Type.SOLDIER, null), + CHO_CHARIOT_LEFT(0, 0, Camp.CHO, Type.CHARIOT, null), + CHO_ELEPHANT_LEFT(0, 1, Camp.CHO, Type.ELEPHANT, null), + CHO_HORSE_LEFT(0, 2, Camp.CHO, Type.HORSE, null), + CHO_GUARD_LEFT(0, 3, Camp.CHO, Type.GUARD, null), + CHO_GUARD_RIGHT(0, 5, Camp.CHO, Type.GUARD, null), + CHO_ELEPHANT_RIGHT(0, 6, Camp.CHO, Type.ELEPHANT, null), + CHO_HORSE_RIGHT(0, 7, Camp.CHO, Type.HORSE, null), + CHO_CHARIOT_RIGHT(0, 8, Camp.CHO, Type.CHARIOT, null), + CHO_GENERAL(1, 4, Camp.CHO, Type.GENERAL, null), + CHO_CANNON_LEFT(2, 1, Camp.CHO, Type.CANNON, null), + CHO_CANNON_RIGHT(2, 7, Camp.CHO, Type.CANNON, null), + + HAN_SOLDIER_1(6, 0, Camp.HAN, Type.SOLDIER, null), + HAN_SOLDIER_2(6, 2, Camp.HAN, Type.SOLDIER, null), + HAN_SOLDIER_3(6, 4, Camp.HAN, Type.SOLDIER, null), + HAN_SOLDIER_4(6, 6, Camp.HAN, Type.SOLDIER, null), + HAN_SOLDIER_5(6, 8, Camp.HAN, Type.SOLDIER, null), + HAN_CANNON_LEFT(7, 1, Camp.HAN, Type.CANNON, null), + HAN_CANNON_RIGHT(7, 7, Camp.HAN, Type.CANNON, null), + HAN_GENERAL(8, 4, Camp.HAN, Type.GENERAL, null), + HAN_CHARIOT_LEFT(9, 0, Camp.HAN, Type.CHARIOT, null), + HAN_ELEPHANT_LEFT(9, 1, Camp.HAN, Type.ELEPHANT, null), + HAN_HORSE_LEFT(9, 2, Camp.HAN, Type.HORSE, null), + HAN_GUARD_LEFT(9, 3, Camp.HAN, Type.GUARD, null), + HAN_GUARD_RIGHT(9, 5, Camp.HAN, Type.GUARD, null), + HAN_ELEPHANT_RIGHT(9, 6, Camp.HAN, Type.ELEPHANT, null), + HAN_HORSE_RIGHT(9, 7, Camp.HAN, Type.HORSE, null), + HAN_CHARIOT_RIGHT(9, 8, Camp.HAN, Type.CHARIOT, null); + + private final int row; + private final int column; + private final Camp camp; + private final Type type; + private final MoveStrategy moveStrategy; + + InitialPiecePlacement(int row, int column, Camp camp, Type type, MoveStrategy moveStrategy) { + this.row = row; + this.column = column; + this.camp = camp; + this.type = type; + this.moveStrategy = moveStrategy; + } + + public static Map init() { + Map board = new HashMap<>(); + for (InitialPiecePlacement piece : values()) { + board.put(new Position(piece.row, piece.column) + , new Piece(piece.type, piece.camp, piece.moveStrategy)); + } + return board; + } +} diff --git a/src/main/java/janggi/domain/board/StandardBoardInitializer.java b/src/main/java/janggi/domain/board/StandardBoardInitializer.java new file mode 100644 index 0000000000..388439adb5 --- /dev/null +++ b/src/main/java/janggi/domain/board/StandardBoardInitializer.java @@ -0,0 +1,13 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Piece; +import java.util.Map; + +public final class StandardBoardInitializer implements BoardInitializer { + + @Override + public Map initialize() { + return InitialPiecePlacement.init(); + } +} diff --git a/src/test/java/janggi/domain/BoardInitializerTest.java b/src/test/java/janggi/domain/BoardInitializerTest.java new file mode 100644 index 0000000000..e39ff63c22 --- /dev/null +++ b/src/test/java/janggi/domain/BoardInitializerTest.java @@ -0,0 +1,68 @@ +package janggi.domain; + +import janggi.domain.board.BoardInitializer; +import janggi.domain.board.StandardBoardInitializer; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Type; +import java.util.HashMap; +import java.util.Map; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +public class BoardInitializerTest { + + @Test + void 보드의_기물들을_초기화한다() { + // given + BoardInitializer initializer = new StandardBoardInitializer(); + Map expectedBoard = createExpectedBoard(); + // when + Map board = initializer.initialize(); + // then + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(board).hasSize(32); + assertSoftly.assertThat(board).isEqualTo(expectedBoard); + }); + } + + private Map createExpectedBoard() { + Map expectedBoard = new HashMap<>(); + + putPieces(expectedBoard, Type.CHARIOT, Camp.CHO, createPosition(0, 0), createPosition(0, 8)); + putPieces(expectedBoard, Type.ELEPHANT, Camp.CHO, createPosition(0, 1), createPosition(0, 6)); + putPieces(expectedBoard, Type.HORSE, Camp.CHO, createPosition(0, 2), createPosition(0, 7)); + putPieces(expectedBoard, Type.GUARD, Camp.CHO, createPosition(0, 3), createPosition(0, 5)); + putPieces(expectedBoard, Type.GENERAL, Camp.CHO, createPosition(1, 4)); + putPieces(expectedBoard, Type.CANNON, Camp.CHO, createPosition(2, 1), createPosition(2, 7)); + putPieces(expectedBoard, Type.SOLDIER, Camp.CHO, + createPosition(3, 0), createPosition(3, 2), + createPosition(3, 4), createPosition(3, 6), + createPosition(3, 8)); + + putPieces(expectedBoard, Type.CHARIOT, Camp.HAN, createPosition(9, 0), createPosition(9, 8)); + putPieces(expectedBoard, Type.ELEPHANT, Camp.HAN, createPosition(9, 1), createPosition(9, 6)); + putPieces(expectedBoard, Type.HORSE, Camp.HAN, createPosition(9, 2), createPosition(9, 7)); + putPieces(expectedBoard, Type.GUARD, Camp.HAN, createPosition(9, 3), createPosition(9, 5)); + putPieces(expectedBoard, Type.GENERAL, Camp.HAN, createPosition(8, 4)); + putPieces(expectedBoard, Type.CANNON, Camp.HAN, createPosition(7, 1), createPosition(7, 7)); + putPieces(expectedBoard, Type.SOLDIER, Camp.HAN, + createPosition(6, 0), createPosition(6, 2), + createPosition(6, 4), createPosition(6, 6), + createPosition(6, 8)); + + return expectedBoard; + } + + //TODO: moveStrategy 변경 + private void putPieces(Map board, Type type, Camp camp, + Position... positions) { + for (Position position : positions) { + board.put(position, new Piece(type, camp, null)); + } + } + + private Position createPosition(int row, int column) { + return new Position(row, column); + } +} From 0d043017efb6309543151640390a1b3d52276605 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 15:09:15 +0900 Subject: [PATCH 07/68] =?UTF-8?q?refactor:=20Position=20VO=20=EC=A0=84?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Position.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index ad5d977190..1ee8197fec 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -1,16 +1,10 @@ package janggi.domain; -public class Position { +public record Position(int row, int column) { - private final int row; - private final int column; - - public Position(int row, int column) { + public Position { validateRow(row); validateColumn(column); - - this.row = row; - this.column = column; } private void validateRow(int row) { From 053ccf3bc85c36c49c5b51c14dbc937bcec612a2 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 17:41:00 +0900 Subject: [PATCH 08/68] =?UTF-8?q?feat:=20=EC=83=81=EC=B0=A8=EB=A6=BC=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상차림 테스트 추가 --- .../janggi/domain/board/ElephantSetting.java | 103 ++++++++++++++++++ .../domain/board/InitialPiecePlacement.java | 19 ++-- .../board/StandardBoardInitializer.java | 13 ++- ...t.java => JanggiBoardInitializerTest.java} | 53 +++++++-- 4 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 src/main/java/janggi/domain/board/ElephantSetting.java rename src/test/java/janggi/domain/{BoardInitializerTest.java => JanggiBoardInitializerTest.java} (55%) diff --git a/src/main/java/janggi/domain/board/ElephantSetting.java b/src/main/java/janggi/domain/board/ElephantSetting.java new file mode 100644 index 0000000000..b05a57c152 --- /dev/null +++ b/src/main/java/janggi/domain/board/ElephantSetting.java @@ -0,0 +1,103 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Type; +import java.util.Map; + +public enum ElephantSetting { + + // TODO: position 중복 제거, List.of(1,2,6,7) 과 같은 방식 고려... + + CHO_LEFT_ELEPHANT { + @Override + public Map makeElephants() { + return Map.of( + new Position(0, 7), new Piece(Type.ELEPHANT, Camp.CHO, null), + new Position(0, 6), new Piece(Type.HORSE, Camp.CHO, null), + new Position(0, 2), new Piece(Type.ELEPHANT, Camp.CHO, null), + new Position(0, 1), new Piece(Type.HORSE, Camp.CHO, null) + ); + } + }, + CHO_RIGHT_ELEPHANT { + @Override + public Map makeElephants() { + return Map.of( + new Position(0, 7), new Piece(Type.HORSE, Camp.CHO, null), + new Position(0, 6), new Piece(Type.ELEPHANT, Camp.CHO, null), + new Position(0, 2), new Piece(Type.HORSE, Camp.CHO, null), + new Position(0, 1), new Piece(Type.ELEPHANT, Camp.CHO, null) + ); + } + }, + CHO_INNER_ELEPHANT { + @Override + public Map makeElephants() { + return Map.of( + new Position(0, 7), new Piece(Type.HORSE, Camp.CHO, null), + new Position(0, 6), new Piece(Type.ELEPHANT, Camp.CHO, null), + new Position(0, 2), new Piece(Type.ELEPHANT, Camp.CHO, null), + new Position(0, 1), new Piece(Type.HORSE, Camp.CHO, null) + ); + } + }, + CHO_OUTER_ELEPHANT { + @Override + public Map makeElephants() { + return Map.of( + new Position(0, 7), new Piece(Type.ELEPHANT, Camp.CHO, null), + new Position(0, 6), new Piece(Type.HORSE, Camp.CHO, null), + new Position(0, 2), new Piece(Type.HORSE, Camp.CHO, null), + new Position(0, 1), new Piece(Type.ELEPHANT, Camp.CHO, null) + ); + } + }, + HAN_LEFT_ELEPHANT { + @Override + public Map makeElephants() { + return Map.of( + new Position(9, 1), new Piece(Type.ELEPHANT, Camp.HAN, null), + new Position(9, 2), new Piece(Type.HORSE, Camp.HAN, null), + new Position(9, 6), new Piece(Type.ELEPHANT, Camp.HAN, null), + new Position(9, 7), new Piece(Type.HORSE, Camp.HAN, null) + ); + } + }, + HAN_RIGHT_ELEPHANT { + @Override + public Map makeElephants() { + return Map.of( + new Position(9, 1), new Piece(Type.HORSE, Camp.HAN, null), + new Position(9, 2), new Piece(Type.ELEPHANT, Camp.HAN, null), + new Position(9, 6), new Piece(Type.HORSE, Camp.HAN, null), + new Position(9, 7), new Piece(Type.ELEPHANT, Camp.HAN, null) + ); + } + }, + HAN_INNER_ELEPHANT { + @Override + public Map makeElephants() { + return Map.of( + new Position(9, 1), new Piece(Type.HORSE, Camp.HAN, null), + new Position(9, 2), new Piece(Type.ELEPHANT, Camp.HAN, null), + new Position(9, 6), new Piece(Type.ELEPHANT, Camp.HAN, null), + new Position(9, 7), new Piece(Type.HORSE, Camp.HAN, null) + ); + } + }, + HAN_OUTER_ELEPHANT { + @Override + public Map makeElephants() { + return Map.of( + new Position(9, 1), new Piece(Type.ELEPHANT, Camp.HAN, null), + new Position(9, 2), new Piece(Type.HORSE, Camp.HAN, null), + new Position(9, 6), new Piece(Type.HORSE, Camp.HAN, null), + new Position(9, 7), new Piece(Type.ELEPHANT, Camp.HAN, null) + ); + } + }; + + abstract public Map makeElephants(); +} diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java index abc45db657..fcd9a3a759 100644 --- a/src/main/java/janggi/domain/board/InitialPiecePlacement.java +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -9,18 +9,15 @@ import java.util.Map; public enum InitialPiecePlacement { + CHO_SOLDIER_1(3, 0, Camp.CHO, Type.SOLDIER, null), CHO_SOLDIER_2(3, 2, Camp.CHO, Type.SOLDIER, null), CHO_SOLDIER_3(3, 4, Camp.CHO, Type.SOLDIER, null), CHO_SOLDIER_4(3, 6, Camp.CHO, Type.SOLDIER, null), CHO_SOLDIER_5(3, 8, Camp.CHO, Type.SOLDIER, null), CHO_CHARIOT_LEFT(0, 0, Camp.CHO, Type.CHARIOT, null), - CHO_ELEPHANT_LEFT(0, 1, Camp.CHO, Type.ELEPHANT, null), - CHO_HORSE_LEFT(0, 2, Camp.CHO, Type.HORSE, null), CHO_GUARD_LEFT(0, 3, Camp.CHO, Type.GUARD, null), CHO_GUARD_RIGHT(0, 5, Camp.CHO, Type.GUARD, null), - CHO_ELEPHANT_RIGHT(0, 6, Camp.CHO, Type.ELEPHANT, null), - CHO_HORSE_RIGHT(0, 7, Camp.CHO, Type.HORSE, null), CHO_CHARIOT_RIGHT(0, 8, Camp.CHO, Type.CHARIOT, null), CHO_GENERAL(1, 4, Camp.CHO, Type.GENERAL, null), CHO_CANNON_LEFT(2, 1, Camp.CHO, Type.CANNON, null), @@ -35,12 +32,8 @@ public enum InitialPiecePlacement { HAN_CANNON_RIGHT(7, 7, Camp.HAN, Type.CANNON, null), HAN_GENERAL(8, 4, Camp.HAN, Type.GENERAL, null), HAN_CHARIOT_LEFT(9, 0, Camp.HAN, Type.CHARIOT, null), - HAN_ELEPHANT_LEFT(9, 1, Camp.HAN, Type.ELEPHANT, null), - HAN_HORSE_LEFT(9, 2, Camp.HAN, Type.HORSE, null), HAN_GUARD_LEFT(9, 3, Camp.HAN, Type.GUARD, null), HAN_GUARD_RIGHT(9, 5, Camp.HAN, Type.GUARD, null), - HAN_ELEPHANT_RIGHT(9, 6, Camp.HAN, Type.ELEPHANT, null), - HAN_HORSE_RIGHT(9, 7, Camp.HAN, Type.HORSE, null), HAN_CHARIOT_RIGHT(9, 8, Camp.HAN, Type.CHARIOT, null); private final int row; @@ -57,12 +50,20 @@ public enum InitialPiecePlacement { this.moveStrategy = moveStrategy; } - public static Map init() { + // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 + public static Map init(ElephantSetting choElephantSetting, ElephantSetting hanElephantSetting) { Map board = new HashMap<>(); + for (InitialPiecePlacement piece : values()) { board.put(new Position(piece.row, piece.column) , new Piece(piece.type, piece.camp, piece.moveStrategy)); } + + Map choElephants = choElephantSetting.makeElephants(); + Map hanElephants = hanElephantSetting.makeElephants(); + + board.putAll(choElephants); + board.putAll(hanElephants); return board; } } diff --git a/src/main/java/janggi/domain/board/StandardBoardInitializer.java b/src/main/java/janggi/domain/board/StandardBoardInitializer.java index 388439adb5..873e3bf1c8 100644 --- a/src/main/java/janggi/domain/board/StandardBoardInitializer.java +++ b/src/main/java/janggi/domain/board/StandardBoardInitializer.java @@ -4,10 +4,19 @@ import janggi.domain.piece.Piece; import java.util.Map; -public final class StandardBoardInitializer implements BoardInitializer { +public class StandardBoardInitializer implements BoardInitializer { + + private final ElephantSetting choElephantSetting; + private final ElephantSetting hanElephantSetting; + + // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 + public StandardBoardInitializer(ElephantSetting choElephantSetting, ElephantSetting hanElephantSetting) { + this.choElephantSetting = choElephantSetting; + this.hanElephantSetting = hanElephantSetting; + } @Override public Map initialize() { - return InitialPiecePlacement.init(); + return InitialPiecePlacement.init(choElephantSetting, hanElephantSetting); } } diff --git a/src/test/java/janggi/domain/BoardInitializerTest.java b/src/test/java/janggi/domain/JanggiBoardInitializerTest.java similarity index 55% rename from src/test/java/janggi/domain/BoardInitializerTest.java rename to src/test/java/janggi/domain/JanggiBoardInitializerTest.java index e39ff63c22..bdff318a74 100644 --- a/src/test/java/janggi/domain/BoardInitializerTest.java +++ b/src/test/java/janggi/domain/JanggiBoardInitializerTest.java @@ -1,6 +1,7 @@ package janggi.domain; import janggi.domain.board.BoardInitializer; +import janggi.domain.board.ElephantSetting; import janggi.domain.board.StandardBoardInitializer; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; @@ -10,13 +11,31 @@ import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; -public class BoardInitializerTest { +public class JanggiBoardInitializerTest { @Test - void 보드의_기물들을_초기화한다() { + void 초_마상마상_한_상마상마_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer(); + BoardInitializer initializer = new StandardBoardInitializer(ElephantSetting.CHO_RIGHT_ELEPHANT, + ElephantSetting.HAN_LEFT_ELEPHANT); Map expectedBoard = createExpectedBoard(); + expectedBoard.putAll(createChoLeftHanRightBoard()); + // when + Map board = initializer.initialize(); + // then + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(board).hasSize(32); + assertSoftly.assertThat(board).isEqualTo(expectedBoard); + }); + } + + @Test + void 초_마상상마_한_상마마상_으로_보드를_초기화한다() { + // given + BoardInitializer initializer = new StandardBoardInitializer(ElephantSetting.CHO_INNER_ELEPHANT, + ElephantSetting.HAN_OUTER_ELEPHANT); + Map expectedBoard = createExpectedBoard(); + expectedBoard.putAll(createChoInnerHanOuterBoard()); // when Map board = initializer.initialize(); // then @@ -30,8 +49,6 @@ private Map createExpectedBoard() { Map expectedBoard = new HashMap<>(); putPieces(expectedBoard, Type.CHARIOT, Camp.CHO, createPosition(0, 0), createPosition(0, 8)); - putPieces(expectedBoard, Type.ELEPHANT, Camp.CHO, createPosition(0, 1), createPosition(0, 6)); - putPieces(expectedBoard, Type.HORSE, Camp.CHO, createPosition(0, 2), createPosition(0, 7)); putPieces(expectedBoard, Type.GUARD, Camp.CHO, createPosition(0, 3), createPosition(0, 5)); putPieces(expectedBoard, Type.GENERAL, Camp.CHO, createPosition(1, 4)); putPieces(expectedBoard, Type.CANNON, Camp.CHO, createPosition(2, 1), createPosition(2, 7)); @@ -41,8 +58,6 @@ private Map createExpectedBoard() { createPosition(3, 8)); putPieces(expectedBoard, Type.CHARIOT, Camp.HAN, createPosition(9, 0), createPosition(9, 8)); - putPieces(expectedBoard, Type.ELEPHANT, Camp.HAN, createPosition(9, 1), createPosition(9, 6)); - putPieces(expectedBoard, Type.HORSE, Camp.HAN, createPosition(9, 2), createPosition(9, 7)); putPieces(expectedBoard, Type.GUARD, Camp.HAN, createPosition(9, 3), createPosition(9, 5)); putPieces(expectedBoard, Type.GENERAL, Camp.HAN, createPosition(8, 4)); putPieces(expectedBoard, Type.CANNON, Camp.HAN, createPosition(7, 1), createPosition(7, 7)); @@ -54,6 +69,30 @@ private Map createExpectedBoard() { return expectedBoard; } + private Map createChoLeftHanRightBoard() { + Map board = new HashMap<>(); + + putPieces(board, Type.ELEPHANT, Camp.CHO, createPosition(0, 1), createPosition(0, 6)); + putPieces(board, Type.HORSE, Camp.CHO, createPosition(0, 2), createPosition(0, 7)); + + putPieces(board, Type.ELEPHANT, Camp.HAN, createPosition(9, 1), createPosition(9, 6)); + putPieces(board, Type.HORSE, Camp.HAN, createPosition(9, 2), createPosition(9, 7)); + + return board; + } + + private Map createChoInnerHanOuterBoard() { + Map board = new HashMap<>(); + + putPieces(board, Type.ELEPHANT, Camp.CHO, createPosition(0, 2), createPosition(0, 6)); + putPieces(board, Type.HORSE, Camp.CHO, createPosition(0, 1), createPosition(0, 7)); + + putPieces(board, Type.ELEPHANT, Camp.HAN, createPosition(9, 1), createPosition(9, 7)); + putPieces(board, Type.HORSE, Camp.HAN, createPosition(9, 2), createPosition(9, 6)); + + return board; + } + //TODO: moveStrategy 변경 private void putPieces(Map board, Type type, Camp camp, Position... positions) { From c07bf10173024ec5dac9428e789c7f6798b79198 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 19:40:48 +0900 Subject: [PATCH 09/68] =?UTF-8?q?feat:=20=EC=A1=B8/=EB=B3=91=20=ED=9B=84?= =?UTF-8?q?=EC=A7=84=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/piece/Camp.java | 16 ++- src/main/java/janggi/domain/piece/Piece.java | 2 +- .../domain/piece/strategy/MoveStrategy.java | 3 +- .../piece/strategy/SoldierStrategy.java | 8 +- src/test/java/janggi/domain/PieceTest.java | 108 ++++++++++++------ 5 files changed, 100 insertions(+), 37 deletions(-) diff --git a/src/main/java/janggi/domain/piece/Camp.java b/src/main/java/janggi/domain/piece/Camp.java index 0dd5b4e496..40af47f944 100644 --- a/src/main/java/janggi/domain/piece/Camp.java +++ b/src/main/java/janggi/domain/piece/Camp.java @@ -1,5 +1,19 @@ package janggi.domain.piece; public enum Camp { - HAN, CHO + + HAN(1), + CHO(-1); + + private final int forwardDirection; + + Camp(int forwardDirection) { + this.forwardDirection = forwardDirection; + } + + public void validateForwardDirection(int rowDiff) { + if (forwardDirection != rowDiff && rowDiff != 0) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 910b4472df..f53010e07d 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -18,7 +18,7 @@ public Piece(Type type, Camp camp, MoveStrategy moveStrategy) { } public boolean canMove(Position from, Position to) { - List path = moveStrategy.findPath(from, to); + List path = moveStrategy.findPath(from, to, camp); return true; } diff --git a/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java b/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java index d76d766f5a..5721e8f7e7 100644 --- a/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java @@ -1,9 +1,10 @@ package janggi.domain.piece.strategy; import janggi.domain.Position; +import janggi.domain.piece.Camp; import java.util.List; public interface MoveStrategy { - List findPath(Position from, Position to); + List findPath(Position from, Position to, Camp camp); } diff --git a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java index 20865e4d94..11268129c7 100644 --- a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java @@ -1,13 +1,17 @@ package janggi.domain.piece.strategy; import janggi.domain.Position; +import janggi.domain.piece.Camp; import java.util.List; public class SoldierStrategy implements MoveStrategy { @Override - public List findPath(Position from, Position to) { - int rowDiff = Math.abs(from.calculateRowDistance(to)); + public List findPath(Position from, Position to, Camp camp) { + int rowDiff = from.calculateRowDistance(to); + camp.validateForwardDirection(rowDiff); + + rowDiff = Math.abs(rowDiff); int colDiff = Math.abs(from.calculateColumnDistance(to)); if (rowDiff + colDiff != 1) { diff --git a/src/test/java/janggi/domain/PieceTest.java b/src/test/java/janggi/domain/PieceTest.java index 2f15db58a5..13847d3bf5 100644 --- a/src/test/java/janggi/domain/PieceTest.java +++ b/src/test/java/janggi/domain/PieceTest.java @@ -8,47 +8,91 @@ import janggi.domain.piece.Type; import janggi.domain.piece.strategy.SoldierStrategy; import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; public class PieceTest { - public static Stream successMovePositions() { - return Stream.of( - Arguments.of(new Position(6, 0), new Position(5, 0)), - Arguments.of(new Position(5, 1), new Position(4, 1)), - Arguments.of(new Position(6, 0), new Position(6, 1)), - Arguments.of(new Position(6, 1), new Position(6, 0)) - ); - } + @DisplayName("졸/병 테스트") + @Nested + class Soldier { + public static Stream successMovePositions() { + return Stream.of( + Arguments.of(new Position(6, 0), new Position(5, 0)), + Arguments.of(new Position(5, 1), new Position(4, 1)), + Arguments.of(new Position(6, 0), new Position(6, 1)), + Arguments.of(new Position(6, 1), new Position(6, 0)) + ); + } - @ParameterizedTest - @MethodSource("successMovePositions") - void 병은_1칸_이동_여부를_확인한다(Position from, Position to) { - //given - Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); - //when - boolean result = piece.canMove(from, to); - //then - assertTrue(result); - } + @ParameterizedTest + @MethodSource("successMovePositions") + void 병의_1칸_이동_여부를_확인한다(Position from, Position to) { + //given + Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); + //when + boolean result = piece.canMove(from, to); + //then + assertTrue(result); + } - public static Stream exceptionMovePositions() { - return Stream.of( - Arguments.of(new Position(6, 0), new Position(5, 1)), - Arguments.of(new Position(6, 0), new Position(6, 2)), - Arguments.of(new Position(3, 2), new Position(2, 3)) - ); - } + public static Stream exceptionMovePositions() { + return Stream.of( + Arguments.of(new Position(6, 0), new Position(5, 1)), + Arguments.of(new Position(6, 0), new Position(6, 2)), + Arguments.of(new Position(3, 2), new Position(2, 3)) + ); + } + + @ParameterizedTest + @MethodSource("exceptionMovePositions") + void 병은_1칸_이동이_아니면_예외가_발생한다(Position from, Position to) { + Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); + + assertThatThrownBy(() -> piece.canMove(from, to)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 병은_후진_시_예외가_발생한다() { + Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); + + assertThatThrownBy(() -> piece.canMove(new Position(6, 0), new Position(7, 0))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 졸의_1칸_이동_여부를_확인한다() { + //given + Piece piece = new Piece(Type.SOLDIER, Camp.CHO, new SoldierStrategy()); + //when + boolean result = piece.canMove(new Position(3, 0), new Position(4, 0)); + //then + assertTrue(result); + } + + @Test + void 졸은_1칸_이동이_아니면_예외가_발생한다() { + Piece piece = new Piece(Type.SOLDIER, Camp.CHO, new SoldierStrategy()); + + assertThatThrownBy(() -> piece.canMove(new Position(3, 0), new Position(5, 0))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } - @ParameterizedTest - @MethodSource("exceptionMovePositions") - void 병은_1칸_이동이_아니면_예외가_발생한다(Position from, Position to) { - Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); + @Test + void 졸은_후진_시_예외가_발생한다() { + Piece piece = new Piece(Type.SOLDIER, Camp.CHO, new SoldierStrategy()); - assertThatThrownBy(() -> piece.canMove(from, to)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + assertThatThrownBy(() -> piece.canMove(new Position(3, 0), new Position(2, 0))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } } } From 23e026f36ed39dbdb83a0a5e7a24bdfbab16d90a Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 20:06:42 +0900 Subject: [PATCH 10/68] =?UTF-8?q?feat:=20=EC=A1=B8/=EB=B3=91=20=ED=9B=84?= =?UTF-8?q?=EC=A7=84=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategy/GeneralAndGuardStrategy.java | 19 ++++++++ src/test/java/janggi/domain/PieceTest.java | 46 ++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/main/java/janggi/domain/piece/strategy/GeneralAndGuardStrategy.java diff --git a/src/main/java/janggi/domain/piece/strategy/GeneralAndGuardStrategy.java b/src/main/java/janggi/domain/piece/strategy/GeneralAndGuardStrategy.java new file mode 100644 index 0000000000..4dfe1983ec --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/GeneralAndGuardStrategy.java @@ -0,0 +1,19 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.List; + +public class GeneralAndGuardStrategy implements MoveStrategy { + + @Override + public List findPath(Position from, Position to, Camp camp) { + int rowDiff = Math.abs(from.calculateRowDistance(to)); + int colDiff = Math.abs(from.calculateColumnDistance(to)); + + if (rowDiff + colDiff != 1) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + return List.of(to); + } +} diff --git a/src/test/java/janggi/domain/PieceTest.java b/src/test/java/janggi/domain/PieceTest.java index 13847d3bf5..0f05f868b3 100644 --- a/src/test/java/janggi/domain/PieceTest.java +++ b/src/test/java/janggi/domain/PieceTest.java @@ -6,6 +6,7 @@ import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.Type; +import janggi.domain.piece.strategy.GeneralAndGuardStrategy; import janggi.domain.piece.strategy.SoldierStrategy; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; @@ -13,6 +14,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; public class PieceTest { @@ -20,7 +22,7 @@ public class PieceTest { @DisplayName("졸/병 테스트") @Nested class Soldier { - public static Stream successMovePositions() { + private static Stream successMovePositions() { return Stream.of( Arguments.of(new Position(6, 0), new Position(5, 0)), Arguments.of(new Position(5, 1), new Position(4, 1)), @@ -40,7 +42,7 @@ public static Stream successMovePositions() { assertTrue(result); } - public static Stream exceptionMovePositions() { + private static Stream exceptionMovePositions() { return Stream.of( Arguments.of(new Position(6, 0), new Position(5, 1)), Arguments.of(new Position(6, 0), new Position(6, 2)), @@ -95,4 +97,44 @@ public static Stream exceptionMovePositions() { .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } + + @DisplayName("궁/사 테스트") + @Nested + class GeneralAndGuard { + + private static Stream successMovePositions() { + return Stream.of( + Arguments.of(Type.GENERAL, new Position(1, 4), new Position(1, 5)), + Arguments.of(Type.GENERAL, new Position(1, 4), new Position(1, 3)), + Arguments.of(Type.GENERAL, new Position(1, 4), new Position(0, 4)), + Arguments.of(Type.GENERAL, new Position(1, 4), new Position(2, 4)), + + Arguments.of(Type.GUARD, new Position(1, 4), new Position(1, 5)), + Arguments.of(Type.GUARD, new Position(1, 4), new Position(1, 3)), + Arguments.of(Type.GUARD, new Position(1, 4), new Position(0, 4)), + Arguments.of(Type.GUARD, new Position(1, 4), new Position(2, 4)) + ); + } + + @ParameterizedTest + @MethodSource("successMovePositions") + void 궁과_사는_상하좌우_1칸_이동한다(Type type, Position from, Position to) { + //given + Piece piece = new Piece(type, Camp.CHO, new GeneralAndGuardStrategy()); + //when + boolean result = piece.canMove(from, to); + //then + assertTrue(result); + } + + @ParameterizedTest + @CsvSource(value = {"GENERAL, GUARD"}) + void 궁과_사는_1칸_이동이_아니면_예외가_발생한다(Type type) { + Piece piece = new Piece(type, Camp.CHO, new GeneralAndGuardStrategy()); + + assertThatThrownBy(() -> piece.canMove(new Position(3, 0), new Position(5, 0))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } } From a4612555bb446ea5916e2b3e8ce9084a9c74a58f Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 20:22:34 +0900 Subject: [PATCH 11/68] =?UTF-8?q?test:=20=ED=96=89=EB=A7=88=EB=B2=95=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 Piece 테스트 제거 --- src/test/java/janggi/domain/PieceTest.java | 140 ------------------ .../StandardBoardInitializerTest.java} | 8 +- .../strategy/GeneralAndGuardStrategyTest.java | 46 ++++++ .../piece/strategy/SoldierStrategyTest.java | 98 ++++++++++++ 4 files changed, 147 insertions(+), 145 deletions(-) delete mode 100644 src/test/java/janggi/domain/PieceTest.java rename src/test/java/janggi/domain/{JanggiBoardInitializerTest.java => board/StandardBoardInitializerTest.java} (95%) create mode 100644 src/test/java/janggi/domain/piece/strategy/GeneralAndGuardStrategyTest.java create mode 100644 src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java diff --git a/src/test/java/janggi/domain/PieceTest.java b/src/test/java/janggi/domain/PieceTest.java deleted file mode 100644 index 0f05f868b3..0000000000 --- a/src/test/java/janggi/domain/PieceTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package janggi.domain; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import janggi.domain.piece.Camp; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Type; -import janggi.domain.piece.strategy.GeneralAndGuardStrategy; -import janggi.domain.piece.strategy.SoldierStrategy; -import java.util.stream.Stream; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.MethodSource; - -public class PieceTest { - - @DisplayName("졸/병 테스트") - @Nested - class Soldier { - private static Stream successMovePositions() { - return Stream.of( - Arguments.of(new Position(6, 0), new Position(5, 0)), - Arguments.of(new Position(5, 1), new Position(4, 1)), - Arguments.of(new Position(6, 0), new Position(6, 1)), - Arguments.of(new Position(6, 1), new Position(6, 0)) - ); - } - - @ParameterizedTest - @MethodSource("successMovePositions") - void 병의_1칸_이동_여부를_확인한다(Position from, Position to) { - //given - Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); - //when - boolean result = piece.canMove(from, to); - //then - assertTrue(result); - } - - private static Stream exceptionMovePositions() { - return Stream.of( - Arguments.of(new Position(6, 0), new Position(5, 1)), - Arguments.of(new Position(6, 0), new Position(6, 2)), - Arguments.of(new Position(3, 2), new Position(2, 3)) - ); - } - - @ParameterizedTest - @MethodSource("exceptionMovePositions") - void 병은_1칸_이동이_아니면_예외가_발생한다(Position from, Position to) { - Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); - - assertThatThrownBy(() -> piece.canMove(from, to)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); - } - - @Test - void 병은_후진_시_예외가_발생한다() { - Piece piece = new Piece(Type.SOLDIER, Camp.HAN, new SoldierStrategy()); - - assertThatThrownBy(() -> piece.canMove(new Position(6, 0), new Position(7, 0))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); - } - - @Test - void 졸의_1칸_이동_여부를_확인한다() { - //given - Piece piece = new Piece(Type.SOLDIER, Camp.CHO, new SoldierStrategy()); - //when - boolean result = piece.canMove(new Position(3, 0), new Position(4, 0)); - //then - assertTrue(result); - } - - @Test - void 졸은_1칸_이동이_아니면_예외가_발생한다() { - Piece piece = new Piece(Type.SOLDIER, Camp.CHO, new SoldierStrategy()); - - assertThatThrownBy(() -> piece.canMove(new Position(3, 0), new Position(5, 0))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); - } - - @Test - void 졸은_후진_시_예외가_발생한다() { - Piece piece = new Piece(Type.SOLDIER, Camp.CHO, new SoldierStrategy()); - - assertThatThrownBy(() -> piece.canMove(new Position(3, 0), new Position(2, 0))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); - } - } - - @DisplayName("궁/사 테스트") - @Nested - class GeneralAndGuard { - - private static Stream successMovePositions() { - return Stream.of( - Arguments.of(Type.GENERAL, new Position(1, 4), new Position(1, 5)), - Arguments.of(Type.GENERAL, new Position(1, 4), new Position(1, 3)), - Arguments.of(Type.GENERAL, new Position(1, 4), new Position(0, 4)), - Arguments.of(Type.GENERAL, new Position(1, 4), new Position(2, 4)), - - Arguments.of(Type.GUARD, new Position(1, 4), new Position(1, 5)), - Arguments.of(Type.GUARD, new Position(1, 4), new Position(1, 3)), - Arguments.of(Type.GUARD, new Position(1, 4), new Position(0, 4)), - Arguments.of(Type.GUARD, new Position(1, 4), new Position(2, 4)) - ); - } - - @ParameterizedTest - @MethodSource("successMovePositions") - void 궁과_사는_상하좌우_1칸_이동한다(Type type, Position from, Position to) { - //given - Piece piece = new Piece(type, Camp.CHO, new GeneralAndGuardStrategy()); - //when - boolean result = piece.canMove(from, to); - //then - assertTrue(result); - } - - @ParameterizedTest - @CsvSource(value = {"GENERAL, GUARD"}) - void 궁과_사는_1칸_이동이_아니면_예외가_발생한다(Type type) { - Piece piece = new Piece(type, Camp.CHO, new GeneralAndGuardStrategy()); - - assertThatThrownBy(() -> piece.canMove(new Position(3, 0), new Position(5, 0))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); - } - } -} diff --git a/src/test/java/janggi/domain/JanggiBoardInitializerTest.java b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java similarity index 95% rename from src/test/java/janggi/domain/JanggiBoardInitializerTest.java rename to src/test/java/janggi/domain/board/StandardBoardInitializerTest.java index bdff318a74..4d6073ba30 100644 --- a/src/test/java/janggi/domain/JanggiBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java @@ -1,8 +1,6 @@ -package janggi.domain; +package janggi.domain.board; -import janggi.domain.board.BoardInitializer; -import janggi.domain.board.ElephantSetting; -import janggi.domain.board.StandardBoardInitializer; +import janggi.domain.Position; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.Type; @@ -11,7 +9,7 @@ import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; -public class JanggiBoardInitializerTest { +public class StandardBoardInitializerTest { @Test void 초_마상마상_한_상마상마_으로_보드를_초기화한다() { diff --git a/src/test/java/janggi/domain/piece/strategy/GeneralAndGuardStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/GeneralAndGuardStrategyTest.java new file mode 100644 index 0000000000..f9f46a3010 --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/GeneralAndGuardStrategyTest.java @@ -0,0 +1,46 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class GeneralAndGuardStrategyTest { + + private final MoveStrategy strategy = new GeneralAndGuardStrategy(); + + private static Stream successMovePositions() { + return Stream.of( + Arguments.of(new Position(1, 4), new Position(1, 5)), + Arguments.of(new Position(1, 4), new Position(1, 3)), + Arguments.of(new Position(1, 4), new Position(0, 4)), + Arguments.of(new Position(1, 4), new Position(2, 4)) + ); + } + + @ParameterizedTest + @MethodSource("successMovePositions") + void 궁과_사는_상하좌우_1칸_이동한다(Position from, Position to) { + //when + List path = strategy.findPath(from, to, Camp.HAN); + //then + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(1); + assertSoftly.assertThat(path).containsExactly(to); + }); + } + + @Test + void 궁과_사는_1칸_이동이_아니면_예외가_발생한다() { + assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.HAN)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java new file mode 100644 index 0000000000..d8c966c675 --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java @@ -0,0 +1,98 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SoldierStrategyTest { + + private final MoveStrategy strategy = new SoldierStrategy(); + + @DisplayName("병 행마법 테스트") + @Nested + class Byung { + private static Stream successMovePositions() { + return Stream.of( + Arguments.of(new Position(6, 0), new Position(5, 0)), + Arguments.of(new Position(5, 1), new Position(4, 1)), + Arguments.of(new Position(6, 0), new Position(6, 1)), + Arguments.of(new Position(6, 1), new Position(6, 0)) + ); + } + + private static Stream exceptionMovePositions() { + return Stream.of( + Arguments.of(new Position(6, 0), new Position(5, 1)), + Arguments.of(new Position(6, 0), new Position(6, 2)), + Arguments.of(new Position(3, 2), new Position(2, 3)) + ); + } + + @ParameterizedTest + @MethodSource("successMovePositions") + void 병의_1칸_이동_여부를_확인한다(Position from, Position to) { + List path = strategy.findPath(from, to, Camp.HAN); + + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(1); + assertSoftly.assertThat(path).containsExactly(to); + }); + } + + @ParameterizedTest + @MethodSource("exceptionMovePositions") + void 병은_1칸_이동이_아니면_예외가_발생한다(Position from, Position to) { + assertThatThrownBy(() -> strategy.findPath(from, to, Camp.HAN)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 병은_후진_시_예외가_발생한다() { + assertThatThrownBy(() -> strategy.findPath(new Position(6, 0), new Position(7, 0), Camp.HAN)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + @DisplayName("졸 행마법 테스트") + @Nested + class Zol { + @Test + void 졸의_1칸_이동_여부를_확인한다() { + Position from = new Position(3, 0); + Position to = new Position(4, 0); + + List path = strategy.findPath(from, to, Camp.CHO); + + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(1); + assertSoftly.assertThat(path).containsExactly(to); + }); + } + + @Test + void 졸은_1칸_이동이_아니면_예외가_발생한다() { + assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 졸은_후진_시_예외가_발생한다() { + assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(2, 0), Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } +} From 84cf94751c7e84cc1bed73622de6a767a856e32d Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 21:29:13 +0900 Subject: [PATCH 12/68] =?UTF-8?q?feat:=20=EC=B0=A8=20=ED=96=89=EB=A7=88?= =?UTF-8?q?=EB=B2=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 차 행마법 테스트 추가 - Position 위치 1칸 이동 기능 추가 --- src/main/java/janggi/domain/Position.java | 8 ++ .../piece/strategy/ChariotStrategy.java | 66 ++++++++++++++ .../piece/strategy/ChariotStrategyTest.java | 90 +++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 src/main/java/janggi/domain/piece/strategy/ChariotStrategy.java create mode 100644 src/test/java/janggi/domain/piece/strategy/ChariotStrategyTest.java diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index 1ee8197fec..d28cfefb5e 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -26,4 +26,12 @@ public int calculateRowDistance(Position other) { public int calculateColumnDistance(Position other) { return column - other.column; } + + public Position moveRow(int direction) { + return new Position(row + direction, column); + } + + public Position moveCol(int direction) { + return new Position(row, column + direction); + } } diff --git a/src/main/java/janggi/domain/piece/strategy/ChariotStrategy.java b/src/main/java/janggi/domain/piece/strategy/ChariotStrategy.java new file mode 100644 index 0000000000..5bbbb1711e --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/ChariotStrategy.java @@ -0,0 +1,66 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.ArrayList; +import java.util.List; + +public class ChariotStrategy implements MoveStrategy { + + @Override + public List findPath(Position from, Position to, Camp camp) { + int rowDiff = to.calculateRowDistance(from); + int colDiff = to.calculateColumnDistance(from); + + validateStraightMove(rowDiff, colDiff); + + List path = new ArrayList<>(); + + path.addAll(createRowPath(from, rowDiff)); + path.addAll(createColumnPath(from, colDiff)); + + return path; + } + + private void validateStraightMove(int rowDiff, int colDiff) { + int sum = rowDiff + colDiff; + + if (sum != rowDiff && sum != colDiff) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + private List createRowPath(Position from, int rowDiff) { + if (rowDiff == 0) { + return List.of(); + } + + List path = new ArrayList<>(); + + int rowDirection = rowDiff / Math.abs(rowDiff); + while (rowDiff != 0) { + from = from.moveRow(rowDirection); + path.add(from); + rowDiff -= rowDirection; + } + + return path; + } + + private List createColumnPath(Position from, int colDiff) { + if (colDiff == 0) { + return List.of(); + } + + List path = new ArrayList<>(); + + int columnDirection = colDiff / Math.abs(colDiff); + while (colDiff != 0) { + from = from.moveCol(columnDirection); + path.add(from); + colDiff -= columnDirection; + } + + return path; + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/ChariotStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/ChariotStrategyTest.java new file mode 100644 index 0000000000..dda37a13b8 --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/ChariotStrategyTest.java @@ -0,0 +1,90 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ChariotStrategyTest { + + private final MoveStrategy strategy = new ChariotStrategy(); + + private static Stream createPositionsAndPath() { + return Stream.of( + Arguments.of(new Position(0, 0), new Position(9, 0), 9, + List.of( + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + new Position(4, 0), + new Position(5, 0), + new Position(6, 0), + new Position(7, 0), + new Position(8, 0), + new Position(9, 0) + )), + Arguments.of(new Position(0, 0), new Position(0, 8), 8, + List.of( + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4), + new Position(0, 5), + new Position(0, 6), + new Position(0, 7), + new Position(0, 8) + ) + ), + Arguments.of(new Position(0, 8), new Position(0, 0), 8, + List.of( + new Position(0, 7), + new Position(0, 6), + new Position(0, 5), + new Position(0, 4), + new Position(0, 3), + new Position(0, 2), + new Position(0, 1), + new Position(0, 0) + ) + ), + Arguments.of(new Position(9, 0), new Position(0, 0), 9, + List.of( + new Position(8, 0), + new Position(7, 0), + new Position(6, 0), + new Position(5, 0), + new Position(4, 0), + new Position(3, 0), + new Position(2, 0), + new Position(1, 0), + new Position(0, 0) + ) + ) + ); + } + + @ParameterizedTest + @MethodSource("createPositionsAndPath") + void 차는_한_방향으로만_1칸_이상_이동_할_수_있다(Position from, Position to, int size, List expectedPath) { + List path = strategy.findPath(from, to, Camp.HAN); + + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(size); + assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); + }); + } + + @Test + void 차는_한_방향으로_이동하지_않으면_예외가_발생한다() { + assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(5, 5), Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } +} From c8f94212f90707a9e56dd3f75b9a823009f6e3f0 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Wed, 25 Mar 2026 21:30:45 +0900 Subject: [PATCH 13/68] =?UTF-8?q?docs:=20=EA=B5=AC=ED=98=84=ED=95=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=99=84=EB=A3=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e6f1f85c6d..0ee67df679 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ ### 보드 초기화 -- [ ] 『한』 상차림 -- [ ] 『초』 상차림 +- [x] 『한』 상차림 +- [x] 『초』 상차림 ```text - 좌측 상단을 0행 0열 이라 가정 @@ -89,15 +89,15 @@ ### 기물 이동 - [ ] 이동 시키려는 기물의 좌표를 확인한다. - - [ ] 좌표가 장기판 범위 벗어난 좌표일 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 좌표가 장기판 범위 벗어난 좌표일 경우 `IllegalArgumentException`을 발생시킨다. - [ ] 좌표에 기물이 존재 하지 않을 경우 `IllegalArgumentException`을 발생시킨다. - [ ] 좌표의 기물이 자신의 진영이 아닐 경우 `IllegalArgumentException`을 발생시킨다. - [ ] 기물을 목적지 좌표로 이동한다. - - [ ] 목적지 좌표가 장기판 범위 벗어난 좌표일 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 목적지 좌표가 장기판 범위 벗어난 좌표일 경우 `IllegalArgumentException`을 발생시킨다. - [ ] 기물 규칙 및 제약에 맞게 이동한다. - [차(車)] - - [ ] 상하좌우로 장애물을 만날 때까지 칸 수 제한 없이 이동한다. + - [x] 상하좌우로 장애물을 만날 때까지 칸 수 제한 없이 이동한다. - [ ] 이동하려는 경로 중간에 다른 기물이 **있**다면 `IllegalArgumentException`을 발생시킨다. - [포(包)] @@ -115,10 +115,10 @@ - [ ] 목적지 좌표(직선 1칸 또는 대각선 1칸)에 다른 기물이 있으면 `IllegalArgumentException`을 발생시킨다. - [궁(楚/漢), 사(士)] - - [ ] 상하좌우 직선 1칸 이동한다. + - [x] 상하좌우 직선 1칸 이동한다. - [졸/병(卒/兵)] - - [ ] 상좌우 직선 1칸 이동한다. 후진은 불가 주의 + - [x] 상좌우 직선 1칸 이동한다. 후진은 불가 주의 --- From 4df7f3d9c57fb1beada2fb8bf94050f1a1e523a1 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 10:59:33 +0900 Subject: [PATCH 14/68] =?UTF-8?q?refactor:=20=EC=A0=84=EB=9E=B5=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ChariotStrategy.java => MultiStepStraightStrategy.java} | 2 +- ...lAndGuardStrategy.java => SingleStepStraightStrategy.java} | 2 +- ...otStrategyTest.java => MultiStepStraightStrategyTest.java} | 4 ++-- ...dStrategyTest.java => SingleStepStraightStrategyTest.java} | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/janggi/domain/piece/strategy/{ChariotStrategy.java => MultiStepStraightStrategy.java} (96%) rename src/main/java/janggi/domain/piece/strategy/{GeneralAndGuardStrategy.java => SingleStepStraightStrategy.java} (89%) rename src/test/java/janggi/domain/piece/strategy/{ChariotStrategyTest.java => MultiStepStraightStrategyTest.java} (96%) rename src/test/java/janggi/domain/piece/strategy/{GeneralAndGuardStrategyTest.java => SingleStepStraightStrategyTest.java} (93%) diff --git a/src/main/java/janggi/domain/piece/strategy/ChariotStrategy.java b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java similarity index 96% rename from src/main/java/janggi/domain/piece/strategy/ChariotStrategy.java rename to src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java index 5bbbb1711e..c395b50cff 100644 --- a/src/main/java/janggi/domain/piece/strategy/ChariotStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java @@ -5,7 +5,7 @@ import java.util.ArrayList; import java.util.List; -public class ChariotStrategy implements MoveStrategy { +public class MultiStepStraightStrategy implements MoveStrategy { @Override public List findPath(Position from, Position to, Camp camp) { diff --git a/src/main/java/janggi/domain/piece/strategy/GeneralAndGuardStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java similarity index 89% rename from src/main/java/janggi/domain/piece/strategy/GeneralAndGuardStrategy.java rename to src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java index 4dfe1983ec..7610c6671a 100644 --- a/src/main/java/janggi/domain/piece/strategy/GeneralAndGuardStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java @@ -4,7 +4,7 @@ import janggi.domain.piece.Camp; import java.util.List; -public class GeneralAndGuardStrategy implements MoveStrategy { +public class SingleStepStraightStrategy implements MoveStrategy { @Override public List findPath(Position from, Position to, Camp camp) { diff --git a/src/test/java/janggi/domain/piece/strategy/ChariotStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java similarity index 96% rename from src/test/java/janggi/domain/piece/strategy/ChariotStrategyTest.java rename to src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java index dda37a13b8..25b85e4002 100644 --- a/src/test/java/janggi/domain/piece/strategy/ChariotStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java @@ -12,9 +12,9 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -public class ChariotStrategyTest { +public class MultiStepStraightStrategyTest { - private final MoveStrategy strategy = new ChariotStrategy(); + private final MoveStrategy strategy = new MultiStepStraightStrategy(); private static Stream createPositionsAndPath() { return Stream.of( diff --git a/src/test/java/janggi/domain/piece/strategy/GeneralAndGuardStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java similarity index 93% rename from src/test/java/janggi/domain/piece/strategy/GeneralAndGuardStrategyTest.java rename to src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java index f9f46a3010..dec68908e0 100644 --- a/src/test/java/janggi/domain/piece/strategy/GeneralAndGuardStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java @@ -12,9 +12,9 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class GeneralAndGuardStrategyTest { +class SingleStepStraightStrategyTest { - private final MoveStrategy strategy = new GeneralAndGuardStrategy(); + private final MoveStrategy strategy = new SingleStepStraightStrategy(); private static Stream successMovePositions() { return Stream.of( From 462827a4f871b21ff28d3de1bf5b9471d43f5513 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 11:01:06 +0900 Subject: [PATCH 15/68] =?UTF-8?q?refactor:=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategy/MultiStepStraightStrategy.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java index c395b50cff..423ba56413 100644 --- a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java @@ -14,12 +14,10 @@ public List findPath(Position from, Position to, Camp camp) { validateStraightMove(rowDiff, colDiff); - List path = new ArrayList<>(); - - path.addAll(createRowPath(from, rowDiff)); - path.addAll(createColumnPath(from, colDiff)); - - return path; + if (rowDiff != 0) { + return createRowPath(from, rowDiff); + } + return createColumnPath(from, colDiff); } private void validateStraightMove(int rowDiff, int colDiff) { @@ -31,10 +29,6 @@ private void validateStraightMove(int rowDiff, int colDiff) { } private List createRowPath(Position from, int rowDiff) { - if (rowDiff == 0) { - return List.of(); - } - List path = new ArrayList<>(); int rowDirection = rowDiff / Math.abs(rowDiff); @@ -43,15 +37,10 @@ private List createRowPath(Position from, int rowDiff) { path.add(from); rowDiff -= rowDirection; } - return path; } private List createColumnPath(Position from, int colDiff) { - if (colDiff == 0) { - return List.of(); - } - List path = new ArrayList<>(); int columnDirection = colDiff / Math.abs(colDiff); @@ -60,7 +49,6 @@ private List createColumnPath(Position from, int colDiff) { path.add(from); colDiff -= columnDirection; } - return path; } } From 99c17c17c6c8837660933bb1e15df93a4a040006 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 11:01:54 +0900 Subject: [PATCH 16/68] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/piece/strategy/MultiStepStraightStrategyTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java index 25b85e4002..ae7efca257 100644 --- a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java @@ -72,7 +72,7 @@ private static Stream createPositionsAndPath() { @ParameterizedTest @MethodSource("createPositionsAndPath") - void 차는_한_방향으로만_1칸_이상_이동_할_수_있다(Position from, Position to, int size, List expectedPath) { + void 차와_포는_한_방향으로만_1칸_이상_이동_할_수_있다(Position from, Position to, int size, List expectedPath) { List path = strategy.findPath(from, to, Camp.HAN); SoftAssertions.assertSoftly(assertSoftly -> { @@ -82,7 +82,7 @@ private static Stream createPositionsAndPath() { } @Test - void 차는_한_방향으로_이동하지_않으면_예외가_발생한다() { + void 차와_포는_한_방향으로_이동하지_않으면_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(5, 5), Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); From d78aef3d49aaae29a856f8bdbd2cb726c6e4c6f2 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 14:12:37 +0900 Subject: [PATCH 17/68] =?UTF-8?q?feat:=20=EC=B0=A8,=ED=8F=AC=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=90=EB=A6=AC=20=EC=9D=B4=EB=8F=99=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/piece/strategy/MultiStepStraightStrategy.java | 4 ++++ .../piece/strategy/MultiStepStraightStrategyTest.java | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java index 423ba56413..89d5d32bba 100644 --- a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java @@ -26,6 +26,10 @@ private void validateStraightMove(int rowDiff, int colDiff) { if (sum != rowDiff && sum != colDiff) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } + + if (rowDiff == 0 && colDiff == 0) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } } private List createRowPath(Position from, int rowDiff) { diff --git a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java index ae7efca257..259fd946eb 100644 --- a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java @@ -87,4 +87,11 @@ private static Stream createPositionsAndPath() { .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } + + @Test + void 차와_포는_제자리_이동_시_예외가_발생한다1() { + assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(0, 0), Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } } From b3b9198c22eea9cda5adc3b1f45fc2e95d13cdd2 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 14:12:50 +0900 Subject: [PATCH 18/68] =?UTF-8?q?feat:=20=EB=A7=88=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 마 행마법 테스트 추가 --- src/main/java/janggi/domain/Position.java | 4 + .../domain/piece/strategy/HorseStrategy.java | 56 ++++++++++++ .../piece/strategy/HorseStrategyTest.java | 89 +++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 src/main/java/janggi/domain/piece/strategy/HorseStrategy.java create mode 100644 src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index d28cfefb5e..635e8f9049 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -34,4 +34,8 @@ public Position moveRow(int direction) { public Position moveCol(int direction) { return new Position(row, column + direction); } + + public Position moveDiagonal(int rowDirection, int colDirection) { + return new Position(row + rowDirection, column + colDirection); + } } diff --git a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java new file mode 100644 index 0000000000..c4be66f7a8 --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java @@ -0,0 +1,56 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.ArrayList; +import java.util.List; + +public class HorseStrategy implements MoveStrategy { + + @Override + public List findPath(Position from, Position to, Camp camp) { + int rowDifference = to.calculateRowDistance(from); + int colDifference = to.calculateColumnDistance(from); + + int absRowDifference = Math.abs(rowDifference); + int absColDifference = Math.abs(colDifference); + + validateHorseMovement(absRowDifference, absColDifference); + + int rowDirection = rowDifference / absRowDifference; + int colDirection = colDifference / absColDifference; + + if (absRowDifference > absColDifference) { + return createRowFirstPath(from, rowDirection, colDirection); + } + return createColFirstPath(from, rowDirection, colDirection); + } + + private List createRowFirstPath(Position from, int rowDirection, int colDirection) { + List path = new ArrayList<>(); + + from = from.moveRow(rowDirection); + path.add(from); + + from = from.moveDiagonal(rowDirection, colDirection); + path.add(from); + return path; + } + + private List createColFirstPath(Position from, int rowDirection, int colDirection) { + List path = new ArrayList<>(); + + from = from.moveCol(colDirection); + path.add(from); + + from = from.moveDiagonal(rowDirection, colDirection); + path.add(from); + return path; + } + + private void validateHorseMovement(int rowDifference, int colDifference) { + if ((rowDifference != 1 || colDifference != 2) && (rowDifference != 2 || colDifference != 1)) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java new file mode 100644 index 0000000000..e60a9b6147 --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java @@ -0,0 +1,89 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class HorseStrategyTest { + + private final MoveStrategy strategy = new HorseStrategy(); + + private static Stream createPositionsAndPath() { + return Stream.of( + Arguments.of(new Position(6, 4), new Position(8, 5), + List.of( + new Position(7, 4), + new Position(8, 5) + )), + Arguments.of(new Position(6, 4), new Position(8, 3), + List.of( + new Position(7, 4), + new Position(8, 3) + )), + Arguments.of(new Position(6, 4), new Position(4, 3), + List.of( + new Position(5, 4), + new Position(4, 3) + )), + Arguments.of(new Position(6, 4), new Position(4, 5), + List.of( + new Position(5, 4), + new Position(4, 5) + )), + Arguments.of(new Position(6, 4), new Position(5, 2), + List.of( + new Position(6, 3), + new Position(5, 2) + )), + Arguments.of(new Position(6, 4), new Position(7, 2), + List.of( + new Position(6, 3), + new Position(7, 2) + )), + Arguments.of(new Position(6, 4), new Position(5, 6), + List.of( + new Position(6, 5), + new Position(5, 6) + )), + Arguments.of(new Position(6, 4), new Position(7, 6), + List.of( + new Position(6, 5), + new Position(7, 6) + )) + ); + } + + @ParameterizedTest + @MethodSource("createPositionsAndPath") + void 마는_직선_1칸_이동_후_대각선_1칸_이동한다(Position from, Position to, List expectedPath) { + // when + List path = strategy.findPath(from, to, Camp.HAN); + // then + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(expectedPath.size()); + assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); + }); + } + + private static Stream createExceptionPosition() { + return Stream.of( + Arguments.of(new Position(6, 4), new Position(0, 1)), + Arguments.of(new Position(6, 4), new Position(6, 4)) + ); + } + + @ParameterizedTest + @MethodSource("createExceptionPosition") + void 마는_행마법_대로_움직이지_않으면_예외가_발생한다(Position from, Position to) { + assertThatThrownBy(() -> strategy.findPath(from, to, Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } +} From f92203ff2991f9e9e9e5c038f341b1ab36814de1 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 15:02:25 +0900 Subject: [PATCH 19/68] =?UTF-8?q?feat:=20=EC=83=81=20=ED=96=89=EB=A7=88?= =?UTF-8?q?=EB=B2=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상 행마법 테스트 추가 - DirectionInformation 값 객체 추가 --- .../piece/strategy/DirectionInformation.java | 22 +++++ .../piece/strategy/ElephantStrategy.java | 58 +++++++++++ .../piece/strategy/ElephantStrategyTest.java | 97 +++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 src/main/java/janggi/domain/piece/strategy/DirectionInformation.java create mode 100644 src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java create mode 100644 src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java diff --git a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java new file mode 100644 index 0000000000..be92043f7f --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java @@ -0,0 +1,22 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; + +public record DirectionInformation(int rowDifference, int colDifference) { + + public DirectionInformation(Position from, Position to) { + this(to.calculateRowDistance(from), to.calculateColumnDistance(from)); + } + + public int calculateRowDirection() { + return rowDifference / Math.abs(rowDifference); + } + + public int calculateColDirection() { + return colDifference / Math.abs(colDifference); + } + + public boolean isRowBiggerThanCol() { + return Math.abs(rowDifference) > Math.abs(colDifference); + } +} diff --git a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java new file mode 100644 index 0000000000..4fbc7184ad --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java @@ -0,0 +1,58 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.ArrayList; +import java.util.List; + +public class ElephantStrategy implements MoveStrategy { + + @Override + public List findPath(Position from, Position to, Camp camp) { + DirectionInformation directionInfo = new DirectionInformation(from, to); + + validateElephantMovement(directionInfo); + + if (directionInfo.isRowBiggerThanCol()) { + return createRowFirstPath(from, directionInfo); + } + return createColFirstPath(from, directionInfo); + } + + private void validateElephantMovement(DirectionInformation directionInfo) { + if ((directionInfo.rowDifference() != 2 || directionInfo.colDifference() != 3) + && (directionInfo.rowDifference() != 3 || directionInfo.colDifference() != 2)) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + private List createRowFirstPath(Position from, DirectionInformation directionInfo) { + List path = new ArrayList<>(); + + from = from.moveRow(directionInfo.calculateRowDirection()); + path.add(from); + + path.addAll(moveDiagonal(from, directionInfo)); + return path; + } + + private List createColFirstPath(Position from, DirectionInformation directionInfo) { + List path = new ArrayList<>(); + + from = from.moveCol(directionInfo.calculateColDirection()); + path.add(from); + + path.addAll(moveDiagonal(from, directionInfo)); + return path; + } + + private List moveDiagonal(Position from, DirectionInformation directionInfo) { + List path = new ArrayList<>(); + + for (int i = 0; i < 2; i++) { + from = from.moveDiagonal(directionInfo.calculateRowDirection(), directionInfo.calculateColDirection()); + path.add(from); + } + return path; + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java new file mode 100644 index 0000000000..df80ea03c4 --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java @@ -0,0 +1,97 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ElephantStrategyTest { + + private final MoveStrategy strategy = new ElephantStrategy(); + + private static Stream createPositionsAndPath() { + return Stream.of( + Arguments.of(new Position(6, 4), new Position(3, 2), + List.of( + new Position(5, 4), + new Position(4, 3), + new Position(3, 2) + )), + Arguments.of(new Position(6, 4), new Position(4, 1), + List.of( + new Position(6, 3), + new Position(5, 2), + new Position(4, 1) + )), + Arguments.of(new Position(6, 4), new Position(8, 1), + List.of( + new Position(6, 3), + new Position(7, 2), + new Position(8, 1) + )), + Arguments.of(new Position(6, 4), new Position(9, 2), + List.of( + new Position(7, 4), + new Position(8, 3), + new Position(9, 2) + )), + Arguments.of(new Position(6, 4), new Position(9, 6), + List.of( + new Position(7, 4), + new Position(8, 5), + new Position(9, 6) + )), + Arguments.of(new Position(6, 4), new Position(8, 7), + List.of( + new Position(6, 5), + new Position(7, 6), + new Position(8, 7) + )), + Arguments.of(new Position(6, 4), new Position(4, 7), + List.of( + new Position(6, 5), + new Position(5, 6), + new Position(4, 7) + )), + Arguments.of(new Position(6, 4), new Position(3, 6), + List.of( + new Position(5, 4), + new Position(4, 5), + new Position(3, 6) + )) + ); + } + + @ParameterizedTest + @MethodSource("createPositionsAndPath") + void 상은_직선_1칸_이동_후_대각선_2칸_이동한다(Position from, Position to, List expectedPath) { + // when + List path = strategy.findPath(from, to, Camp.HAN); + // then + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(expectedPath.size()); + assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); + }); + } + + private static Stream createExceptionPosition() { + return Stream.of( + Arguments.of(new Position(6, 4), new Position(3, 4)), + Arguments.of(new Position(6, 4), new Position(6, 4)) + ); + } + + @ParameterizedTest + @MethodSource("createExceptionPosition") + void 상은_행마법_대로_움직이지_않으면_예외가_발생한다(Position from, Position to) { + assertThatThrownBy(() -> strategy.findPath(from, to, Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } +} From 3f6229174a474ebb053b6f83395b1951e7c3992f Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 15:02:30 +0900 Subject: [PATCH 20/68] =?UTF-8?q?refactor:=20DirectionInformation=20?= =?UTF-8?q?=EA=B0=92=20=EA=B0=9D=EC=B2=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/piece/strategy/HorseStrategy.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java index c4be66f7a8..51069b67d0 100644 --- a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java @@ -9,26 +9,21 @@ public class HorseStrategy implements MoveStrategy { @Override public List findPath(Position from, Position to, Camp camp) { - int rowDifference = to.calculateRowDistance(from); - int colDifference = to.calculateColumnDistance(from); + DirectionInformation directionInformation = new DirectionInformation(from, to); - int absRowDifference = Math.abs(rowDifference); - int absColDifference = Math.abs(colDifference); + validateHorseMovement(directionInformation); - validateHorseMovement(absRowDifference, absColDifference); - - int rowDirection = rowDifference / absRowDifference; - int colDirection = colDifference / absColDifference; - - if (absRowDifference > absColDifference) { - return createRowFirstPath(from, rowDirection, colDirection); + if (directionInformation.isRowBiggerThanCol()) { + return createRowFirstPath(from, directionInformation); } - return createColFirstPath(from, rowDirection, colDirection); + return createColFirstPath(from, directionInformation); } - private List createRowFirstPath(Position from, int rowDirection, int colDirection) { + private List createRowFirstPath(Position from, DirectionInformation directionInformation) { List path = new ArrayList<>(); + int rowDirection = directionInformation.calculateRowDirection(); + int colDirection = directionInformation.calculateColDirection(); from = from.moveRow(rowDirection); path.add(from); @@ -37,9 +32,11 @@ private List createRowFirstPath(Position from, int rowDirection, int c return path; } - private List createColFirstPath(Position from, int rowDirection, int colDirection) { + private List createColFirstPath(Position from, DirectionInformation directionInformation) { List path = new ArrayList<>(); + int rowDirection = directionInformation.calculateRowDirection(); + int colDirection = directionInformation.calculateColDirection(); from = from.moveCol(colDirection); path.add(from); @@ -48,8 +45,9 @@ private List createColFirstPath(Position from, int rowDirection, int c return path; } - private void validateHorseMovement(int rowDifference, int colDifference) { - if ((rowDifference != 1 || colDifference != 2) && (rowDifference != 2 || colDifference != 1)) { + private void validateHorseMovement(DirectionInformation directionInformation) { + if ((directionInformation.rowDifference() != 1 || directionInformation.colDifference() != 2) + && (directionInformation.rowDifference() != 2 || directionInformation.colDifference() != 1)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } From 49914a93806dd2d7a8e04300f63f7ad7a5993ff2 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 15:47:47 +0900 Subject: [PATCH 21/68] =?UTF-8?q?refactor:=20DirectionInformation=20?= =?UTF-8?q?=EA=B0=92=20=EA=B0=9D=EC=B2=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/piece/Camp.java | 4 +- .../piece/strategy/DirectionInformation.java | 18 +++++++++ .../piece/strategy/ElephantStrategy.java | 4 +- .../domain/piece/strategy/HorseStrategy.java | 7 +++- .../strategy/MultiStepStraightStrategy.java | 37 ++++++++++--------- .../strategy/SingleStepStraightStrategy.java | 11 ++++-- .../piece/strategy/SoldierStrategy.java | 14 ++++--- 7 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/main/java/janggi/domain/piece/Camp.java b/src/main/java/janggi/domain/piece/Camp.java index 40af47f944..bcb68370e8 100644 --- a/src/main/java/janggi/domain/piece/Camp.java +++ b/src/main/java/janggi/domain/piece/Camp.java @@ -2,8 +2,8 @@ public enum Camp { - HAN(1), - CHO(-1); + HAN(-1), + CHO(1); private final int forwardDirection; diff --git a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java index be92043f7f..6120484af1 100644 --- a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java +++ b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java @@ -8,15 +8,33 @@ public DirectionInformation(Position from, Position to) { this(to.calculateRowDistance(from), to.calculateColumnDistance(from)); } + public int calculateAbsRowDifference() { + return Math.abs(rowDifference); + } + + public int calculateAbsColDifference() { + return Math.abs(colDifference); + } + public int calculateRowDirection() { + if (rowDifference == 0) { + return 0; + } return rowDifference / Math.abs(rowDifference); } public int calculateColDirection() { + if (colDifference == 0) { + return 0; + } return colDifference / Math.abs(colDifference); } public boolean isRowBiggerThanCol() { return Math.abs(rowDifference) > Math.abs(colDifference); } + + public int addAllDifference() { + return rowDifference + colDifference; + } } diff --git a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java index 4fbc7184ad..091b68a720 100644 --- a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java @@ -20,8 +20,8 @@ public List findPath(Position from, Position to, Camp camp) { } private void validateElephantMovement(DirectionInformation directionInfo) { - if ((directionInfo.rowDifference() != 2 || directionInfo.colDifference() != 3) - && (directionInfo.rowDifference() != 3 || directionInfo.colDifference() != 2)) { + if ((directionInfo.calculateAbsRowDifference() != 2 || directionInfo.calculateAbsColDifference() != 3) + && (directionInfo.calculateAbsRowDifference() != 3 || directionInfo.calculateAbsColDifference() != 2)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } diff --git a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java index 51069b67d0..755daff3de 100644 --- a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java @@ -46,8 +46,11 @@ private List createColFirstPath(Position from, DirectionInformation di } private void validateHorseMovement(DirectionInformation directionInformation) { - if ((directionInformation.rowDifference() != 1 || directionInformation.colDifference() != 2) - && (directionInformation.rowDifference() != 2 || directionInformation.colDifference() != 1)) { + int absRowDifference = directionInformation.calculateAbsRowDifference(); + int absColDifference = directionInformation.calculateAbsColDifference(); + + if ((absRowDifference != 1 || absColDifference != 2) + && (absRowDifference != 2 || absColDifference != 1)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } diff --git a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java index 89d5d32bba..9e9b7776df 100644 --- a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java @@ -9,49 +9,50 @@ public class MultiStepStraightStrategy implements MoveStrategy { @Override public List findPath(Position from, Position to, Camp camp) { - int rowDiff = to.calculateRowDistance(from); - int colDiff = to.calculateColumnDistance(from); + DirectionInformation directionInformation = new DirectionInformation(from, to); - validateStraightMove(rowDiff, colDiff); + validateStraightMove(directionInformation); - if (rowDiff != 0) { - return createRowPath(from, rowDiff); + if (directionInformation.isRowBiggerThanCol()) { + return createRowPath(from, directionInformation.rowDifference()); } - return createColumnPath(from, colDiff); + return createColumnPath(from, directionInformation.colDifference()); } - private void validateStraightMove(int rowDiff, int colDiff) { - int sum = rowDiff + colDiff; + private void validateStraightMove(DirectionInformation directionInformation) { + int sum = directionInformation.addAllDifference(); + int rowDifference = directionInformation.rowDifference(); + int colDifference = directionInformation.colDifference(); - if (sum != rowDiff && sum != colDiff) { + if (sum != rowDifference && sum != colDifference) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } - if (rowDiff == 0 && colDiff == 0) { + if (rowDifference == 0 && colDifference == 0) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } - private List createRowPath(Position from, int rowDiff) { + private List createRowPath(Position from, int rowDifference) { List path = new ArrayList<>(); - int rowDirection = rowDiff / Math.abs(rowDiff); - while (rowDiff != 0) { + int rowDirection = rowDifference / Math.abs(rowDifference); + while (rowDifference != 0) { from = from.moveRow(rowDirection); path.add(from); - rowDiff -= rowDirection; + rowDifference -= rowDirection; } return path; } - private List createColumnPath(Position from, int colDiff) { + private List createColumnPath(Position from, int colDifference) { List path = new ArrayList<>(); - int columnDirection = colDiff / Math.abs(colDiff); - while (colDiff != 0) { + int columnDirection = colDifference / Math.abs(colDifference); + while (colDifference != 0) { from = from.moveCol(columnDirection); path.add(from); - colDiff -= columnDirection; + colDifference -= columnDirection; } return path; } diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java index 7610c6671a..4d8bf4f167 100644 --- a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java @@ -8,12 +8,15 @@ public class SingleStepStraightStrategy implements MoveStrategy { @Override public List findPath(Position from, Position to, Camp camp) { - int rowDiff = Math.abs(from.calculateRowDistance(to)); - int colDiff = Math.abs(from.calculateColumnDistance(to)); + DirectionInformation directionInformation = new DirectionInformation(from, to); + validateSingleStepMovement(directionInformation); - if (rowDiff + colDiff != 1) { + return List.of(to); + } + + private void validateSingleStepMovement(DirectionInformation directionInformation) { + if (directionInformation.calculateAbsRowDifference() + directionInformation.calculateAbsColDifference() != 1) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } - return List.of(to); } } diff --git a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java index 11268129c7..7c15377304 100644 --- a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java @@ -8,15 +8,17 @@ public class SoldierStrategy implements MoveStrategy { @Override public List findPath(Position from, Position to, Camp camp) { - int rowDiff = from.calculateRowDistance(to); - camp.validateForwardDirection(rowDiff); + DirectionInformation directionInformation = new DirectionInformation(from, to); - rowDiff = Math.abs(rowDiff); - int colDiff = Math.abs(from.calculateColumnDistance(to)); + camp.validateForwardDirection(directionInformation.rowDifference()); + validateSoldierMovement(directionInformation); - if (rowDiff + colDiff != 1) { + return List.of(to); + } + + private void validateSoldierMovement(DirectionInformation directionInformation) { + if (directionInformation.calculateAbsRowDifference() + directionInformation.calculateAbsColDifference() != 1) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } - return List.of(to); } } From dfb2afe2f73df15e86a3f3425035dd78735a680f Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 15:54:10 +0900 Subject: [PATCH 22/68] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20PieceRule=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../janggi/domain/board/ElephantSetting.java | 66 +++++++++---------- .../domain/board/InitialPiecePlacement.java | 58 ++++++++-------- src/main/java/janggi/domain/piece/Piece.java | 14 ++-- .../java/janggi/domain/piece/PieceRule.java | 31 +++++++++ src/main/java/janggi/domain/piece/Type.java | 17 ----- .../board/StandardBoardInitializerTest.java | 42 ++++++------ 6 files changed, 120 insertions(+), 108 deletions(-) create mode 100644 src/main/java/janggi/domain/piece/PieceRule.java delete mode 100644 src/main/java/janggi/domain/piece/Type.java diff --git a/src/main/java/janggi/domain/board/ElephantSetting.java b/src/main/java/janggi/domain/board/ElephantSetting.java index b05a57c152..bd50155f70 100644 --- a/src/main/java/janggi/domain/board/ElephantSetting.java +++ b/src/main/java/janggi/domain/board/ElephantSetting.java @@ -3,7 +3,7 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; -import janggi.domain.piece.Type; +import janggi.domain.piece.PieceRule; import java.util.Map; public enum ElephantSetting { @@ -14,10 +14,10 @@ public enum ElephantSetting { @Override public Map makeElephants() { return Map.of( - new Position(0, 7), new Piece(Type.ELEPHANT, Camp.CHO, null), - new Position(0, 6), new Piece(Type.HORSE, Camp.CHO, null), - new Position(0, 2), new Piece(Type.ELEPHANT, Camp.CHO, null), - new Position(0, 1), new Piece(Type.HORSE, Camp.CHO, null) + new Position(0, 7), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), + new Position(0, 6), new Piece(PieceRule.HORSE, Camp.CHO, null), + new Position(0, 2), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), + new Position(0, 1), new Piece(PieceRule.HORSE, Camp.CHO, null) ); } }, @@ -25,10 +25,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(0, 7), new Piece(Type.HORSE, Camp.CHO, null), - new Position(0, 6), new Piece(Type.ELEPHANT, Camp.CHO, null), - new Position(0, 2), new Piece(Type.HORSE, Camp.CHO, null), - new Position(0, 1), new Piece(Type.ELEPHANT, Camp.CHO, null) + new Position(0, 7), new Piece(PieceRule.HORSE, Camp.CHO, null), + new Position(0, 6), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), + new Position(0, 2), new Piece(PieceRule.HORSE, Camp.CHO, null), + new Position(0, 1), new Piece(PieceRule.ELEPHANT, Camp.CHO, null) ); } }, @@ -36,10 +36,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(0, 7), new Piece(Type.HORSE, Camp.CHO, null), - new Position(0, 6), new Piece(Type.ELEPHANT, Camp.CHO, null), - new Position(0, 2), new Piece(Type.ELEPHANT, Camp.CHO, null), - new Position(0, 1), new Piece(Type.HORSE, Camp.CHO, null) + new Position(0, 7), new Piece(PieceRule.HORSE, Camp.CHO, null), + new Position(0, 6), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), + new Position(0, 2), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), + new Position(0, 1), new Piece(PieceRule.HORSE, Camp.CHO, null) ); } }, @@ -47,10 +47,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(0, 7), new Piece(Type.ELEPHANT, Camp.CHO, null), - new Position(0, 6), new Piece(Type.HORSE, Camp.CHO, null), - new Position(0, 2), new Piece(Type.HORSE, Camp.CHO, null), - new Position(0, 1), new Piece(Type.ELEPHANT, Camp.CHO, null) + new Position(0, 7), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), + new Position(0, 6), new Piece(PieceRule.HORSE, Camp.CHO, null), + new Position(0, 2), new Piece(PieceRule.HORSE, Camp.CHO, null), + new Position(0, 1), new Piece(PieceRule.ELEPHANT, Camp.CHO, null) ); } }, @@ -58,10 +58,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(9, 1), new Piece(Type.ELEPHANT, Camp.HAN, null), - new Position(9, 2), new Piece(Type.HORSE, Camp.HAN, null), - new Position(9, 6), new Piece(Type.ELEPHANT, Camp.HAN, null), - new Position(9, 7), new Piece(Type.HORSE, Camp.HAN, null) + new Position(9, 1), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), + new Position(9, 2), new Piece(PieceRule.HORSE, Camp.HAN, null), + new Position(9, 6), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), + new Position(9, 7), new Piece(PieceRule.HORSE, Camp.HAN, null) ); } }, @@ -69,10 +69,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(9, 1), new Piece(Type.HORSE, Camp.HAN, null), - new Position(9, 2), new Piece(Type.ELEPHANT, Camp.HAN, null), - new Position(9, 6), new Piece(Type.HORSE, Camp.HAN, null), - new Position(9, 7), new Piece(Type.ELEPHANT, Camp.HAN, null) + new Position(9, 1), new Piece(PieceRule.HORSE, Camp.HAN, null), + new Position(9, 2), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), + new Position(9, 6), new Piece(PieceRule.HORSE, Camp.HAN, null), + new Position(9, 7), new Piece(PieceRule.ELEPHANT, Camp.HAN, null) ); } }, @@ -80,10 +80,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(9, 1), new Piece(Type.HORSE, Camp.HAN, null), - new Position(9, 2), new Piece(Type.ELEPHANT, Camp.HAN, null), - new Position(9, 6), new Piece(Type.ELEPHANT, Camp.HAN, null), - new Position(9, 7), new Piece(Type.HORSE, Camp.HAN, null) + new Position(9, 1), new Piece(PieceRule.HORSE, Camp.HAN, null), + new Position(9, 2), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), + new Position(9, 6), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), + new Position(9, 7), new Piece(PieceRule.HORSE, Camp.HAN, null) ); } }, @@ -91,10 +91,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(9, 1), new Piece(Type.ELEPHANT, Camp.HAN, null), - new Position(9, 2), new Piece(Type.HORSE, Camp.HAN, null), - new Position(9, 6), new Piece(Type.HORSE, Camp.HAN, null), - new Position(9, 7), new Piece(Type.ELEPHANT, Camp.HAN, null) + new Position(9, 1), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), + new Position(9, 2), new Piece(PieceRule.HORSE, Camp.HAN, null), + new Position(9, 6), new Piece(PieceRule.HORSE, Camp.HAN, null), + new Position(9, 7), new Piece(PieceRule.ELEPHANT, Camp.HAN, null) ); } }; diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java index fcd9a3a759..73ceef5bdd 100644 --- a/src/main/java/janggi/domain/board/InitialPiecePlacement.java +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -3,50 +3,50 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; -import janggi.domain.piece.Type; +import janggi.domain.piece.PieceRule; import janggi.domain.piece.strategy.MoveStrategy; import java.util.HashMap; import java.util.Map; public enum InitialPiecePlacement { - CHO_SOLDIER_1(3, 0, Camp.CHO, Type.SOLDIER, null), - CHO_SOLDIER_2(3, 2, Camp.CHO, Type.SOLDIER, null), - CHO_SOLDIER_3(3, 4, Camp.CHO, Type.SOLDIER, null), - CHO_SOLDIER_4(3, 6, Camp.CHO, Type.SOLDIER, null), - CHO_SOLDIER_5(3, 8, Camp.CHO, Type.SOLDIER, null), - CHO_CHARIOT_LEFT(0, 0, Camp.CHO, Type.CHARIOT, null), - CHO_GUARD_LEFT(0, 3, Camp.CHO, Type.GUARD, null), - CHO_GUARD_RIGHT(0, 5, Camp.CHO, Type.GUARD, null), - CHO_CHARIOT_RIGHT(0, 8, Camp.CHO, Type.CHARIOT, null), - CHO_GENERAL(1, 4, Camp.CHO, Type.GENERAL, null), - CHO_CANNON_LEFT(2, 1, Camp.CHO, Type.CANNON, null), - CHO_CANNON_RIGHT(2, 7, Camp.CHO, Type.CANNON, null), + CHO_SOLDIER_1(3, 0, Camp.CHO, PieceRule.SOLDIER, null), + CHO_SOLDIER_2(3, 2, Camp.CHO, PieceRule.SOLDIER, null), + CHO_SOLDIER_3(3, 4, Camp.CHO, PieceRule.SOLDIER, null), + CHO_SOLDIER_4(3, 6, Camp.CHO, PieceRule.SOLDIER, null), + CHO_SOLDIER_5(3, 8, Camp.CHO, PieceRule.SOLDIER, null), + CHO_CHARIOT_LEFT(0, 0, Camp.CHO, PieceRule.CHARIOT, null), + CHO_GUARD_LEFT(0, 3, Camp.CHO, PieceRule.GUARD, null), + CHO_GUARD_RIGHT(0, 5, Camp.CHO, PieceRule.GUARD, null), + CHO_CHARIOT_RIGHT(0, 8, Camp.CHO, PieceRule.CHARIOT, null), + CHO_GENERAL(1, 4, Camp.CHO, PieceRule.GENERAL, null), + CHO_CANNON_LEFT(2, 1, Camp.CHO, PieceRule.CANNON, null), + CHO_CANNON_RIGHT(2, 7, Camp.CHO, PieceRule.CANNON, null), - HAN_SOLDIER_1(6, 0, Camp.HAN, Type.SOLDIER, null), - HAN_SOLDIER_2(6, 2, Camp.HAN, Type.SOLDIER, null), - HAN_SOLDIER_3(6, 4, Camp.HAN, Type.SOLDIER, null), - HAN_SOLDIER_4(6, 6, Camp.HAN, Type.SOLDIER, null), - HAN_SOLDIER_5(6, 8, Camp.HAN, Type.SOLDIER, null), - HAN_CANNON_LEFT(7, 1, Camp.HAN, Type.CANNON, null), - HAN_CANNON_RIGHT(7, 7, Camp.HAN, Type.CANNON, null), - HAN_GENERAL(8, 4, Camp.HAN, Type.GENERAL, null), - HAN_CHARIOT_LEFT(9, 0, Camp.HAN, Type.CHARIOT, null), - HAN_GUARD_LEFT(9, 3, Camp.HAN, Type.GUARD, null), - HAN_GUARD_RIGHT(9, 5, Camp.HAN, Type.GUARD, null), - HAN_CHARIOT_RIGHT(9, 8, Camp.HAN, Type.CHARIOT, null); + HAN_SOLDIER_1(6, 0, Camp.HAN, PieceRule.SOLDIER, null), + HAN_SOLDIER_2(6, 2, Camp.HAN, PieceRule.SOLDIER, null), + HAN_SOLDIER_3(6, 4, Camp.HAN, PieceRule.SOLDIER, null), + HAN_SOLDIER_4(6, 6, Camp.HAN, PieceRule.SOLDIER, null), + HAN_SOLDIER_5(6, 8, Camp.HAN, PieceRule.SOLDIER, null), + HAN_CANNON_LEFT(7, 1, Camp.HAN, PieceRule.CANNON, null), + HAN_CANNON_RIGHT(7, 7, Camp.HAN, PieceRule.CANNON, null), + HAN_GENERAL(8, 4, Camp.HAN, PieceRule.GENERAL, null), + HAN_CHARIOT_LEFT(9, 0, Camp.HAN, PieceRule.CHARIOT, null), + HAN_GUARD_LEFT(9, 3, Camp.HAN, PieceRule.GUARD, null), + HAN_GUARD_RIGHT(9, 5, Camp.HAN, PieceRule.GUARD, null), + HAN_CHARIOT_RIGHT(9, 8, Camp.HAN, PieceRule.CHARIOT, null); private final int row; private final int column; private final Camp camp; - private final Type type; + private final PieceRule pieceRule; private final MoveStrategy moveStrategy; - InitialPiecePlacement(int row, int column, Camp camp, Type type, MoveStrategy moveStrategy) { + InitialPiecePlacement(int row, int column, Camp camp, PieceRule pieceRule, MoveStrategy moveStrategy) { this.row = row; this.column = column; this.camp = camp; - this.type = type; + this.pieceRule = pieceRule; this.moveStrategy = moveStrategy; } @@ -56,7 +56,7 @@ public static Map init(ElephantSetting choElephantSetting, Elep for (InitialPiecePlacement piece : values()) { board.put(new Position(piece.row, piece.column) - , new Piece(piece.type, piece.camp, piece.moveStrategy)); + , new Piece(piece.pieceRule, piece.camp, piece.moveStrategy)); } Map choElephants = choElephantSetting.makeElephants(); diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index f53010e07d..4ef8deb602 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -7,18 +7,16 @@ public class Piece { - private final Type type; + private final PieceRule pieceRule; private final Camp camp; - private final MoveStrategy moveStrategy; - public Piece(Type type, Camp camp, MoveStrategy moveStrategy) { - this.type = type; + public Piece(PieceRule pieceRule, Camp camp, MoveStrategy moveStrategy) { + this.pieceRule = pieceRule; this.camp = camp; - this.moveStrategy = moveStrategy; } public boolean canMove(Position from, Position to) { - List path = moveStrategy.findPath(from, to, camp); + List path = pieceRule.findPath(from, to, camp); return true; } @@ -28,11 +26,11 @@ public boolean equals(Object o) { return false; } Piece piece = (Piece) o; - return type == piece.type && camp == piece.camp && Objects.equals(moveStrategy, piece.moveStrategy); + return pieceRule == piece.pieceRule && camp == piece.camp; } @Override public int hashCode() { - return Objects.hash(type, camp, moveStrategy); + return Objects.hash(pieceRule, camp); } } diff --git a/src/main/java/janggi/domain/piece/PieceRule.java b/src/main/java/janggi/domain/piece/PieceRule.java new file mode 100644 index 0000000000..27367db994 --- /dev/null +++ b/src/main/java/janggi/domain/piece/PieceRule.java @@ -0,0 +1,31 @@ +package janggi.domain.piece; + +import janggi.domain.Position; +import janggi.domain.piece.strategy.ElephantStrategy; +import janggi.domain.piece.strategy.HorseStrategy; +import janggi.domain.piece.strategy.MoveStrategy; +import janggi.domain.piece.strategy.MultiStepStraightStrategy; +import janggi.domain.piece.strategy.SingleStepStraightStrategy; +import janggi.domain.piece.strategy.SoldierStrategy; +import java.util.List; + +public enum PieceRule { + + GENERAL(new SingleStepStraightStrategy()), + CHARIOT(new MultiStepStraightStrategy()), + HORSE(new HorseStrategy()), + CANNON(new MultiStepStraightStrategy()), + GUARD(new SingleStepStraightStrategy()), + ELEPHANT(new ElephantStrategy()), + SOLDIER(new SoldierStrategy()); + + private final MoveStrategy moveStrategy; + + PieceRule(MoveStrategy moveStrategy) { + this.moveStrategy = moveStrategy; + } + + public List findPath(Position from, Position to, Camp camp) { + return moveStrategy.findPath(from, to, camp); + } +} diff --git a/src/main/java/janggi/domain/piece/Type.java b/src/main/java/janggi/domain/piece/Type.java deleted file mode 100644 index 5dde8624c9..0000000000 --- a/src/main/java/janggi/domain/piece/Type.java +++ /dev/null @@ -1,17 +0,0 @@ -package janggi.domain.piece; - -public enum Type { - GENERAL("將"), - CHARIOT("車"), - HORSE("馬"), - CANNON("包"), - GUARD("士"), - ELEPHANT("象"), - SOLDIER("卒"); - - private final String hanja; - - Type(String hanja) { - this.hanja = hanja; - } -} diff --git a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java index 4d6073ba30..0a8031a3a4 100644 --- a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java @@ -3,7 +3,7 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; -import janggi.domain.piece.Type; +import janggi.domain.piece.PieceRule; import java.util.HashMap; import java.util.Map; import org.assertj.core.api.SoftAssertions; @@ -46,20 +46,20 @@ public class StandardBoardInitializerTest { private Map createExpectedBoard() { Map expectedBoard = new HashMap<>(); - putPieces(expectedBoard, Type.CHARIOT, Camp.CHO, createPosition(0, 0), createPosition(0, 8)); - putPieces(expectedBoard, Type.GUARD, Camp.CHO, createPosition(0, 3), createPosition(0, 5)); - putPieces(expectedBoard, Type.GENERAL, Camp.CHO, createPosition(1, 4)); - putPieces(expectedBoard, Type.CANNON, Camp.CHO, createPosition(2, 1), createPosition(2, 7)); - putPieces(expectedBoard, Type.SOLDIER, Camp.CHO, + putPieces(expectedBoard, PieceRule.CHARIOT, Camp.CHO, createPosition(0, 0), createPosition(0, 8)); + putPieces(expectedBoard, PieceRule.GUARD, Camp.CHO, createPosition(0, 3), createPosition(0, 5)); + putPieces(expectedBoard, PieceRule.GENERAL, Camp.CHO, createPosition(1, 4)); + putPieces(expectedBoard, PieceRule.CANNON, Camp.CHO, createPosition(2, 1), createPosition(2, 7)); + putPieces(expectedBoard, PieceRule.SOLDIER, Camp.CHO, createPosition(3, 0), createPosition(3, 2), createPosition(3, 4), createPosition(3, 6), createPosition(3, 8)); - putPieces(expectedBoard, Type.CHARIOT, Camp.HAN, createPosition(9, 0), createPosition(9, 8)); - putPieces(expectedBoard, Type.GUARD, Camp.HAN, createPosition(9, 3), createPosition(9, 5)); - putPieces(expectedBoard, Type.GENERAL, Camp.HAN, createPosition(8, 4)); - putPieces(expectedBoard, Type.CANNON, Camp.HAN, createPosition(7, 1), createPosition(7, 7)); - putPieces(expectedBoard, Type.SOLDIER, Camp.HAN, + putPieces(expectedBoard, PieceRule.CHARIOT, Camp.HAN, createPosition(9, 0), createPosition(9, 8)); + putPieces(expectedBoard, PieceRule.GUARD, Camp.HAN, createPosition(9, 3), createPosition(9, 5)); + putPieces(expectedBoard, PieceRule.GENERAL, Camp.HAN, createPosition(8, 4)); + putPieces(expectedBoard, PieceRule.CANNON, Camp.HAN, createPosition(7, 1), createPosition(7, 7)); + putPieces(expectedBoard, PieceRule.SOLDIER, Camp.HAN, createPosition(6, 0), createPosition(6, 2), createPosition(6, 4), createPosition(6, 6), createPosition(6, 8)); @@ -70,11 +70,11 @@ private Map createExpectedBoard() { private Map createChoLeftHanRightBoard() { Map board = new HashMap<>(); - putPieces(board, Type.ELEPHANT, Camp.CHO, createPosition(0, 1), createPosition(0, 6)); - putPieces(board, Type.HORSE, Camp.CHO, createPosition(0, 2), createPosition(0, 7)); + putPieces(board, PieceRule.ELEPHANT, Camp.CHO, createPosition(0, 1), createPosition(0, 6)); + putPieces(board, PieceRule.HORSE, Camp.CHO, createPosition(0, 2), createPosition(0, 7)); - putPieces(board, Type.ELEPHANT, Camp.HAN, createPosition(9, 1), createPosition(9, 6)); - putPieces(board, Type.HORSE, Camp.HAN, createPosition(9, 2), createPosition(9, 7)); + putPieces(board, PieceRule.ELEPHANT, Camp.HAN, createPosition(9, 1), createPosition(9, 6)); + putPieces(board, PieceRule.HORSE, Camp.HAN, createPosition(9, 2), createPosition(9, 7)); return board; } @@ -82,20 +82,20 @@ private Map createChoLeftHanRightBoard() { private Map createChoInnerHanOuterBoard() { Map board = new HashMap<>(); - putPieces(board, Type.ELEPHANT, Camp.CHO, createPosition(0, 2), createPosition(0, 6)); - putPieces(board, Type.HORSE, Camp.CHO, createPosition(0, 1), createPosition(0, 7)); + putPieces(board, PieceRule.ELEPHANT, Camp.CHO, createPosition(0, 2), createPosition(0, 6)); + putPieces(board, PieceRule.HORSE, Camp.CHO, createPosition(0, 1), createPosition(0, 7)); - putPieces(board, Type.ELEPHANT, Camp.HAN, createPosition(9, 1), createPosition(9, 7)); - putPieces(board, Type.HORSE, Camp.HAN, createPosition(9, 2), createPosition(9, 6)); + putPieces(board, PieceRule.ELEPHANT, Camp.HAN, createPosition(9, 1), createPosition(9, 7)); + putPieces(board, PieceRule.HORSE, Camp.HAN, createPosition(9, 2), createPosition(9, 6)); return board; } //TODO: moveStrategy 변경 - private void putPieces(Map board, Type type, Camp camp, + private void putPieces(Map board, PieceRule pieceRule, Camp camp, Position... positions) { for (Position position : positions) { - board.put(position, new Piece(type, camp, null)); + board.put(position, new Piece(pieceRule, camp, null)); } } From 7ef3df44f33135daac396a7e551f0dd2cb79cf12 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 16:12:13 +0900 Subject: [PATCH 23/68] =?UTF-8?q?refactor:=20=EC=83=9D=EC=84=B1=EC=9E=90?= =?UTF-8?q?=EC=97=90=EC=84=9C=20MoveStrategy=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../janggi/domain/board/ElephantSetting.java | 64 +++++++++---------- .../domain/board/InitialPiecePlacement.java | 55 ++++++++-------- src/main/java/janggi/domain/piece/Piece.java | 3 +- .../board/StandardBoardInitializerTest.java | 2 +- 4 files changed, 60 insertions(+), 64 deletions(-) diff --git a/src/main/java/janggi/domain/board/ElephantSetting.java b/src/main/java/janggi/domain/board/ElephantSetting.java index bd50155f70..065440e0b7 100644 --- a/src/main/java/janggi/domain/board/ElephantSetting.java +++ b/src/main/java/janggi/domain/board/ElephantSetting.java @@ -14,10 +14,10 @@ public enum ElephantSetting { @Override public Map makeElephants() { return Map.of( - new Position(0, 7), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), - new Position(0, 6), new Piece(PieceRule.HORSE, Camp.CHO, null), - new Position(0, 2), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), - new Position(0, 1), new Piece(PieceRule.HORSE, Camp.CHO, null) + new Position(0, 7), new Piece(PieceRule.ELEPHANT, Camp.CHO), + new Position(0, 6), new Piece(PieceRule.HORSE, Camp.CHO), + new Position(0, 2), new Piece(PieceRule.ELEPHANT, Camp.CHO), + new Position(0, 1), new Piece(PieceRule.HORSE, Camp.CHO) ); } }, @@ -25,10 +25,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(0, 7), new Piece(PieceRule.HORSE, Camp.CHO, null), - new Position(0, 6), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), - new Position(0, 2), new Piece(PieceRule.HORSE, Camp.CHO, null), - new Position(0, 1), new Piece(PieceRule.ELEPHANT, Camp.CHO, null) + new Position(0, 7), new Piece(PieceRule.HORSE, Camp.CHO), + new Position(0, 6), new Piece(PieceRule.ELEPHANT, Camp.CHO), + new Position(0, 2), new Piece(PieceRule.HORSE, Camp.CHO), + new Position(0, 1), new Piece(PieceRule.ELEPHANT, Camp.CHO) ); } }, @@ -36,10 +36,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(0, 7), new Piece(PieceRule.HORSE, Camp.CHO, null), - new Position(0, 6), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), - new Position(0, 2), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), - new Position(0, 1), new Piece(PieceRule.HORSE, Camp.CHO, null) + new Position(0, 7), new Piece(PieceRule.HORSE, Camp.CHO), + new Position(0, 6), new Piece(PieceRule.ELEPHANT, Camp.CHO), + new Position(0, 2), new Piece(PieceRule.ELEPHANT, Camp.CHO), + new Position(0, 1), new Piece(PieceRule.HORSE, Camp.CHO) ); } }, @@ -47,10 +47,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(0, 7), new Piece(PieceRule.ELEPHANT, Camp.CHO, null), - new Position(0, 6), new Piece(PieceRule.HORSE, Camp.CHO, null), - new Position(0, 2), new Piece(PieceRule.HORSE, Camp.CHO, null), - new Position(0, 1), new Piece(PieceRule.ELEPHANT, Camp.CHO, null) + new Position(0, 7), new Piece(PieceRule.ELEPHANT, Camp.CHO), + new Position(0, 6), new Piece(PieceRule.HORSE, Camp.CHO), + new Position(0, 2), new Piece(PieceRule.HORSE, Camp.CHO), + new Position(0, 1), new Piece(PieceRule.ELEPHANT, Camp.CHO) ); } }, @@ -58,10 +58,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(9, 1), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), - new Position(9, 2), new Piece(PieceRule.HORSE, Camp.HAN, null), - new Position(9, 6), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), - new Position(9, 7), new Piece(PieceRule.HORSE, Camp.HAN, null) + new Position(9, 1), new Piece(PieceRule.ELEPHANT, Camp.HAN), + new Position(9, 2), new Piece(PieceRule.HORSE, Camp.HAN), + new Position(9, 6), new Piece(PieceRule.ELEPHANT, Camp.HAN), + new Position(9, 7), new Piece(PieceRule.HORSE, Camp.HAN) ); } }, @@ -69,10 +69,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(9, 1), new Piece(PieceRule.HORSE, Camp.HAN, null), - new Position(9, 2), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), - new Position(9, 6), new Piece(PieceRule.HORSE, Camp.HAN, null), - new Position(9, 7), new Piece(PieceRule.ELEPHANT, Camp.HAN, null) + new Position(9, 1), new Piece(PieceRule.HORSE, Camp.HAN), + new Position(9, 2), new Piece(PieceRule.ELEPHANT, Camp.HAN), + new Position(9, 6), new Piece(PieceRule.HORSE, Camp.HAN), + new Position(9, 7), new Piece(PieceRule.ELEPHANT, Camp.HAN) ); } }, @@ -80,10 +80,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(9, 1), new Piece(PieceRule.HORSE, Camp.HAN, null), - new Position(9, 2), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), - new Position(9, 6), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), - new Position(9, 7), new Piece(PieceRule.HORSE, Camp.HAN, null) + new Position(9, 1), new Piece(PieceRule.HORSE, Camp.HAN), + new Position(9, 2), new Piece(PieceRule.ELEPHANT, Camp.HAN), + new Position(9, 6), new Piece(PieceRule.ELEPHANT, Camp.HAN), + new Position(9, 7), new Piece(PieceRule.HORSE, Camp.HAN) ); } }, @@ -91,10 +91,10 @@ public Map makeElephants() { @Override public Map makeElephants() { return Map.of( - new Position(9, 1), new Piece(PieceRule.ELEPHANT, Camp.HAN, null), - new Position(9, 2), new Piece(PieceRule.HORSE, Camp.HAN, null), - new Position(9, 6), new Piece(PieceRule.HORSE, Camp.HAN, null), - new Position(9, 7), new Piece(PieceRule.ELEPHANT, Camp.HAN, null) + new Position(9, 1), new Piece(PieceRule.ELEPHANT, Camp.HAN), + new Position(9, 2), new Piece(PieceRule.HORSE, Camp.HAN), + new Position(9, 6), new Piece(PieceRule.HORSE, Camp.HAN), + new Position(9, 7), new Piece(PieceRule.ELEPHANT, Camp.HAN) ); } }; diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java index 73ceef5bdd..3d8ad76fbf 100644 --- a/src/main/java/janggi/domain/board/InitialPiecePlacement.java +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -4,50 +4,47 @@ import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; -import janggi.domain.piece.strategy.MoveStrategy; import java.util.HashMap; import java.util.Map; public enum InitialPiecePlacement { - CHO_SOLDIER_1(3, 0, Camp.CHO, PieceRule.SOLDIER, null), - CHO_SOLDIER_2(3, 2, Camp.CHO, PieceRule.SOLDIER, null), - CHO_SOLDIER_3(3, 4, Camp.CHO, PieceRule.SOLDIER, null), - CHO_SOLDIER_4(3, 6, Camp.CHO, PieceRule.SOLDIER, null), - CHO_SOLDIER_5(3, 8, Camp.CHO, PieceRule.SOLDIER, null), - CHO_CHARIOT_LEFT(0, 0, Camp.CHO, PieceRule.CHARIOT, null), - CHO_GUARD_LEFT(0, 3, Camp.CHO, PieceRule.GUARD, null), - CHO_GUARD_RIGHT(0, 5, Camp.CHO, PieceRule.GUARD, null), - CHO_CHARIOT_RIGHT(0, 8, Camp.CHO, PieceRule.CHARIOT, null), - CHO_GENERAL(1, 4, Camp.CHO, PieceRule.GENERAL, null), - CHO_CANNON_LEFT(2, 1, Camp.CHO, PieceRule.CANNON, null), - CHO_CANNON_RIGHT(2, 7, Camp.CHO, PieceRule.CANNON, null), + CHO_SOLDIER_1(3, 0, Camp.CHO, PieceRule.SOLDIER), + CHO_SOLDIER_2(3, 2, Camp.CHO, PieceRule.SOLDIER), + CHO_SOLDIER_3(3, 4, Camp.CHO, PieceRule.SOLDIER), + CHO_SOLDIER_4(3, 6, Camp.CHO, PieceRule.SOLDIER), + CHO_SOLDIER_5(3, 8, Camp.CHO, PieceRule.SOLDIER), + CHO_CHARIOT_LEFT(0, 0, Camp.CHO, PieceRule.CHARIOT), + CHO_GUARD_LEFT(0, 3, Camp.CHO, PieceRule.GUARD), + CHO_GUARD_RIGHT(0, 5, Camp.CHO, PieceRule.GUARD), + CHO_CHARIOT_RIGHT(0, 8, Camp.CHO, PieceRule.CHARIOT), + CHO_GENERAL(1, 4, Camp.CHO, PieceRule.GENERAL), + CHO_CANNON_LEFT(2, 1, Camp.CHO, PieceRule.CANNON), + CHO_CANNON_RIGHT(2, 7, Camp.CHO, PieceRule.CANNON), - HAN_SOLDIER_1(6, 0, Camp.HAN, PieceRule.SOLDIER, null), - HAN_SOLDIER_2(6, 2, Camp.HAN, PieceRule.SOLDIER, null), - HAN_SOLDIER_3(6, 4, Camp.HAN, PieceRule.SOLDIER, null), - HAN_SOLDIER_4(6, 6, Camp.HAN, PieceRule.SOLDIER, null), - HAN_SOLDIER_5(6, 8, Camp.HAN, PieceRule.SOLDIER, null), - HAN_CANNON_LEFT(7, 1, Camp.HAN, PieceRule.CANNON, null), - HAN_CANNON_RIGHT(7, 7, Camp.HAN, PieceRule.CANNON, null), - HAN_GENERAL(8, 4, Camp.HAN, PieceRule.GENERAL, null), - HAN_CHARIOT_LEFT(9, 0, Camp.HAN, PieceRule.CHARIOT, null), - HAN_GUARD_LEFT(9, 3, Camp.HAN, PieceRule.GUARD, null), - HAN_GUARD_RIGHT(9, 5, Camp.HAN, PieceRule.GUARD, null), - HAN_CHARIOT_RIGHT(9, 8, Camp.HAN, PieceRule.CHARIOT, null); + HAN_SOLDIER_1(6, 0, Camp.HAN, PieceRule.SOLDIER), + HAN_SOLDIER_2(6, 2, Camp.HAN, PieceRule.SOLDIER), + HAN_SOLDIER_3(6, 4, Camp.HAN, PieceRule.SOLDIER), + HAN_SOLDIER_4(6, 6, Camp.HAN, PieceRule.SOLDIER), + HAN_SOLDIER_5(6, 8, Camp.HAN, PieceRule.SOLDIER), + HAN_CANNON_LEFT(7, 1, Camp.HAN, PieceRule.CANNON), + HAN_CANNON_RIGHT(7, 7, Camp.HAN, PieceRule.CANNON), + HAN_GENERAL(8, 4, Camp.HAN, PieceRule.GENERAL), + HAN_CHARIOT_LEFT(9, 0, Camp.HAN, PieceRule.CHARIOT), + HAN_GUARD_LEFT(9, 3, Camp.HAN, PieceRule.GUARD), + HAN_GUARD_RIGHT(9, 5, Camp.HAN, PieceRule.GUARD), + HAN_CHARIOT_RIGHT(9, 8, Camp.HAN, PieceRule.CHARIOT); private final int row; private final int column; private final Camp camp; private final PieceRule pieceRule; - private final MoveStrategy moveStrategy; - InitialPiecePlacement(int row, int column, Camp camp, PieceRule pieceRule, MoveStrategy moveStrategy) { + InitialPiecePlacement(int row, int column, Camp camp, PieceRule pieceRule) { this.row = row; this.column = column; this.camp = camp; this.pieceRule = pieceRule; - this.moveStrategy = moveStrategy; } // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 @@ -56,7 +53,7 @@ public static Map init(ElephantSetting choElephantSetting, Elep for (InitialPiecePlacement piece : values()) { board.put(new Position(piece.row, piece.column) - , new Piece(piece.pieceRule, piece.camp, piece.moveStrategy)); + , new Piece(piece.pieceRule, piece.camp)); } Map choElephants = choElephantSetting.makeElephants(); diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 4ef8deb602..daa19dec6e 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -1,7 +1,6 @@ package janggi.domain.piece; import janggi.domain.Position; -import janggi.domain.piece.strategy.MoveStrategy; import java.util.List; import java.util.Objects; @@ -10,7 +9,7 @@ public class Piece { private final PieceRule pieceRule; private final Camp camp; - public Piece(PieceRule pieceRule, Camp camp, MoveStrategy moveStrategy) { + public Piece(PieceRule pieceRule, Camp camp) { this.pieceRule = pieceRule; this.camp = camp; } diff --git a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java index 0a8031a3a4..5134ca9244 100644 --- a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java @@ -95,7 +95,7 @@ private Map createChoInnerHanOuterBoard() { private void putPieces(Map board, PieceRule pieceRule, Camp camp, Position... positions) { for (Position position : positions) { - board.put(position, new Piece(pieceRule, camp, null)); + board.put(position, new Piece(pieceRule, camp)); } } From d8fa73b45f6bb0b1a0a0ce2551f9825f4455f9db Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 16:34:38 +0900 Subject: [PATCH 24/68] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=EC=83=81=20?= =?UTF-8?q?=EA=B8=B0=EB=AC=BC=EC=9D=B4=20=EC=97=86=EC=96=B4=EC=95=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 경로 상 기물 없는 조건 확인하는 테스트 추가 --- .../java/janggi/domain/board/JanggiBoard.java | 23 ++++++++ src/main/java/janggi/domain/piece/Piece.java | 4 ++ .../piece/condition/EmptyCondition.java | 29 ++++++++++ .../domain/piece/condition/MoveCondition.java | 11 ++++ .../EmptyConditionTestBoardInitializer.java | 15 +++++ .../piece/condition/EmptyConditionTest.java | 57 +++++++++++++++++++ 6 files changed, 139 insertions(+) create mode 100644 src/main/java/janggi/domain/board/JanggiBoard.java create mode 100644 src/main/java/janggi/domain/piece/condition/EmptyCondition.java create mode 100644 src/main/java/janggi/domain/piece/condition/MoveCondition.java create mode 100644 src/test/java/janggi/domain/board/EmptyConditionTestBoardInitializer.java create mode 100644 src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java diff --git a/src/main/java/janggi/domain/board/JanggiBoard.java b/src/main/java/janggi/domain/board/JanggiBoard.java new file mode 100644 index 0000000000..5948ea5d9c --- /dev/null +++ b/src/main/java/janggi/domain/board/JanggiBoard.java @@ -0,0 +1,23 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Piece; +import java.util.HashMap; +import java.util.Map; + +public class JanggiBoard { + + private final Map board = new HashMap<>(); + + public JanggiBoard(BoardInitializer boardInitializer) { + board.putAll(boardInitializer.initialize()); + } + + public Map getBoard() { + return board; + } + + public Piece findPiece(Position position) { + return board.get(position); + } +} diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index daa19dec6e..7bd75b0c16 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -19,6 +19,10 @@ public boolean canMove(Position from, Position to) { return true; } + public boolean isSameCamp(Camp camp) { + return this.camp == camp; + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { diff --git a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java new file mode 100644 index 0000000000..1d9ea74aa0 --- /dev/null +++ b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java @@ -0,0 +1,29 @@ +package janggi.domain.piece.condition; + +import janggi.domain.Position; +import janggi.domain.board.JanggiBoard; +import janggi.domain.piece.Camp; +import java.util.List; + +public class EmptyCondition implements MoveCondition { + + @Override + public void checkPath(List path, Camp camp, JanggiBoard board) { + for (int i = 0; i < path.size() - 1; i++) { + validateEmptyPosition(path, board, i); + } + validateGoalPosition(path.getLast(), camp, board); + } + + private void validateEmptyPosition(List path, JanggiBoard board, int i) { + if (board.getBoard().containsKey(path.get(i))) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + private void validateGoalPosition(Position lastPosition, Camp camp, JanggiBoard board) { + if (board.findPiece(lastPosition).isSameCamp(camp)) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } +} diff --git a/src/main/java/janggi/domain/piece/condition/MoveCondition.java b/src/main/java/janggi/domain/piece/condition/MoveCondition.java new file mode 100644 index 0000000000..68adfdc06d --- /dev/null +++ b/src/main/java/janggi/domain/piece/condition/MoveCondition.java @@ -0,0 +1,11 @@ +package janggi.domain.piece.condition; + +import janggi.domain.Position; +import janggi.domain.board.JanggiBoard; +import janggi.domain.piece.Camp; +import java.util.List; + +public interface MoveCondition { + + void checkPath(List path, Camp camp, JanggiBoard board); +} diff --git a/src/test/java/janggi/domain/board/EmptyConditionTestBoardInitializer.java b/src/test/java/janggi/domain/board/EmptyConditionTestBoardInitializer.java new file mode 100644 index 0000000000..1f971a7a6a --- /dev/null +++ b/src/test/java/janggi/domain/board/EmptyConditionTestBoardInitializer.java @@ -0,0 +1,15 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; +import java.util.Map; + +public class EmptyConditionTestBoardInitializer implements BoardInitializer { + + @Override + public Map initialize() { + return Map.of(new Position(0, 4), new Piece(PieceRule.CHARIOT, Camp.HAN)); + } +} diff --git a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java new file mode 100644 index 0000000000..f4527e25c9 --- /dev/null +++ b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java @@ -0,0 +1,57 @@ +package janggi.domain.piece.condition; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.board.BoardInitializer; +import janggi.domain.board.EmptyConditionTestBoardInitializer; +import janggi.domain.board.JanggiBoard; +import janggi.domain.piece.Camp; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class EmptyConditionTest { + + MoveCondition condition = new EmptyCondition(); + + @Test + void 이동하려는_경로에_기물이_존재하면_예외가_발생한다() { + //given + List path = List.of( + new Position(0, 0), + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4), + new Position(0, 5) + ); + Camp camp = Camp.HAN; + + BoardInitializer boardInitializer = new EmptyConditionTestBoardInitializer(); + JanggiBoard board = new JanggiBoard(boardInitializer); + //when & then + assertThatThrownBy(() -> condition.checkPath(path, camp, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 도착지점에_아군_기물이_존재하면_예외가_발생한다() { + //given + List path = List.of( + new Position(0, 0), + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4) + ); + Camp camp = Camp.HAN; + + BoardInitializer boardInitializer = new EmptyConditionTestBoardInitializer(); + JanggiBoard board = new JanggiBoard(boardInitializer); + //when & then + assertThatThrownBy(() -> condition.checkPath(path, camp, board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } +} From 74efd5af93e99aeee184be8118d919ce47181ba6 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 19:50:04 +0900 Subject: [PATCH 25/68] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=EC=83=81=20?= =?UTF-8?q?=EA=B8=B0=EB=AC=BC=EC=9D=B4=201=EA=B0=9C=20=EC=9E=88=EC=96=B4?= =?UTF-8?q?=EC=95=BC=20=EC=9D=B4=EB=8F=99=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EC=A1=B0=EA=B1=B4=20=ED=99=95=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 경로 상 기물이 1개 있는 조건 확인하는 테스트 추가 --- src/main/java/janggi/domain/board/Board.java | 14 ++ .../java/janggi/domain/board/JanggiBoard.java | 26 +++- src/main/java/janggi/domain/piece/Piece.java | 7 +- .../domain/piece/condition/MoveCondition.java | 5 +- .../condition/OnePieceExistsCondition.java | 48 +++++++ .../OnePieceExistsConditionTest.java | 132 ++++++++++++++++++ 6 files changed, 220 insertions(+), 12 deletions(-) create mode 100644 src/main/java/janggi/domain/board/Board.java create mode 100644 src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java create mode 100644 src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java new file mode 100644 index 0000000000..650855c15b --- /dev/null +++ b/src/main/java/janggi/domain/board/Board.java @@ -0,0 +1,14 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; + +public interface Board { + + boolean hasPieceAt(Position position); + + boolean isSameCampPieceAt(Position position, Camp camp); + + boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule); +} diff --git a/src/main/java/janggi/domain/board/JanggiBoard.java b/src/main/java/janggi/domain/board/JanggiBoard.java index 5948ea5d9c..49c2f2a243 100644 --- a/src/main/java/janggi/domain/board/JanggiBoard.java +++ b/src/main/java/janggi/domain/board/JanggiBoard.java @@ -1,11 +1,13 @@ package janggi.domain.board; import janggi.domain.Position; +import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; import java.util.HashMap; import java.util.Map; -public class JanggiBoard { +public class JanggiBoard implements Board { private final Map board = new HashMap<>(); @@ -13,11 +15,25 @@ public JanggiBoard(BoardInitializer boardInitializer) { board.putAll(boardInitializer.initialize()); } - public Map getBoard() { - return board; + @Override + public boolean hasPieceAt(Position position) { + return board.containsKey(position); } - public Piece findPiece(Position position) { - return board.get(position); + @Override + public boolean isSameCampPieceAt(Position position, Camp camp) { + if (board.containsKey(position)) { + return board.get(position).isSameCamp(camp); + } + return false; + } + + @Override + public boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule) { + if (board.containsKey(position)) { + Piece foundPiece = board.get(position); + return foundPiece.isSamePieceRule(pieceRule); + } + return false; } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 7bd75b0c16..2d9660439c 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -1,7 +1,5 @@ package janggi.domain.piece; -import janggi.domain.Position; -import java.util.List; import java.util.Objects; public class Piece { @@ -14,9 +12,8 @@ public Piece(PieceRule pieceRule, Camp camp) { this.camp = camp; } - public boolean canMove(Position from, Position to) { - List path = pieceRule.findPath(from, to, camp); - return true; + public boolean isSamePieceRule(PieceRule pieceRule) { + return this.pieceRule == pieceRule; } public boolean isSameCamp(Camp camp) { diff --git a/src/main/java/janggi/domain/piece/condition/MoveCondition.java b/src/main/java/janggi/domain/piece/condition/MoveCondition.java index 68adfdc06d..3c633ac15e 100644 --- a/src/main/java/janggi/domain/piece/condition/MoveCondition.java +++ b/src/main/java/janggi/domain/piece/condition/MoveCondition.java @@ -1,11 +1,12 @@ package janggi.domain.piece.condition; import janggi.domain.Position; -import janggi.domain.board.JanggiBoard; +import janggi.domain.board.Board; import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; import java.util.List; public interface MoveCondition { - void checkPath(List path, Camp camp, JanggiBoard board); + void checkPath(List path, Camp camp, Board board, PieceRule pieceRule); } diff --git a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java new file mode 100644 index 0000000000..9bfa41fa3f --- /dev/null +++ b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java @@ -0,0 +1,48 @@ +package janggi.domain.piece.condition; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; +import java.util.List; + +public class OnePieceExistsCondition implements MoveCondition { + + @Override + public void checkPath(List path, Camp camp, Board board, PieceRule pieceRule) { + int countOfPiece = 0; + for (int i = 0; i < path.size() - 1; i++) { + Position position = path.get(i); + countOfPiece += countPieceAt(board, pieceRule, position); + } + + validateExactPieceCount(countOfPiece); + validateGoalPosition(path.getLast(), camp, board, pieceRule); + } + + private int countPieceAt(Board board, PieceRule pieceRule, Position position) { + if (board.hasPieceAt(position)) { + validateSamePieceRule(board, pieceRule, position); + return 1; + } + return 0; + } + + private void validateSamePieceRule(Board board, PieceRule pieceRule, Position position) { + if (board.hasSamePieceRuleAt(position, pieceRule)) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + private void validateExactPieceCount(int flag) { + if (flag != 1) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + private void validateGoalPosition(Position lastPosition, Camp camp, Board board, PieceRule pieceRule) { + if (board.isSameCampPieceAt(lastPosition, camp) || board.hasSamePieceRuleAt(lastPosition, pieceRule)) { + throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } +} diff --git a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java new file mode 100644 index 0000000000..e3144f5bed --- /dev/null +++ b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java @@ -0,0 +1,132 @@ +package janggi.domain.piece.condition; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.board.BoardInitializer; +import janggi.domain.board.JanggiBoard; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class OnePieceExistsConditionTest { + + MoveCondition condition = new OnePieceExistsCondition(); + + @Test + void 이동하려는_경로에_기물이_존재하지_않으면_예외가_발생한다() { + //given + List path = List.of( + new Position(0, 0), + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4), + new Position(0, 5) + ); + Camp camp = Camp.HAN; + + BoardInitializer boardInitializer = Map::of; + JanggiBoard board = new JanggiBoard(boardInitializer); + //when & then + assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 이동하려는_경로에_기물이_2개_이상_존재하면_예외가_발생한다() { + //given + List path = List.of( + new Position(0, 0), + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4), + new Position(0, 5) + ); + Camp camp = Camp.HAN; + + BoardInitializer boardInitializer = () -> Map.of( + new Position(0, 3), new Piece(PieceRule.CHARIOT, Camp.HAN), + new Position(0, 4), new Piece(PieceRule.ELEPHANT, Camp.HAN) + ); + JanggiBoard board = new JanggiBoard(boardInitializer); + //when & then + assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 이동하려는_경로에_있는_기물이_포이면_예외가_발생한다() { + //given + List path = List.of( + new Position(0, 0), + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4), + new Position(0, 5) + ); + Camp camp = Camp.HAN; + + BoardInitializer boardInitializer = () -> Map.of( + new Position(0, 4), new Piece(PieceRule.CANNON, Camp.CHO) + ); + JanggiBoard board = new JanggiBoard(boardInitializer); + //when & then + assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 목적지에_있는_기물이_아군이면_예외가_발생한다() { + //given + List path = List.of( + new Position(0, 0), + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4), + new Position(0, 5) + ); + Camp camp = Camp.HAN; + + BoardInitializer boardInitializer = () -> Map.of( + new Position(0, 5), new Piece(PieceRule.CHARIOT, Camp.HAN) + ); + JanggiBoard board = new JanggiBoard(boardInitializer); + //when & then + assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 목적지에_있는_기물이_상대_포면_예외가_발생한다() { + //given + List path = List.of( + new Position(0, 0), + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4), + new Position(0, 5) + ); + Camp camp = Camp.HAN; + + BoardInitializer boardInitializer = () -> Map.of( + new Position(0, 5), new Piece(PieceRule.CANNON, Camp.CHO) + ); + JanggiBoard board = new JanggiBoard(boardInitializer); + //when & then + assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } +} From 4fe8ab9f6aaf90b97ac134f0945e8b2838904087 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 19:51:34 +0900 Subject: [PATCH 26/68] =?UTF-8?q?refactor:=20MoveCondition=20=EB=A7=A4?= =?UTF-8?q?=EA=B0=9C=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/piece/condition/EmptyCondition.java | 15 ++++++++------- .../piece/condition/EmptyConditionTest.java | 5 +++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java index 1d9ea74aa0..4aa7a7b9a7 100644 --- a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java +++ b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java @@ -1,28 +1,29 @@ package janggi.domain.piece.condition; import janggi.domain.Position; -import janggi.domain.board.JanggiBoard; +import janggi.domain.board.Board; import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; import java.util.List; public class EmptyCondition implements MoveCondition { @Override - public void checkPath(List path, Camp camp, JanggiBoard board) { + public void checkPath(List path, Camp camp, Board board, PieceRule pieceRule) { for (int i = 0; i < path.size() - 1; i++) { - validateEmptyPosition(path, board, i); + validateEmptyPosition(path.get(i), board); } validateGoalPosition(path.getLast(), camp, board); } - private void validateEmptyPosition(List path, JanggiBoard board, int i) { - if (board.getBoard().containsKey(path.get(i))) { + private void validateEmptyPosition(Position position, Board board) { + if (board.hasPieceAt(position)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } - private void validateGoalPosition(Position lastPosition, Camp camp, JanggiBoard board) { - if (board.findPiece(lastPosition).isSameCamp(camp)) { + private void validateGoalPosition(Position lastPosition, Camp camp, Board board) { + if (board.isSameCampPieceAt(lastPosition, camp)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } diff --git a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java index f4527e25c9..3c2879f74b 100644 --- a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java @@ -7,6 +7,7 @@ import janggi.domain.board.EmptyConditionTestBoardInitializer; import janggi.domain.board.JanggiBoard; import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; import java.util.List; import org.junit.jupiter.api.Test; @@ -30,7 +31,7 @@ public class EmptyConditionTest { BoardInitializer boardInitializer = new EmptyConditionTestBoardInitializer(); JanggiBoard board = new JanggiBoard(boardInitializer); //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board)) + assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } @@ -50,7 +51,7 @@ public class EmptyConditionTest { BoardInitializer boardInitializer = new EmptyConditionTestBoardInitializer(); JanggiBoard board = new JanggiBoard(boardInitializer); //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board)) + assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } From 0eabf5551961ef798afaade0278a66cbea561469 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 19:52:16 +0900 Subject: [PATCH 27/68] =?UTF-8?q?feat:=20MoveCondition=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/janggi/domain/piece/PieceRule.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/janggi/domain/piece/PieceRule.java b/src/main/java/janggi/domain/piece/PieceRule.java index 27367db994..9c30bd2e0e 100644 --- a/src/main/java/janggi/domain/piece/PieceRule.java +++ b/src/main/java/janggi/domain/piece/PieceRule.java @@ -1,31 +1,30 @@ package janggi.domain.piece; -import janggi.domain.Position; +import janggi.domain.piece.condition.EmptyCondition; +import janggi.domain.piece.condition.MoveCondition; +import janggi.domain.piece.condition.OnePieceExistsCondition; import janggi.domain.piece.strategy.ElephantStrategy; import janggi.domain.piece.strategy.HorseStrategy; import janggi.domain.piece.strategy.MoveStrategy; import janggi.domain.piece.strategy.MultiStepStraightStrategy; import janggi.domain.piece.strategy.SingleStepStraightStrategy; import janggi.domain.piece.strategy.SoldierStrategy; -import java.util.List; public enum PieceRule { - GENERAL(new SingleStepStraightStrategy()), - CHARIOT(new MultiStepStraightStrategy()), - HORSE(new HorseStrategy()), - CANNON(new MultiStepStraightStrategy()), - GUARD(new SingleStepStraightStrategy()), - ELEPHANT(new ElephantStrategy()), - SOLDIER(new SoldierStrategy()); + GENERAL(new SingleStepStraightStrategy(), new EmptyCondition()), + CHARIOT(new MultiStepStraightStrategy(), new EmptyCondition()), + HORSE(new HorseStrategy(), new EmptyCondition()), + CANNON(new MultiStepStraightStrategy(), new OnePieceExistsCondition()), + GUARD(new SingleStepStraightStrategy(), new EmptyCondition()), + ELEPHANT(new ElephantStrategy(), new EmptyCondition()), + SOLDIER(new SoldierStrategy(), new EmptyCondition()); private final MoveStrategy moveStrategy; + private final MoveCondition moveCondition; - PieceRule(MoveStrategy moveStrategy) { + PieceRule(MoveStrategy moveStrategy, MoveCondition moveCondition) { this.moveStrategy = moveStrategy; - } - - public List findPath(Position from, Position to, Camp camp) { - return moveStrategy.findPath(from, to, camp); + this.moveCondition = moveCondition; } } From 544508270e1df700fe3ed5fae7febe7b25291e41 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 20:27:48 +0900 Subject: [PATCH 28/68] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기물 이동 검증 기능 테스트 추가 --- src/main/java/janggi/domain/piece/Piece.java | 8 + .../java/janggi/domain/piece/PieceRule.java | 11 + .../java/janggi/domain/piece/PieceTest.java | 324 ++++++++++++++++++ 3 files changed, 343 insertions(+) create mode 100644 src/test/java/janggi/domain/piece/PieceTest.java diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 2d9660439c..49d37009f7 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -1,5 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Position; +import janggi.domain.board.Board; +import java.util.List; import java.util.Objects; public class Piece { @@ -12,6 +15,11 @@ public Piece(PieceRule pieceRule, Camp camp) { this.camp = camp; } + public void validateMove(Position from, Position to, Board board) { + List path = pieceRule.findPath(from, to, camp); + pieceRule.checkPath(path, camp, board); + } + public boolean isSamePieceRule(PieceRule pieceRule) { return this.pieceRule == pieceRule; } diff --git a/src/main/java/janggi/domain/piece/PieceRule.java b/src/main/java/janggi/domain/piece/PieceRule.java index 9c30bd2e0e..aae76ecc49 100644 --- a/src/main/java/janggi/domain/piece/PieceRule.java +++ b/src/main/java/janggi/domain/piece/PieceRule.java @@ -1,5 +1,7 @@ package janggi.domain.piece; +import janggi.domain.Position; +import janggi.domain.board.Board; import janggi.domain.piece.condition.EmptyCondition; import janggi.domain.piece.condition.MoveCondition; import janggi.domain.piece.condition.OnePieceExistsCondition; @@ -9,6 +11,7 @@ import janggi.domain.piece.strategy.MultiStepStraightStrategy; import janggi.domain.piece.strategy.SingleStepStraightStrategy; import janggi.domain.piece.strategy.SoldierStrategy; +import java.util.List; public enum PieceRule { @@ -27,4 +30,12 @@ public enum PieceRule { this.moveStrategy = moveStrategy; this.moveCondition = moveCondition; } + + public List findPath(Position from, Position to, Camp camp) { + return moveStrategy.findPath(from, to, camp); + } + + public void checkPath(List path, Camp camp, Board board) { + moveCondition.checkPath(path, camp, board, this); + } } diff --git a/src/test/java/janggi/domain/piece/PieceTest.java b/src/test/java/janggi/domain/piece/PieceTest.java new file mode 100644 index 0000000000..47f3447424 --- /dev/null +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -0,0 +1,324 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.board.JanggiBoard; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class PieceTest { + + @Test + void 같은_룰이_적용되는_기물인지_확인한다() { + // given + Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); + // when + boolean result = piece.isSamePieceRule(PieceRule.CANNON); + // then + assertThat(result).isTrue(); + } + + @Test + void 기물이_같은_진영인지_확인한다() { + // given + Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); + // when + boolean result = piece.isSameCamp(Camp.CHO); + // then + assertThat(result).isTrue(); + } + + @DisplayName("궁 행마법 테스트") + @Nested + class General { + @Test + void 궁은_이동_경로에_기물이_없으면_정상_이동한다() { + // given + Piece piece = new Piece(PieceRule.GENERAL, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + assertDoesNotThrow(() -> + piece.validateMove(new Position(1, 4), new Position(2, 4), board) + ); + } + + @Test + void 궁은_행마법을_따르지_않으면_예외가_발생한다() { + // given + Piece piece = new Piece(PieceRule.GENERAL, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(1, 4), new Position(3, 4), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + @DisplayName("사 행마법 테스트") + @Nested + class Guard { + @Test + void 사는_이동_경로에_기물이_없으면_정상_이동한다() { + // given + Piece piece = new Piece(PieceRule.GUARD, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + assertDoesNotThrow(() -> + piece.validateMove(new Position(0, 3), new Position(0, 4), board) + ); + } + + @Test + void 사는_행마법을_따르지_않으면_예외가_발생한다() { + // given + Piece piece = new Piece(PieceRule.GUARD, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(0, 3), new Position(0, 5), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + @DisplayName("마 행마법 테스트") + @Nested + class Horse { + @Test + void 마는_이동_경로에_기물이_없으면_정상_이동한다() { + // given + Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + assertDoesNotThrow(() -> + piece.validateMove(new Position(0, 1), new Position(2, 2), board) + ); + } + + @Test + void 마는_이동_경로에_기물이_있으면_예외가_발생한다() { + // given + Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); + Board board = new JanggiBoard(() -> Map.of( + new Position(1, 1), new Piece(PieceRule.CHARIOT, Camp.HAN) + )); + // when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(0, 1), new Position(2, 2), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 마는_행마법을_따르지_않으면_예외가_발생한다() { + // given + Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(0, 1), new Position(0, 7), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + @DisplayName("상 행마법 테스트") + @Nested + class Elephant { + @Test + void 상은_이동_경로에_기물이_없으면_정상_이동한다() { + // given + Piece piece = new Piece(PieceRule.ELEPHANT, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + assertDoesNotThrow(() -> + piece.validateMove(new Position(0, 6), new Position(3, 4), board) + ); + } + + @Test + void 상은_행마법을_따르지_않으면_예외가_발생한다() { + //given + Piece piece = new Piece(PieceRule.ELEPHANT, Camp.CHO); + Board board = new JanggiBoard(Map::of); + //when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(0, 6), new Position(3, 3), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + @DisplayName("포 행마법 테스트") + @Nested + class Cannon { + @Test + void 포는_이동_경로에_기물이_없으면_예외가_발생한다() { + //given + Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); + Board board = new JanggiBoard(Map::of); + //when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(2, 1), new Position(8, 1), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 포는_이동_경로에_기물이_2개_이상_있으면_예외가_발생한다() { + //given + Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); + Board board = new JanggiBoard(() -> Map.of( + new Position(3, 1), new Piece(PieceRule.CHARIOT, Camp.HAN), + new Position(5, 1), new Piece(PieceRule.CHARIOT, Camp.CHO) + + )); + //when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(2, 1), new Position(8, 1), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 포는_이동_경로에_기물이_1개만_있으면_정상_이동한다() { + // given + Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); + Board board = new JanggiBoard(() -> Map.of( + new Position(3, 1), new Piece(PieceRule.CHARIOT, Camp.HAN) + )); + // when & then + assertDoesNotThrow(() -> + piece.validateMove(new Position(2, 1), new Position(8, 1), board) + ); + } + } + + @DisplayName("차 행마법 테스트") + @Nested + class Chariot { + @Test + void 차는_이동_경로에_기물이_없으면_정상_이동한다() { + // given + Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + assertDoesNotThrow(() -> + piece.validateMove(new Position(0, 0), new Position(9, 0), board) + ); + } + + @Test + void 차는_이동_경로에_기물이_1개_이상_있으면_예외가_발생한다() { + //given + Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); + Board board = new JanggiBoard(() -> Map.of( + new Position(1, 0), new Piece(PieceRule.CHARIOT, Camp.HAN), + new Position(5, 1), new Piece(PieceRule.CHARIOT, Camp.CHO) + + )); + //when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(0, 0), new Position(9, 0), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 차는_행마법을_따르지_않으면_예외가_발생한다() { + //given + Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); + Board board = new JanggiBoard(Map::of); + //when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(0, 0), new Position(3, 3), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + @DisplayName("졸 행마법 테스트") + @Nested + class SoldierCho { + @Test + void 졸은_이동_경로에_기물이_없으면_정상_이동한다() { + // given + Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + assertDoesNotThrow(() -> + piece.validateMove(new Position(3, 0), new Position(4, 0), board) + ); + } + + @Test + void 졸은_후진할_시_예외가_발생한다() { + // given + Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(3, 0), new Position(2, 0), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 졸은_행마법을_따르지_않으면_예외가_발생한다() { + // given + Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); + Board board = new JanggiBoard(Map::of); + // when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(3, 0), new Position(5, 0), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } + + @DisplayName("병 행마법 테스트") + @Nested + class SoldierHan { + @Test + void 병은_이동_경로에_기물이_없으면_정상_이동한다() { + // given + Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); + Board board = new JanggiBoard(Map::of); + // when & then + assertDoesNotThrow(() -> + piece.validateMove(new Position(6, 0), new Position(5, 0), board) + ); + } + + @Test + void 병은_후진할_시_예외가_발생한다() { + // given + Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); + Board board = new JanggiBoard(Map::of); + // when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(6, 0), new Position(7, 0), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + + @Test + void 병은_행마법을_따르지_않으면_예외가_발생한다() { + // given + Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); + Board board = new JanggiBoard(Map::of); + // when & then + Assertions.assertThatThrownBy( + () -> piece.validateMove(new Position(6, 0), new Position(1, 0), board)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + } + } +} From dc41b106b5092c81002f822be68b83f7eae24a60 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Thu, 26 Mar 2026 20:40:51 +0900 Subject: [PATCH 29/68] =?UTF-8?q?feat:=20=EB=B3=B4=EB=93=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=AC=BC=20=EC=9D=B4=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기물 이동 기능 테스트 추가 --- .../java/janggi/domain/board/JanggiBoard.java | 7 ++++ .../janggi/domain/board/JanggiBoardTest.java | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/test/java/janggi/domain/board/JanggiBoardTest.java diff --git a/src/main/java/janggi/domain/board/JanggiBoard.java b/src/main/java/janggi/domain/board/JanggiBoard.java index 49c2f2a243..2966dfa588 100644 --- a/src/main/java/janggi/domain/board/JanggiBoard.java +++ b/src/main/java/janggi/domain/board/JanggiBoard.java @@ -36,4 +36,11 @@ public boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule) { } return false; } + + public void movePiece(Position from, Position to) { + Piece piece = board.get(from); + piece.validateMove(from, to, this); + board.put(to, piece); + board.remove(from); + } } diff --git a/src/test/java/janggi/domain/board/JanggiBoardTest.java b/src/test/java/janggi/domain/board/JanggiBoardTest.java new file mode 100644 index 0000000000..216b995d86 --- /dev/null +++ b/src/test/java/janggi/domain/board/JanggiBoardTest.java @@ -0,0 +1,34 @@ +package janggi.domain.board; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class JanggiBoardTest { + + @Test + void 목적지에_반대_진영_기물이_있으면_해당_기물을_제거한다() { + // given + Position from = new Position(7, 1); + Position to = new Position(0, 1); + + JanggiBoard board = new JanggiBoard(() -> Map.of( + new Position(4, 1), new Piece(PieceRule.SOLDIER, Camp.HAN), + to, new Piece(PieceRule.HORSE, Camp.CHO), + from, new Piece(PieceRule.CANNON, Camp.HAN) + )); + // when + board.movePiece(from, to); + // then + boolean GoalPositionExist = board.hasSamePieceRuleAt(to, PieceRule.CANNON); + boolean startPositionExist = board.hasPieceAt(from); + + assertThat(GoalPositionExist).isTrue(); + assertThat(startPositionExist).isFalse(); + } +} From a13c5117557570d920c471da567e194550a00425 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 15:44:52 +0900 Subject: [PATCH 30/68] =?UTF-8?q?refactor:=20=EC=83=81=EC=B0=A8=EB=A6=BC?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../janggi/domain/board/ElephantSetting.java | 127 ++++++------------ .../domain/board/InitialPiecePlacement.java | 12 +- .../board/StandardBoardInitializer.java | 14 +- src/main/java/janggi/domain/piece/Camp.java | 13 +- .../board/StandardBoardInitializerTest.java | 6 +- 5 files changed, 64 insertions(+), 108 deletions(-) diff --git a/src/main/java/janggi/domain/board/ElephantSetting.java b/src/main/java/janggi/domain/board/ElephantSetting.java index 065440e0b7..2c1845895f 100644 --- a/src/main/java/janggi/domain/board/ElephantSetting.java +++ b/src/main/java/janggi/domain/board/ElephantSetting.java @@ -4,100 +4,51 @@ import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Map; public enum ElephantSetting { - // TODO: position 중복 제거, List.of(1,2,6,7) 과 같은 방식 고려... + LEFT_ELEPHANT("1", List.of(PieceRule.ELEPHANT, PieceRule.HORSE, PieceRule.ELEPHANT, PieceRule.HORSE)), + RIGHT_ELEPHANT("2", List.of(PieceRule.HORSE, PieceRule.ELEPHANT, PieceRule.HORSE, PieceRule.ELEPHANT)), + INNER_ELEPHANT("3", List.of(PieceRule.HORSE, PieceRule.ELEPHANT, PieceRule.ELEPHANT, PieceRule.HORSE)), + OUTER_ELEPHANT("4", List.of(PieceRule.ELEPHANT, PieceRule.HORSE, PieceRule.HORSE, PieceRule.ELEPHANT)); - CHO_LEFT_ELEPHANT { - @Override - public Map makeElephants() { - return Map.of( - new Position(0, 7), new Piece(PieceRule.ELEPHANT, Camp.CHO), - new Position(0, 6), new Piece(PieceRule.HORSE, Camp.CHO), - new Position(0, 2), new Piece(PieceRule.ELEPHANT, Camp.CHO), - new Position(0, 1), new Piece(PieceRule.HORSE, Camp.CHO) - ); - } - }, - CHO_RIGHT_ELEPHANT { - @Override - public Map makeElephants() { - return Map.of( - new Position(0, 7), new Piece(PieceRule.HORSE, Camp.CHO), - new Position(0, 6), new Piece(PieceRule.ELEPHANT, Camp.CHO), - new Position(0, 2), new Piece(PieceRule.HORSE, Camp.CHO), - new Position(0, 1), new Piece(PieceRule.ELEPHANT, Camp.CHO) - ); - } - }, - CHO_INNER_ELEPHANT { - @Override - public Map makeElephants() { - return Map.of( - new Position(0, 7), new Piece(PieceRule.HORSE, Camp.CHO), - new Position(0, 6), new Piece(PieceRule.ELEPHANT, Camp.CHO), - new Position(0, 2), new Piece(PieceRule.ELEPHANT, Camp.CHO), - new Position(0, 1), new Piece(PieceRule.HORSE, Camp.CHO) - ); - } - }, - CHO_OUTER_ELEPHANT { - @Override - public Map makeElephants() { - return Map.of( - new Position(0, 7), new Piece(PieceRule.ELEPHANT, Camp.CHO), - new Position(0, 6), new Piece(PieceRule.HORSE, Camp.CHO), - new Position(0, 2), new Piece(PieceRule.HORSE, Camp.CHO), - new Position(0, 1), new Piece(PieceRule.ELEPHANT, Camp.CHO) - ); - } - }, - HAN_LEFT_ELEPHANT { - @Override - public Map makeElephants() { - return Map.of( - new Position(9, 1), new Piece(PieceRule.ELEPHANT, Camp.HAN), - new Position(9, 2), new Piece(PieceRule.HORSE, Camp.HAN), - new Position(9, 6), new Piece(PieceRule.ELEPHANT, Camp.HAN), - new Position(9, 7), new Piece(PieceRule.HORSE, Camp.HAN) - ); - } - }, - HAN_RIGHT_ELEPHANT { - @Override - public Map makeElephants() { - return Map.of( - new Position(9, 1), new Piece(PieceRule.HORSE, Camp.HAN), - new Position(9, 2), new Piece(PieceRule.ELEPHANT, Camp.HAN), - new Position(9, 6), new Piece(PieceRule.HORSE, Camp.HAN), - new Position(9, 7), new Piece(PieceRule.ELEPHANT, Camp.HAN) - ); - } - }, - HAN_INNER_ELEPHANT { - @Override - public Map makeElephants() { - return Map.of( - new Position(9, 1), new Piece(PieceRule.HORSE, Camp.HAN), - new Position(9, 2), new Piece(PieceRule.ELEPHANT, Camp.HAN), - new Position(9, 6), new Piece(PieceRule.ELEPHANT, Camp.HAN), - new Position(9, 7), new Piece(PieceRule.HORSE, Camp.HAN) - ); + private static final List SETTING_COLS = List.of(1, 2, 6, 7); + + private final String command; + private final List elephantOrder; + + ElephantSetting(String command, List elephantOrder) { + this.command = command; + this.elephantOrder = elephantOrder; + } + + public static ElephantSetting findElephantSettingBy(String command) { + return Arrays.stream(values()) + .filter(element -> element.command.equals(command)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("[ERROR] 존재하지 않는 상차림 입니다.")); + } + + public Map createElephantOrder(Camp camp) { + if (camp == Camp.HAN) { + return createByCamp(camp, SETTING_COLS); } - }, - HAN_OUTER_ELEPHANT { - @Override - public Map makeElephants() { - return Map.of( - new Position(9, 1), new Piece(PieceRule.ELEPHANT, Camp.HAN), - new Position(9, 2), new Piece(PieceRule.HORSE, Camp.HAN), - new Position(9, 6), new Piece(PieceRule.HORSE, Camp.HAN), - new Position(9, 7), new Piece(PieceRule.ELEPHANT, Camp.HAN) + return createByCamp(camp, SETTING_COLS.reversed()); + } + + private Map createByCamp(Camp camp, List settingCols) { + Map map = new HashMap<>(); + + for (int i = 0; i < settingCols.size(); i++) { + map.put( + new Position(camp.getStartRowPosition(), settingCols.get(i)), + new Piece(elephantOrder.get(i), camp) ); } - }; - - abstract public Map makeElephants(); + return map; + } } diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java index 3d8ad76fbf..3b41f1a1d6 100644 --- a/src/main/java/janggi/domain/board/InitialPiecePlacement.java +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -48,19 +48,15 @@ public enum InitialPiecePlacement { } // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 - public static Map init(ElephantSetting choElephantSetting, ElephantSetting hanElephantSetting) { + public static Map init(ElephantSetting choChoice, ElephantSetting hanChoice) { Map board = new HashMap<>(); for (InitialPiecePlacement piece : values()) { - board.put(new Position(piece.row, piece.column) - , new Piece(piece.pieceRule, piece.camp)); + board.put(new Position(piece.row, piece.column), new Piece(piece.pieceRule, piece.camp)); } - Map choElephants = choElephantSetting.makeElephants(); - Map hanElephants = hanElephantSetting.makeElephants(); - - board.putAll(choElephants); - board.putAll(hanElephants); + board.putAll(choChoice.createElephantOrder(Camp.CHO)); + board.putAll(hanChoice.createElephantOrder(Camp.HAN)); return board; } } diff --git a/src/main/java/janggi/domain/board/StandardBoardInitializer.java b/src/main/java/janggi/domain/board/StandardBoardInitializer.java index 873e3bf1c8..dc6bf3085a 100644 --- a/src/main/java/janggi/domain/board/StandardBoardInitializer.java +++ b/src/main/java/janggi/domain/board/StandardBoardInitializer.java @@ -6,17 +6,21 @@ public class StandardBoardInitializer implements BoardInitializer { - private final ElephantSetting choElephantSetting; - private final ElephantSetting hanElephantSetting; + private final String choChoice; + private final String hanChoice; // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 - public StandardBoardInitializer(ElephantSetting choElephantSetting, ElephantSetting hanElephantSetting) { - this.choElephantSetting = choElephantSetting; - this.hanElephantSetting = hanElephantSetting; + // TODO : String으로 바꿔도 같은 문제 발생 + public StandardBoardInitializer(String choChoice, String hanChoice) { + this.choChoice = choChoice; + this.hanChoice = hanChoice; } @Override public Map initialize() { + ElephantSetting choElephantSetting = ElephantSetting.findElephantSettingBy(choChoice); + ElephantSetting hanElephantSetting = ElephantSetting.findElephantSettingBy(hanChoice); + return InitialPiecePlacement.init(choElephantSetting, hanElephantSetting); } } diff --git a/src/main/java/janggi/domain/piece/Camp.java b/src/main/java/janggi/domain/piece/Camp.java index bcb68370e8..b53f7879a0 100644 --- a/src/main/java/janggi/domain/piece/Camp.java +++ b/src/main/java/janggi/domain/piece/Camp.java @@ -2,13 +2,16 @@ public enum Camp { - HAN(-1), - CHO(1); + HAN(-1, 9), + CHO(1, 0), + ; private final int forwardDirection; + private final int startRowPosition; - Camp(int forwardDirection) { + Camp(int forwardDirection, int startRowPosition) { this.forwardDirection = forwardDirection; + this.startRowPosition = startRowPosition; } public void validateForwardDirection(int rowDiff) { @@ -16,4 +19,8 @@ public void validateForwardDirection(int rowDiff) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } + + public int getStartRowPosition() { + return startRowPosition; + } } diff --git a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java index 5134ca9244..b55d53dbb3 100644 --- a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java @@ -14,8 +14,7 @@ public class StandardBoardInitializerTest { @Test void 초_마상마상_한_상마상마_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer(ElephantSetting.CHO_RIGHT_ELEPHANT, - ElephantSetting.HAN_LEFT_ELEPHANT); + BoardInitializer initializer = new StandardBoardInitializer("2", "1"); Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoLeftHanRightBoard()); // when @@ -30,8 +29,7 @@ public class StandardBoardInitializerTest { @Test void 초_마상상마_한_상마마상_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer(ElephantSetting.CHO_INNER_ELEPHANT, - ElephantSetting.HAN_OUTER_ELEPHANT); + BoardInitializer initializer = new StandardBoardInitializer("3", "4"); Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoInnerHanOuterBoard()); // when From b83e12e91503547f7626e9f6581ee5e55c23421c Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 16:05:48 +0900 Subject: [PATCH 31/68] =?UTF-8?q?feat:=20=EC=83=81=EC=B0=A8=EB=A6=BC=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cho, han 매개변수 위치 수정 --- src/main/java/janggi/Janggi.java | 17 +++++++ .../domain/board/InitialPiecePlacement.java | 4 +- .../board/StandardBoardInitializer.java | 10 ++-- src/main/java/janggi/view/InputView.java | 48 +++++++++++++++++++ src/main/java/janggi/view/OutputView.java | 4 ++ .../board/StandardBoardInitializerTest.java | 4 +- 6 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 src/main/java/janggi/Janggi.java create mode 100644 src/main/java/janggi/view/InputView.java create mode 100644 src/main/java/janggi/view/OutputView.java diff --git a/src/main/java/janggi/Janggi.java b/src/main/java/janggi/Janggi.java new file mode 100644 index 0000000000..9121d09966 --- /dev/null +++ b/src/main/java/janggi/Janggi.java @@ -0,0 +1,17 @@ +package janggi; + +import janggi.domain.board.BoardInitializer; +import janggi.domain.board.JanggiBoard; +import janggi.domain.board.StandardBoardInitializer; +import janggi.view.InputView; + +public class Janggi { + + public static void main(String[] args) { + String hanCommand = InputView.readHanElephantSettingCommand(); + String choCommand = InputView.readChoElephantSettingCommand(); + + BoardInitializer initializer = new StandardBoardInitializer(hanCommand, choCommand); + JanggiBoard board = new JanggiBoard(initializer); + } +} diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java index 3b41f1a1d6..9099e0c978 100644 --- a/src/main/java/janggi/domain/board/InitialPiecePlacement.java +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -48,15 +48,15 @@ public enum InitialPiecePlacement { } // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 - public static Map init(ElephantSetting choChoice, ElephantSetting hanChoice) { + public static Map init(ElephantSetting hanChoice, ElephantSetting choChoice) { Map board = new HashMap<>(); for (InitialPiecePlacement piece : values()) { board.put(new Position(piece.row, piece.column), new Piece(piece.pieceRule, piece.camp)); } - board.putAll(choChoice.createElephantOrder(Camp.CHO)); board.putAll(hanChoice.createElephantOrder(Camp.HAN)); + board.putAll(choChoice.createElephantOrder(Camp.CHO)); return board; } } diff --git a/src/main/java/janggi/domain/board/StandardBoardInitializer.java b/src/main/java/janggi/domain/board/StandardBoardInitializer.java index dc6bf3085a..4e15a3243e 100644 --- a/src/main/java/janggi/domain/board/StandardBoardInitializer.java +++ b/src/main/java/janggi/domain/board/StandardBoardInitializer.java @@ -6,21 +6,21 @@ public class StandardBoardInitializer implements BoardInitializer { - private final String choChoice; private final String hanChoice; + private final String choChoice; // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 // TODO : String으로 바꿔도 같은 문제 발생 - public StandardBoardInitializer(String choChoice, String hanChoice) { - this.choChoice = choChoice; + public StandardBoardInitializer(String hanChoice, String choChoice) { this.hanChoice = hanChoice; + this.choChoice = choChoice; } @Override public Map initialize() { - ElephantSetting choElephantSetting = ElephantSetting.findElephantSettingBy(choChoice); ElephantSetting hanElephantSetting = ElephantSetting.findElephantSettingBy(hanChoice); + ElephantSetting choElephantSetting = ElephantSetting.findElephantSettingBy(choChoice); - return InitialPiecePlacement.init(choElephantSetting, hanElephantSetting); + return InitialPiecePlacement.init(hanElephantSetting, choElephantSetting); } } diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java new file mode 100644 index 0000000000..df6f083b07 --- /dev/null +++ b/src/main/java/janggi/view/InputView.java @@ -0,0 +1,48 @@ +package janggi.view; + +import java.util.Scanner; + +public final class InputView { + + private static final Scanner SCANNER = new Scanner(System.in); + private static final String DELIMITER = ","; + private static final String NEW_LINE = System.lineSeparator(); + + private static final String HAN = "한"; + private static final String CHO = "초"; + + private static final String ELEPHANT_SETTING = """ + + %s나라의 상차림을 선택해주세요. + 1. 상마상마 + 2. 마상마상 + 3. 마상상마 + 4. 상마마상"""; + + private InputView() { + } + + public static String readHanElephantSettingCommand() { + System.out.println(String.format(ELEPHANT_SETTING, HAN)); + return readLine(); + } + + public static String readChoElephantSettingCommand() { + System.out.println(String.format(ELEPHANT_SETTING, CHO)); + return readLine(); + } + + private static String readLine() { + String input = SCANNER.nextLine().strip(); + validateInput(input); + return input; + } + + private static void validateInput(String input) { + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("[ERROR] qls"); + } + } + + +} diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java new file mode 100644 index 0000000000..35d4ddfe29 --- /dev/null +++ b/src/main/java/janggi/view/OutputView.java @@ -0,0 +1,4 @@ +package janggi.view; + +public class OutputView { +} diff --git a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java index b55d53dbb3..34ce15917e 100644 --- a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java @@ -14,7 +14,7 @@ public class StandardBoardInitializerTest { @Test void 초_마상마상_한_상마상마_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer("2", "1"); + BoardInitializer initializer = new StandardBoardInitializer("1", "2"); Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoLeftHanRightBoard()); // when @@ -29,7 +29,7 @@ public class StandardBoardInitializerTest { @Test void 초_마상상마_한_상마마상_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer("3", "4"); + BoardInitializer initializer = new StandardBoardInitializer("4", "3"); Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoInnerHanOuterBoard()); // when From f5172e3cb4fee71c075844ba3a3d5e2e3bff9a5c Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 17:59:17 +0900 Subject: [PATCH 32/68] =?UTF-8?q?feat:=20=EC=9E=A5=EA=B8=B0=20=EC=9E=85?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Piece record로 전환 - View 전달용 DTO 구현 - 각종 클래스 이름 어울리게 변경 - 파서 구현 --- src/main/java/janggi/Janggi.java | 17 ---- src/main/java/janggi/JanggiApplication.java | 8 ++ src/main/java/janggi/JanggiGame.java | 59 +++++++++++++ src/main/java/janggi/domain/board/Board.java | 46 +++++++++- .../janggi/domain/board/BoardChecker.java | 14 ++++ .../java/janggi/domain/board/JanggiBoard.java | 46 ---------- src/main/java/janggi/domain/piece/Piece.java | 29 +------ .../java/janggi/domain/piece/PieceRule.java | 4 +- .../piece/condition/EmptyCondition.java | 8 +- .../domain/piece/condition/MoveCondition.java | 4 +- .../condition/OnePieceExistsCondition.java | 17 ++-- src/main/java/janggi/dto/CampDto.java | 11 +++ .../java/janggi/dto/PiecePositionDto.java | 22 +++++ .../java/janggi/formatter/CampFormatter.java | 16 ++++ .../java/janggi/formatter/PieceFormatter.java | 43 ++++++++++ src/main/java/janggi/util/Parser.java | 25 ++++++ src/main/java/janggi/view/InputView.java | 33 +++++--- src/main/java/janggi/view/OutputView.java | 84 ++++++++++++++++++- .../{JanggiBoardTest.java => BoardTest.java} | 4 +- .../java/janggi/domain/piece/PieceTest.java | 44 +++++----- .../piece/condition/EmptyConditionTest.java | 6 +- .../OnePieceExistsConditionTest.java | 12 +-- 22 files changed, 399 insertions(+), 153 deletions(-) delete mode 100644 src/main/java/janggi/Janggi.java create mode 100644 src/main/java/janggi/JanggiApplication.java create mode 100644 src/main/java/janggi/JanggiGame.java create mode 100644 src/main/java/janggi/domain/board/BoardChecker.java delete mode 100644 src/main/java/janggi/domain/board/JanggiBoard.java create mode 100644 src/main/java/janggi/dto/CampDto.java create mode 100644 src/main/java/janggi/dto/PiecePositionDto.java create mode 100644 src/main/java/janggi/formatter/CampFormatter.java create mode 100644 src/main/java/janggi/formatter/PieceFormatter.java create mode 100644 src/main/java/janggi/util/Parser.java rename src/test/java/janggi/domain/board/{JanggiBoardTest.java => BoardTest.java} (92%) diff --git a/src/main/java/janggi/Janggi.java b/src/main/java/janggi/Janggi.java deleted file mode 100644 index 9121d09966..0000000000 --- a/src/main/java/janggi/Janggi.java +++ /dev/null @@ -1,17 +0,0 @@ -package janggi; - -import janggi.domain.board.BoardInitializer; -import janggi.domain.board.JanggiBoard; -import janggi.domain.board.StandardBoardInitializer; -import janggi.view.InputView; - -public class Janggi { - - public static void main(String[] args) { - String hanCommand = InputView.readHanElephantSettingCommand(); - String choCommand = InputView.readChoElephantSettingCommand(); - - BoardInitializer initializer = new StandardBoardInitializer(hanCommand, choCommand); - JanggiBoard board = new JanggiBoard(initializer); - } -} diff --git a/src/main/java/janggi/JanggiApplication.java b/src/main/java/janggi/JanggiApplication.java new file mode 100644 index 0000000000..548a04ecda --- /dev/null +++ b/src/main/java/janggi/JanggiApplication.java @@ -0,0 +1,8 @@ +package janggi; + +public class JanggiApplication { + public static void main(String[] args) { + JanggiGame game = new JanggiGame(); + game.run(); + } +} diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java new file mode 100644 index 0000000000..2e054b4f0d --- /dev/null +++ b/src/main/java/janggi/JanggiGame.java @@ -0,0 +1,59 @@ +package janggi; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.board.BoardInitializer; +import janggi.domain.board.StandardBoardInitializer; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.dto.PiecePositionDto; +import janggi.view.InputView; +import janggi.view.OutputView; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +public class JanggiGame { + + private final Queue turns = new ArrayDeque<>(List.of(Camp.CHO, Camp.HAN)); + + public void run() { + Board board = createBoard(); + OutputView.printBoard(toPiecePositions(board.getBoard())); + play(board); + } + + private Board createBoard() { + String hanCommand = InputView.readElephantSettingCommand(Camp.HAN); + String choCommand = InputView.readElephantSettingCommand(Camp.CHO); + BoardInitializer initializer = new StandardBoardInitializer(hanCommand, choCommand); + return new Board(initializer); + } + + private void play(Board board) { + int playCount = 100; + while (playCount-- > 0) { + Camp turn = turns.poll(); + OutputView.printBoard(playTurn(board, turn)); + turns.offer(turn); + } + } + + private List playTurn(Board board, Camp camp) { + Position from = toPosition(InputView.readStartPosition(camp)); + Position to = toPosition(InputView.readGoalPosition()); + Map boardState = board.movePiece(from, to); + return toPiecePositions(boardState); + } + + private Position toPosition(List rawPosition) { + return new Position(rawPosition.get(0), rawPosition.get(1)); + } + + private List toPiecePositions(Map boardState) { + return boardState.entrySet().stream() + .map(entry -> PiecePositionDto.of(entry.getKey(), entry.getValue())) + .toList(); + } +} diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 650855c15b..d6b40e1a96 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -2,13 +2,51 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; +import java.util.HashMap; +import java.util.Map; -public interface Board { +public class Board implements BoardChecker { - boolean hasPieceAt(Position position); + private final Map board = new HashMap<>(); - boolean isSameCampPieceAt(Position position, Camp camp); + public Board(BoardInitializer boardInitializer) { + board.putAll(boardInitializer.initialize()); + } - boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule); + @Override + public boolean hasPieceAt(Position position) { + return board.containsKey(position); + } + + @Override + public boolean isSameCampPieceAt(Position position, Camp camp) { + if (board.containsKey(position)) { + return board.get(position).isSameCamp(camp); + } + return false; + } + + @Override + public boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule) { + if (board.containsKey(position)) { + Piece foundPiece = board.get(position); + return foundPiece.isSamePieceRule(pieceRule); + } + return false; + } + + public Map movePiece(Position from, Position to) { + Piece piece = board.get(from); + piece.validateMove(from, to, this); + board.put(to, piece); + board.remove(from); + + return Map.copyOf(board); + } + + public Map getBoard() { + return Map.copyOf(board); + } } diff --git a/src/main/java/janggi/domain/board/BoardChecker.java b/src/main/java/janggi/domain/board/BoardChecker.java new file mode 100644 index 0000000000..9937109b3c --- /dev/null +++ b/src/main/java/janggi/domain/board/BoardChecker.java @@ -0,0 +1,14 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; + +public interface BoardChecker { + + boolean hasPieceAt(Position position); + + boolean isSameCampPieceAt(Position position, Camp camp); + + boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule); +} diff --git a/src/main/java/janggi/domain/board/JanggiBoard.java b/src/main/java/janggi/domain/board/JanggiBoard.java deleted file mode 100644 index 2966dfa588..0000000000 --- a/src/main/java/janggi/domain/board/JanggiBoard.java +++ /dev/null @@ -1,46 +0,0 @@ -package janggi.domain.board; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceRule; -import java.util.HashMap; -import java.util.Map; - -public class JanggiBoard implements Board { - - private final Map board = new HashMap<>(); - - public JanggiBoard(BoardInitializer boardInitializer) { - board.putAll(boardInitializer.initialize()); - } - - @Override - public boolean hasPieceAt(Position position) { - return board.containsKey(position); - } - - @Override - public boolean isSameCampPieceAt(Position position, Camp camp) { - if (board.containsKey(position)) { - return board.get(position).isSameCamp(camp); - } - return false; - } - - @Override - public boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule) { - if (board.containsKey(position)) { - Piece foundPiece = board.get(position); - return foundPiece.isSamePieceRule(pieceRule); - } - return false; - } - - public void movePiece(Position from, Position to) { - Piece piece = board.get(from); - piece.validateMove(from, to, this); - board.put(to, piece); - board.remove(from); - } -} diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 49d37009f7..c57d28ebba 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -1,21 +1,12 @@ package janggi.domain.piece; import janggi.domain.Position; -import janggi.domain.board.Board; +import janggi.domain.board.BoardChecker; import java.util.List; -import java.util.Objects; -public class Piece { +public record Piece(PieceRule pieceRule, Camp camp) { - private final PieceRule pieceRule; - private final Camp camp; - - public Piece(PieceRule pieceRule, Camp camp) { - this.pieceRule = pieceRule; - this.camp = camp; - } - - public void validateMove(Position from, Position to, Board board) { + public void validateMove(Position from, Position to, BoardChecker board) { List path = pieceRule.findPath(from, to, camp); pieceRule.checkPath(path, camp, board); } @@ -27,18 +18,4 @@ public boolean isSamePieceRule(PieceRule pieceRule) { public boolean isSameCamp(Camp camp) { return this.camp == camp; } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; - } - Piece piece = (Piece) o; - return pieceRule == piece.pieceRule && camp == piece.camp; - } - - @Override - public int hashCode() { - return Objects.hash(pieceRule, camp); - } } diff --git a/src/main/java/janggi/domain/piece/PieceRule.java b/src/main/java/janggi/domain/piece/PieceRule.java index aae76ecc49..f0c3b8745e 100644 --- a/src/main/java/janggi/domain/piece/PieceRule.java +++ b/src/main/java/janggi/domain/piece/PieceRule.java @@ -1,7 +1,7 @@ package janggi.domain.piece; import janggi.domain.Position; -import janggi.domain.board.Board; +import janggi.domain.board.BoardChecker; import janggi.domain.piece.condition.EmptyCondition; import janggi.domain.piece.condition.MoveCondition; import janggi.domain.piece.condition.OnePieceExistsCondition; @@ -35,7 +35,7 @@ public List findPath(Position from, Position to, Camp camp) { return moveStrategy.findPath(from, to, camp); } - public void checkPath(List path, Camp camp, Board board) { + public void checkPath(List path, Camp camp, BoardChecker board) { moveCondition.checkPath(path, camp, board, this); } } diff --git a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java index 4aa7a7b9a7..c6108d3edf 100644 --- a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java +++ b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java @@ -1,7 +1,7 @@ package janggi.domain.piece.condition; import janggi.domain.Position; -import janggi.domain.board.Board; +import janggi.domain.board.BoardChecker; import janggi.domain.piece.Camp; import janggi.domain.piece.PieceRule; import java.util.List; @@ -9,20 +9,20 @@ public class EmptyCondition implements MoveCondition { @Override - public void checkPath(List path, Camp camp, Board board, PieceRule pieceRule) { + public void checkPath(List path, Camp camp, BoardChecker board, PieceRule pieceRule) { for (int i = 0; i < path.size() - 1; i++) { validateEmptyPosition(path.get(i), board); } validateGoalPosition(path.getLast(), camp, board); } - private void validateEmptyPosition(Position position, Board board) { + private void validateEmptyPosition(Position position, BoardChecker board) { if (board.hasPieceAt(position)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } - private void validateGoalPosition(Position lastPosition, Camp camp, Board board) { + private void validateGoalPosition(Position lastPosition, Camp camp, BoardChecker board) { if (board.isSameCampPieceAt(lastPosition, camp)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } diff --git a/src/main/java/janggi/domain/piece/condition/MoveCondition.java b/src/main/java/janggi/domain/piece/condition/MoveCondition.java index 3c633ac15e..9392c1f877 100644 --- a/src/main/java/janggi/domain/piece/condition/MoveCondition.java +++ b/src/main/java/janggi/domain/piece/condition/MoveCondition.java @@ -1,12 +1,12 @@ package janggi.domain.piece.condition; import janggi.domain.Position; -import janggi.domain.board.Board; +import janggi.domain.board.BoardChecker; import janggi.domain.piece.Camp; import janggi.domain.piece.PieceRule; import java.util.List; public interface MoveCondition { - void checkPath(List path, Camp camp, Board board, PieceRule pieceRule); + void checkPath(List path, Camp camp, BoardChecker board, PieceRule pieceRule); } diff --git a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java index 9bfa41fa3f..63bbcc9314 100644 --- a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java +++ b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java @@ -1,7 +1,7 @@ package janggi.domain.piece.condition; import janggi.domain.Position; -import janggi.domain.board.Board; +import janggi.domain.board.BoardChecker; import janggi.domain.piece.Camp; import janggi.domain.piece.PieceRule; import java.util.List; @@ -9,7 +9,7 @@ public class OnePieceExistsCondition implements MoveCondition { @Override - public void checkPath(List path, Camp camp, Board board, PieceRule pieceRule) { + public void checkPath(List path, Camp camp, BoardChecker board, PieceRule pieceRule) { int countOfPiece = 0; for (int i = 0; i < path.size() - 1; i++) { Position position = path.get(i); @@ -20,7 +20,7 @@ public void checkPath(List path, Camp camp, Board board, PieceRule pie validateGoalPosition(path.getLast(), camp, board, pieceRule); } - private int countPieceAt(Board board, PieceRule pieceRule, Position position) { + private int countPieceAt(BoardChecker board, PieceRule pieceRule, Position position) { if (board.hasPieceAt(position)) { validateSamePieceRule(board, pieceRule, position); return 1; @@ -28,7 +28,7 @@ private int countPieceAt(Board board, PieceRule pieceRule, Position position) { return 0; } - private void validateSamePieceRule(Board board, PieceRule pieceRule, Position position) { + private void validateSamePieceRule(BoardChecker board, PieceRule pieceRule, Position position) { if (board.hasSamePieceRuleAt(position, pieceRule)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } @@ -40,9 +40,16 @@ private void validateExactPieceCount(int flag) { } } - private void validateGoalPosition(Position lastPosition, Camp camp, Board board, PieceRule pieceRule) { + private void validateGoalPosition(Position lastPosition, Camp camp, BoardChecker board, PieceRule pieceRule) { if (board.isSameCampPieceAt(lastPosition, camp) || board.hasSamePieceRuleAt(lastPosition, pieceRule)) { throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); } } } + +// todo : PieceRule 제거 + +// todo : view 구현 + +// todo : 상수 처리 +// todo : 예외 구체화 diff --git a/src/main/java/janggi/dto/CampDto.java b/src/main/java/janggi/dto/CampDto.java new file mode 100644 index 0000000000..d0c3890b92 --- /dev/null +++ b/src/main/java/janggi/dto/CampDto.java @@ -0,0 +1,11 @@ +package janggi.dto; + +import janggi.domain.piece.Camp; +import janggi.formatter.CampFormatter; + +public record CampDto(String camp) { + + public static CampDto from(Camp camp) { + return new CampDto(CampFormatter.format(camp)); + } +} diff --git a/src/main/java/janggi/dto/PiecePositionDto.java b/src/main/java/janggi/dto/PiecePositionDto.java new file mode 100644 index 0000000000..05b7f52835 --- /dev/null +++ b/src/main/java/janggi/dto/PiecePositionDto.java @@ -0,0 +1,22 @@ +package janggi.dto; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.formatter.PieceFormatter; + +public record PiecePositionDto( + int row, + int col, + String type, + Camp camp +) { + public static PiecePositionDto of(Position position, Piece piece) { + return new PiecePositionDto( + position.row(), + position.column(), + PieceFormatter.format(piece), + piece.camp() + ); + } +} diff --git a/src/main/java/janggi/formatter/CampFormatter.java b/src/main/java/janggi/formatter/CampFormatter.java new file mode 100644 index 0000000000..f332921d8f --- /dev/null +++ b/src/main/java/janggi/formatter/CampFormatter.java @@ -0,0 +1,16 @@ +package janggi.formatter; + +import janggi.domain.piece.Camp; + +public final class CampFormatter { + + private CampFormatter() { + } + + public static String format(Camp camp) { + return switch (camp) { + case CHO -> "초"; + case HAN -> "한"; + }; + } +} diff --git a/src/main/java/janggi/formatter/PieceFormatter.java b/src/main/java/janggi/formatter/PieceFormatter.java new file mode 100644 index 0000000000..ef84cfa376 --- /dev/null +++ b/src/main/java/janggi/formatter/PieceFormatter.java @@ -0,0 +1,43 @@ +package janggi.formatter; + +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; + +public final class PieceFormatter { + + private PieceFormatter() { + } + + public static String format(Piece piece) { + if (piece.isSameCamp(Camp.CHO)) { + return choFormat(piece.pieceRule()); + } + return hanFormat(piece.pieceRule()); + + } + + private static String choFormat(PieceRule pieceRule) { + return switch (pieceRule) { + case GENERAL -> "楚"; + case CHARIOT -> "車"; + case HORSE -> "馬"; + case CANNON -> "包"; + case GUARD -> "士"; + case ELEPHANT -> "象"; + case SOLDIER -> "卒"; + }; + } + + private static String hanFormat(PieceRule pieceRule) { + return switch (pieceRule) { + case GENERAL -> "漢"; + case CHARIOT -> "車"; + case HORSE -> "馬"; + case CANNON -> "包"; + case GUARD -> "士"; + case ELEPHANT -> "象"; + case SOLDIER -> "兵"; + }; + } +} diff --git a/src/main/java/janggi/util/Parser.java b/src/main/java/janggi/util/Parser.java new file mode 100644 index 0000000000..9bee76e8e7 --- /dev/null +++ b/src/main/java/janggi/util/Parser.java @@ -0,0 +1,25 @@ +package janggi.util; + +import java.util.Arrays; +import java.util.List; + +public final class Parser { + + private Parser() { + } + + public static List parseByDelimiter(String delimiter, String input) { + return Arrays.stream(input.split(delimiter)) + .map(String::strip) + .map(Parser::parseToInt) + .toList(); + } + + private static int parseToInt(String number) { + try { + return Integer.parseInt(number); + } catch (NumberFormatException numberFormatException) { + throw new IllegalArgumentException("[ERROR] 숫자만 입력 가능합니다"); + } + } +} diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index df6f083b07..a003168f84 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -1,34 +1,32 @@ package janggi.view; +import janggi.domain.piece.Camp; +import janggi.formatter.CampFormatter; +import janggi.util.Parser; +import java.util.List; import java.util.Scanner; public final class InputView { private static final Scanner SCANNER = new Scanner(System.in); private static final String DELIMITER = ","; - private static final String NEW_LINE = System.lineSeparator(); - - private static final String HAN = "한"; - private static final String CHO = "초"; private static final String ELEPHANT_SETTING = """ - %s나라의 상차림을 선택해주세요. 1. 상마상마 2. 마상마상 3. 마상상마 4. 상마마상"""; - private InputView() { - } + private static final String TURN = "%s나라 차례 입니다."; + private static final String START_POSITION = "공격할 기물의 좌표를 입력해주세요."; + private static final String GOAL_POSITION = "이동 시킬 목적지 좌표를 입력해주세요."; - public static String readHanElephantSettingCommand() { - System.out.println(String.format(ELEPHANT_SETTING, HAN)); - return readLine(); + private InputView() { } - public static String readChoElephantSettingCommand() { - System.out.println(String.format(ELEPHANT_SETTING, CHO)); + public static String readElephantSettingCommand(Camp camp) { + System.out.println(String.format(ELEPHANT_SETTING, CampFormatter.format(camp))); return readLine(); } @@ -40,9 +38,18 @@ private static String readLine() { private static void validateInput(String input) { if (input == null || input.isBlank()) { - throw new IllegalArgumentException("[ERROR] qls"); + throw new IllegalArgumentException("[ERROR] 잘못된 입력 형식입니다."); } } + public static List readStartPosition(Camp camp) { + System.out.println(String.format(TURN, CampFormatter.format(camp))); + System.out.println(START_POSITION); + return Parser.parseByDelimiter(DELIMITER, readLine()); + } + public static List readGoalPosition() { + System.out.println(GOAL_POSITION); + return Parser.parseByDelimiter(DELIMITER, readLine()); + } } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index 35d4ddfe29..af392c70de 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -1,4 +1,86 @@ package janggi.view; -public class OutputView { +import static java.util.stream.Collectors.joining; + +import janggi.domain.piece.Camp; +import janggi.dto.PiecePositionDto; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +public final class OutputView { + + private static final String LINE_SEPARATOR = System.lineSeparator(); + + private static final int ROW_SIZE = 10; + private static final int COLUMN_SIZE = 9; + + private static final String TITLE = "[장기판]"; + private static final String EMPTY_CELL = "."; + + private static final String RESET = "\u001B[0m"; + private static final String CHO_COLOR = "\u001B[32m"; + private static final String HAN_COLOR = "\u001B[31m"; + + private static final String[] FULL_WIDTH_NUMBERS = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" + }; + + private OutputView() { + } + + public static void printBoard(List piecePositions) { + System.out.println(renderBoard(piecePositions)); + } + + private static String renderBoard(List piecePositions) { + String[][] board = initializeBoard(); + applyPieces(board, piecePositions); + return TITLE + LINE_SEPARATOR + renderHeader() + LINE_SEPARATOR + renderRows(board); + } + + private static String[][] initializeBoard() { + String[][] board = new String[ROW_SIZE][COLUMN_SIZE]; + for (String[] row : board) { + Arrays.fill(row, EMPTY_CELL); + } + return board; + } + + private static String renderHeader() { + return " " + IntStream.range(0, COLUMN_SIZE) + .mapToObj(OutputView::fullWidthNumber) + .collect(joining(" ")); + } + + private static void applyPieces(String[][] board, List piecePositions) { + for (PiecePositionDto piecePosition : piecePositions) { + board[piecePosition.row()][piecePosition.col()] = colorize(piecePosition); + } + } + + private static String renderRows(String[][] board) { + return IntStream.range(0, ROW_SIZE) + .mapToObj(row -> renderRow(row, board[row])) + .collect(joining(LINE_SEPARATOR)); + } + + private static String renderRow(int row, String[] cells) { + return fullWidthNumber(row) + ' ' + String.join(" ", cells); + } + + private static String colorize(PiecePositionDto piecePosition) { + return colorOf(piecePosition.camp()) + piecePosition.type() + RESET; + } + + private static String colorOf(Camp camp) { + if (camp == Camp.CHO) { + return CHO_COLOR; + } + return HAN_COLOR; + } + + private static String fullWidthNumber(int number) { + return FULL_WIDTH_NUMBERS[number]; + } } diff --git a/src/test/java/janggi/domain/board/JanggiBoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java similarity index 92% rename from src/test/java/janggi/domain/board/JanggiBoardTest.java rename to src/test/java/janggi/domain/board/BoardTest.java index 216b995d86..9747c6c9ab 100644 --- a/src/test/java/janggi/domain/board/JanggiBoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -9,7 +9,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; -class JanggiBoardTest { +class BoardTest { @Test void 목적지에_반대_진영_기물이_있으면_해당_기물을_제거한다() { @@ -17,7 +17,7 @@ class JanggiBoardTest { Position from = new Position(7, 1); Position to = new Position(0, 1); - JanggiBoard board = new JanggiBoard(() -> Map.of( + Board board = new Board(() -> Map.of( new Position(4, 1), new Piece(PieceRule.SOLDIER, Camp.HAN), to, new Piece(PieceRule.HORSE, Camp.CHO), from, new Piece(PieceRule.CANNON, Camp.HAN) diff --git a/src/test/java/janggi/domain/piece/PieceTest.java b/src/test/java/janggi/domain/piece/PieceTest.java index 47f3447424..9e82d1c1ca 100644 --- a/src/test/java/janggi/domain/piece/PieceTest.java +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -5,7 +5,7 @@ import janggi.domain.Position; import janggi.domain.board.Board; -import janggi.domain.board.JanggiBoard; +import janggi.domain.board.BoardChecker; import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -41,7 +41,7 @@ class General { void 궁은_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.GENERAL, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(1, 4), new Position(2, 4), board) @@ -52,7 +52,7 @@ class General { void 궁은_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.GENERAL, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(1, 4), new Position(3, 4), board)) @@ -68,7 +68,7 @@ class Guard { void 사는_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.GUARD, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(0, 3), new Position(0, 4), board) @@ -79,7 +79,7 @@ class Guard { void 사는_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.GUARD, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 3), new Position(0, 5), board)) @@ -95,7 +95,7 @@ class Horse { void 마는_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(0, 1), new Position(2, 2), board) @@ -106,7 +106,7 @@ class Horse { void 마는_이동_경로에_기물이_있으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); - Board board = new JanggiBoard(() -> Map.of( + BoardChecker board = new Board(() -> Map.of( new Position(1, 1), new Piece(PieceRule.CHARIOT, Camp.HAN) )); // when & then @@ -120,7 +120,7 @@ class Horse { void 마는_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 1), new Position(0, 7), board)) @@ -136,7 +136,7 @@ class Elephant { void 상은_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.ELEPHANT, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(0, 6), new Position(3, 4), board) @@ -147,7 +147,7 @@ class Elephant { void 상은_행마법을_따르지_않으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.ELEPHANT, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); //when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 6), new Position(3, 3), board)) @@ -163,7 +163,7 @@ class Cannon { void 포는_이동_경로에_기물이_없으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); //when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(2, 1), new Position(8, 1), board)) @@ -175,7 +175,7 @@ class Cannon { void 포는_이동_경로에_기물이_2개_이상_있으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); - Board board = new JanggiBoard(() -> Map.of( + BoardChecker board = new Board(() -> Map.of( new Position(3, 1), new Piece(PieceRule.CHARIOT, Camp.HAN), new Position(5, 1), new Piece(PieceRule.CHARIOT, Camp.CHO) @@ -191,7 +191,7 @@ class Cannon { void 포는_이동_경로에_기물이_1개만_있으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); - Board board = new JanggiBoard(() -> Map.of( + BoardChecker board = new Board(() -> Map.of( new Position(3, 1), new Piece(PieceRule.CHARIOT, Camp.HAN) )); // when & then @@ -208,7 +208,7 @@ class Chariot { void 차는_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(0, 0), new Position(9, 0), board) @@ -219,7 +219,7 @@ class Chariot { void 차는_이동_경로에_기물이_1개_이상_있으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); - Board board = new JanggiBoard(() -> Map.of( + BoardChecker board = new Board(() -> Map.of( new Position(1, 0), new Piece(PieceRule.CHARIOT, Camp.HAN), new Position(5, 1), new Piece(PieceRule.CHARIOT, Camp.CHO) @@ -235,7 +235,7 @@ class Chariot { void 차는_행마법을_따르지_않으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); //when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 0), new Position(3, 3), board)) @@ -251,7 +251,7 @@ class SoldierCho { void 졸은_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(3, 0), new Position(4, 0), board) @@ -262,7 +262,7 @@ class SoldierCho { void 졸은_후진할_시_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(3, 0), new Position(2, 0), board)) @@ -274,7 +274,7 @@ class SoldierCho { void 졸은_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(3, 0), new Position(5, 0), board)) @@ -290,7 +290,7 @@ class SoldierHan { void 병은_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(6, 0), new Position(5, 0), board) @@ -301,7 +301,7 @@ class SoldierHan { void 병은_후진할_시_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(6, 0), new Position(7, 0), board)) @@ -313,7 +313,7 @@ class SoldierHan { void 병은_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); - Board board = new JanggiBoard(Map::of); + BoardChecker board = new Board(Map::of); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(6, 0), new Position(1, 0), board)) diff --git a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java index 3c2879f74b..8b7b9ba8a5 100644 --- a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java @@ -3,9 +3,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import janggi.domain.Position; +import janggi.domain.board.Board; import janggi.domain.board.BoardInitializer; import janggi.domain.board.EmptyConditionTestBoardInitializer; -import janggi.domain.board.JanggiBoard; import janggi.domain.piece.Camp; import janggi.domain.piece.PieceRule; import java.util.List; @@ -29,7 +29,7 @@ public class EmptyConditionTest { Camp camp = Camp.HAN; BoardInitializer boardInitializer = new EmptyConditionTestBoardInitializer(); - JanggiBoard board = new JanggiBoard(boardInitializer); + Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) @@ -49,7 +49,7 @@ public class EmptyConditionTest { Camp camp = Camp.HAN; BoardInitializer boardInitializer = new EmptyConditionTestBoardInitializer(); - JanggiBoard board = new JanggiBoard(boardInitializer); + Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) diff --git a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java index e3144f5bed..f908ad9caf 100644 --- a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java @@ -3,8 +3,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import janggi.domain.Position; +import janggi.domain.board.Board; import janggi.domain.board.BoardInitializer; -import janggi.domain.board.JanggiBoard; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; @@ -30,7 +30,7 @@ public class OnePieceExistsConditionTest { Camp camp = Camp.HAN; BoardInitializer boardInitializer = Map::of; - JanggiBoard board = new JanggiBoard(boardInitializer); + Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) @@ -54,7 +54,7 @@ public class OnePieceExistsConditionTest { new Position(0, 3), new Piece(PieceRule.CHARIOT, Camp.HAN), new Position(0, 4), new Piece(PieceRule.ELEPHANT, Camp.HAN) ); - JanggiBoard board = new JanggiBoard(boardInitializer); + Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) @@ -77,7 +77,7 @@ public class OnePieceExistsConditionTest { BoardInitializer boardInitializer = () -> Map.of( new Position(0, 4), new Piece(PieceRule.CANNON, Camp.CHO) ); - JanggiBoard board = new JanggiBoard(boardInitializer); + Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) @@ -100,7 +100,7 @@ public class OnePieceExistsConditionTest { BoardInitializer boardInitializer = () -> Map.of( new Position(0, 5), new Piece(PieceRule.CHARIOT, Camp.HAN) ); - JanggiBoard board = new JanggiBoard(boardInitializer); + Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) @@ -123,7 +123,7 @@ public class OnePieceExistsConditionTest { BoardInitializer boardInitializer = () -> Map.of( new Position(0, 5), new Piece(PieceRule.CANNON, Camp.CHO) ); - JanggiBoard board = new JanggiBoard(boardInitializer); + Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) From 7a76925e5365f738cf95cc2e86183878904654aa Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 19:47:25 +0900 Subject: [PATCH 33/68] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EB=B0=8F=20=EB=A7=A4=EC=A7=81=20=EB=84=98?= =?UTF-8?q?=EB=B2=84=20=EC=83=81=EC=88=98=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/constant/GameRule.java | 20 +++++++ src/main/java/janggi/domain/Position.java | 14 +++-- .../janggi/domain/board/ElephantSetting.java | 3 +- src/main/java/janggi/domain/piece/Camp.java | 8 ++- .../piece/condition/EmptyCondition.java | 5 +- .../condition/OnePieceExistsCondition.java | 26 ++++----- .../piece/strategy/ElephantStrategy.java | 15 +++-- .../domain/piece/strategy/HorseStrategy.java | 10 +++- .../strategy/MultiStepStraightStrategy.java | 5 +- .../strategy/SingleStepStraightStrategy.java | 8 ++- .../piece/strategy/SoldierStrategy.java | 10 +++- .../janggi/exception/ExceptionMessage.java | 55 +++++++++++++++++++ src/main/java/janggi/util/Parser.java | 3 +- src/main/java/janggi/view/InputView.java | 3 +- src/test/java/janggi/domain/PositionTest.java | 5 +- .../java/janggi/domain/piece/PieceTest.java | 28 +++++----- .../piece/condition/EmptyConditionTest.java | 5 +- .../OnePieceExistsConditionTest.java | 13 +++-- .../piece/strategy/ElephantStrategyTest.java | 3 +- .../piece/strategy/HorseStrategyTest.java | 3 +- .../MultiStepStraightStrategyTest.java | 5 +- .../SingleStepStraightStrategyTest.java | 3 +- .../piece/strategy/SoldierStrategyTest.java | 11 ++-- 23 files changed, 189 insertions(+), 72 deletions(-) create mode 100644 src/main/java/janggi/constant/GameRule.java create mode 100644 src/main/java/janggi/exception/ExceptionMessage.java diff --git a/src/main/java/janggi/constant/GameRule.java b/src/main/java/janggi/constant/GameRule.java new file mode 100644 index 0000000000..b2e33fed03 --- /dev/null +++ b/src/main/java/janggi/constant/GameRule.java @@ -0,0 +1,20 @@ +package janggi.constant; + +public final class GameRule { + + public static final int MIN_POSITION_INDEX = 0; + public static final int MAX_ROW_INDEX = 9; + public static final int MAX_COLUMN_INDEX = 8; + + public static final int SINGLE_STEP_DISTANCE = 1; + public static final int PASS_PIECE_COUNT = 1; + + public static final int HORSE_STRAIGHT_MOVE_DISTANCE = 1; + public static final int HORSE_DIAGONAL_MOVE_DISTANCE = 1; + + public static final int ELEPHANT_STRAIGHT_MOVE_DISTANCE = 1; + public static final int ELEPHANT_DIAGONAL_MOVE_DISTANCE = 2; + + private GameRule() { + } +} diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index 635e8f9049..07a25a3190 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -1,5 +1,11 @@ package janggi.domain; +import static janggi.constant.GameRule.MAX_COLUMN_INDEX; +import static janggi.constant.GameRule.MAX_ROW_INDEX; +import static janggi.constant.GameRule.MIN_POSITION_INDEX; + +import janggi.exception.ExceptionMessage; + public record Position(int row, int column) { public Position { @@ -8,14 +14,14 @@ public record Position(int row, int column) { } private void validateRow(int row) { - if (row < 0 || 9 < row) { - throw new IllegalArgumentException("[ERROR] 행은 0행 이상 9행 이하여야 합니다."); + if (row < MIN_POSITION_INDEX || MAX_ROW_INDEX < row) { + throw new IllegalArgumentException(ExceptionMessage.ROW_OUT_OF_RANGE.getMessage()); } } private void validateColumn(int column) { - if (column < 0 || 8 < column) { - throw new IllegalArgumentException("[ERROR] 열은 0열 이상 8열 이하여야 합니다."); + if (column < MIN_POSITION_INDEX || MAX_COLUMN_INDEX < column) { + throw new IllegalArgumentException(ExceptionMessage.COLUMN_OUT_OF_RANGE.getMessage()); } } diff --git a/src/main/java/janggi/domain/board/ElephantSetting.java b/src/main/java/janggi/domain/board/ElephantSetting.java index 2c1845895f..4007c4fd94 100644 --- a/src/main/java/janggi/domain/board/ElephantSetting.java +++ b/src/main/java/janggi/domain/board/ElephantSetting.java @@ -4,6 +4,7 @@ import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -30,7 +31,7 @@ public static ElephantSetting findElephantSettingBy(String command) { return Arrays.stream(values()) .filter(element -> element.command.equals(command)) .findFirst() - .orElseThrow(() -> new IllegalArgumentException("[ERROR] 존재하지 않는 상차림 입니다.")); + .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INVALID_ELEPHANT_SETTING.getMessage())); } public Map createElephantOrder(Camp camp) { diff --git a/src/main/java/janggi/domain/piece/Camp.java b/src/main/java/janggi/domain/piece/Camp.java index b53f7879a0..4309bbf83b 100644 --- a/src/main/java/janggi/domain/piece/Camp.java +++ b/src/main/java/janggi/domain/piece/Camp.java @@ -1,5 +1,7 @@ package janggi.domain.piece; +import janggi.exception.ExceptionMessage; + public enum Camp { HAN(-1, 9), @@ -14,9 +16,9 @@ public enum Camp { this.startRowPosition = startRowPosition; } - public void validateForwardDirection(int rowDiff) { - if (forwardDirection != rowDiff && rowDiff != 0) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + public void validateForwardDirection(int rowDirection) { + if (forwardDirection != rowDirection && rowDirection != 0) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); } } diff --git a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java index c6108d3edf..3c23f967fe 100644 --- a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java +++ b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java @@ -4,6 +4,7 @@ import janggi.domain.board.BoardChecker; import janggi.domain.piece.Camp; import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; import java.util.List; public class EmptyCondition implements MoveCondition { @@ -18,13 +19,13 @@ public void checkPath(List path, Camp camp, BoardChecker board, PieceR private void validateEmptyPosition(Position position, BoardChecker board) { if (board.hasPieceAt(position)) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + throw new IllegalArgumentException(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); } } private void validateGoalPosition(Position lastPosition, Camp camp, BoardChecker board) { if (board.isSameCampPieceAt(lastPosition, camp)) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } } } diff --git a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java index 63bbcc9314..218f6c8b48 100644 --- a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java +++ b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java @@ -1,9 +1,12 @@ package janggi.domain.piece.condition; +import static janggi.constant.GameRule.PASS_PIECE_COUNT; + import janggi.domain.Position; import janggi.domain.board.BoardChecker; import janggi.domain.piece.Camp; import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; import java.util.List; public class OnePieceExistsCondition implements MoveCondition { @@ -30,26 +33,23 @@ private int countPieceAt(BoardChecker board, PieceRule pieceRule, Position posit private void validateSamePieceRule(BoardChecker board, PieceRule pieceRule, Position position) { if (board.hasSamePieceRuleAt(position, pieceRule)) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + throw new IllegalArgumentException(ExceptionMessage.SAME_PIECE_TYPE_IN_PATH.getMessage()); } } - private void validateExactPieceCount(int flag) { - if (flag != 1) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + private void validateExactPieceCount(int countOfPiece) { + if (countOfPiece != PASS_PIECE_COUNT) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); } } private void validateGoalPosition(Position lastPosition, Camp camp, BoardChecker board, PieceRule pieceRule) { - if (board.isSameCampPieceAt(lastPosition, camp) || board.hasSamePieceRuleAt(lastPosition, pieceRule)) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + if (board.isSameCampPieceAt(lastPosition, camp)) { + throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); + } + + if (board.hasSamePieceRuleAt(lastPosition, pieceRule)) { + throw new IllegalArgumentException(ExceptionMessage.SAME_PIECE_TYPE_AT_DESTINATION.getMessage()); } } } - -// todo : PieceRule 제거 - -// todo : view 구현 - -// todo : 상수 처리 -// todo : 예외 구체화 diff --git a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java index 091b68a720..24918dd9e6 100644 --- a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java @@ -2,11 +2,16 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.ArrayList; import java.util.List; public class ElephantStrategy implements MoveStrategy { + private static final int DIAGONAL_COUNT = 2; + private static final int MIN_ABS_DELTA = 2; + private static final int MAX_ABS_DELTA = 3; + @Override public List findPath(Position from, Position to, Camp camp) { DirectionInformation directionInfo = new DirectionInformation(from, to); @@ -20,9 +25,11 @@ public List findPath(Position from, Position to, Camp camp) { } private void validateElephantMovement(DirectionInformation directionInfo) { - if ((directionInfo.calculateAbsRowDifference() != 2 || directionInfo.calculateAbsColDifference() != 3) - && (directionInfo.calculateAbsRowDifference() != 3 || directionInfo.calculateAbsColDifference() != 2)) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + if ((directionInfo.calculateAbsRowDifference() != MIN_ABS_DELTA + || directionInfo.calculateAbsColDifference() != MAX_ABS_DELTA) + && (directionInfo.calculateAbsRowDifference() != MAX_ABS_DELTA + || directionInfo.calculateAbsColDifference() != MIN_ABS_DELTA)) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage()); } } @@ -49,7 +56,7 @@ private List createColFirstPath(Position from, DirectionInformation di private List moveDiagonal(Position from, DirectionInformation directionInfo) { List path = new ArrayList<>(); - for (int i = 0; i < 2; i++) { + for (int i = 0; i < DIAGONAL_COUNT; i++) { from = from.moveDiagonal(directionInfo.calculateRowDirection(), directionInfo.calculateColDirection()); path.add(from); } diff --git a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java index 755daff3de..3128e251c8 100644 --- a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java @@ -2,11 +2,15 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.ArrayList; import java.util.List; public class HorseStrategy implements MoveStrategy { + private static final int MIN_ABS_DELTA = 1; + private static final int MAX_ABS_DELTA = 2; + @Override public List findPath(Position from, Position to, Camp camp) { DirectionInformation directionInformation = new DirectionInformation(from, to); @@ -49,9 +53,9 @@ private void validateHorseMovement(DirectionInformation directionInformation) { int absRowDifference = directionInformation.calculateAbsRowDifference(); int absColDifference = directionInformation.calculateAbsColDifference(); - if ((absRowDifference != 1 || absColDifference != 2) - && (absRowDifference != 2 || absColDifference != 1)) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + if ((absRowDifference != MIN_ABS_DELTA || absColDifference != MAX_ABS_DELTA) + && (absRowDifference != MAX_ABS_DELTA || absColDifference != MIN_ABS_DELTA)) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_HORSE_MOVE.getMessage()); } } } diff --git a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java index 9e9b7776df..9c6c651dd4 100644 --- a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java @@ -2,6 +2,7 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.ArrayList; import java.util.List; @@ -25,11 +26,11 @@ private void validateStraightMove(DirectionInformation directionInformation) { int colDifference = directionInformation.colDifference(); if (sum != rowDifference && sum != colDifference) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + throw new IllegalArgumentException(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); } if (rowDifference == 0 && colDifference == 0) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + throw new IllegalArgumentException(ExceptionMessage.PIECE_MUST_MOVE.getMessage()); } } diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java index 4d8bf4f167..ea120c95b2 100644 --- a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java @@ -1,7 +1,10 @@ package janggi.domain.piece.strategy; +import static janggi.constant.GameRule.SINGLE_STEP_DISTANCE; + import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.List; public class SingleStepStraightStrategy implements MoveStrategy { @@ -15,8 +18,9 @@ public List findPath(Position from, Position to, Camp camp) { } private void validateSingleStepMovement(DirectionInformation directionInformation) { - if (directionInformation.calculateAbsRowDifference() + directionInformation.calculateAbsColDifference() != 1) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + if (directionInformation.calculateAbsRowDifference() + + directionInformation.calculateAbsColDifference() != SINGLE_STEP_DISTANCE) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); } } } diff --git a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java index 7c15377304..2140c537b8 100644 --- a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java @@ -1,7 +1,10 @@ package janggi.domain.piece.strategy; +import static janggi.constant.GameRule.SINGLE_STEP_DISTANCE; + import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.List; public class SoldierStrategy implements MoveStrategy { @@ -10,15 +13,16 @@ public class SoldierStrategy implements MoveStrategy { public List findPath(Position from, Position to, Camp camp) { DirectionInformation directionInformation = new DirectionInformation(from, to); - camp.validateForwardDirection(directionInformation.rowDifference()); + camp.validateForwardDirection(directionInformation.calculateRowDirection()); validateSoldierMovement(directionInformation); return List.of(to); } private void validateSoldierMovement(DirectionInformation directionInformation) { - if (directionInformation.calculateAbsRowDifference() + directionInformation.calculateAbsColDifference() != 1) { - throw new IllegalArgumentException("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + if (directionInformation.calculateAbsRowDifference() + + directionInformation.calculateAbsColDifference() != SINGLE_STEP_DISTANCE) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); } } } diff --git a/src/main/java/janggi/exception/ExceptionMessage.java b/src/main/java/janggi/exception/ExceptionMessage.java new file mode 100644 index 0000000000..93033e742a --- /dev/null +++ b/src/main/java/janggi/exception/ExceptionMessage.java @@ -0,0 +1,55 @@ +package janggi.exception; + +import static janggi.constant.GameRule.ELEPHANT_DIAGONAL_MOVE_DISTANCE; +import static janggi.constant.GameRule.ELEPHANT_STRAIGHT_MOVE_DISTANCE; +import static janggi.constant.GameRule.HORSE_DIAGONAL_MOVE_DISTANCE; +import static janggi.constant.GameRule.HORSE_STRAIGHT_MOVE_DISTANCE; +import static janggi.constant.GameRule.MAX_COLUMN_INDEX; +import static janggi.constant.GameRule.MAX_ROW_INDEX; +import static janggi.constant.GameRule.MIN_POSITION_INDEX; +import static janggi.constant.GameRule.PASS_PIECE_COUNT; +import static janggi.constant.GameRule.SINGLE_STEP_DISTANCE; + +public enum ExceptionMessage { + + INVALID_BACKWARD_MOVEMENT("해당 기물은 후진할 수 없습니다."), + ONLY_NUMBERS_ALLOWED("숫자만 입력 가능합니다"), + ROW_OUT_OF_RANGE(String.format("행은 %d행 이상 %d행 이하여야 합니다.", MIN_POSITION_INDEX, MAX_ROW_INDEX)), + COLUMN_OUT_OF_RANGE(String.format("열은 %d열 이상 %d열 이하여야 합니다.", MIN_POSITION_INDEX, MAX_COLUMN_INDEX)), + INVALID_INPUT_FORMAT("잘못된 입력 형식입니다."), + ONLY_STRAIGHT_MOVE_ALLOWED("해당 기물은 직선 이동만 가능합니다."), + PIECE_MUST_MOVE("기물은 반드시 이동해야 합니다."), + INVALID_ELEPHANT_SETTING("존재하지 않는 상차림 입니다."), + SAME_PIECE_TYPE_IN_PATH("경로상에 같은 종류의 기물이 존재합니다."), + INVALID_JUMPED_PIECE_COUNT(String.format("해당 기물은 정확히 %d개의 기물만 뛰어넘을 수 있습니다.", PASS_PIECE_COUNT)), + SAME_CAMP_PIECE_AT_DESTINATION("목적지에 같은 진영의 기물이 존재합니다."), + SAME_PIECE_TYPE_AT_DESTINATION("목적지에 같은 종류의 기물이 존재합니다."), + INVALID_SOLDIER_MOVE(String.format("해당 기물은 %d칸만 이동할 수 있습니다.", SINGLE_STEP_DISTANCE)), + INVALID_HORSE_MOVE(String.format( + "해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다.", + HORSE_STRAIGHT_MOVE_DISTANCE, + HORSE_DIAGONAL_MOVE_DISTANCE + )), + PATH_NOT_EMPTY("경로 상에 기물이 존재합니다."), + INVALID_SINGLE_STEP_STRAIGHT_MOVE(String.format( + "해당 기물은 직선으로 %d칸 이동해야 합니다.", + SINGLE_STEP_DISTANCE + )), + INVALID_ELEPHANT_MOVE(String.format( + "해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다.", + ELEPHANT_STRAIGHT_MOVE_DISTANCE, + ELEPHANT_DIAGONAL_MOVE_DISTANCE + )); + + private static final String ERROR_PREFIX = "[ERROR] "; + + private final String message; + + ExceptionMessage(String message) { + this.message = ERROR_PREFIX + message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/janggi/util/Parser.java b/src/main/java/janggi/util/Parser.java index 9bee76e8e7..7518e7b898 100644 --- a/src/main/java/janggi/util/Parser.java +++ b/src/main/java/janggi/util/Parser.java @@ -1,5 +1,6 @@ package janggi.util; +import janggi.exception.ExceptionMessage; import java.util.Arrays; import java.util.List; @@ -19,7 +20,7 @@ private static int parseToInt(String number) { try { return Integer.parseInt(number); } catch (NumberFormatException numberFormatException) { - throw new IllegalArgumentException("[ERROR] 숫자만 입력 가능합니다"); + throw new IllegalArgumentException(ExceptionMessage.ONLY_NUMBERS_ALLOWED.getMessage()); } } } diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index a003168f84..b9fa33c54d 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -1,6 +1,7 @@ package janggi.view; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import janggi.formatter.CampFormatter; import janggi.util.Parser; import java.util.List; @@ -38,7 +39,7 @@ private static String readLine() { private static void validateInput(String input) { if (input == null || input.isBlank()) { - throw new IllegalArgumentException("[ERROR] 잘못된 입력 형식입니다."); + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT_FORMAT.getMessage()); } } diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java index 431099ff55..21a6fa8a39 100644 --- a/src/test/java/janggi/domain/PositionTest.java +++ b/src/test/java/janggi/domain/PositionTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import janggi.exception.ExceptionMessage; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -17,7 +18,7 @@ public class PositionTest { void 포지션의_행이_0부터_9행까지가_아닐_경우_예외가_발생한다(int row, int col) { Assertions.assertThatThrownBy(() -> new Position(row, col)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 행은 0행 이상 9행 이하여야 합니다."); + .hasMessage(ExceptionMessage.ROW_OUT_OF_RANGE.getMessage()); } @ParameterizedTest @@ -28,7 +29,7 @@ public class PositionTest { void 포지션의_열이_0부터_8열까지가_아닐_경우_예외가_발생한다(int row, int col) { Assertions.assertThatThrownBy(() -> new Position(row, col)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 열은 0열 이상 8열 이하여야 합니다."); + .hasMessage(ExceptionMessage.COLUMN_OUT_OF_RANGE.getMessage()); } @Test diff --git a/src/test/java/janggi/domain/piece/PieceTest.java b/src/test/java/janggi/domain/piece/PieceTest.java index 9e82d1c1ca..b466422bb4 100644 --- a/src/test/java/janggi/domain/piece/PieceTest.java +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -6,6 +6,7 @@ import janggi.domain.Position; import janggi.domain.board.Board; import janggi.domain.board.BoardChecker; +import janggi.exception.ExceptionMessage; import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -57,7 +58,7 @@ class General { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(1, 4), new Position(3, 4), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); } } @@ -84,7 +85,7 @@ class Guard { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 3), new Position(0, 5), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); } } @@ -113,7 +114,7 @@ class Horse { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 1), new Position(2, 2), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); } @Test @@ -125,7 +126,7 @@ class Horse { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 1), new Position(0, 7), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage()); } } @@ -152,7 +153,7 @@ class Elephant { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 6), new Position(3, 3), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage()); } } @@ -168,7 +169,7 @@ class Cannon { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(2, 1), new Position(8, 1), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); } @Test @@ -178,13 +179,12 @@ class Cannon { BoardChecker board = new Board(() -> Map.of( new Position(3, 1), new Piece(PieceRule.CHARIOT, Camp.HAN), new Position(5, 1), new Piece(PieceRule.CHARIOT, Camp.CHO) - )); //when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(2, 1), new Position(8, 1), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); } @Test @@ -228,7 +228,7 @@ class Chariot { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 0), new Position(9, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); } @Test @@ -240,7 +240,7 @@ class Chariot { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 0), new Position(3, 3), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); } } @@ -267,7 +267,7 @@ class SoldierCho { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(3, 0), new Position(2, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); } @Test @@ -279,7 +279,7 @@ class SoldierCho { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(3, 0), new Position(5, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); } } @@ -306,7 +306,7 @@ class SoldierHan { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(6, 0), new Position(7, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); } @Test @@ -318,7 +318,7 @@ class SoldierHan { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(6, 0), new Position(1, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); } } } diff --git a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java index 8b7b9ba8a5..3ec4f1997f 100644 --- a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java @@ -8,6 +8,7 @@ import janggi.domain.board.EmptyConditionTestBoardInitializer; import janggi.domain.piece.Camp; import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; import java.util.List; import org.junit.jupiter.api.Test; @@ -33,7 +34,7 @@ public class EmptyConditionTest { //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); } @Test @@ -53,6 +54,6 @@ public class EmptyConditionTest { //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } } diff --git a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java index f908ad9caf..3bdf46d362 100644 --- a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java @@ -8,6 +8,7 @@ import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; @@ -34,7 +35,7 @@ public class OnePieceExistsConditionTest { //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); } @Test @@ -58,7 +59,7 @@ public class OnePieceExistsConditionTest { //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); } @Test @@ -81,7 +82,7 @@ public class OnePieceExistsConditionTest { //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.SAME_PIECE_TYPE_IN_PATH.getMessage()); } @Test @@ -98,13 +99,14 @@ public class OnePieceExistsConditionTest { Camp camp = Camp.HAN; BoardInitializer boardInitializer = () -> Map.of( + new Position(0, 3), new Piece(PieceRule.SOLDIER, Camp.HAN), new Position(0, 5), new Piece(PieceRule.CHARIOT, Camp.HAN) ); Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } @Test @@ -121,12 +123,13 @@ public class OnePieceExistsConditionTest { Camp camp = Camp.HAN; BoardInitializer boardInitializer = () -> Map.of( + new Position(0, 3), new Piece(PieceRule.SOLDIER, Camp.HAN), new Position(0, 5), new Piece(PieceRule.CANNON, Camp.CHO) ); Board board = new Board(boardInitializer); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.SAME_PIECE_TYPE_AT_DESTINATION.getMessage()); } } diff --git a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java index df80ea03c4..1bc2e6fc70 100644 --- a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java @@ -4,6 +4,7 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; @@ -92,6 +93,6 @@ private static Stream createExceptionPosition() { void 상은_행마법_대로_움직이지_않으면_예외가_발생한다(Position from, Position to) { assertThatThrownBy(() -> strategy.findPath(from, to, Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage()); } } diff --git a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java index e60a9b6147..64e5dda5c2 100644 --- a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java @@ -4,6 +4,7 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; @@ -84,6 +85,6 @@ private static Stream createExceptionPosition() { void 마는_행마법_대로_움직이지_않으면_예외가_발생한다(Position from, Position to) { assertThatThrownBy(() -> strategy.findPath(from, to, Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage()); } } diff --git a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java index 259fd946eb..f04d3a1410 100644 --- a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java @@ -4,6 +4,7 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; @@ -85,13 +86,13 @@ private static Stream createPositionsAndPath() { void 차와_포는_한_방향으로_이동하지_않으면_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(5, 5), Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); } @Test void 차와_포는_제자리_이동_시_예외가_발생한다1() { assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(0, 0), Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.PIECE_MUST_MOVE.getMessage()); } } diff --git a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java index dec68908e0..def69e1be8 100644 --- a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java @@ -4,6 +4,7 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; @@ -41,6 +42,6 @@ private static Stream successMovePositions() { void 궁과_사는_1칸_이동이_아니면_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.HAN)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); } } diff --git a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java index d8c966c675..a353132f56 100644 --- a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java @@ -4,6 +4,7 @@ import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; @@ -54,14 +55,14 @@ private static Stream exceptionMovePositions() { void 병은_1칸_이동이_아니면_예외가_발생한다(Position from, Position to) { assertThatThrownBy(() -> strategy.findPath(from, to, Camp.HAN)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); } @Test void 병은_후진_시_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(6, 0), new Position(7, 0), Camp.HAN)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); } } @@ -85,14 +86,14 @@ class Zol { void 졸은_1칸_이동이_아니면_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); + .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); } @Test void 졸은_후진_시_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(2, 0), Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 기물이 이동할 수 없는 위치입니다."); - } + .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); } } +} From f4e38f4383e7a629da33b963bddc5c156a6644f6 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 19:53:02 +0900 Subject: [PATCH 34/68] =?UTF-8?q?test:=20=ED=8C=8C=EC=84=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/janggi/util/ParserTest.java | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/test/java/janggi/util/ParserTest.java diff --git a/src/test/java/janggi/util/ParserTest.java b/src/test/java/janggi/util/ParserTest.java new file mode 100644 index 0000000000..3a5769c301 --- /dev/null +++ b/src/test/java/janggi/util/ParserTest.java @@ -0,0 +1,24 @@ +package janggi.util; + + +import java.util.List; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +class ParserTest { + + @Test + void 구분자를_기준으로_문자열을_분리한다() { + // given + String expression = "10,20"; + String delimiter = ","; + // when + List parsedExpression = Parser.parseByDelimiter(delimiter, expression); + // then + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(parsedExpression).hasSize(2); + assertSoftly.assertThat(parsedExpression.getFirst()).isEqualTo(10); + assertSoftly.assertThat(parsedExpression.getLast()).isEqualTo(20); + }); + } +} From 9df0ca5543bb29804faa3085dfcf62c7db562be6 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 19:53:15 +0900 Subject: [PATCH 35/68] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=9C=EC=84=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../janggi/exception/ExceptionMessage.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/janggi/exception/ExceptionMessage.java b/src/main/java/janggi/exception/ExceptionMessage.java index 93033e742a..9ab0e90d14 100644 --- a/src/main/java/janggi/exception/ExceptionMessage.java +++ b/src/main/java/janggi/exception/ExceptionMessage.java @@ -12,29 +12,29 @@ public enum ExceptionMessage { - INVALID_BACKWARD_MOVEMENT("해당 기물은 후진할 수 없습니다."), ONLY_NUMBERS_ALLOWED("숫자만 입력 가능합니다"), - ROW_OUT_OF_RANGE(String.format("행은 %d행 이상 %d행 이하여야 합니다.", MIN_POSITION_INDEX, MAX_ROW_INDEX)), - COLUMN_OUT_OF_RANGE(String.format("열은 %d열 이상 %d열 이하여야 합니다.", MIN_POSITION_INDEX, MAX_COLUMN_INDEX)), INVALID_INPUT_FORMAT("잘못된 입력 형식입니다."), - ONLY_STRAIGHT_MOVE_ALLOWED("해당 기물은 직선 이동만 가능합니다."), - PIECE_MUST_MOVE("기물은 반드시 이동해야 합니다."), INVALID_ELEPHANT_SETTING("존재하지 않는 상차림 입니다."), - SAME_PIECE_TYPE_IN_PATH("경로상에 같은 종류의 기물이 존재합니다."), - INVALID_JUMPED_PIECE_COUNT(String.format("해당 기물은 정확히 %d개의 기물만 뛰어넘을 수 있습니다.", PASS_PIECE_COUNT)), + ROW_OUT_OF_RANGE(String.format("행은 %d행 이상 %d행 이하여야 합니다.", MIN_POSITION_INDEX, MAX_ROW_INDEX)), + COLUMN_OUT_OF_RANGE(String.format("열은 %d열 이상 %d열 이하여야 합니다.", MIN_POSITION_INDEX, MAX_COLUMN_INDEX)), + PATH_NOT_EMPTY("경로 상에 기물이 존재합니다."), SAME_CAMP_PIECE_AT_DESTINATION("목적지에 같은 진영의 기물이 존재합니다."), + SAME_PIECE_TYPE_IN_PATH("경로상에 같은 종류의 기물이 존재합니다."), SAME_PIECE_TYPE_AT_DESTINATION("목적지에 같은 종류의 기물이 존재합니다."), + ONLY_STRAIGHT_MOVE_ALLOWED("해당 기물은 직선 이동만 가능합니다."), + PIECE_MUST_MOVE("기물은 반드시 이동해야 합니다."), + INVALID_SINGLE_STEP_STRAIGHT_MOVE(String.format( + "해당 기물은 직선으로 %d칸 이동해야 합니다.", + SINGLE_STEP_DISTANCE + )), + INVALID_BACKWARD_MOVEMENT("해당 기물은 후진할 수 없습니다."), INVALID_SOLDIER_MOVE(String.format("해당 기물은 %d칸만 이동할 수 있습니다.", SINGLE_STEP_DISTANCE)), + INVALID_JUMPED_PIECE_COUNT(String.format("해당 기물은 정확히 %d개의 기물만 뛰어넘을 수 있습니다.", PASS_PIECE_COUNT)), INVALID_HORSE_MOVE(String.format( "해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다.", HORSE_STRAIGHT_MOVE_DISTANCE, HORSE_DIAGONAL_MOVE_DISTANCE )), - PATH_NOT_EMPTY("경로 상에 기물이 존재합니다."), - INVALID_SINGLE_STEP_STRAIGHT_MOVE(String.format( - "해당 기물은 직선으로 %d칸 이동해야 합니다.", - SINGLE_STEP_DISTANCE - )), INVALID_ELEPHANT_MOVE(String.format( "해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다.", ELEPHANT_STRAIGHT_MOVE_DISTANCE, From a4adc922008108d7ee3a406bda934deed761a6ae Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 20:00:54 +0900 Subject: [PATCH 36/68] =?UTF-8?q?refactor:=20=EC=B6=9C=EB=B0=9C=EC=A7=80,?= =?UTF-8?q?=20=EB=AA=A9=EC=A0=81=EC=A7=80=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiGame.java | 6 ++-- src/main/java/janggi/domain/board/Board.java | 10 +++---- src/main/java/janggi/domain/piece/Piece.java | 4 +-- .../java/janggi/domain/piece/PieceRule.java | 4 +-- .../piece/condition/EmptyCondition.java | 6 ++-- .../condition/OnePieceExistsCondition.java | 8 ++--- .../piece/strategy/DirectionInformation.java | 4 +-- .../piece/strategy/ElephantStrategy.java | 30 +++++++++---------- .../domain/piece/strategy/HorseStrategy.java | 28 ++++++++--------- .../domain/piece/strategy/MoveStrategy.java | 2 +- .../strategy/MultiStepStraightStrategy.java | 20 ++++++------- .../strategy/SingleStepStraightStrategy.java | 6 ++-- .../piece/strategy/SoldierStrategy.java | 6 ++-- src/main/java/janggi/view/InputView.java | 12 ++++---- src/test/java/janggi/domain/PositionTest.java | 12 ++++---- .../java/janggi/domain/board/BoardTest.java | 18 +++++------ .../piece/strategy/ElephantStrategyTest.java | 8 ++--- .../piece/strategy/HorseStrategyTest.java | 8 ++--- .../MultiStepStraightStrategyTest.java | 4 +-- .../SingleStepStraightStrategyTest.java | 6 ++-- .../piece/strategy/SoldierStrategyTest.java | 18 +++++------ 21 files changed, 110 insertions(+), 110 deletions(-) diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index 2e054b4f0d..148dddfa92 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -41,9 +41,9 @@ private void play(Board board) { } private List playTurn(Board board, Camp camp) { - Position from = toPosition(InputView.readStartPosition(camp)); - Position to = toPosition(InputView.readGoalPosition()); - Map boardState = board.movePiece(from, to); + Position source = toPosition(InputView.readSource(camp)); + Position destination = toPosition(InputView.readDestination()); + Map boardState = board.movePiece(source, destination); return toPiecePositions(boardState); } diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index d6b40e1a96..4c343d3835 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -37,11 +37,11 @@ public boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule) { return false; } - public Map movePiece(Position from, Position to) { - Piece piece = board.get(from); - piece.validateMove(from, to, this); - board.put(to, piece); - board.remove(from); + public Map movePiece(Position source, Position destination) { + Piece piece = board.get(source); + piece.validateMove(source, destination, this); + board.put(destination, piece); + board.remove(source); return Map.copyOf(board); } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index c57d28ebba..78f328baef 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -6,8 +6,8 @@ public record Piece(PieceRule pieceRule, Camp camp) { - public void validateMove(Position from, Position to, BoardChecker board) { - List path = pieceRule.findPath(from, to, camp); + public void validateMove(Position source, Position destination, BoardChecker board) { + List path = pieceRule.findPath(source, destination, camp); pieceRule.checkPath(path, camp, board); } diff --git a/src/main/java/janggi/domain/piece/PieceRule.java b/src/main/java/janggi/domain/piece/PieceRule.java index f0c3b8745e..ea8c15437c 100644 --- a/src/main/java/janggi/domain/piece/PieceRule.java +++ b/src/main/java/janggi/domain/piece/PieceRule.java @@ -31,8 +31,8 @@ public enum PieceRule { this.moveCondition = moveCondition; } - public List findPath(Position from, Position to, Camp camp) { - return moveStrategy.findPath(from, to, camp); + public List findPath(Position source, Position destination, Camp camp) { + return moveStrategy.findPath(source, destination, camp); } public void checkPath(List path, Camp camp, BoardChecker board) { diff --git a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java index 3c23f967fe..e2d9c129ce 100644 --- a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java +++ b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java @@ -14,7 +14,7 @@ public void checkPath(List path, Camp camp, BoardChecker board, PieceR for (int i = 0; i < path.size() - 1; i++) { validateEmptyPosition(path.get(i), board); } - validateGoalPosition(path.getLast(), camp, board); + validateDestination(path.getLast(), camp, board); } private void validateEmptyPosition(Position position, BoardChecker board) { @@ -23,8 +23,8 @@ private void validateEmptyPosition(Position position, BoardChecker board) { } } - private void validateGoalPosition(Position lastPosition, Camp camp, BoardChecker board) { - if (board.isSameCampPieceAt(lastPosition, camp)) { + private void validateDestination(Position destination, Camp camp, BoardChecker board) { + if (board.isSameCampPieceAt(destination, camp)) { throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } } diff --git a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java index 218f6c8b48..399d6dc458 100644 --- a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java +++ b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java @@ -20,7 +20,7 @@ public void checkPath(List path, Camp camp, BoardChecker board, PieceR } validateExactPieceCount(countOfPiece); - validateGoalPosition(path.getLast(), camp, board, pieceRule); + validateDestination(path.getLast(), camp, board, pieceRule); } private int countPieceAt(BoardChecker board, PieceRule pieceRule, Position position) { @@ -43,12 +43,12 @@ private void validateExactPieceCount(int countOfPiece) { } } - private void validateGoalPosition(Position lastPosition, Camp camp, BoardChecker board, PieceRule pieceRule) { - if (board.isSameCampPieceAt(lastPosition, camp)) { + private void validateDestination(Position destination, Camp camp, BoardChecker board, PieceRule pieceRule) { + if (board.isSameCampPieceAt(destination, camp)) { throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } - if (board.hasSamePieceRuleAt(lastPosition, pieceRule)) { + if (board.hasSamePieceRuleAt(destination, pieceRule)) { throw new IllegalArgumentException(ExceptionMessage.SAME_PIECE_TYPE_AT_DESTINATION.getMessage()); } } diff --git a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java index 6120484af1..759618314f 100644 --- a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java +++ b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java @@ -4,8 +4,8 @@ public record DirectionInformation(int rowDifference, int colDifference) { - public DirectionInformation(Position from, Position to) { - this(to.calculateRowDistance(from), to.calculateColumnDistance(from)); + public DirectionInformation(Position source, Position destination) { + this(destination.calculateRowDistance(source), destination.calculateColumnDistance(source)); } public int calculateAbsRowDifference() { diff --git a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java index 24918dd9e6..19b3cffe5c 100644 --- a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java @@ -13,15 +13,15 @@ public class ElephantStrategy implements MoveStrategy { private static final int MAX_ABS_DELTA = 3; @Override - public List findPath(Position from, Position to, Camp camp) { - DirectionInformation directionInfo = new DirectionInformation(from, to); + public List findPath(Position source, Position destination, Camp camp) { + DirectionInformation directionInfo = new DirectionInformation(source, destination); validateElephantMovement(directionInfo); if (directionInfo.isRowBiggerThanCol()) { - return createRowFirstPath(from, directionInfo); + return createRowFirstPath(source, directionInfo); } - return createColFirstPath(from, directionInfo); + return createColFirstPath(source, directionInfo); } private void validateElephantMovement(DirectionInformation directionInfo) { @@ -33,32 +33,32 @@ private void validateElephantMovement(DirectionInformation directionInfo) { } } - private List createRowFirstPath(Position from, DirectionInformation directionInfo) { + private List createRowFirstPath(Position source, DirectionInformation directionInfo) { List path = new ArrayList<>(); - from = from.moveRow(directionInfo.calculateRowDirection()); - path.add(from); + source = source.moveRow(directionInfo.calculateRowDirection()); + path.add(source); - path.addAll(moveDiagonal(from, directionInfo)); + path.addAll(moveDiagonal(source, directionInfo)); return path; } - private List createColFirstPath(Position from, DirectionInformation directionInfo) { + private List createColFirstPath(Position source, DirectionInformation directionInfo) { List path = new ArrayList<>(); - from = from.moveCol(directionInfo.calculateColDirection()); - path.add(from); + source = source.moveCol(directionInfo.calculateColDirection()); + path.add(source); - path.addAll(moveDiagonal(from, directionInfo)); + path.addAll(moveDiagonal(source, directionInfo)); return path; } - private List moveDiagonal(Position from, DirectionInformation directionInfo) { + private List moveDiagonal(Position source, DirectionInformation directionInfo) { List path = new ArrayList<>(); for (int i = 0; i < DIAGONAL_COUNT; i++) { - from = from.moveDiagonal(directionInfo.calculateRowDirection(), directionInfo.calculateColDirection()); - path.add(from); + source = source.moveDiagonal(directionInfo.calculateRowDirection(), directionInfo.calculateColDirection()); + path.add(source); } return path; } diff --git a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java index 3128e251c8..1b9bbc0a6e 100644 --- a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java @@ -12,40 +12,40 @@ public class HorseStrategy implements MoveStrategy { private static final int MAX_ABS_DELTA = 2; @Override - public List findPath(Position from, Position to, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(from, to); + public List findPath(Position source, Position destination, Camp camp) { + DirectionInformation directionInformation = new DirectionInformation(source, destination); validateHorseMovement(directionInformation); if (directionInformation.isRowBiggerThanCol()) { - return createRowFirstPath(from, directionInformation); + return createRowFirstPath(source, directionInformation); } - return createColFirstPath(from, directionInformation); + return createColFirstPath(source, directionInformation); } - private List createRowFirstPath(Position from, DirectionInformation directionInformation) { + private List createRowFirstPath(Position source, DirectionInformation directionInformation) { List path = new ArrayList<>(); int rowDirection = directionInformation.calculateRowDirection(); int colDirection = directionInformation.calculateColDirection(); - from = from.moveRow(rowDirection); - path.add(from); + source = source.moveRow(rowDirection); + path.add(source); - from = from.moveDiagonal(rowDirection, colDirection); - path.add(from); + source = source.moveDiagonal(rowDirection, colDirection); + path.add(source); return path; } - private List createColFirstPath(Position from, DirectionInformation directionInformation) { + private List createColFirstPath(Position source, DirectionInformation directionInformation) { List path = new ArrayList<>(); int rowDirection = directionInformation.calculateRowDirection(); int colDirection = directionInformation.calculateColDirection(); - from = from.moveCol(colDirection); - path.add(from); + source = source.moveCol(colDirection); + path.add(source); - from = from.moveDiagonal(rowDirection, colDirection); - path.add(from); + source = source.moveDiagonal(rowDirection, colDirection); + path.add(source); return path; } diff --git a/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java b/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java index 5721e8f7e7..17e88a8b69 100644 --- a/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java @@ -6,5 +6,5 @@ public interface MoveStrategy { - List findPath(Position from, Position to, Camp camp); + List findPath(Position source, Position destination, Camp camp); } diff --git a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java index 9c6c651dd4..e232a4e1b4 100644 --- a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java @@ -9,15 +9,15 @@ public class MultiStepStraightStrategy implements MoveStrategy { @Override - public List findPath(Position from, Position to, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(from, to); + public List findPath(Position source, Position destination, Camp camp) { + DirectionInformation directionInformation = new DirectionInformation(source, destination); validateStraightMove(directionInformation); if (directionInformation.isRowBiggerThanCol()) { - return createRowPath(from, directionInformation.rowDifference()); + return createRowPath(source, directionInformation.rowDifference()); } - return createColumnPath(from, directionInformation.colDifference()); + return createColumnPath(source, directionInformation.colDifference()); } private void validateStraightMove(DirectionInformation directionInformation) { @@ -34,25 +34,25 @@ private void validateStraightMove(DirectionInformation directionInformation) { } } - private List createRowPath(Position from, int rowDifference) { + private List createRowPath(Position source, int rowDifference) { List path = new ArrayList<>(); int rowDirection = rowDifference / Math.abs(rowDifference); while (rowDifference != 0) { - from = from.moveRow(rowDirection); - path.add(from); + source = source.moveRow(rowDirection); + path.add(source); rowDifference -= rowDirection; } return path; } - private List createColumnPath(Position from, int colDifference) { + private List createColumnPath(Position source, int colDifference) { List path = new ArrayList<>(); int columnDirection = colDifference / Math.abs(colDifference); while (colDifference != 0) { - from = from.moveCol(columnDirection); - path.add(from); + source = source.moveCol(columnDirection); + path.add(source); colDifference -= columnDirection; } return path; diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java index ea120c95b2..f71513f681 100644 --- a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java @@ -10,11 +10,11 @@ public class SingleStepStraightStrategy implements MoveStrategy { @Override - public List findPath(Position from, Position to, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(from, to); + public List findPath(Position source, Position destination, Camp camp) { + DirectionInformation directionInformation = new DirectionInformation(source, destination); validateSingleStepMovement(directionInformation); - return List.of(to); + return List.of(destination); } private void validateSingleStepMovement(DirectionInformation directionInformation) { diff --git a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java index 2140c537b8..ac250b5eb4 100644 --- a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java @@ -10,13 +10,13 @@ public class SoldierStrategy implements MoveStrategy { @Override - public List findPath(Position from, Position to, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(from, to); + public List findPath(Position source, Position destination, Camp camp) { + DirectionInformation directionInformation = new DirectionInformation(source, destination); camp.validateForwardDirection(directionInformation.calculateRowDirection()); validateSoldierMovement(directionInformation); - return List.of(to); + return List.of(destination); } private void validateSoldierMovement(DirectionInformation directionInformation) { diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index b9fa33c54d..ac86a13c76 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -20,8 +20,8 @@ public final class InputView { 4. 상마마상"""; private static final String TURN = "%s나라 차례 입니다."; - private static final String START_POSITION = "공격할 기물의 좌표를 입력해주세요."; - private static final String GOAL_POSITION = "이동 시킬 목적지 좌표를 입력해주세요."; + private static final String SOURCE = "공격할 기물의 좌표를 입력해주세요."; + private static final String DESTINATION = "이동 시킬 목적지 좌표를 입력해주세요."; private InputView() { } @@ -43,14 +43,14 @@ private static void validateInput(String input) { } } - public static List readStartPosition(Camp camp) { + public static List readSource(Camp camp) { System.out.println(String.format(TURN, CampFormatter.format(camp))); - System.out.println(START_POSITION); + System.out.println(SOURCE); return Parser.parseByDelimiter(DELIMITER, readLine()); } - public static List readGoalPosition() { - System.out.println(GOAL_POSITION); + public static List readDestination() { + System.out.println(DESTINATION); return Parser.parseByDelimiter(DELIMITER, readLine()); } } diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java index 21a6fa8a39..7ddfa3930b 100644 --- a/src/test/java/janggi/domain/PositionTest.java +++ b/src/test/java/janggi/domain/PositionTest.java @@ -35,10 +35,10 @@ public class PositionTest { @Test void 행의_차이를_계산한다() { //given - Position from = new Position(5, 3); - Position to = new Position(3, 3); + Position source = new Position(5, 3); + Position destination = new Position(3, 3); //when - int result = from.calculateRowDistance(to); + int result = source.calculateRowDistance(destination); //then assertThat(result).isEqualTo(2); } @@ -46,10 +46,10 @@ public class PositionTest { @Test void 열의_차이를_계산한다() { //given - Position from = new Position(5, 3); - Position to = new Position(5, 0); + Position source = new Position(5, 3); + Position destination = new Position(5, 0); //when - int result = from.calculateColumnDistance(to); + int result = source.calculateColumnDistance(destination); //then assertThat(result).isEqualTo(3); } diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 9747c6c9ab..ed3c86d0b0 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -14,21 +14,21 @@ class BoardTest { @Test void 목적지에_반대_진영_기물이_있으면_해당_기물을_제거한다() { // given - Position from = new Position(7, 1); - Position to = new Position(0, 1); + Position source = new Position(7, 1); + Position destination = new Position(0, 1); Board board = new Board(() -> Map.of( new Position(4, 1), new Piece(PieceRule.SOLDIER, Camp.HAN), - to, new Piece(PieceRule.HORSE, Camp.CHO), - from, new Piece(PieceRule.CANNON, Camp.HAN) + destination, new Piece(PieceRule.HORSE, Camp.CHO), + source, new Piece(PieceRule.CANNON, Camp.HAN) )); // when - board.movePiece(from, to); + board.movePiece(source, destination); // then - boolean GoalPositionExist = board.hasSamePieceRuleAt(to, PieceRule.CANNON); - boolean startPositionExist = board.hasPieceAt(from); + boolean destinationExists = board.hasSamePieceRuleAt(destination, PieceRule.CANNON); + boolean sourceExists = board.hasPieceAt(source); - assertThat(GoalPositionExist).isTrue(); - assertThat(startPositionExist).isFalse(); + assertThat(destinationExists).isTrue(); + assertThat(sourceExists).isFalse(); } } diff --git a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java index 1bc2e6fc70..ca1f957175 100644 --- a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java @@ -71,9 +71,9 @@ private static Stream createPositionsAndPath() { @ParameterizedTest @MethodSource("createPositionsAndPath") - void 상은_직선_1칸_이동_후_대각선_2칸_이동한다(Position from, Position to, List expectedPath) { + void 상은_직선_1칸_이동_후_대각선_2칸_이동한다(Position source, Position destination, List expectedPath) { // when - List path = strategy.findPath(from, to, Camp.HAN); + List path = strategy.findPath(source, destination, Camp.HAN); // then SoftAssertions.assertSoftly(assertSoftly -> { assertSoftly.assertThat(path).hasSize(expectedPath.size()); @@ -90,8 +90,8 @@ private static Stream createExceptionPosition() { @ParameterizedTest @MethodSource("createExceptionPosition") - void 상은_행마법_대로_움직이지_않으면_예외가_발생한다(Position from, Position to) { - assertThatThrownBy(() -> strategy.findPath(from, to, Camp.CHO)) + void 상은_행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { + assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage()); } diff --git a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java index 64e5dda5c2..f5a34c9e7f 100644 --- a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java @@ -63,9 +63,9 @@ private static Stream createPositionsAndPath() { @ParameterizedTest @MethodSource("createPositionsAndPath") - void 마는_직선_1칸_이동_후_대각선_1칸_이동한다(Position from, Position to, List expectedPath) { + void 마는_직선_1칸_이동_후_대각선_1칸_이동한다(Position source, Position destination, List expectedPath) { // when - List path = strategy.findPath(from, to, Camp.HAN); + List path = strategy.findPath(source, destination, Camp.HAN); // then SoftAssertions.assertSoftly(assertSoftly -> { assertSoftly.assertThat(path).hasSize(expectedPath.size()); @@ -82,8 +82,8 @@ private static Stream createExceptionPosition() { @ParameterizedTest @MethodSource("createExceptionPosition") - void 마는_행마법_대로_움직이지_않으면_예외가_발생한다(Position from, Position to) { - assertThatThrownBy(() -> strategy.findPath(from, to, Camp.CHO)) + void 마는_행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { + assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage()); } diff --git a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java index f04d3a1410..2500243906 100644 --- a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java @@ -73,8 +73,8 @@ private static Stream createPositionsAndPath() { @ParameterizedTest @MethodSource("createPositionsAndPath") - void 차와_포는_한_방향으로만_1칸_이상_이동_할_수_있다(Position from, Position to, int size, List expectedPath) { - List path = strategy.findPath(from, to, Camp.HAN); + void 차와_포는_한_방향으로만_1칸_이상_이동_할_수_있다(Position source, Position destination, int size, List expectedPath) { + List path = strategy.findPath(source, destination, Camp.HAN); SoftAssertions.assertSoftly(assertSoftly -> { assertSoftly.assertThat(path).hasSize(size); diff --git a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java index def69e1be8..c74c09456c 100644 --- a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java @@ -28,13 +28,13 @@ private static Stream successMovePositions() { @ParameterizedTest @MethodSource("successMovePositions") - void 궁과_사는_상하좌우_1칸_이동한다(Position from, Position to) { + void 궁과_사는_상하좌우_1칸_이동한다(Position source, Position destination) { //when - List path = strategy.findPath(from, to, Camp.HAN); + List path = strategy.findPath(source, destination, Camp.HAN); //then SoftAssertions.assertSoftly(assertSoftly -> { assertSoftly.assertThat(path).hasSize(1); - assertSoftly.assertThat(path).containsExactly(to); + assertSoftly.assertThat(path).containsExactly(destination); }); } diff --git a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java index a353132f56..22b2d27ff1 100644 --- a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java @@ -41,19 +41,19 @@ private static Stream exceptionMovePositions() { @ParameterizedTest @MethodSource("successMovePositions") - void 병의_1칸_이동_여부를_확인한다(Position from, Position to) { - List path = strategy.findPath(from, to, Camp.HAN); + void 병의_1칸_이동_여부를_확인한다(Position source, Position destination) { + List path = strategy.findPath(source, destination, Camp.HAN); SoftAssertions.assertSoftly(assertSoftly -> { assertSoftly.assertThat(path).hasSize(1); - assertSoftly.assertThat(path).containsExactly(to); + assertSoftly.assertThat(path).containsExactly(destination); }); } @ParameterizedTest @MethodSource("exceptionMovePositions") - void 병은_1칸_이동이_아니면_예외가_발생한다(Position from, Position to) { - assertThatThrownBy(() -> strategy.findPath(from, to, Camp.HAN)) + void 병은_1칸_이동이_아니면_예외가_발생한다(Position source, Position destination) { + assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.HAN)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); } @@ -71,14 +71,14 @@ private static Stream exceptionMovePositions() { class Zol { @Test void 졸의_1칸_이동_여부를_확인한다() { - Position from = new Position(3, 0); - Position to = new Position(4, 0); + Position source = new Position(3, 0); + Position destination = new Position(4, 0); - List path = strategy.findPath(from, to, Camp.CHO); + List path = strategy.findPath(source, destination, Camp.CHO); SoftAssertions.assertSoftly(assertSoftly -> { assertSoftly.assertThat(path).hasSize(1); - assertSoftly.assertThat(path).containsExactly(to); + assertSoftly.assertThat(path).containsExactly(destination); }); } From 083405664468740df1426f42c98dc88b15a090d4 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 20:03:08 +0900 Subject: [PATCH 37/68] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 0ee67df679..8204a7762d 100644 --- a/README.md +++ b/README.md @@ -20,24 +20,25 @@ ## ✅ 입력 -- [ ] 게임 시작 시 상차림 입력 받기 - - [ ] 지정된 명령어 이외의 문자열 및 숫자 값은 `IllegalArgumentException`을 발생시킨다. +- [x] 게임 시작 시 상차림 입력 받기 + - [x] 지정된 명령어 이외의 문자열 및 숫자 값은 `IllegalArgumentException`을 발생시킨다. -- [ ] 플레이어의 선택 기물 좌표 입력 - - [ ] 공백일 경우 `IllegalArgumentException`을 발생시킨다. - - [ ] 숫자가 아닐 경우 `IllegalArgumentException`을 발생시킨다. - - [ ] 구분자가 `,`가 아닐 경우 `IllegalArgumentException`을 발생시킨다. +- [x] 플레이어의 선택 기물 좌표 입력 + - [x] 공백일 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 숫자가 아닐 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 구분자가 `,`가 아닐 경우 `IllegalArgumentException`을 발생시킨다. -- [ ] 플레이어의 목표 좌표 입력 - - [ ] 공백일 경우 `IllegalArgumentException`을 발생시킨다. - - [ ] 숫자가 아닐 경우 `IllegalArgumentException`을 발생시킨다. - - [ ] 구분자가 `,`가 아닐 경우 `IllegalArgumentException`을 발생시킨다. +- [x] 플레이어의 목표 좌표 입력 + - [x] 공백일 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 숫자가 아닐 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 구분자가 `,`가 아닐 경우 `IllegalArgumentException`을 발생시킨다. ## ✅ 출력 -- [ ] 장기판 출력 +- [x] 장기판 출력 + - 상차림 선택 후 - 목표 좌표 입력 후 -- [ ] 현재 차례인 나 라 출력 +- [x] 현재 차례인 나라 출력 - 『한』, 『초』 ## ✅ 비지니스 기능 @@ -88,31 +89,31 @@ ### 기물 이동 -- [ ] 이동 시키려는 기물의 좌표를 확인한다. +- [x] 이동 시키려는 기물의 좌표를 확인한다. - [x] 좌표가 장기판 범위 벗어난 좌표일 경우 `IllegalArgumentException`을 발생시킨다. - - [ ] 좌표에 기물이 존재 하지 않을 경우 `IllegalArgumentException`을 발생시킨다. - - [ ] 좌표의 기물이 자신의 진영이 아닐 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 좌표에 기물이 존재 하지 않을 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 좌표의 기물이 자신의 진영이 아닐 경우 `IllegalArgumentException`을 발생시킨다. -- [ ] 기물을 목적지 좌표로 이동한다. +- [x] 기물을 목적지 좌표로 이동한다. - [x] 목적지 좌표가 장기판 범위 벗어난 좌표일 경우 `IllegalArgumentException`을 발생시킨다. - - [ ] 기물 규칙 및 제약에 맞게 이동한다. + - [x] 기물 규칙 및 제약에 맞게 이동한다. - [차(車)] - [x] 상하좌우로 장애물을 만날 때까지 칸 수 제한 없이 이동한다. - - [ ] 이동하려는 경로 중간에 다른 기물이 **있**다면 `IllegalArgumentException`을 발생시킨다. + - [x] 이동하려는 경로 중간에 다른 기물이 **있**다면 `IllegalArgumentException`을 발생시킨다. - [포(包)] - - [ ] 상하좌우로 이동하되, 반드시 중간에 다른 기물을 하나 뛰어넘어야 합니다. - - [ ] 이동하려는 경로 중간에 다른 기물이 **없**다면 `IllegalArgumentException`을 발생시킨다. - - [ ] 넘으려는 기물이 포(包) 일 경우 `IllegalArgumentException`을 발생시킨다. - - [ ] 목적지 좌표의 기물이 포(包)일 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 상하좌우로 이동하되, 반드시 중간에 다른 기물을 하나 뛰어넘어야 합니다. + - [x] 이동하려는 경로 중간에 다른 기물이 **없**다면 `IllegalArgumentException`을 발생시킨다. + - [x] 넘으려는 기물이 포(包) 일 경우 `IllegalArgumentException`을 발생시킨다. + - [x] 목적지 좌표의 기물이 포(包)일 경우 `IllegalArgumentException`을 발생시킨다. - [마(馬)] - - [ ] 직선 1칸 + 대각선 1칸(日) 이동한다. - - [ ] 목적지 좌표(직선 1칸 또는 대각선 1칸)에 다른 기물이 있으면 `IllegalArgumentException`을 발생시킨다. + - [x] 직선 1칸 + 대각선 1칸(日) 이동한다. + - [x] 목적지 좌표(직선 1칸 또는 대각선 1칸)에 다른 기물이 있으면 `IllegalArgumentException`을 발생시킨다. - [상(象)] - - [ ] 직선 1칸 + 대각선 2칸(用) 이동한다. - - [ ] 목적지 좌표(직선 1칸 또는 대각선 1칸)에 다른 기물이 있으면 `IllegalArgumentException`을 발생시킨다. + - [x] 직선 1칸 + 대각선 2칸(用) 이동한다. + - [x] 목적지 좌표(직선 1칸 또는 대각선 1칸)에 다른 기물이 있으면 `IllegalArgumentException`을 발생시킨다. - [궁(楚/漢), 사(士)] - [x] 상하좌우 직선 1칸 이동한다. From 9bafbe4749bd33711c5f5143dfd1a7837fc38adb Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Fri, 27 Mar 2026 20:07:22 +0900 Subject: [PATCH 38/68] =?UTF-8?q?refactor:=20=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/view/InputView.java | 6 ++++-- src/main/java/janggi/view/OutputView.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index ac86a13c76..ee8a1b4620 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -11,17 +11,19 @@ public final class InputView { private static final Scanner SCANNER = new Scanner(System.in); private static final String DELIMITER = ","; + private static final String LINE_SEPARATOR = System.lineSeparator(); private static final String ELEPHANT_SETTING = """ + %s나라의 상차림을 선택해주세요. 1. 상마상마 2. 마상마상 3. 마상상마 4. 상마마상"""; - private static final String TURN = "%s나라 차례 입니다."; + private static final String TURN = LINE_SEPARATOR + "%s나라 차례 입니다."; private static final String SOURCE = "공격할 기물의 좌표를 입력해주세요."; - private static final String DESTINATION = "이동 시킬 목적지 좌표를 입력해주세요."; + private static final String DESTINATION = LINE_SEPARATOR + "이동 시킬 목적지 좌표를 입력해주세요."; private InputView() { } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index af392c70de..ca4fbe0f48 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -15,7 +15,7 @@ public final class OutputView { private static final int ROW_SIZE = 10; private static final int COLUMN_SIZE = 9; - private static final String TITLE = "[장기판]"; + private static final String TITLE = LINE_SEPARATOR + "[장기판]"; private static final String EMPTY_CELL = "."; private static final String RESET = "\u001B[0m"; From db55fcf161472fdcb784f0f978e3963685ade400 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Sat, 28 Mar 2026 00:59:50 +0900 Subject: [PATCH 39/68] =?UTF-8?q?feat:=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 보드 예외 테스트 추가 - 좌표 입력 예외 처리 추가 - 상차림 입력 예외 처리 추가 - 출발지 기물 없을 경우 예외 처리 추가 --- src/main/java/janggi/JanggiGame.java | 44 ++++++++++++++----- src/main/java/janggi/domain/board/Board.java | 18 +++++++- .../board/StandardBoardInitializer.java | 14 +++--- .../janggi/exception/ExceptionMessage.java | 2 + src/main/java/janggi/util/RetryHandler.java | 20 +++++++++ src/main/java/janggi/view/InputView.java | 4 +- src/main/java/janggi/view/OutputView.java | 4 ++ .../java/janggi/domain/board/BoardTest.java | 43 +++++++++++++++--- .../board/StandardBoardInitializerTest.java | 10 +++-- 9 files changed, 127 insertions(+), 32 deletions(-) create mode 100644 src/main/java/janggi/util/RetryHandler.java diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index 148dddfa92..14d7dbb942 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -3,10 +3,13 @@ import janggi.domain.Position; import janggi.domain.board.Board; import janggi.domain.board.BoardInitializer; +import janggi.domain.board.ElephantSetting; import janggi.domain.board.StandardBoardInitializer; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.dto.PiecePositionDto; +import janggi.exception.ExceptionMessage; +import janggi.util.RetryHandler; import janggi.view.InputView; import janggi.view.OutputView; import java.util.ArrayDeque; @@ -24,33 +27,52 @@ public void run() { play(board); } - private Board createBoard() { - String hanCommand = InputView.readElephantSettingCommand(Camp.HAN); - String choCommand = InputView.readElephantSettingCommand(Camp.CHO); - BoardInitializer initializer = new StandardBoardInitializer(hanCommand, choCommand); - return new Board(initializer); - } - private void play(Board board) { int playCount = 100; while (playCount-- > 0) { Camp turn = turns.poll(); - OutputView.printBoard(playTurn(board, turn)); + OutputView.printBoard(RetryHandler.retryOnInvalidInput(() -> playTurn(board, turn))); turns.offer(turn); } } + private Board createBoard() { + ElephantSetting hanElephantSetting = RetryHandler.retryOnInvalidInput( + () -> ElephantSetting.findElephantSettingBy(InputView.readElephantSettingCommand(Camp.HAN))); + + ElephantSetting choElephantSetting = RetryHandler.retryOnInvalidInput( + () -> ElephantSetting.findElephantSettingBy(InputView.readElephantSettingCommand(Camp.CHO))); + + BoardInitializer initializer = new StandardBoardInitializer(hanElephantSetting, choElephantSetting); + return new Board(initializer); + } + private List playTurn(Board board, Camp camp) { - Position source = toPosition(InputView.readSource(camp)); - Position destination = toPosition(InputView.readDestination()); - Map boardState = board.movePiece(source, destination); + Position source = readSource(board, camp); + Position destination = RetryHandler.retryOnInvalidInput(() -> toPosition(InputView.readDestination())); + Map boardState = board.movePiece(source, destination, camp); return toPiecePositions(boardState); } + private Position readSource(Board board, Camp camp) { + return RetryHandler.retryOnInvalidInput(() -> { + Position source = toPosition(InputView.readSource(camp)); + board.validateCampTurn(source, camp); + return source; + }); + } + private Position toPosition(List rawPosition) { + validatePositionSize(rawPosition); return new Position(rawPosition.get(0), rawPosition.get(1)); } + private void validatePositionSize(List rawPosition) { + if (rawPosition.size() != 2) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT_FORMAT.getMessage()); + } + } + private List toPiecePositions(Map boardState) { return boardState.entrySet().stream() .map(entry -> PiecePositionDto.of(entry.getKey(), entry.getValue())) diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 4c343d3835..6ac19c2695 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -4,6 +4,7 @@ import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; import java.util.HashMap; import java.util.Map; @@ -37,7 +38,8 @@ public boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule) { return false; } - public Map movePiece(Position source, Position destination) { + public Map movePiece(Position source, Position destination, Camp turn) { + validateCampTurn(source, turn); Piece piece = board.get(source); piece.validateMove(source, destination, this); board.put(destination, piece); @@ -46,6 +48,20 @@ public Map movePiece(Position source, Position destination) { return Map.copyOf(board); } + public void validateCampTurn(Position source, Camp turn) { + validateSource(source); + Piece piece = board.get(source); + if (!piece.isSameCamp(turn)) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_CAMP_PIECE.getMessage()); + } + } + + private void validateSource(Position source) { + if (!board.containsKey(source)) { + throw new IllegalArgumentException(ExceptionMessage.SOURCE_NOT_EXISTS.getMessage()); + } + } + public Map getBoard() { return Map.copyOf(board); } diff --git a/src/main/java/janggi/domain/board/StandardBoardInitializer.java b/src/main/java/janggi/domain/board/StandardBoardInitializer.java index 4e15a3243e..17639128d9 100644 --- a/src/main/java/janggi/domain/board/StandardBoardInitializer.java +++ b/src/main/java/janggi/domain/board/StandardBoardInitializer.java @@ -6,21 +6,17 @@ public class StandardBoardInitializer implements BoardInitializer { - private final String hanChoice; - private final String choChoice; + private final ElephantSetting hanElephantSetting; + private final ElephantSetting choElephantSetting; // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 - // TODO : String으로 바꿔도 같은 문제 발생 - public StandardBoardInitializer(String hanChoice, String choChoice) { - this.hanChoice = hanChoice; - this.choChoice = choChoice; + public StandardBoardInitializer(ElephantSetting hanElephantSetting, ElephantSetting choElephantSetting) { + this.hanElephantSetting = hanElephantSetting; + this.choElephantSetting = choElephantSetting; } @Override public Map initialize() { - ElephantSetting hanElephantSetting = ElephantSetting.findElephantSettingBy(hanChoice); - ElephantSetting choElephantSetting = ElephantSetting.findElephantSettingBy(choChoice); - return InitialPiecePlacement.init(hanElephantSetting, choElephantSetting); } } diff --git a/src/main/java/janggi/exception/ExceptionMessage.java b/src/main/java/janggi/exception/ExceptionMessage.java index 9ab0e90d14..b6d510ce95 100644 --- a/src/main/java/janggi/exception/ExceptionMessage.java +++ b/src/main/java/janggi/exception/ExceptionMessage.java @@ -17,6 +17,8 @@ public enum ExceptionMessage { INVALID_ELEPHANT_SETTING("존재하지 않는 상차림 입니다."), ROW_OUT_OF_RANGE(String.format("행은 %d행 이상 %d행 이하여야 합니다.", MIN_POSITION_INDEX, MAX_ROW_INDEX)), COLUMN_OUT_OF_RANGE(String.format("열은 %d열 이상 %d열 이하여야 합니다.", MIN_POSITION_INDEX, MAX_COLUMN_INDEX)), + SOURCE_NOT_EXISTS("출발지에 기물이 존재하지 않습니다."), + INVALID_CAMP_PIECE("상대 진영의 기물은 이동할 수 없습니다."), PATH_NOT_EMPTY("경로 상에 기물이 존재합니다."), SAME_CAMP_PIECE_AT_DESTINATION("목적지에 같은 진영의 기물이 존재합니다."), SAME_PIECE_TYPE_IN_PATH("경로상에 같은 종류의 기물이 존재합니다."), diff --git a/src/main/java/janggi/util/RetryHandler.java b/src/main/java/janggi/util/RetryHandler.java new file mode 100644 index 0000000000..a2909a09b4 --- /dev/null +++ b/src/main/java/janggi/util/RetryHandler.java @@ -0,0 +1,20 @@ +package janggi.util; + +import janggi.view.OutputView; +import java.util.function.Supplier; + +public final class RetryHandler { + + private RetryHandler() { + } + + public static T retryOnInvalidInput(Supplier input) { + while (true) { + try { + return input.get(); + } catch (IllegalArgumentException e) { + OutputView.printError(e.getMessage()); + } + } + } +} diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index ee8a1b4620..c9b07dddc6 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -22,8 +22,8 @@ public final class InputView { 4. 상마마상"""; private static final String TURN = LINE_SEPARATOR + "%s나라 차례 입니다."; - private static final String SOURCE = "공격할 기물의 좌표를 입력해주세요."; - private static final String DESTINATION = LINE_SEPARATOR + "이동 시킬 목적지 좌표를 입력해주세요."; + private static final String SOURCE = "공격할 기물의 좌표를 행,열 순으로 입력해 주세요. (예: 9,8)"; + private static final String DESTINATION = LINE_SEPARATOR + "이동 시킬 목적지의 좌표를 행,열 순으로 입력해 주세요. (예: 2,0)"; private InputView() { } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index ca4fbe0f48..9b0dcffdf0 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -29,6 +29,10 @@ public final class OutputView { private OutputView() { } + public static void printError(String errorMessage) { + System.out.println(errorMessage); + } + public static void printBoard(List piecePositions) { System.out.println(renderBoard(piecePositions)); } diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index ed3c86d0b0..0ad08de1a4 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -1,12 +1,13 @@ package janggi.domain.board; -import static org.assertj.core.api.Assertions.assertThat; - import janggi.domain.Position; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; import java.util.Map; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; class BoardTest { @@ -23,12 +24,44 @@ destination, new Piece(PieceRule.HORSE, Camp.CHO), source, new Piece(PieceRule.CANNON, Camp.HAN) )); // when - board.movePiece(source, destination); + board.movePiece(source, destination, Camp.HAN); // then boolean destinationExists = board.hasSamePieceRuleAt(destination, PieceRule.CANNON); boolean sourceExists = board.hasPieceAt(source); - assertThat(destinationExists).isTrue(); - assertThat(sourceExists).isFalse(); + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(destinationExists).isTrue(); + assertSoftly.assertThat(sourceExists).isFalse(); + }); + } + + @Test + void 상대_진영의_기물을_이동_시키면_예외가_발생한다() { + // given + Position source = new Position(7, 1); + Position destination = new Position(0, 1); + + // when + Board board = new Board(() -> Map.of( + destination, new Piece(PieceRule.HORSE, Camp.CHO), + source, new Piece(PieceRule.CANNON, Camp.CHO) + )); + // then + Assertions.assertThatThrownBy(() -> board.movePiece(source, destination, Camp.HAN)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_CAMP_PIECE.getMessage()); + } + + @Test + void 출발지에_기물이_존재하지_않으면_예외가_발생한다() { + // given + Position source = new Position(7, 1); + Position destination = new Position(0, 1); + // when + Board board = new Board(Map::of); + // then + Assertions.assertThatThrownBy(() -> board.movePiece(source, destination, Camp.HAN)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.SOURCE_NOT_EXISTS.getMessage()); } } diff --git a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java index 34ce15917e..e0e539a238 100644 --- a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java @@ -12,9 +12,10 @@ public class StandardBoardInitializerTest { @Test - void 초_마상마상_한_상마상마_으로_보드를_초기화한다() { + void 한_상마상마_초_마상마상_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer("1", "2"); + BoardInitializer initializer = new StandardBoardInitializer( + ElephantSetting.LEFT_ELEPHANT, ElephantSetting.RIGHT_ELEPHANT); Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoLeftHanRightBoard()); // when @@ -27,9 +28,10 @@ public class StandardBoardInitializerTest { } @Test - void 초_마상상마_한_상마마상_으로_보드를_초기화한다() { + void 한_상마마상_초_마상상마_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer("4", "3"); + BoardInitializer initializer = new StandardBoardInitializer( + ElephantSetting.OUTER_ELEPHANT, ElephantSetting.INNER_ELEPHANT); Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoInnerHanOuterBoard()); // when From 2dbdde981e5a3500e36e22c40802573f120e7030 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Sat, 28 Mar 2026 11:07:56 +0900 Subject: [PATCH 40/68] =?UTF-8?q?refactor:=20=EC=99=84=EB=A3=8C=ED=95=9C?= =?UTF-8?q?=20TODO=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/janggi/domain/board/StandardBoardInitializerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java index e0e539a238..90b61be158 100644 --- a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java @@ -91,7 +91,6 @@ private Map createChoInnerHanOuterBoard() { return board; } - //TODO: moveStrategy 변경 private void putPieces(Map board, PieceRule pieceRule, Camp camp, Position... positions) { for (Position position : positions) { From ebecb3e880b2ddbee2f4ea959568be7dc9cb6688 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Sat, 28 Mar 2026 12:43:40 +0900 Subject: [PATCH 41/68] =?UTF-8?q?refactor:=20View=EC=97=90=20CampDto=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiGame.java | 9 ++++++--- src/main/java/janggi/dto/CampDto.java | 6 ++++++ src/main/java/janggi/dto/PiecePositionDto.java | 5 ++--- src/main/java/janggi/view/InputView.java | 11 +++++------ src/main/java/janggi/view/OutputView.java | 6 +++--- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index 14d7dbb942..131d9a8ba4 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -7,6 +7,7 @@ import janggi.domain.board.StandardBoardInitializer; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; +import janggi.dto.CampDto; import janggi.dto.PiecePositionDto; import janggi.exception.ExceptionMessage; import janggi.util.RetryHandler; @@ -38,10 +39,12 @@ private void play(Board board) { private Board createBoard() { ElephantSetting hanElephantSetting = RetryHandler.retryOnInvalidInput( - () -> ElephantSetting.findElephantSettingBy(InputView.readElephantSettingCommand(Camp.HAN))); + () -> ElephantSetting.findElephantSettingBy( + InputView.readElephantSettingCommand(CampDto.from(Camp.HAN)))); ElephantSetting choElephantSetting = RetryHandler.retryOnInvalidInput( - () -> ElephantSetting.findElephantSettingBy(InputView.readElephantSettingCommand(Camp.CHO))); + () -> ElephantSetting.findElephantSettingBy( + InputView.readElephantSettingCommand(CampDto.from(Camp.CHO)))); BoardInitializer initializer = new StandardBoardInitializer(hanElephantSetting, choElephantSetting); return new Board(initializer); @@ -56,7 +59,7 @@ private List playTurn(Board board, Camp camp) { private Position readSource(Board board, Camp camp) { return RetryHandler.retryOnInvalidInput(() -> { - Position source = toPosition(InputView.readSource(camp)); + Position source = toPosition(InputView.readSource(CampDto.from(camp))); board.validateCampTurn(source, camp); return source; }); diff --git a/src/main/java/janggi/dto/CampDto.java b/src/main/java/janggi/dto/CampDto.java index d0c3890b92..180aeeb670 100644 --- a/src/main/java/janggi/dto/CampDto.java +++ b/src/main/java/janggi/dto/CampDto.java @@ -5,7 +5,13 @@ public record CampDto(String camp) { + private static final String CHO_NAME = "초"; + public static CampDto from(Camp camp) { return new CampDto(CampFormatter.format(camp)); } + + public boolean isCho() { + return camp.equals(CHO_NAME); + } } diff --git a/src/main/java/janggi/dto/PiecePositionDto.java b/src/main/java/janggi/dto/PiecePositionDto.java index 05b7f52835..df28379146 100644 --- a/src/main/java/janggi/dto/PiecePositionDto.java +++ b/src/main/java/janggi/dto/PiecePositionDto.java @@ -1,7 +1,6 @@ package janggi.dto; import janggi.domain.Position; -import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.formatter.PieceFormatter; @@ -9,14 +8,14 @@ public record PiecePositionDto( int row, int col, String type, - Camp camp + CampDto camp ) { public static PiecePositionDto of(Position position, Piece piece) { return new PiecePositionDto( position.row(), position.column(), PieceFormatter.format(piece), - piece.camp() + CampDto.from(piece.camp()) ); } } diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index c9b07dddc6..5656ddab25 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -1,8 +1,7 @@ package janggi.view; -import janggi.domain.piece.Camp; +import janggi.dto.CampDto; import janggi.exception.ExceptionMessage; -import janggi.formatter.CampFormatter; import janggi.util.Parser; import java.util.List; import java.util.Scanner; @@ -28,8 +27,8 @@ public final class InputView { private InputView() { } - public static String readElephantSettingCommand(Camp camp) { - System.out.println(String.format(ELEPHANT_SETTING, CampFormatter.format(camp))); + public static String readElephantSettingCommand(CampDto campDto) { + System.out.println(String.format(ELEPHANT_SETTING, campDto.camp())); return readLine(); } @@ -45,8 +44,8 @@ private static void validateInput(String input) { } } - public static List readSource(Camp camp) { - System.out.println(String.format(TURN, CampFormatter.format(camp))); + public static List readSource(CampDto campDto) { + System.out.println(String.format(TURN, campDto.camp())); System.out.println(SOURCE); return Parser.parseByDelimiter(DELIMITER, readLine()); } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index 9b0dcffdf0..974005589c 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -2,7 +2,7 @@ import static java.util.stream.Collectors.joining; -import janggi.domain.piece.Camp; +import janggi.dto.CampDto; import janggi.dto.PiecePositionDto; import java.util.Arrays; import java.util.List; @@ -77,8 +77,8 @@ private static String colorize(PiecePositionDto piecePosition) { return colorOf(piecePosition.camp()) + piecePosition.type() + RESET; } - private static String colorOf(Camp camp) { - if (camp == Camp.CHO) { + private static String colorOf(CampDto campDto) { + if (campDto.isCho()) { return CHO_COLOR; } return HAN_COLOR; From ef036ce8ef12e25c06c42b8b79d60d219a00c685 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Sat, 28 Mar 2026 18:12:56 +0900 Subject: [PATCH 42/68] =?UTF-8?q?refactor:=20=EC=A7=84=EC=98=81=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=83=81=EC=88=98=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/dto/CampDto.java | 6 ------ src/main/java/janggi/formatter/CampFormatter.java | 7 +++++-- src/main/java/janggi/view/OutputView.java | 7 ++++++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/janggi/dto/CampDto.java b/src/main/java/janggi/dto/CampDto.java index 180aeeb670..d0c3890b92 100644 --- a/src/main/java/janggi/dto/CampDto.java +++ b/src/main/java/janggi/dto/CampDto.java @@ -5,13 +5,7 @@ public record CampDto(String camp) { - private static final String CHO_NAME = "초"; - public static CampDto from(Camp camp) { return new CampDto(CampFormatter.format(camp)); } - - public boolean isCho() { - return camp.equals(CHO_NAME); - } } diff --git a/src/main/java/janggi/formatter/CampFormatter.java b/src/main/java/janggi/formatter/CampFormatter.java index f332921d8f..f633cf5b7f 100644 --- a/src/main/java/janggi/formatter/CampFormatter.java +++ b/src/main/java/janggi/formatter/CampFormatter.java @@ -4,13 +4,16 @@ public final class CampFormatter { + public static final String CHO_NAME = "초"; + public static final String HAN_NAME = "한"; + private CampFormatter() { } public static String format(Camp camp) { return switch (camp) { - case CHO -> "초"; - case HAN -> "한"; + case CHO -> CHO_NAME; + case HAN -> HAN_NAME; }; } } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index 974005589c..1d1fdf6fe7 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -1,5 +1,6 @@ package janggi.view; +import static janggi.formatter.CampFormatter.CHO_NAME; import static java.util.stream.Collectors.joining; import janggi.dto.CampDto; @@ -78,12 +79,16 @@ private static String colorize(PiecePositionDto piecePosition) { } private static String colorOf(CampDto campDto) { - if (campDto.isCho()) { + if (isCho(campDto.camp())) { return CHO_COLOR; } return HAN_COLOR; } + private static boolean isCho(String camp) { + return camp.equals(CHO_NAME); + } + private static String fullWidthNumber(int number) { return FULL_WIDTH_NUMBERS[number]; } From 01783c93a9dda2e2e4dae4c93aad22cee10ffa92 Mon Sep 17 00:00:00 2001 From: MODUGGAGI Date: Sat, 28 Mar 2026 19:08:29 +0900 Subject: [PATCH 43/68] =?UTF-8?q?feat:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20=ED=84=B4=20=EC=A0=84=EC=9A=A9=20=EA=B0=92=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiGame.java | 41 ++++++++++----------- src/main/java/janggi/domain/Turn.java | 15 ++++++++ src/main/java/janggi/domain/piece/Camp.java | 7 ++++ src/main/java/janggi/util/RetryHandler.java | 11 ++++++ 4 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 src/main/java/janggi/domain/Turn.java diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index 131d9a8ba4..f54433a632 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -1,6 +1,7 @@ package janggi; import janggi.domain.Position; +import janggi.domain.Turn; import janggi.domain.board.Board; import janggi.domain.board.BoardInitializer; import janggi.domain.board.ElephantSetting; @@ -13,30 +14,17 @@ import janggi.util.RetryHandler; import janggi.view.InputView; import janggi.view.OutputView; -import java.util.ArrayDeque; import java.util.List; import java.util.Map; -import java.util.Queue; public class JanggiGame { - private final Queue turns = new ArrayDeque<>(List.of(Camp.CHO, Camp.HAN)); - public void run() { Board board = createBoard(); OutputView.printBoard(toPiecePositions(board.getBoard())); play(board); } - private void play(Board board) { - int playCount = 100; - while (playCount-- > 0) { - Camp turn = turns.poll(); - OutputView.printBoard(RetryHandler.retryOnInvalidInput(() -> playTurn(board, turn))); - turns.offer(turn); - } - } - private Board createBoard() { ElephantSetting hanElephantSetting = RetryHandler.retryOnInvalidInput( () -> ElephantSetting.findElephantSettingBy( @@ -50,11 +38,26 @@ private Board createBoard() { return new Board(initializer); } - private List playTurn(Board board, Camp camp) { + private List toPiecePositions(Map boardState) { + return boardState.entrySet().stream() + .map(entry -> PiecePositionDto.of(entry.getKey(), entry.getValue())) + .toList(); + } + + private void play(Board board) { + Turn turn = new Turn(); + while (true) { + RetryHandler.retryOnInvalidInput(() -> playTurn(board, turn)); + OutputView.printBoard(toPiecePositions(board.getBoard())); + } + } + + private void playTurn(Board board, Turn turn) { + Camp camp = turn.currentTurn(); Position source = readSource(board, camp); Position destination = RetryHandler.retryOnInvalidInput(() -> toPosition(InputView.readDestination())); - Map boardState = board.movePiece(source, destination, camp); - return toPiecePositions(boardState); + board.movePiece(source, destination, camp); + turn.finishTurn(); } private Position readSource(Board board, Camp camp) { @@ -75,10 +78,4 @@ private void validatePositionSize(List rawPosition) { throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT_FORMAT.getMessage()); } } - - private List toPiecePositions(Map boardState) { - return boardState.entrySet().stream() - .map(entry -> PiecePositionDto.of(entry.getKey(), entry.getValue())) - .toList(); - } } diff --git a/src/main/java/janggi/domain/Turn.java b/src/main/java/janggi/domain/Turn.java new file mode 100644 index 0000000000..2b6c85c1aa --- /dev/null +++ b/src/main/java/janggi/domain/Turn.java @@ -0,0 +1,15 @@ +package janggi.domain; + +import janggi.domain.piece.Camp; + +public class Turn { + private Camp currentCamp = Camp.CHO; + + public Camp currentTurn() { + return currentCamp; + } + + public void finishTurn() { + currentCamp = currentCamp.next(); + } +} diff --git a/src/main/java/janggi/domain/piece/Camp.java b/src/main/java/janggi/domain/piece/Camp.java index 4309bbf83b..e9fced13d5 100644 --- a/src/main/java/janggi/domain/piece/Camp.java +++ b/src/main/java/janggi/domain/piece/Camp.java @@ -25,4 +25,11 @@ public void validateForwardDirection(int rowDirection) { public int getStartRowPosition() { return startRowPosition; } + + public Camp next() { + if (this == CHO) { + return HAN; + } + return CHO; + } } diff --git a/src/main/java/janggi/util/RetryHandler.java b/src/main/java/janggi/util/RetryHandler.java index a2909a09b4..434abeb0af 100644 --- a/src/main/java/janggi/util/RetryHandler.java +++ b/src/main/java/janggi/util/RetryHandler.java @@ -17,4 +17,15 @@ public static T retryOnInvalidInput(Supplier input) { } } } + + public static void retryOnInvalidInput(Runnable input) { + while (true) { + try { + input.run(); + return; + } catch (IllegalArgumentException e) { + OutputView.printError(e.getMessage()); + } + } + } } From 425b7a36085a6ec38003616623502677214a8a26 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Sun, 29 Mar 2026 00:12:50 +0900 Subject: [PATCH 44/68] =?UTF-8?q?refactor:=20=ED=84=B4=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EB=B0=98=ED=99=98=ED=83=80=EC=9E=85=20void?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/board/Board.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 6ac19c2695..4251a019d3 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -38,14 +38,12 @@ public boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule) { return false; } - public Map movePiece(Position source, Position destination, Camp turn) { + public void movePiece(Position source, Position destination, Camp turn) { validateCampTurn(source, turn); Piece piece = board.get(source); piece.validateMove(source, destination, this); board.put(destination, piece); board.remove(source); - - return Map.copyOf(board); } public void validateCampTurn(Position source, Camp turn) { From 9c0501d134ba43b0203d83b43886c14121a6a0cb Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Sun, 29 Mar 2026 00:13:11 +0900 Subject: [PATCH 45/68] =?UTF-8?q?refactor:=20=EA=B3=B5=EB=B0=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Turn.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/janggi/domain/Turn.java b/src/main/java/janggi/domain/Turn.java index 2b6c85c1aa..2fac1e7bff 100644 --- a/src/main/java/janggi/domain/Turn.java +++ b/src/main/java/janggi/domain/Turn.java @@ -3,6 +3,7 @@ import janggi.domain.piece.Camp; public class Turn { + private Camp currentCamp = Camp.CHO; public Camp currentTurn() { From 766e4816d3452077ee2059a2966eca213b3a9108 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 11:43:12 +0900 Subject: [PATCH 46/68] =?UTF-8?q?refactor:=20PieceFormatter=EC=99=80=20Cam?= =?UTF-8?q?pFormatter=20enum=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20switch=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/dto/CampDto.java | 10 +++- .../java/janggi/dto/PiecePositionDto.java | 5 +- .../janggi/exception/ExceptionMessage.java | 6 +- .../java/janggi/formatter/CampFormatter.java | 19 ------- .../java/janggi/formatter/PieceFormatter.java | 43 -------------- src/main/java/janggi/view/InputView.java | 4 +- src/main/java/janggi/view/OutputView.java | 22 +------ .../java/janggi/view/format/CampFormat.java | 37 ++++++++++++ .../java/janggi/view/format/PieceFormat.java | 57 +++++++++++++++++++ 9 files changed, 114 insertions(+), 89 deletions(-) delete mode 100644 src/main/java/janggi/formatter/CampFormatter.java delete mode 100644 src/main/java/janggi/formatter/PieceFormatter.java create mode 100644 src/main/java/janggi/view/format/CampFormat.java create mode 100644 src/main/java/janggi/view/format/PieceFormat.java diff --git a/src/main/java/janggi/dto/CampDto.java b/src/main/java/janggi/dto/CampDto.java index d0c3890b92..7e51adbbb6 100644 --- a/src/main/java/janggi/dto/CampDto.java +++ b/src/main/java/janggi/dto/CampDto.java @@ -1,11 +1,15 @@ package janggi.dto; import janggi.domain.piece.Camp; -import janggi.formatter.CampFormatter; +import janggi.view.format.CampFormat; -public record CampDto(String camp) { +public record CampDto( + String name, + String color +) { public static CampDto from(Camp camp) { - return new CampDto(CampFormatter.format(camp)); + CampFormat campFormat = CampFormat.from(camp); + return new CampDto(campFormat.getName(), campFormat.getColor()); } } diff --git a/src/main/java/janggi/dto/PiecePositionDto.java b/src/main/java/janggi/dto/PiecePositionDto.java index df28379146..5572c56781 100644 --- a/src/main/java/janggi/dto/PiecePositionDto.java +++ b/src/main/java/janggi/dto/PiecePositionDto.java @@ -2,7 +2,7 @@ import janggi.domain.Position; import janggi.domain.piece.Piece; -import janggi.formatter.PieceFormatter; +import janggi.view.format.PieceFormat; public record PiecePositionDto( int row, @@ -11,10 +11,11 @@ public record PiecePositionDto( CampDto camp ) { public static PiecePositionDto of(Position position, Piece piece) { + PieceFormat pieceFormat = PieceFormat.from(piece); return new PiecePositionDto( position.row(), position.column(), - PieceFormatter.format(piece), + pieceFormat.getFormat(), CampDto.from(piece.camp()) ); } diff --git a/src/main/java/janggi/exception/ExceptionMessage.java b/src/main/java/janggi/exception/ExceptionMessage.java index b6d510ce95..da9abf2af4 100644 --- a/src/main/java/janggi/exception/ExceptionMessage.java +++ b/src/main/java/janggi/exception/ExceptionMessage.java @@ -41,7 +41,11 @@ public enum ExceptionMessage { "해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다.", ELEPHANT_STRAIGHT_MOVE_DISTANCE, ELEPHANT_DIAGONAL_MOVE_DISTANCE - )); + )), + CAMP_FORMAT_NOT_FOUND("존재하지 않는 진영 형식입니다."), + PIECE_FORMAT_NOT_FOUND("존재하지 않는 기물 형식입니다."), + ; + private static final String ERROR_PREFIX = "[ERROR] "; diff --git a/src/main/java/janggi/formatter/CampFormatter.java b/src/main/java/janggi/formatter/CampFormatter.java deleted file mode 100644 index f633cf5b7f..0000000000 --- a/src/main/java/janggi/formatter/CampFormatter.java +++ /dev/null @@ -1,19 +0,0 @@ -package janggi.formatter; - -import janggi.domain.piece.Camp; - -public final class CampFormatter { - - public static final String CHO_NAME = "초"; - public static final String HAN_NAME = "한"; - - private CampFormatter() { - } - - public static String format(Camp camp) { - return switch (camp) { - case CHO -> CHO_NAME; - case HAN -> HAN_NAME; - }; - } -} diff --git a/src/main/java/janggi/formatter/PieceFormatter.java b/src/main/java/janggi/formatter/PieceFormatter.java deleted file mode 100644 index ef84cfa376..0000000000 --- a/src/main/java/janggi/formatter/PieceFormatter.java +++ /dev/null @@ -1,43 +0,0 @@ -package janggi.formatter; - -import janggi.domain.piece.Camp; -import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceRule; - -public final class PieceFormatter { - - private PieceFormatter() { - } - - public static String format(Piece piece) { - if (piece.isSameCamp(Camp.CHO)) { - return choFormat(piece.pieceRule()); - } - return hanFormat(piece.pieceRule()); - - } - - private static String choFormat(PieceRule pieceRule) { - return switch (pieceRule) { - case GENERAL -> "楚"; - case CHARIOT -> "車"; - case HORSE -> "馬"; - case CANNON -> "包"; - case GUARD -> "士"; - case ELEPHANT -> "象"; - case SOLDIER -> "卒"; - }; - } - - private static String hanFormat(PieceRule pieceRule) { - return switch (pieceRule) { - case GENERAL -> "漢"; - case CHARIOT -> "車"; - case HORSE -> "馬"; - case CANNON -> "包"; - case GUARD -> "士"; - case ELEPHANT -> "象"; - case SOLDIER -> "兵"; - }; - } -} diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index 5656ddab25..cf8f0f20c4 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -28,7 +28,7 @@ private InputView() { } public static String readElephantSettingCommand(CampDto campDto) { - System.out.println(String.format(ELEPHANT_SETTING, campDto.camp())); + System.out.println(String.format(ELEPHANT_SETTING, campDto.name())); return readLine(); } @@ -45,7 +45,7 @@ private static void validateInput(String input) { } public static List readSource(CampDto campDto) { - System.out.println(String.format(TURN, campDto.camp())); + System.out.println(String.format(TURN, campDto.name())); System.out.println(SOURCE); return Parser.parseByDelimiter(DELIMITER, readLine()); } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index 1d1fdf6fe7..313979a8f9 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -1,9 +1,7 @@ package janggi.view; -import static janggi.formatter.CampFormatter.CHO_NAME; import static java.util.stream.Collectors.joining; -import janggi.dto.CampDto; import janggi.dto.PiecePositionDto; import java.util.Arrays; import java.util.List; @@ -16,12 +14,9 @@ public final class OutputView { private static final int ROW_SIZE = 10; private static final int COLUMN_SIZE = 9; - private static final String TITLE = LINE_SEPARATOR + "[장기판]"; private static final String EMPTY_CELL = "."; - private static final String RESET = "\u001B[0m"; - private static final String CHO_COLOR = "\u001B[32m"; - private static final String HAN_COLOR = "\u001B[31m"; + private static final String RESET_COLOR = "\u001B[0m"; private static final String[] FULL_WIDTH_NUMBERS = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" @@ -41,7 +36,7 @@ public static void printBoard(List piecePositions) { private static String renderBoard(List piecePositions) { String[][] board = initializeBoard(); applyPieces(board, piecePositions); - return TITLE + LINE_SEPARATOR + renderHeader() + LINE_SEPARATOR + renderRows(board); + return LINE_SEPARATOR + "[장기판]" + LINE_SEPARATOR + renderHeader() + LINE_SEPARATOR + renderRows(board); } private static String[][] initializeBoard() { @@ -75,18 +70,7 @@ private static String renderRow(int row, String[] cells) { } private static String colorize(PiecePositionDto piecePosition) { - return colorOf(piecePosition.camp()) + piecePosition.type() + RESET; - } - - private static String colorOf(CampDto campDto) { - if (isCho(campDto.camp())) { - return CHO_COLOR; - } - return HAN_COLOR; - } - - private static boolean isCho(String camp) { - return camp.equals(CHO_NAME); + return piecePosition.camp().color() + piecePosition.type() + RESET_COLOR; } private static String fullWidthNumber(int number) { diff --git a/src/main/java/janggi/view/format/CampFormat.java b/src/main/java/janggi/view/format/CampFormat.java new file mode 100644 index 0000000000..4f6c7892fc --- /dev/null +++ b/src/main/java/janggi/view/format/CampFormat.java @@ -0,0 +1,37 @@ +package janggi.view.format; + +import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; +import java.util.Arrays; + +public enum CampFormat { + + CHO(Camp.CHO, "초", "\u001B[32m"), + HAN(Camp.HAN, "한", "\u001B[31m"), + ; + + private final Camp camp; + private final String name; + private final String color; + + CampFormat(Camp camp, String name, String color) { + this.camp = camp; + this.name = name; + this.color = color; + } + + public static CampFormat from(Camp camp) { + return Arrays.stream(CampFormat.values()) + .filter(element -> element.camp.equals(camp)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.CAMP_FORMAT_NOT_FOUND.getMessage())); + } + + public String getName() { + return name; + } + + public String getColor() { + return color; + } +} diff --git a/src/main/java/janggi/view/format/PieceFormat.java b/src/main/java/janggi/view/format/PieceFormat.java new file mode 100644 index 0000000000..1f83213623 --- /dev/null +++ b/src/main/java/janggi/view/format/PieceFormat.java @@ -0,0 +1,57 @@ +package janggi.view.format; + +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; +import java.util.Arrays; + +public enum PieceFormat { + + GENERAL_CHO(PieceRule.GENERAL, Camp.CHO, "楚"), + GENERAL_HAN(PieceRule.GENERAL, Camp.HAN, "漢"), + + SOLDIER_CHO(PieceRule.SOLDIER, Camp.CHO, "卒"), + SOLDIER_HAN(PieceRule.SOLDIER, Camp.HAN, "兵"), + + CHARIOT_CHO(PieceRule.CHARIOT, Camp.CHO, "車"), + CHARIOT_HAN(PieceRule.CHARIOT, Camp.HAN, "車"), + + HORSE_CHO(PieceRule.HORSE, Camp.CHO, "馬"), + HORSE_HAN(PieceRule.HORSE, Camp.HAN, "馬"), + + CANNON_CHO(PieceRule.CANNON, Camp.CHO, "包"), + CANNON_HAN(PieceRule.CANNON, Camp.HAN, "包"), + + GUARD_CHO(PieceRule.GUARD, Camp.CHO, "士"), + GUARD_HAN(PieceRule.GUARD, Camp.HAN, "士"), + + ELEPHANT_CHO(PieceRule.ELEPHANT, Camp.CHO, "象"), + ELEPHANT_HAN(PieceRule.ELEPHANT, Camp.HAN, "象"), + ; + + private final PieceRule pieceRule; + private final Camp camp; + private final String format; + + PieceFormat(PieceRule pieceRule, Camp camp, String format) { + this.pieceRule = pieceRule; + this.camp = camp; + this.format = format; + } + + public static PieceFormat from(Piece piece) { + return Arrays.stream(values()) + .filter(element -> element.match(piece)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.PIECE_FORMAT_NOT_FOUND.getMessage())); + } + + private boolean match(Piece piece) { + return piece.isSamePieceRule(pieceRule) && piece.isSameCamp(camp); + } + + public String getFormat() { + return format; + } +} From 024b08b9ae42dbc40d00dabaeb1cafd50508b27e Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 11:54:21 +0900 Subject: [PATCH 47/68] =?UTF-8?q?refactor:=20InitialPiecePlacement=20?= =?UTF-8?q?=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=B6=95=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/InitialPiecePlacement.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java index 9099e0c978..a70a710d79 100644 --- a/src/main/java/janggi/domain/board/InitialPiecePlacement.java +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -35,26 +35,21 @@ public enum InitialPiecePlacement { HAN_GUARD_RIGHT(9, 5, Camp.HAN, PieceRule.GUARD), HAN_CHARIOT_RIGHT(9, 8, Camp.HAN, PieceRule.CHARIOT); - private final int row; - private final int column; - private final Camp camp; - private final PieceRule pieceRule; + private final Position position; + private final Piece piece; InitialPiecePlacement(int row, int column, Camp camp, PieceRule pieceRule) { - this.row = row; - this.column = column; - this.camp = camp; - this.pieceRule = pieceRule; + this.position = new Position(row, column); + this.piece = new Piece(pieceRule, camp); } // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 public static Map init(ElephantSetting hanChoice, ElephantSetting choChoice) { Map board = new HashMap<>(); - for (InitialPiecePlacement piece : values()) { - board.put(new Position(piece.row, piece.column), new Piece(piece.pieceRule, piece.camp)); + for (InitialPiecePlacement placement : values()) { + board.put(placement.position, placement.piece); } - board.putAll(hanChoice.createElephantOrder(Camp.HAN)); board.putAll(choChoice.createElephantOrder(Camp.CHO)); return board; From 53d93d64d01b8adc517d8a4d49578cdaa5d501a4 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 12:02:20 +0900 Subject: [PATCH 48/68] =?UTF-8?q?test:=20Turn=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/janggi/domain/TurnTest.java | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/test/java/janggi/domain/TurnTest.java diff --git a/src/test/java/janggi/domain/TurnTest.java b/src/test/java/janggi/domain/TurnTest.java new file mode 100644 index 0000000000..8e40972fab --- /dev/null +++ b/src/test/java/janggi/domain/TurnTest.java @@ -0,0 +1,34 @@ +package janggi.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.piece.Camp; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +class TurnTest { + + @Test + void 초나라부터_차례를_진행한다() { + // given + Turn turn = new Turn(); + // when + Camp result = turn.currentTurn(); + // then + assertThat(result).isEqualTo(Camp.CHO); + } + + @Test + void 초나라_차례가_끝나면_한나라_차례가_된다() { + // given + Turn turn = new Turn(); + Camp firstTurn = turn.currentTurn(); + // when + turn.finishTurn(); + // then + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(firstTurn).isEqualTo(Camp.CHO); + assertSoftly.assertThat(turn.currentTurn()).isEqualTo(Camp.HAN); + }); + } +} From cd3081986ef12b68e7cf70214400ad68d06f2e36 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 14:29:09 +0900 Subject: [PATCH 49/68] =?UTF-8?q?test:=20ElephantSetting=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/ElephantSettingTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/test/java/janggi/domain/board/ElephantSettingTest.java diff --git a/src/test/java/janggi/domain/board/ElephantSettingTest.java b/src/test/java/janggi/domain/board/ElephantSettingTest.java new file mode 100644 index 0000000000..5c1f88d724 --- /dev/null +++ b/src/test/java/janggi/domain/board/ElephantSettingTest.java @@ -0,0 +1,23 @@ +package janggi.domain.board; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ElephantSettingTest { + + @ParameterizedTest + @CsvSource(value = { + "1, LEFT_ELEPHANT", + "2, RIGHT_ELEPHANT", + "3, INNER_ELEPHANT", + "4, OUTER_ELEPHANT" + }) + void 명령어와_매칭되는_상차림을_반환한다(String command, ElephantSetting expectedElephantSetting) { + // when + ElephantSetting result = ElephantSetting.findElephantSettingBy(command); + // then + assertThat(result).isEqualTo(expectedElephantSetting); + } +} From c8ebd7f377fb876050c81cafc0f22cde6c45426d Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 16:06:41 +0900 Subject: [PATCH 50/68] =?UTF-8?q?test:=20DirectionInformation=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategy/DirectionInformationTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/test/java/janggi/domain/piece/strategy/DirectionInformationTest.java diff --git a/src/test/java/janggi/domain/piece/strategy/DirectionInformationTest.java b/src/test/java/janggi/domain/piece/strategy/DirectionInformationTest.java new file mode 100644 index 0000000000..0b854bf018 --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/DirectionInformationTest.java @@ -0,0 +1,39 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class DirectionInformationTest { + + @ParameterizedTest + @CsvSource({ + "5, 1", + "-3, -1", + "0, 0" + }) + void 목적지와의_행_차이에_따라_행_방향을_결정한다(int rowDifference, int expectedResult) { + // given + DirectionInformation direction = new DirectionInformation(rowDifference, 0); + // when + int result = direction.calculateRowDirection(); + // then + assertThat(result).isEqualTo(expectedResult); + } + + @ParameterizedTest + @CsvSource({ + "7, 1", + "-4, -1", + "0, 0" + }) + void 목적지와의_열_차이에_따라_열_방향을_결정한다(int colDifference, int expectedResult) { + // given + DirectionInformation direction = new DirectionInformation(0, colDifference); + // when + int result = direction.calculateColDirection(); + // then + assertThat(result).isEqualTo(expectedResult); + } +} From 38f94258a246cbfcf0a264d77d07d6f5486af0f0 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 17:00:13 +0900 Subject: [PATCH 51/68] =?UTF-8?q?test:=20Camp=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/janggi/domain/piece/CampTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/test/java/janggi/domain/piece/CampTest.java diff --git a/src/test/java/janggi/domain/piece/CampTest.java b/src/test/java/janggi/domain/piece/CampTest.java new file mode 100644 index 0000000000..8bb9470bec --- /dev/null +++ b/src/test/java/janggi/domain/piece/CampTest.java @@ -0,0 +1,34 @@ +package janggi.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.exception.ExceptionMessage; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class CampTest { + + @ParameterizedTest + @CsvSource({ + "CHO, HAN", + "HAN, CHO" + }) + void next를_호출하면_상대_진영을_반환한다(Camp current, Camp expectedResult) { + // when + Camp next = current.next(); + // then + assertThat(next).isEqualTo(expectedResult); + } + + @ParameterizedTest + @CsvSource({ + "CHO, -1", + "HAN, 1" + }) + void 후진하려고하면_예외가_발생한다(Camp camp, int rowDirection) { + assertThatThrownBy(() -> camp.validateForwardDirection(rowDirection)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); + } +} From 6ef4515bb85b6c027aece26bfc37a8843ae62f00 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 17:00:38 +0900 Subject: [PATCH 52/68] =?UTF-8?q?test:=20HorseStrategy=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/janggi/domain/piece/strategy/HorseStrategy.java | 7 +++++-- .../janggi/domain/piece/strategy/HorseStrategyTest.java | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java index 1b9bbc0a6e..6321611341 100644 --- a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java @@ -53,8 +53,11 @@ private void validateHorseMovement(DirectionInformation directionInformation) { int absRowDifference = directionInformation.calculateAbsRowDifference(); int absColDifference = directionInformation.calculateAbsColDifference(); - if ((absRowDifference != MIN_ABS_DELTA || absColDifference != MAX_ABS_DELTA) - && (absRowDifference != MAX_ABS_DELTA || absColDifference != MIN_ABS_DELTA)) { + boolean isValidHorseMove = + (absRowDifference == MIN_ABS_DELTA && absColDifference == MAX_ABS_DELTA) + || (absRowDifference == MAX_ABS_DELTA && absColDifference == MIN_ABS_DELTA); + + if (!isValidHorseMove) { throw new IllegalArgumentException(ExceptionMessage.INVALID_HORSE_MOVE.getMessage()); } } diff --git a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java index f5a34c9e7f..c548dcef16 100644 --- a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java @@ -75,7 +75,8 @@ private static Stream createPositionsAndPath() { private static Stream createExceptionPosition() { return Stream.of( - Arguments.of(new Position(6, 4), new Position(0, 1)), + Arguments.of(new Position(6, 4), new Position(5, 1)), + Arguments.of(new Position(6, 4), new Position(4, 2)), Arguments.of(new Position(6, 4), new Position(6, 4)) ); } From e60c9e690695842d6fa2880ab4f7410a2fcfa22b Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 18:29:35 +0900 Subject: [PATCH 53/68] =?UTF-8?q?refactor:=20EmptyConditionTestBoardInitia?= =?UTF-8?q?lizer=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EmptyConditionTestBoardInitializer.java | 15 ------------- .../piece/condition/EmptyConditionTest.java | 21 +++++++++++-------- 2 files changed, 12 insertions(+), 24 deletions(-) delete mode 100644 src/test/java/janggi/domain/board/EmptyConditionTestBoardInitializer.java diff --git a/src/test/java/janggi/domain/board/EmptyConditionTestBoardInitializer.java b/src/test/java/janggi/domain/board/EmptyConditionTestBoardInitializer.java deleted file mode 100644 index 1f971a7a6a..0000000000 --- a/src/test/java/janggi/domain/board/EmptyConditionTestBoardInitializer.java +++ /dev/null @@ -1,15 +0,0 @@ -package janggi.domain.board; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceRule; -import java.util.Map; - -public class EmptyConditionTestBoardInitializer implements BoardInitializer { - - @Override - public Map initialize() { - return Map.of(new Position(0, 4), new Piece(PieceRule.CHARIOT, Camp.HAN)); - } -} diff --git a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java index 3ec4f1997f..29f0c8dd76 100644 --- a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java @@ -5,11 +5,12 @@ import janggi.domain.Position; import janggi.domain.board.Board; import janggi.domain.board.BoardInitializer; -import janggi.domain.board.EmptyConditionTestBoardInitializer; import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; import janggi.exception.ExceptionMessage; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; public class EmptyConditionTest { @@ -27,12 +28,13 @@ public class EmptyConditionTest { new Position(0, 4), new Position(0, 5) ); - Camp camp = Camp.HAN; - - BoardInitializer boardInitializer = new EmptyConditionTestBoardInitializer(); + Position blockingPosition = new Position(0, 4); + BoardInitializer boardInitializer = () -> Map.of( + blockingPosition, new Piece(PieceRule.CHARIOT, Camp.HAN) + ); Board board = new Board(boardInitializer); //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CHARIOT)) + assertThatThrownBy(() -> condition.checkPath(path, Camp.HAN, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); } @@ -47,12 +49,13 @@ public class EmptyConditionTest { new Position(0, 3), new Position(0, 4) ); - Camp camp = Camp.HAN; - - BoardInitializer boardInitializer = new EmptyConditionTestBoardInitializer(); + Position sameCampPiecePosition = new Position(0, 4); + BoardInitializer boardInitializer = () -> Map.of( + sameCampPiecePosition, new Piece(PieceRule.CHARIOT, Camp.HAN) + ); Board board = new Board(boardInitializer); //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CHARIOT)) + assertThatThrownBy(() -> condition.checkPath(path, Camp.HAN, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } From da6cd239d678851e7dc1ac4568c2de6f57c26f2f Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 20:36:10 +0900 Subject: [PATCH 54/68] =?UTF-8?q?refactor:=20OnePieceExistsCondition=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9D=B4=EB=A6=84=20=EC=B6=94?= =?UTF-8?q?=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/piece/condition/OnePieceExistsCondition.java | 4 ++-- .../domain/piece/condition/OnePieceExistsConditionTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java index 399d6dc458..8adb68b2b0 100644 --- a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java +++ b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java @@ -25,13 +25,13 @@ public void checkPath(List path, Camp camp, BoardChecker board, PieceR private int countPieceAt(BoardChecker board, PieceRule pieceRule, Position position) { if (board.hasPieceAt(position)) { - validateSamePieceRule(board, pieceRule, position); + validateDifferentPieceRule(board, pieceRule, position); return 1; } return 0; } - private void validateSamePieceRule(BoardChecker board, PieceRule pieceRule, Position position) { + private void validateDifferentPieceRule(BoardChecker board, PieceRule pieceRule, Position position) { if (board.hasSamePieceRuleAt(position, pieceRule)) { throw new IllegalArgumentException(ExceptionMessage.SAME_PIECE_TYPE_IN_PATH.getMessage()); } diff --git a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java index 3bdf46d362..3f9f833ef5 100644 --- a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java @@ -63,7 +63,7 @@ public class OnePieceExistsConditionTest { } @Test - void 이동하려는_경로에_있는_기물이_포이면_예외가_발생한다() { + void 이동하려는_경로에_있는_기물이_같은_타입이면_예외가_발생한다() { //given List path = List.of( new Position(0, 0), @@ -110,7 +110,7 @@ public class OnePieceExistsConditionTest { } @Test - void 목적지에_있는_기물이_상대_포면_예외가_발생한다() { + void 목적지에_있는_기물이_같은_타입이면_예외가_발생한다() { //given List path = List.of( new Position(0, 0), From 6325339fa343d47e8fbcbf73358737f0ba5a7cdf Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 21:04:22 +0900 Subject: [PATCH 55/68] =?UTF-8?q?refactor:=20ElephantStrategyTest=20?= =?UTF-8?q?=EB=82=B4=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../piece/strategy/ElephantStrategyTest.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java index ca1f957175..8e5a89ce7a 100644 --- a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java @@ -18,49 +18,49 @@ public class ElephantStrategyTest { private static Stream createPositionsAndPath() { return Stream.of( - Arguments.of(new Position(6, 4), new Position(3, 2), + Arguments.of(new Position(6, 4), List.of( new Position(5, 4), new Position(4, 3), new Position(3, 2) )), - Arguments.of(new Position(6, 4), new Position(4, 1), + Arguments.of(new Position(6, 4), List.of( new Position(6, 3), new Position(5, 2), new Position(4, 1) )), - Arguments.of(new Position(6, 4), new Position(8, 1), + Arguments.of(new Position(6, 4), List.of( new Position(6, 3), new Position(7, 2), new Position(8, 1) )), - Arguments.of(new Position(6, 4), new Position(9, 2), + Arguments.of(new Position(6, 4), List.of( new Position(7, 4), new Position(8, 3), new Position(9, 2) )), - Arguments.of(new Position(6, 4), new Position(9, 6), + Arguments.of(new Position(6, 4), List.of( new Position(7, 4), new Position(8, 5), new Position(9, 6) )), - Arguments.of(new Position(6, 4), new Position(8, 7), + Arguments.of(new Position(6, 4), List.of( new Position(6, 5), new Position(7, 6), new Position(8, 7) )), - Arguments.of(new Position(6, 4), new Position(4, 7), + Arguments.of(new Position(6, 4), List.of( new Position(6, 5), new Position(5, 6), new Position(4, 7) )), - Arguments.of(new Position(6, 4), new Position(3, 6), + Arguments.of(new Position(6, 4), List.of( new Position(5, 4), new Position(4, 5), @@ -71,7 +71,10 @@ private static Stream createPositionsAndPath() { @ParameterizedTest @MethodSource("createPositionsAndPath") - void 상은_직선_1칸_이동_후_대각선_2칸_이동한다(Position source, Position destination, List expectedPath) { + void 상은_직선_1칸_이동_후_대각선_2칸_이동한다(Position source, List expectedPath) { + // given + + Position destination = expectedPath.getLast(); // when List path = strategy.findPath(source, destination, Camp.HAN); // then From 69e6abbbbabb94db17813fdb47eff8ad08984cdf Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 21:48:05 +0900 Subject: [PATCH 56/68] =?UTF-8?q?refactor:=20MultiStepStraightStrategyTest?= =?UTF-8?q?,=20SingleStepStraightStrategyTest=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=AA=85=20=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MultiStepStraightStrategyTest.java | 21 ++++++++++--------- .../SingleStepStraightStrategyTest.java | 4 ++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java index 2500243906..1b1923b1fe 100644 --- a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java @@ -19,7 +19,7 @@ public class MultiStepStraightStrategyTest { private static Stream createPositionsAndPath() { return Stream.of( - Arguments.of(new Position(0, 0), new Position(9, 0), 9, + Arguments.of(new Position(0, 0), List.of( new Position(1, 0), new Position(2, 0), @@ -31,7 +31,7 @@ private static Stream createPositionsAndPath() { new Position(8, 0), new Position(9, 0) )), - Arguments.of(new Position(0, 0), new Position(0, 8), 8, + Arguments.of(new Position(0, 0), List.of( new Position(0, 1), new Position(0, 2), @@ -43,7 +43,7 @@ private static Stream createPositionsAndPath() { new Position(0, 8) ) ), - Arguments.of(new Position(0, 8), new Position(0, 0), 8, + Arguments.of(new Position(0, 8), List.of( new Position(0, 7), new Position(0, 6), @@ -55,7 +55,7 @@ private static Stream createPositionsAndPath() { new Position(0, 0) ) ), - Arguments.of(new Position(9, 0), new Position(0, 0), 9, + Arguments.of(new Position(9, 0), List.of( new Position(8, 0), new Position(7, 0), @@ -73,24 +73,25 @@ private static Stream createPositionsAndPath() { @ParameterizedTest @MethodSource("createPositionsAndPath") - void 차와_포는_한_방향으로만_1칸_이상_이동_할_수_있다(Position source, Position destination, int size, List expectedPath) { - List path = strategy.findPath(source, destination, Camp.HAN); - + void 직선으로_1칸_이상_이동하는_경로를_계산한다(Position source, List expectedPath) { + // when + List path = strategy.findPath(source, expectedPath.getLast(), Camp.HAN); + // then SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(size); + assertSoftly.assertThat(path).hasSize(expectedPath.size()); assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); }); } @Test - void 차와_포는_한_방향으로_이동하지_않으면_예외가_발생한다() { + void 직선_이동이_아닌_경우_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(5, 5), Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); } @Test - void 차와_포는_제자리_이동_시_예외가_발생한다1() { + void 제자리_이동이면_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(0, 0), Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.PIECE_MUST_MOVE.getMessage()); diff --git a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java index c74c09456c..99ef8d6cb8 100644 --- a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java @@ -28,7 +28,7 @@ private static Stream successMovePositions() { @ParameterizedTest @MethodSource("successMovePositions") - void 궁과_사는_상하좌우_1칸_이동한다(Position source, Position destination) { + void 직선_방향으로_1칸만_이동한다(Position source, Position destination) { //when List path = strategy.findPath(source, destination, Camp.HAN); //then @@ -39,7 +39,7 @@ private static Stream successMovePositions() { } @Test - void 궁과_사는_1칸_이동이_아니면_예외가_발생한다() { + void 직선_방향으로_1칸만_이동하지_않으면_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.HAN)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); From 12a3328f89e8f2cd88066d38cc34aa3e23c81235 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Mon, 30 Mar 2026 22:07:09 +0900 Subject: [PATCH 57/68] =?UTF-8?q?refactor:=20PieceTest=20=EB=82=B4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/janggi/domain/piece/PieceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/janggi/domain/piece/PieceTest.java b/src/test/java/janggi/domain/piece/PieceTest.java index b466422bb4..db4cac882f 100644 --- a/src/test/java/janggi/domain/piece/PieceTest.java +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -16,7 +16,7 @@ class PieceTest { @Test - void 같은_룰이_적용되는_기물인지_확인한다() { + void 같은_전략_및_이동_조건이_적용되는_기물인지_확인한다() { // given Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); // when From 70cab4307964407bcb86cff4e01fe15e3928008e Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Tue, 31 Mar 2026 14:24:55 +0900 Subject: [PATCH 58/68] =?UTF-8?q?refactor:=20GameRule=20=EC=83=81=EC=88=98?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/constant/GameRule.java | 20 ---------- src/main/java/janggi/domain/Position.java | 12 +++--- .../condition/OnePieceExistsCondition.java | 6 +-- .../piece/strategy/ElephantStrategy.java | 8 +++- .../domain/piece/strategy/HorseStrategy.java | 7 +++- .../strategy/SingleStepStraightStrategy.java | 6 +-- .../piece/strategy/SoldierStrategy.java | 6 +-- .../janggi/exception/ExceptionMessage.java | 39 +++++-------------- src/test/java/janggi/domain/PositionTest.java | 8 +++- .../java/janggi/domain/piece/PieceTest.java | 23 +++++++---- .../OnePieceExistsConditionTest.java | 6 ++- .../piece/strategy/ElephantStrategyTest.java | 5 ++- .../piece/strategy/HorseStrategyTest.java | 5 ++- .../SingleStepStraightStrategyTest.java | 6 ++- .../piece/strategy/SoldierStrategyTest.java | 6 ++- 15 files changed, 78 insertions(+), 85 deletions(-) delete mode 100644 src/main/java/janggi/constant/GameRule.java diff --git a/src/main/java/janggi/constant/GameRule.java b/src/main/java/janggi/constant/GameRule.java deleted file mode 100644 index b2e33fed03..0000000000 --- a/src/main/java/janggi/constant/GameRule.java +++ /dev/null @@ -1,20 +0,0 @@ -package janggi.constant; - -public final class GameRule { - - public static final int MIN_POSITION_INDEX = 0; - public static final int MAX_ROW_INDEX = 9; - public static final int MAX_COLUMN_INDEX = 8; - - public static final int SINGLE_STEP_DISTANCE = 1; - public static final int PASS_PIECE_COUNT = 1; - - public static final int HORSE_STRAIGHT_MOVE_DISTANCE = 1; - public static final int HORSE_DIAGONAL_MOVE_DISTANCE = 1; - - public static final int ELEPHANT_STRAIGHT_MOVE_DISTANCE = 1; - public static final int ELEPHANT_DIAGONAL_MOVE_DISTANCE = 2; - - private GameRule() { - } -} diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index 07a25a3190..07e192d59a 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -1,13 +1,13 @@ package janggi.domain; -import static janggi.constant.GameRule.MAX_COLUMN_INDEX; -import static janggi.constant.GameRule.MAX_ROW_INDEX; -import static janggi.constant.GameRule.MIN_POSITION_INDEX; - import janggi.exception.ExceptionMessage; public record Position(int row, int column) { + private static final int MIN_POSITION_INDEX = 0; + private static final int MAX_ROW_INDEX = 9; + private static final int MAX_COLUMN_INDEX = 8; + public Position { validateRow(row); validateColumn(column); @@ -15,13 +15,13 @@ public record Position(int row, int column) { private void validateRow(int row) { if (row < MIN_POSITION_INDEX || MAX_ROW_INDEX < row) { - throw new IllegalArgumentException(ExceptionMessage.ROW_OUT_OF_RANGE.getMessage()); + throw new IllegalArgumentException(ExceptionMessage.ROW_OUT_OF_RANGE.getMessage(MIN_POSITION_INDEX, MAX_ROW_INDEX)); } } private void validateColumn(int column) { if (column < MIN_POSITION_INDEX || MAX_COLUMN_INDEX < column) { - throw new IllegalArgumentException(ExceptionMessage.COLUMN_OUT_OF_RANGE.getMessage()); + throw new IllegalArgumentException(ExceptionMessage.COLUMN_OUT_OF_RANGE.getMessage(MIN_POSITION_INDEX, MAX_COLUMN_INDEX)); } } diff --git a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java index 8adb68b2b0..7fde51d0b9 100644 --- a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java +++ b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java @@ -1,7 +1,5 @@ package janggi.domain.piece.condition; -import static janggi.constant.GameRule.PASS_PIECE_COUNT; - import janggi.domain.Position; import janggi.domain.board.BoardChecker; import janggi.domain.piece.Camp; @@ -11,6 +9,8 @@ public class OnePieceExistsCondition implements MoveCondition { + private static final int PASS_PIECE_COUNT = 1; + @Override public void checkPath(List path, Camp camp, BoardChecker board, PieceRule pieceRule) { int countOfPiece = 0; @@ -39,7 +39,7 @@ private void validateDifferentPieceRule(BoardChecker board, PieceRule pieceRule, private void validateExactPieceCount(int countOfPiece) { if (countOfPiece != PASS_PIECE_COUNT) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); + throw new IllegalArgumentException(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(PASS_PIECE_COUNT)); } } diff --git a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java index 19b3cffe5c..73d2205aa2 100644 --- a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java @@ -8,6 +8,9 @@ public class ElephantStrategy implements MoveStrategy { + private static final int ELEPHANT_STRAIGHT_MOVE_DISTANCE = 1; + private static final int ELEPHANT_DIAGONAL_MOVE_DISTANCE = 2; + private static final int DIAGONAL_COUNT = 2; private static final int MIN_ABS_DELTA = 2; private static final int MAX_ABS_DELTA = 3; @@ -29,7 +32,10 @@ private void validateElephantMovement(DirectionInformation directionInfo) { || directionInfo.calculateAbsColDifference() != MAX_ABS_DELTA) && (directionInfo.calculateAbsRowDifference() != MAX_ABS_DELTA || directionInfo.calculateAbsColDifference() != MIN_ABS_DELTA)) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage()); + throw new IllegalArgumentException(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage( + ELEPHANT_STRAIGHT_MOVE_DISTANCE, + ELEPHANT_DIAGONAL_MOVE_DISTANCE + )); } } diff --git a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java index 6321611341..497cdde2f6 100644 --- a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java @@ -8,6 +8,8 @@ public class HorseStrategy implements MoveStrategy { + private static final int HORSE_STRAIGHT_MOVE_DISTANCE = 1; + private static final int HORSE_DIAGONAL_MOVE_DISTANCE = 1; private static final int MIN_ABS_DELTA = 1; private static final int MAX_ABS_DELTA = 2; @@ -58,7 +60,10 @@ private void validateHorseMovement(DirectionInformation directionInformation) { || (absRowDifference == MAX_ABS_DELTA && absColDifference == MIN_ABS_DELTA); if (!isValidHorseMove) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_HORSE_MOVE.getMessage()); + throw new IllegalArgumentException(ExceptionMessage.INVALID_HORSE_MOVE.getMessage( + HORSE_STRAIGHT_MOVE_DISTANCE, + HORSE_DIAGONAL_MOVE_DISTANCE + )); } } } diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java index f71513f681..463a69bf69 100644 --- a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java @@ -1,7 +1,5 @@ package janggi.domain.piece.strategy; -import static janggi.constant.GameRule.SINGLE_STEP_DISTANCE; - import janggi.domain.Position; import janggi.domain.piece.Camp; import janggi.exception.ExceptionMessage; @@ -9,6 +7,8 @@ public class SingleStepStraightStrategy implements MoveStrategy { + private static final int SINGLE_STEP_DISTANCE = 1; + @Override public List findPath(Position source, Position destination, Camp camp) { DirectionInformation directionInformation = new DirectionInformation(source, destination); @@ -20,7 +20,7 @@ public List findPath(Position source, Position destination, Camp camp) private void validateSingleStepMovement(DirectionInformation directionInformation) { if (directionInformation.calculateAbsRowDifference() + directionInformation.calculateAbsColDifference() != SINGLE_STEP_DISTANCE) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); + throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } } diff --git a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java index ac250b5eb4..a6a0f5cfa7 100644 --- a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java @@ -1,7 +1,5 @@ package janggi.domain.piece.strategy; -import static janggi.constant.GameRule.SINGLE_STEP_DISTANCE; - import janggi.domain.Position; import janggi.domain.piece.Camp; import janggi.exception.ExceptionMessage; @@ -9,6 +7,8 @@ public class SoldierStrategy implements MoveStrategy { + private static final int SINGLE_STEP_DISTANCE = 1; + @Override public List findPath(Position source, Position destination, Camp camp) { DirectionInformation directionInformation = new DirectionInformation(source, destination); @@ -22,7 +22,7 @@ public List findPath(Position source, Position destination, Camp camp) private void validateSoldierMovement(DirectionInformation directionInformation) { if (directionInformation.calculateAbsRowDifference() + directionInformation.calculateAbsColDifference() != SINGLE_STEP_DISTANCE) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); + throw new IllegalArgumentException(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } } diff --git a/src/main/java/janggi/exception/ExceptionMessage.java b/src/main/java/janggi/exception/ExceptionMessage.java index da9abf2af4..a3e5e5f177 100644 --- a/src/main/java/janggi/exception/ExceptionMessage.java +++ b/src/main/java/janggi/exception/ExceptionMessage.java @@ -1,22 +1,12 @@ package janggi.exception; -import static janggi.constant.GameRule.ELEPHANT_DIAGONAL_MOVE_DISTANCE; -import static janggi.constant.GameRule.ELEPHANT_STRAIGHT_MOVE_DISTANCE; -import static janggi.constant.GameRule.HORSE_DIAGONAL_MOVE_DISTANCE; -import static janggi.constant.GameRule.HORSE_STRAIGHT_MOVE_DISTANCE; -import static janggi.constant.GameRule.MAX_COLUMN_INDEX; -import static janggi.constant.GameRule.MAX_ROW_INDEX; -import static janggi.constant.GameRule.MIN_POSITION_INDEX; -import static janggi.constant.GameRule.PASS_PIECE_COUNT; -import static janggi.constant.GameRule.SINGLE_STEP_DISTANCE; - public enum ExceptionMessage { ONLY_NUMBERS_ALLOWED("숫자만 입력 가능합니다"), INVALID_INPUT_FORMAT("잘못된 입력 형식입니다."), INVALID_ELEPHANT_SETTING("존재하지 않는 상차림 입니다."), - ROW_OUT_OF_RANGE(String.format("행은 %d행 이상 %d행 이하여야 합니다.", MIN_POSITION_INDEX, MAX_ROW_INDEX)), - COLUMN_OUT_OF_RANGE(String.format("열은 %d열 이상 %d열 이하여야 합니다.", MIN_POSITION_INDEX, MAX_COLUMN_INDEX)), + ROW_OUT_OF_RANGE("행은 %d행 이상 %d행 이하여야 합니다."), + COLUMN_OUT_OF_RANGE("열은 %d열 이상 %d열 이하여야 합니다."), SOURCE_NOT_EXISTS("출발지에 기물이 존재하지 않습니다."), INVALID_CAMP_PIECE("상대 진영의 기물은 이동할 수 없습니다."), PATH_NOT_EMPTY("경로 상에 기물이 존재합니다."), @@ -25,23 +15,12 @@ public enum ExceptionMessage { SAME_PIECE_TYPE_AT_DESTINATION("목적지에 같은 종류의 기물이 존재합니다."), ONLY_STRAIGHT_MOVE_ALLOWED("해당 기물은 직선 이동만 가능합니다."), PIECE_MUST_MOVE("기물은 반드시 이동해야 합니다."), - INVALID_SINGLE_STEP_STRAIGHT_MOVE(String.format( - "해당 기물은 직선으로 %d칸 이동해야 합니다.", - SINGLE_STEP_DISTANCE - )), + INVALID_SINGLE_STEP_STRAIGHT_MOVE("해당 기물은 직선으로 %d칸 이동해야 합니다."), INVALID_BACKWARD_MOVEMENT("해당 기물은 후진할 수 없습니다."), - INVALID_SOLDIER_MOVE(String.format("해당 기물은 %d칸만 이동할 수 있습니다.", SINGLE_STEP_DISTANCE)), - INVALID_JUMPED_PIECE_COUNT(String.format("해당 기물은 정확히 %d개의 기물만 뛰어넘을 수 있습니다.", PASS_PIECE_COUNT)), - INVALID_HORSE_MOVE(String.format( - "해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다.", - HORSE_STRAIGHT_MOVE_DISTANCE, - HORSE_DIAGONAL_MOVE_DISTANCE - )), - INVALID_ELEPHANT_MOVE(String.format( - "해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다.", - ELEPHANT_STRAIGHT_MOVE_DISTANCE, - ELEPHANT_DIAGONAL_MOVE_DISTANCE - )), + INVALID_SOLDIER_MOVE("해당 기물은 %d칸만 이동할 수 있습니다."), + INVALID_JUMPED_PIECE_COUNT("해당 기물은 정확히 %d개의 기물만 뛰어넘을 수 있습니다."), + INVALID_HORSE_MOVE("해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다."), + INVALID_ELEPHANT_MOVE("해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다."), CAMP_FORMAT_NOT_FOUND("존재하지 않는 진영 형식입니다."), PIECE_FORMAT_NOT_FOUND("존재하지 않는 기물 형식입니다."), ; @@ -55,7 +34,7 @@ public enum ExceptionMessage { this.message = ERROR_PREFIX + message; } - public String getMessage() { - return message; + public String getMessage(Object... args) { + return String.format(message, args); } } diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java index 7ddfa3930b..e221ad18d0 100644 --- a/src/test/java/janggi/domain/PositionTest.java +++ b/src/test/java/janggi/domain/PositionTest.java @@ -10,6 +10,10 @@ public class PositionTest { + private static final int MIN_POSITION_INDEX = 0; + private static final int MAX_ROW_INDEX = 9; + private static final int MAX_COLUMN_INDEX = 8; + @ParameterizedTest @CsvSource(value = { "-1,8", @@ -18,7 +22,7 @@ public class PositionTest { void 포지션의_행이_0부터_9행까지가_아닐_경우_예외가_발생한다(int row, int col) { Assertions.assertThatThrownBy(() -> new Position(row, col)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.ROW_OUT_OF_RANGE.getMessage()); + .hasMessage(ExceptionMessage.ROW_OUT_OF_RANGE.getMessage(MIN_POSITION_INDEX, MAX_ROW_INDEX)); } @ParameterizedTest @@ -29,7 +33,7 @@ public class PositionTest { void 포지션의_열이_0부터_8열까지가_아닐_경우_예외가_발생한다(int row, int col) { Assertions.assertThatThrownBy(() -> new Position(row, col)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.COLUMN_OUT_OF_RANGE.getMessage()); + .hasMessage(ExceptionMessage.COLUMN_OUT_OF_RANGE.getMessage(MIN_POSITION_INDEX, MAX_COLUMN_INDEX)); } @Test diff --git a/src/test/java/janggi/domain/piece/PieceTest.java b/src/test/java/janggi/domain/piece/PieceTest.java index db4cac882f..512462f45d 100644 --- a/src/test/java/janggi/domain/piece/PieceTest.java +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -15,6 +15,13 @@ class PieceTest { + private static final int SINGLE_STEP_DISTANCE = 1; + private static final int PASS_PIECE_COUNT = 1; + private static final int HORSE_STRAIGHT_MOVE_DISTANCE = 1; + private static final int HORSE_DIAGONAL_MOVE_DISTANCE = 1; + private static final int ELEPHANT_STRAIGHT_MOVE_DISTANCE = 1; + private static final int ELEPHANT_DIAGONAL_MOVE_DISTANCE = 2; + @Test void 같은_전략_및_이동_조건이_적용되는_기물인지_확인한다() { // given @@ -58,7 +65,7 @@ class General { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(1, 4), new Position(3, 4), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } @@ -85,7 +92,7 @@ class Guard { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 3), new Position(0, 5), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } @@ -126,7 +133,7 @@ class Horse { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 1), new Position(0, 7), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage(HORSE_STRAIGHT_MOVE_DISTANCE, HORSE_DIAGONAL_MOVE_DISTANCE)); } } @@ -153,7 +160,7 @@ class Elephant { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 6), new Position(3, 3), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage(ELEPHANT_STRAIGHT_MOVE_DISTANCE, ELEPHANT_DIAGONAL_MOVE_DISTANCE)); } } @@ -169,7 +176,7 @@ class Cannon { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(2, 1), new Position(8, 1), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(PASS_PIECE_COUNT)); } @Test @@ -184,7 +191,7 @@ class Cannon { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(2, 1), new Position(8, 1), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(PASS_PIECE_COUNT)); } @Test @@ -279,7 +286,7 @@ class SoldierCho { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(3, 0), new Position(5, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } @@ -318,7 +325,7 @@ class SoldierHan { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(6, 0), new Position(1, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } } diff --git a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java index 3f9f833ef5..dab441bb18 100644 --- a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java @@ -15,6 +15,8 @@ public class OnePieceExistsConditionTest { + private static final int PASS_PIECE_COUNT = 1; + MoveCondition condition = new OnePieceExistsCondition(); @Test @@ -35,7 +37,7 @@ public class OnePieceExistsConditionTest { //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(PASS_PIECE_COUNT)); } @Test @@ -59,7 +61,7 @@ public class OnePieceExistsConditionTest { //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage()); + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(PASS_PIECE_COUNT)); } @Test diff --git a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java index 8e5a89ce7a..9040e22639 100644 --- a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java @@ -14,6 +14,9 @@ public class ElephantStrategyTest { + private static final int ELEPHANT_STRAIGHT_MOVE_DISTANCE = 1; + private static final int ELEPHANT_DIAGONAL_MOVE_DISTANCE = 2; + private final MoveStrategy strategy = new ElephantStrategy(); private static Stream createPositionsAndPath() { @@ -96,6 +99,6 @@ private static Stream createExceptionPosition() { void 상은_행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage(ELEPHANT_STRAIGHT_MOVE_DISTANCE, ELEPHANT_DIAGONAL_MOVE_DISTANCE)); } } diff --git a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java index c548dcef16..44ddb014b8 100644 --- a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java @@ -14,6 +14,9 @@ public class HorseStrategyTest { + private static final int HORSE_STRAIGHT_MOVE_DISTANCE = 1; + private static final int HORSE_DIAGONAL_MOVE_DISTANCE = 1; + private final MoveStrategy strategy = new HorseStrategy(); private static Stream createPositionsAndPath() { @@ -86,6 +89,6 @@ private static Stream createExceptionPosition() { void 마는_행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage(HORSE_STRAIGHT_MOVE_DISTANCE, HORSE_DIAGONAL_MOVE_DISTANCE)); } } diff --git a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java index 99ef8d6cb8..1ce5c5f000 100644 --- a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java @@ -15,6 +15,8 @@ class SingleStepStraightStrategyTest { + private static final int SINGLE_STEP_DISTANCE = 1; + private final MoveStrategy strategy = new SingleStepStraightStrategy(); private static Stream successMovePositions() { @@ -39,9 +41,9 @@ private static Stream successMovePositions() { } @Test - void 직선_방향으로_1칸만_이동하지_않으면_예외가_발생한다() { + void 직선_방향으로_1칸_만_이동하지_않으면_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.HAN)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } diff --git a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java index 22b2d27ff1..240878193c 100644 --- a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java @@ -17,6 +17,8 @@ class SoldierStrategyTest { + private static final int SINGLE_STEP_DISTANCE = 1; + private final MoveStrategy strategy = new SoldierStrategy(); @DisplayName("병 행마법 테스트") @@ -55,7 +57,7 @@ private static Stream exceptionMovePositions() { void 병은_1칸_이동이_아니면_예외가_발생한다(Position source, Position destination) { assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.HAN)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } @Test @@ -86,7 +88,7 @@ class Zol { void 졸은_1칸_이동이_아니면_예외가_발생한다() { assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.CHO)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage()); + .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } @Test From 423ea0fefb35ac6296d5d4382d3fb0d03056f8f8 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Tue, 31 Mar 2026 17:23:09 +0900 Subject: [PATCH 59/68] =?UTF-8?q?refactor:=20=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=8B=9C=20=EC=83=81=EC=B0=A8?= =?UTF-8?q?=EB=A6=BC=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiGame.java | 41 ++++++-------- src/main/java/janggi/domain/Position.java | 13 +++++ .../domain/board/ElephantFormation.java | 42 ++++++++++++++ .../janggi/domain/board/ElephantSetUp.java | 25 +++++++++ .../janggi/domain/board/ElephantSetting.java | 55 ------------------- .../domain/board/InitialPiecePlacement.java | 13 +---- .../board/StandardBoardInitializer.java | 21 ++++--- .../janggi/exception/ExceptionMessage.java | 2 +- src/main/java/janggi/view/InputView.java | 42 ++++++-------- .../view/format/ElephantSetUpFormat.java | 43 +++++++++++++++ .../board/StandardBoardInitializerTest.java | 6 +- .../format/ElephantSetUpFormatTest.java} | 10 ++-- 12 files changed, 183 insertions(+), 130 deletions(-) create mode 100644 src/main/java/janggi/domain/board/ElephantFormation.java create mode 100644 src/main/java/janggi/domain/board/ElephantSetUp.java delete mode 100644 src/main/java/janggi/domain/board/ElephantSetting.java create mode 100644 src/main/java/janggi/view/format/ElephantSetUpFormat.java rename src/test/java/janggi/{domain/board/ElephantSettingTest.java => view/format/ElephantSetUpFormatTest.java} (61%) diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index f54433a632..ba0e5233e1 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -4,13 +4,13 @@ import janggi.domain.Turn; import janggi.domain.board.Board; import janggi.domain.board.BoardInitializer; -import janggi.domain.board.ElephantSetting; +import janggi.domain.board.ElephantFormation; +import janggi.domain.board.ElephantSetUp; import janggi.domain.board.StandardBoardInitializer; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.dto.CampDto; import janggi.dto.PiecePositionDto; -import janggi.exception.ExceptionMessage; import janggi.util.RetryHandler; import janggi.view.InputView; import janggi.view.OutputView; @@ -26,18 +26,21 @@ public void run() { } private Board createBoard() { - ElephantSetting hanElephantSetting = RetryHandler.retryOnInvalidInput( - () -> ElephantSetting.findElephantSettingBy( - InputView.readElephantSettingCommand(CampDto.from(Camp.HAN)))); - - ElephantSetting choElephantSetting = RetryHandler.retryOnInvalidInput( - () -> ElephantSetting.findElephantSettingBy( - InputView.readElephantSettingCommand(CampDto.from(Camp.CHO)))); - - BoardInitializer initializer = new StandardBoardInitializer(hanElephantSetting, choElephantSetting); + ElephantFormation hanElephantFormation = readElephantFormation(Camp.HAN); + ElephantFormation choElephantFormation = readElephantFormation(Camp.CHO); + BoardInitializer initializer = new StandardBoardInitializer(hanElephantFormation, choElephantFormation); return new Board(initializer); } + private static ElephantFormation readElephantFormation(Camp camp) { + return RetryHandler.retryOnInvalidInput(() -> { + ElephantSetUp elephantSetUp = InputView.readElephantSettingCommand(CampDto.from(camp)) + .getElephantSetUp(); + return new ElephantFormation(camp, elephantSetUp); + } + ); + } + private List toPiecePositions(Map boardState) { return boardState.entrySet().stream() .map(entry -> PiecePositionDto.of(entry.getKey(), entry.getValue())) @@ -55,27 +58,17 @@ private void play(Board board) { private void playTurn(Board board, Turn turn) { Camp camp = turn.currentTurn(); Position source = readSource(board, camp); - Position destination = RetryHandler.retryOnInvalidInput(() -> toPosition(InputView.readDestination())); + Position destination = RetryHandler.retryOnInvalidInput(() -> Position.from(InputView.readDestination())); board.movePiece(source, destination, camp); turn.finishTurn(); } private Position readSource(Board board, Camp camp) { return RetryHandler.retryOnInvalidInput(() -> { - Position source = toPosition(InputView.readSource(CampDto.from(camp))); + List rawPosition = InputView.readSource(CampDto.from(camp)); + Position source = Position.from(rawPosition); board.validateCampTurn(source, camp); return source; }); } - - private Position toPosition(List rawPosition) { - validatePositionSize(rawPosition); - return new Position(rawPosition.get(0), rawPosition.get(1)); - } - - private void validatePositionSize(List rawPosition) { - if (rawPosition.size() != 2) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT_FORMAT.getMessage()); - } - } } diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index 07e192d59a..35e8b33d2f 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -1,13 +1,26 @@ package janggi.domain; import janggi.exception.ExceptionMessage; +import java.util.List; public record Position(int row, int column) { + private static final int POSITION_SIZE = 2; private static final int MIN_POSITION_INDEX = 0; private static final int MAX_ROW_INDEX = 9; private static final int MAX_COLUMN_INDEX = 8; + public static Position from(List rawPosition) { + validatePositionSize(rawPosition); + return new Position(rawPosition.get(0), rawPosition.get(1)); + } + + private static void validatePositionSize(List position) { + if (position.size() != POSITION_SIZE) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT_FORMAT.getMessage()); + } + } + public Position { validateRow(row); validateColumn(column); diff --git a/src/main/java/janggi/domain/board/ElephantFormation.java b/src/main/java/janggi/domain/board/ElephantFormation.java new file mode 100644 index 0000000000..86fb4df30f --- /dev/null +++ b/src/main/java/janggi/domain/board/ElephantFormation.java @@ -0,0 +1,42 @@ +package janggi.domain.board; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ElephantFormation { + + private static final List SETTING_COLS = List.of(1, 2, 6, 7); + + private final Camp camp; + private final ElephantSetUp setUp; + + public ElephantFormation(Camp camp, ElephantSetUp setUp) { + this.camp = camp; + this.setUp = setUp; + } + + public Map placeElephantSetUpPieces() { + if (camp == Camp.HAN) { + return createByCamp(SETTING_COLS); + } + return createByCamp(SETTING_COLS.reversed()); + } + + private Map createByCamp(List settingCols) { + Map map = new HashMap<>(); + + for (int i = 0; i < settingCols.size(); i++) { + map.put( + new Position(camp.getStartRowPosition(), settingCols.get(i)), + new Piece(setUp.getPieceRules().get(i), camp) + ); + } + return map; + } + + +} diff --git a/src/main/java/janggi/domain/board/ElephantSetUp.java b/src/main/java/janggi/domain/board/ElephantSetUp.java new file mode 100644 index 0000000000..feb89ad378 --- /dev/null +++ b/src/main/java/janggi/domain/board/ElephantSetUp.java @@ -0,0 +1,25 @@ +package janggi.domain.board; + +import static janggi.domain.piece.PieceRule.ELEPHANT; +import static janggi.domain.piece.PieceRule.HORSE; + +import janggi.domain.piece.PieceRule; +import java.util.List; + +public enum ElephantSetUp { + + LEFT_ELEPHANT(List.of(ELEPHANT, HORSE, ELEPHANT, HORSE)), + RIGHT_ELEPHANT(List.of(HORSE, ELEPHANT, HORSE, ELEPHANT)), + INNER_ELEPHANT(List.of(HORSE, ELEPHANT, ELEPHANT, HORSE)), + OUTER_ELEPHANT(List.of(ELEPHANT, HORSE, HORSE, ELEPHANT)); + + private final List pieceRules; + + ElephantSetUp(List pieceRules) { + this.pieceRules = pieceRules; + } + + public List getPieceRules() { + return pieceRules; + } +} diff --git a/src/main/java/janggi/domain/board/ElephantSetting.java b/src/main/java/janggi/domain/board/ElephantSetting.java deleted file mode 100644 index 4007c4fd94..0000000000 --- a/src/main/java/janggi/domain/board/ElephantSetting.java +++ /dev/null @@ -1,55 +0,0 @@ -package janggi.domain.board; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceRule; -import janggi.exception.ExceptionMessage; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public enum ElephantSetting { - - LEFT_ELEPHANT("1", List.of(PieceRule.ELEPHANT, PieceRule.HORSE, PieceRule.ELEPHANT, PieceRule.HORSE)), - RIGHT_ELEPHANT("2", List.of(PieceRule.HORSE, PieceRule.ELEPHANT, PieceRule.HORSE, PieceRule.ELEPHANT)), - INNER_ELEPHANT("3", List.of(PieceRule.HORSE, PieceRule.ELEPHANT, PieceRule.ELEPHANT, PieceRule.HORSE)), - OUTER_ELEPHANT("4", List.of(PieceRule.ELEPHANT, PieceRule.HORSE, PieceRule.HORSE, PieceRule.ELEPHANT)); - - private static final List SETTING_COLS = List.of(1, 2, 6, 7); - - private final String command; - private final List elephantOrder; - - ElephantSetting(String command, List elephantOrder) { - this.command = command; - this.elephantOrder = elephantOrder; - } - - public static ElephantSetting findElephantSettingBy(String command) { - return Arrays.stream(values()) - .filter(element -> element.command.equals(command)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INVALID_ELEPHANT_SETTING.getMessage())); - } - - public Map createElephantOrder(Camp camp) { - if (camp == Camp.HAN) { - return createByCamp(camp, SETTING_COLS); - } - return createByCamp(camp, SETTING_COLS.reversed()); - } - - private Map createByCamp(Camp camp, List settingCols) { - Map map = new HashMap<>(); - - for (int i = 0; i < settingCols.size(); i++) { - map.put( - new Position(camp.getStartRowPosition(), settingCols.get(i)), - new Piece(elephantOrder.get(i), camp) - ); - } - return map; - } -} diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java index a70a710d79..84b56ea6a0 100644 --- a/src/main/java/janggi/domain/board/InitialPiecePlacement.java +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -4,7 +4,6 @@ import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; -import java.util.HashMap; import java.util.Map; public enum InitialPiecePlacement { @@ -43,15 +42,7 @@ public enum InitialPiecePlacement { this.piece = new Piece(pieceRule, camp); } - // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 - public static Map init(ElephantSetting hanChoice, ElephantSetting choChoice) { - Map board = new HashMap<>(); - - for (InitialPiecePlacement placement : values()) { - board.put(placement.position, placement.piece); - } - board.putAll(hanChoice.createElephantOrder(Camp.HAN)); - board.putAll(choChoice.createElephantOrder(Camp.CHO)); - return board; + public void placeOn(Map board) { + board.put(position, piece); } } diff --git a/src/main/java/janggi/domain/board/StandardBoardInitializer.java b/src/main/java/janggi/domain/board/StandardBoardInitializer.java index 17639128d9..9d3deed12a 100644 --- a/src/main/java/janggi/domain/board/StandardBoardInitializer.java +++ b/src/main/java/janggi/domain/board/StandardBoardInitializer.java @@ -2,21 +2,28 @@ import janggi.domain.Position; import janggi.domain.piece.Piece; +import java.util.HashMap; import java.util.Map; public class StandardBoardInitializer implements BoardInitializer { - private final ElephantSetting hanElephantSetting; - private final ElephantSetting choElephantSetting; + private final ElephantFormation hanFormation; + private final ElephantFormation choFormation; - // TODO : 둘 다 같은 나라의 ElephantSetting 들어와도 컴파일 에러 X -> 타입 강제 고려하기 - public StandardBoardInitializer(ElephantSetting hanElephantSetting, ElephantSetting choElephantSetting) { - this.hanElephantSetting = hanElephantSetting; - this.choElephantSetting = choElephantSetting; + public StandardBoardInitializer(ElephantFormation hanFormation, ElephantFormation choFormation) { + this.hanFormation = hanFormation; + this.choFormation = choFormation; } @Override public Map initialize() { - return InitialPiecePlacement.init(hanElephantSetting, choElephantSetting); + Map board = new HashMap<>(); + + for (InitialPiecePlacement placement : InitialPiecePlacement.values()) { + placement.placeOn(board); + } + board.putAll(hanFormation.placeElephantSetUpPieces()); + board.putAll(choFormation.placeElephantSetUpPieces()); + return board; } } diff --git a/src/main/java/janggi/exception/ExceptionMessage.java b/src/main/java/janggi/exception/ExceptionMessage.java index a3e5e5f177..ff2247d2ca 100644 --- a/src/main/java/janggi/exception/ExceptionMessage.java +++ b/src/main/java/janggi/exception/ExceptionMessage.java @@ -4,7 +4,7 @@ public enum ExceptionMessage { ONLY_NUMBERS_ALLOWED("숫자만 입력 가능합니다"), INVALID_INPUT_FORMAT("잘못된 입력 형식입니다."), - INVALID_ELEPHANT_SETTING("존재하지 않는 상차림 입니다."), + INVALID_ELEPHANT_SET_UP_FORMAT("존재하지 않는 상차림 입니다."), ROW_OUT_OF_RANGE("행은 %d행 이상 %d행 이하여야 합니다."), COLUMN_OUT_OF_RANGE("열은 %d열 이상 %d열 이하여야 합니다."), SOURCE_NOT_EXISTS("출발지에 기물이 존재하지 않습니다."), diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index cf8f0f20c4..82bd3e7c57 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -3,6 +3,7 @@ import janggi.dto.CampDto; import janggi.exception.ExceptionMessage; import janggi.util.Parser; +import janggi.view.format.ElephantSetUpFormat; import java.util.List; import java.util.Scanner; @@ -12,24 +13,26 @@ public final class InputView { private static final String DELIMITER = ","; private static final String LINE_SEPARATOR = System.lineSeparator(); - private static final String ELEPHANT_SETTING = """ - - %s나라의 상차림을 선택해주세요. - 1. 상마상마 - 2. 마상마상 - 3. 마상상마 - 4. 상마마상"""; + private InputView() { + } - private static final String TURN = LINE_SEPARATOR + "%s나라 차례 입니다."; - private static final String SOURCE = "공격할 기물의 좌표를 행,열 순으로 입력해 주세요. (예: 9,8)"; - private static final String DESTINATION = LINE_SEPARATOR + "이동 시킬 목적지의 좌표를 행,열 순으로 입력해 주세요. (예: 2,0)"; + public static ElephantSetUpFormat readElephantSettingCommand(CampDto campDto) { + System.out.println("%s나라의 상차림을 선택해주세요.".formatted(campDto.name())); + for (ElephantSetUpFormat elephantSetUpFormat : ElephantSetUpFormat.values()) { + System.out.println(elephantSetUpFormat.getCommand() + ". " + elephantSetUpFormat.getDescription()); + } + return ElephantSetUpFormat.findElephantSettingBy(readLine()); + } - private InputView() { + public static List readSource(CampDto campDto) { + System.out.println(LINE_SEPARATOR + "%s나라 차례 입니다.".formatted(campDto.name())); + System.out.println("공격할 기물의 좌표를 행,열 순으로 입력해 주세요. (예: 9,8)"); + return Parser.parseByDelimiter(DELIMITER, readLine()); } - public static String readElephantSettingCommand(CampDto campDto) { - System.out.println(String.format(ELEPHANT_SETTING, campDto.name())); - return readLine(); + public static List readDestination() { + System.out.println(LINE_SEPARATOR + "이동 시킬 목적지의 좌표를 행,열 순으로 입력해 주세요. (예: 2,0)"); + return Parser.parseByDelimiter(DELIMITER, readLine()); } private static String readLine() { @@ -43,15 +46,4 @@ private static void validateInput(String input) { throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT_FORMAT.getMessage()); } } - - public static List readSource(CampDto campDto) { - System.out.println(String.format(TURN, campDto.name())); - System.out.println(SOURCE); - return Parser.parseByDelimiter(DELIMITER, readLine()); - } - - public static List readDestination() { - System.out.println(DESTINATION); - return Parser.parseByDelimiter(DELIMITER, readLine()); - } } diff --git a/src/main/java/janggi/view/format/ElephantSetUpFormat.java b/src/main/java/janggi/view/format/ElephantSetUpFormat.java new file mode 100644 index 0000000000..f8fe82a482 --- /dev/null +++ b/src/main/java/janggi/view/format/ElephantSetUpFormat.java @@ -0,0 +1,43 @@ +package janggi.view.format; + +import janggi.domain.board.ElephantSetUp; +import janggi.exception.ExceptionMessage; +import java.util.Arrays; + +public enum ElephantSetUpFormat { + + LEFT_ELEPHANT("1", "상마상마", ElephantSetUp.LEFT_ELEPHANT), + RIGHT_ELEPHANT("2", "마상마상", ElephantSetUp.RIGHT_ELEPHANT), + INNER_ELEPHANT("3", "마상상마", ElephantSetUp.INNER_ELEPHANT), + OUTER_ELEPHANT("4", "상마마상", ElephantSetUp.OUTER_ELEPHANT), + ; + + private final String command; + private final String description; + private final ElephantSetUp elephantSetUp; + + ElephantSetUpFormat(String command, String description, ElephantSetUp elephantSetUp) { + this.command = command; + this.description = description; + this.elephantSetUp = elephantSetUp; + } + + public static ElephantSetUpFormat findElephantSettingBy(String command) { + return Arrays.stream(values()) + .filter(element -> element.command.equals(command)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INVALID_ELEPHANT_SET_UP_FORMAT.getMessage())); + } + + public String getCommand() { + return command; + } + + public String getDescription() { + return description; + } + + public ElephantSetUp getElephantSetUp() { + return elephantSetUp; + } +} diff --git a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java index 90b61be158..b366d0cfd9 100644 --- a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java @@ -15,7 +15,8 @@ public class StandardBoardInitializerTest { void 한_상마상마_초_마상마상_으로_보드를_초기화한다() { // given BoardInitializer initializer = new StandardBoardInitializer( - ElephantSetting.LEFT_ELEPHANT, ElephantSetting.RIGHT_ELEPHANT); + new ElephantFormation(Camp.HAN, ElephantSetUp.LEFT_ELEPHANT), + new ElephantFormation(Camp.CHO, ElephantSetUp.RIGHT_ELEPHANT)); Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoLeftHanRightBoard()); // when @@ -31,7 +32,8 @@ public class StandardBoardInitializerTest { void 한_상마마상_초_마상상마_으로_보드를_초기화한다() { // given BoardInitializer initializer = new StandardBoardInitializer( - ElephantSetting.OUTER_ELEPHANT, ElephantSetting.INNER_ELEPHANT); + new ElephantFormation(Camp.HAN, ElephantSetUp.OUTER_ELEPHANT), + new ElephantFormation(Camp.CHO, ElephantSetUp.INNER_ELEPHANT)); Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoInnerHanOuterBoard()); // when diff --git a/src/test/java/janggi/domain/board/ElephantSettingTest.java b/src/test/java/janggi/view/format/ElephantSetUpFormatTest.java similarity index 61% rename from src/test/java/janggi/domain/board/ElephantSettingTest.java rename to src/test/java/janggi/view/format/ElephantSetUpFormatTest.java index 5c1f88d724..41b85a4f40 100644 --- a/src/test/java/janggi/domain/board/ElephantSettingTest.java +++ b/src/test/java/janggi/view/format/ElephantSetUpFormatTest.java @@ -1,11 +1,11 @@ -package janggi.domain.board; +package janggi.view.format; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -class ElephantSettingTest { +class ElephantSetUpFormatTest { @ParameterizedTest @CsvSource(value = { @@ -14,10 +14,10 @@ class ElephantSettingTest { "3, INNER_ELEPHANT", "4, OUTER_ELEPHANT" }) - void 명령어와_매칭되는_상차림을_반환한다(String command, ElephantSetting expectedElephantSetting) { + void 명령어와_매칭되는_상차림을_반환한다(String command, ElephantSetUpFormat expectedElephantSetUpFormat) { // when - ElephantSetting result = ElephantSetting.findElephantSettingBy(command); + ElephantSetUpFormat result = ElephantSetUpFormat.findElephantSettingBy(command); // then - assertThat(result).isEqualTo(expectedElephantSetting); + assertThat(result).isEqualTo(expectedElephantSetUpFormat); } } From ba433285559d0faa7600c8909bb8464d227d4aec Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Wed, 1 Apr 2026 10:04:09 +0900 Subject: [PATCH 60/68] =?UTF-8?q?refactor:=20=ED=96=89=EB=A7=88=EB=B2=95?= =?UTF-8?q?=20=EC=A0=84=EB=9E=B5=20=EC=B6=94=EC=83=81=ED=99=94=20=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=20=EB=A7=9E=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/janggi/domain/piece/PieceRule.java | 22 ++-- ...trategy.java => DiagonalStepStrategy.java} | 42 +++--- .../domain/piece/strategy/HorseStrategy.java | 69 ---------- .../strategy/SingleStepStraightStrategy.java | 26 ---- .../piece/strategy/SingleStepStrategy.java | 36 +++++ ...ightStrategy.java => SlidingStrategy.java} | 2 +- .../piece/strategy/SoldierStrategy.java | 28 ---- .../janggi/exception/ExceptionMessage.java | 6 +- .../java/janggi/domain/piece/PieceTest.java | 12 +- .../strategy/DiagonalStepStrategyTest.java | 123 ++++++++++++++++++ .../piece/strategy/ElephantStrategyTest.java | 104 --------------- .../piece/strategy/HorseStrategyTest.java | 94 ------------- .../MultiStepStraightStrategyTest.java | 99 -------------- .../SingleStepStraightStrategyTest.java | 49 ------- .../strategy/SingleStepStrategyTest.java | 112 ++++++++++++++++ .../piece/strategy/SlidingStrategyTest.java | 51 ++++++++ .../piece/strategy/SoldierStrategyTest.java | 101 -------------- 17 files changed, 361 insertions(+), 615 deletions(-) rename src/main/java/janggi/domain/piece/strategy/{ElephantStrategy.java => DiagonalStepStrategy.java} (62%) delete mode 100644 src/main/java/janggi/domain/piece/strategy/HorseStrategy.java delete mode 100644 src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java create mode 100644 src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java rename src/main/java/janggi/domain/piece/strategy/{MultiStepStraightStrategy.java => SlidingStrategy.java} (97%) delete mode 100644 src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java create mode 100644 src/test/java/janggi/domain/piece/strategy/DiagonalStepStrategyTest.java delete mode 100644 src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java delete mode 100644 src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java delete mode 100644 src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java delete mode 100644 src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java create mode 100644 src/test/java/janggi/domain/piece/strategy/SingleStepStrategyTest.java create mode 100644 src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java delete mode 100644 src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java diff --git a/src/main/java/janggi/domain/piece/PieceRule.java b/src/main/java/janggi/domain/piece/PieceRule.java index ea8c15437c..7d10f1c1e4 100644 --- a/src/main/java/janggi/domain/piece/PieceRule.java +++ b/src/main/java/janggi/domain/piece/PieceRule.java @@ -5,23 +5,21 @@ import janggi.domain.piece.condition.EmptyCondition; import janggi.domain.piece.condition.MoveCondition; import janggi.domain.piece.condition.OnePieceExistsCondition; -import janggi.domain.piece.strategy.ElephantStrategy; -import janggi.domain.piece.strategy.HorseStrategy; +import janggi.domain.piece.strategy.DiagonalStepStrategy; import janggi.domain.piece.strategy.MoveStrategy; -import janggi.domain.piece.strategy.MultiStepStraightStrategy; -import janggi.domain.piece.strategy.SingleStepStraightStrategy; -import janggi.domain.piece.strategy.SoldierStrategy; +import janggi.domain.piece.strategy.SingleStepStrategy; +import janggi.domain.piece.strategy.SlidingStrategy; import java.util.List; public enum PieceRule { - GENERAL(new SingleStepStraightStrategy(), new EmptyCondition()), - CHARIOT(new MultiStepStraightStrategy(), new EmptyCondition()), - HORSE(new HorseStrategy(), new EmptyCondition()), - CANNON(new MultiStepStraightStrategy(), new OnePieceExistsCondition()), - GUARD(new SingleStepStraightStrategy(), new EmptyCondition()), - ELEPHANT(new ElephantStrategy(), new EmptyCondition()), - SOLDIER(new SoldierStrategy(), new EmptyCondition()); + GENERAL(new SingleStepStrategy(false), new EmptyCondition()), + CHARIOT(new SlidingStrategy(), new EmptyCondition()), + HORSE(new DiagonalStepStrategy(1), new EmptyCondition()), + CANNON(new SlidingStrategy(), new OnePieceExistsCondition()), + GUARD(new SingleStepStrategy(false), new EmptyCondition()), + ELEPHANT(new DiagonalStepStrategy(2), new EmptyCondition()), + SOLDIER(new SingleStepStrategy(true), new EmptyCondition()); private final MoveStrategy moveStrategy; private final MoveCondition moveCondition; diff --git a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java b/src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java similarity index 62% rename from src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java rename to src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java index 73d2205aa2..3714fe0f59 100644 --- a/src/main/java/janggi/domain/piece/strategy/ElephantStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java @@ -6,20 +6,21 @@ import java.util.ArrayList; import java.util.List; -public class ElephantStrategy implements MoveStrategy { +public class DiagonalStepStrategy implements MoveStrategy { - private static final int ELEPHANT_STRAIGHT_MOVE_DISTANCE = 1; - private static final int ELEPHANT_DIAGONAL_MOVE_DISTANCE = 2; + private static final int STRAIGHT_DISTANCE = 1; - private static final int DIAGONAL_COUNT = 2; - private static final int MIN_ABS_DELTA = 2; - private static final int MAX_ABS_DELTA = 3; + private final int diagonalDistance; + + public DiagonalStepStrategy(int diagonalDistance) { + this.diagonalDistance = diagonalDistance; + } @Override public List findPath(Position source, Position destination, Camp camp) { DirectionInformation directionInfo = new DirectionInformation(source, destination); - validateElephantMovement(directionInfo); + validateMovement(directionInfo); if (directionInfo.isRowBiggerThanCol()) { return createRowFirstPath(source, directionInfo); @@ -27,42 +28,39 @@ public List findPath(Position source, Position destination, Camp camp) return createColFirstPath(source, directionInfo); } - private void validateElephantMovement(DirectionInformation directionInfo) { - if ((directionInfo.calculateAbsRowDifference() != MIN_ABS_DELTA - || directionInfo.calculateAbsColDifference() != MAX_ABS_DELTA) - && (directionInfo.calculateAbsRowDifference() != MAX_ABS_DELTA - || directionInfo.calculateAbsColDifference() != MIN_ABS_DELTA)) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage( - ELEPHANT_STRAIGHT_MOVE_DISTANCE, - ELEPHANT_DIAGONAL_MOVE_DISTANCE - )); + private void validateMovement(DirectionInformation directionInfo) { + int absRowDiff = directionInfo.calculateAbsRowDifference(); + int absColDiff = directionInfo.calculateAbsColDifference(); + int longDistance = STRAIGHT_DISTANCE + diagonalDistance; + int shortDistance = diagonalDistance; + + boolean isValid = (absRowDiff == shortDistance && absColDiff == longDistance) + || (absRowDiff == longDistance && absColDiff == shortDistance); + + if (!isValid) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, diagonalDistance)); } } private List createRowFirstPath(Position source, DirectionInformation directionInfo) { List path = new ArrayList<>(); - source = source.moveRow(directionInfo.calculateRowDirection()); path.add(source); - path.addAll(moveDiagonal(source, directionInfo)); return path; } private List createColFirstPath(Position source, DirectionInformation directionInfo) { List path = new ArrayList<>(); - source = source.moveCol(directionInfo.calculateColDirection()); path.add(source); - path.addAll(moveDiagonal(source, directionInfo)); return path; } private List moveDiagonal(Position source, DirectionInformation directionInfo) { List path = new ArrayList<>(); - - for (int i = 0; i < DIAGONAL_COUNT; i++) { + for (int i = 0; i < diagonalDistance; i++) { source = source.moveDiagonal(directionInfo.calculateRowDirection(), directionInfo.calculateColDirection()); path.add(source); } diff --git a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java b/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java deleted file mode 100644 index 497cdde2f6..0000000000 --- a/src/main/java/janggi/domain/piece/strategy/HorseStrategy.java +++ /dev/null @@ -1,69 +0,0 @@ -package janggi.domain.piece.strategy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.ArrayList; -import java.util.List; - -public class HorseStrategy implements MoveStrategy { - - private static final int HORSE_STRAIGHT_MOVE_DISTANCE = 1; - private static final int HORSE_DIAGONAL_MOVE_DISTANCE = 1; - private static final int MIN_ABS_DELTA = 1; - private static final int MAX_ABS_DELTA = 2; - - @Override - public List findPath(Position source, Position destination, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(source, destination); - - validateHorseMovement(directionInformation); - - if (directionInformation.isRowBiggerThanCol()) { - return createRowFirstPath(source, directionInformation); - } - return createColFirstPath(source, directionInformation); - } - - private List createRowFirstPath(Position source, DirectionInformation directionInformation) { - List path = new ArrayList<>(); - - int rowDirection = directionInformation.calculateRowDirection(); - int colDirection = directionInformation.calculateColDirection(); - source = source.moveRow(rowDirection); - path.add(source); - - source = source.moveDiagonal(rowDirection, colDirection); - path.add(source); - return path; - } - - private List createColFirstPath(Position source, DirectionInformation directionInformation) { - List path = new ArrayList<>(); - - int rowDirection = directionInformation.calculateRowDirection(); - int colDirection = directionInformation.calculateColDirection(); - source = source.moveCol(colDirection); - path.add(source); - - source = source.moveDiagonal(rowDirection, colDirection); - path.add(source); - return path; - } - - private void validateHorseMovement(DirectionInformation directionInformation) { - int absRowDifference = directionInformation.calculateAbsRowDifference(); - int absColDifference = directionInformation.calculateAbsColDifference(); - - boolean isValidHorseMove = - (absRowDifference == MIN_ABS_DELTA && absColDifference == MAX_ABS_DELTA) - || (absRowDifference == MAX_ABS_DELTA && absColDifference == MIN_ABS_DELTA); - - if (!isValidHorseMove) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_HORSE_MOVE.getMessage( - HORSE_STRAIGHT_MOVE_DISTANCE, - HORSE_DIAGONAL_MOVE_DISTANCE - )); - } - } -} diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java deleted file mode 100644 index 463a69bf69..0000000000 --- a/src/main/java/janggi/domain/piece/strategy/SingleStepStraightStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -package janggi.domain.piece.strategy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; - -public class SingleStepStraightStrategy implements MoveStrategy { - - private static final int SINGLE_STEP_DISTANCE = 1; - - @Override - public List findPath(Position source, Position destination, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(source, destination); - validateSingleStepMovement(directionInformation); - - return List.of(destination); - } - - private void validateSingleStepMovement(DirectionInformation directionInformation) { - if (directionInformation.calculateAbsRowDifference() - + directionInformation.calculateAbsColDifference() != SINGLE_STEP_DISTANCE) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage(SINGLE_STEP_DISTANCE)); - } - } -} diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java new file mode 100644 index 0000000000..80e9788d05 --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java @@ -0,0 +1,36 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; +import java.util.List; + +public class SingleStepStrategy implements MoveStrategy { + + private static final int SINGLE_STEP_DISTANCE = 1; + + private final boolean forwardOnly; + + public SingleStepStrategy(boolean forwardOnly) { + this.forwardOnly = forwardOnly; + } + + @Override + public List findPath(Position source, Position destination, Camp camp) { + DirectionInformation directionInformation = new DirectionInformation(source, destination); + + if (forwardOnly) { + camp.validateForwardDirection(directionInformation.calculateRowDirection()); + validateMovement(directionInformation); + return List.of(destination); + } + validateMovement(directionInformation); + return List.of(destination); + } + + private void validateMovement(DirectionInformation directionInfo) { + if (directionInfo.calculateAbsRowDifference() + directionInfo.calculateAbsColDifference() != SINGLE_STEP_DISTANCE) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); + } + } +} diff --git a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java b/src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java similarity index 97% rename from src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java rename to src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java index e232a4e1b4..27a8590856 100644 --- a/src/main/java/janggi/domain/piece/strategy/MultiStepStraightStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java @@ -6,7 +6,7 @@ import java.util.ArrayList; import java.util.List; -public class MultiStepStraightStrategy implements MoveStrategy { +public class SlidingStrategy implements MoveStrategy { @Override public List findPath(Position source, Position destination, Camp camp) { diff --git a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java b/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java deleted file mode 100644 index a6a0f5cfa7..0000000000 --- a/src/main/java/janggi/domain/piece/strategy/SoldierStrategy.java +++ /dev/null @@ -1,28 +0,0 @@ -package janggi.domain.piece.strategy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; - -public class SoldierStrategy implements MoveStrategy { - - private static final int SINGLE_STEP_DISTANCE = 1; - - @Override - public List findPath(Position source, Position destination, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(source, destination); - - camp.validateForwardDirection(directionInformation.calculateRowDirection()); - validateSoldierMovement(directionInformation); - - return List.of(destination); - } - - private void validateSoldierMovement(DirectionInformation directionInformation) { - if (directionInformation.calculateAbsRowDifference() - + directionInformation.calculateAbsColDifference() != SINGLE_STEP_DISTANCE) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); - } - } -} diff --git a/src/main/java/janggi/exception/ExceptionMessage.java b/src/main/java/janggi/exception/ExceptionMessage.java index ff2247d2ca..82bc456738 100644 --- a/src/main/java/janggi/exception/ExceptionMessage.java +++ b/src/main/java/janggi/exception/ExceptionMessage.java @@ -15,12 +15,10 @@ public enum ExceptionMessage { SAME_PIECE_TYPE_AT_DESTINATION("목적지에 같은 종류의 기물이 존재합니다."), ONLY_STRAIGHT_MOVE_ALLOWED("해당 기물은 직선 이동만 가능합니다."), PIECE_MUST_MOVE("기물은 반드시 이동해야 합니다."), - INVALID_SINGLE_STEP_STRAIGHT_MOVE("해당 기물은 직선으로 %d칸 이동해야 합니다."), INVALID_BACKWARD_MOVEMENT("해당 기물은 후진할 수 없습니다."), - INVALID_SOLDIER_MOVE("해당 기물은 %d칸만 이동할 수 있습니다."), + INVALID_SINGLE_STEP_MOVE("해당 기물은 %d칸만 이동할 수 있습니다."), INVALID_JUMPED_PIECE_COUNT("해당 기물은 정확히 %d개의 기물만 뛰어넘을 수 있습니다."), - INVALID_HORSE_MOVE("해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다."), - INVALID_ELEPHANT_MOVE("해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다."), + INVALID_DIAGONAL_STEP_MOVE("해당 기물은 직선 %d칸 이동 후 대각선 %d칸 이동만 가능합니다."), CAMP_FORMAT_NOT_FOUND("존재하지 않는 진영 형식입니다."), PIECE_FORMAT_NOT_FOUND("존재하지 않는 기물 형식입니다."), ; diff --git a/src/test/java/janggi/domain/piece/PieceTest.java b/src/test/java/janggi/domain/piece/PieceTest.java index 512462f45d..2240bf9938 100644 --- a/src/test/java/janggi/domain/piece/PieceTest.java +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -65,7 +65,7 @@ class General { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(1, 4), new Position(3, 4), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage(SINGLE_STEP_DISTANCE)); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } @@ -92,7 +92,7 @@ class Guard { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 3), new Position(0, 5), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage(SINGLE_STEP_DISTANCE)); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } @@ -133,7 +133,7 @@ class Horse { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 1), new Position(0, 7), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage(HORSE_STRAIGHT_MOVE_DISTANCE, HORSE_DIAGONAL_MOVE_DISTANCE)); + .hasMessage(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(HORSE_STRAIGHT_MOVE_DISTANCE, HORSE_DIAGONAL_MOVE_DISTANCE)); } } @@ -160,7 +160,7 @@ class Elephant { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 6), new Position(3, 3), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage(ELEPHANT_STRAIGHT_MOVE_DISTANCE, ELEPHANT_DIAGONAL_MOVE_DISTANCE)); + .hasMessage(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(ELEPHANT_STRAIGHT_MOVE_DISTANCE, ELEPHANT_DIAGONAL_MOVE_DISTANCE)); } } @@ -286,7 +286,7 @@ class SoldierCho { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(3, 0), new Position(5, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } @@ -325,7 +325,7 @@ class SoldierHan { Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(6, 0), new Position(1, 0), board)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } } diff --git a/src/test/java/janggi/domain/piece/strategy/DiagonalStepStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/DiagonalStepStrategyTest.java new file mode 100644 index 0000000000..13f24b1eab --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/DiagonalStepStrategyTest.java @@ -0,0 +1,123 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class DiagonalStepStrategyTest { + + @DisplayName("직선 1칸 후 대각선 1칸 이동(마) 테스트") + @Nested + class OneStepDiagonal { + + private static final int STRAIGHT_DISTANCE = 1; + private static final int DIAGONAL_DISTANCE = 1; + private final MoveStrategy strategy = new DiagonalStepStrategy(DIAGONAL_DISTANCE); + + private static Stream successPaths() { + Position source = new Position(6, 4); + return Stream.of( + Arguments.of(source, List.of(new Position(7, 4), new Position(8, 5))), + Arguments.of(source, List.of(new Position(7, 4), new Position(8, 3))), + Arguments.of(source, List.of(new Position(5, 4), new Position(4, 3))), + Arguments.of(source, List.of(new Position(5, 4), new Position(4, 5))), + Arguments.of(source, List.of(new Position(6, 3), new Position(5, 2))), + Arguments.of(source, List.of(new Position(6, 3), new Position(7, 2))), + Arguments.of(source, List.of(new Position(6, 5), new Position(5, 6))), + Arguments.of(source, List.of(new Position(6, 5), new Position(7, 6))) + ); + } + + @ParameterizedTest + @MethodSource("successPaths") + void 직선_1칸_이동_후_대각선_1칸_이동한다(Position source, List expectedPath) { + List path = strategy.findPath(source, expectedPath.getLast(), Camp.HAN); + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(expectedPath.size()); + assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); + }); + } + + private static Stream invalidDistancePositions() { + return Stream.of( + Arguments.of(new Position(6, 4), new Position(5, 1)), + Arguments.of(new Position(6, 4), new Position(4, 2)), + Arguments.of(new Position(6, 4), new Position(6, 4)) + ); + } + + @ParameterizedTest + @MethodSource("invalidDistancePositions") + void 행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { + assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, DIAGONAL_DISTANCE)); + } + } + + @DisplayName("직선 1칸 후 대각선 2칸 이동 테스트") + @Nested + class TwoStepDiagonal { + + private static final int STRAIGHT_DISTANCE = 1; + private static final int DIAGONAL_DISTANCE = 2; + private final MoveStrategy strategy = new DiagonalStepStrategy(DIAGONAL_DISTANCE); + + private static Stream createPaths() { + Position source = new Position(6, 4); + return Stream.of( + Arguments.of(source, + List.of(new Position(5, 4), new Position(4, 3), new Position(3, 2))), + Arguments.of(source, + List.of(new Position(6, 3), new Position(5, 2), new Position(4, 1))), + Arguments.of(source, + List.of(new Position(6, 3), new Position(7, 2), new Position(8, 1))), + Arguments.of(source, + List.of(new Position(7, 4), new Position(8, 3), new Position(9, 2))), + Arguments.of(source, + List.of(new Position(7, 4), new Position(8, 5), new Position(9, 6))), + Arguments.of(source, + List.of(new Position(6, 5), new Position(7, 6), new Position(8, 7))), + Arguments.of(source, + List.of(new Position(6, 5), new Position(5, 6), new Position(4, 7))), + Arguments.of(source, + List.of(new Position(5, 4), new Position(4, 5), new Position(3, 6))) + ); + } + + @ParameterizedTest + @MethodSource("createPaths") + void 직선_1칸_이동_후_대각선_2칸_이동한다(Position source, List expectedPath) { + List path = strategy.findPath(source, expectedPath.getLast(), Camp.HAN); + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(expectedPath.size()); + assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); + }); + } + + private static Stream invalidDistancePositions() { + return Stream.of( + Arguments.of(new Position(6, 4), new Position(3, 4)), + Arguments.of(new Position(6, 4), new Position(6, 4)) + ); + } + + @ParameterizedTest + @MethodSource("invalidDistancePositions") + void 행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { + assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, DIAGONAL_DISTANCE)); + } + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java deleted file mode 100644 index 9040e22639..0000000000 --- a/src/test/java/janggi/domain/piece/strategy/ElephantStrategyTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package janggi.domain.piece.strategy; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -public class ElephantStrategyTest { - - private static final int ELEPHANT_STRAIGHT_MOVE_DISTANCE = 1; - private static final int ELEPHANT_DIAGONAL_MOVE_DISTANCE = 2; - - private final MoveStrategy strategy = new ElephantStrategy(); - - private static Stream createPositionsAndPath() { - return Stream.of( - Arguments.of(new Position(6, 4), - List.of( - new Position(5, 4), - new Position(4, 3), - new Position(3, 2) - )), - Arguments.of(new Position(6, 4), - List.of( - new Position(6, 3), - new Position(5, 2), - new Position(4, 1) - )), - Arguments.of(new Position(6, 4), - List.of( - new Position(6, 3), - new Position(7, 2), - new Position(8, 1) - )), - Arguments.of(new Position(6, 4), - List.of( - new Position(7, 4), - new Position(8, 3), - new Position(9, 2) - )), - Arguments.of(new Position(6, 4), - List.of( - new Position(7, 4), - new Position(8, 5), - new Position(9, 6) - )), - Arguments.of(new Position(6, 4), - List.of( - new Position(6, 5), - new Position(7, 6), - new Position(8, 7) - )), - Arguments.of(new Position(6, 4), - List.of( - new Position(6, 5), - new Position(5, 6), - new Position(4, 7) - )), - Arguments.of(new Position(6, 4), - List.of( - new Position(5, 4), - new Position(4, 5), - new Position(3, 6) - )) - ); - } - - @ParameterizedTest - @MethodSource("createPositionsAndPath") - void 상은_직선_1칸_이동_후_대각선_2칸_이동한다(Position source, List expectedPath) { - // given - - Position destination = expectedPath.getLast(); - // when - List path = strategy.findPath(source, destination, Camp.HAN); - // then - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(expectedPath.size()); - assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); - }); - } - - private static Stream createExceptionPosition() { - return Stream.of( - Arguments.of(new Position(6, 4), new Position(3, 4)), - Arguments.of(new Position(6, 4), new Position(6, 4)) - ); - } - - @ParameterizedTest - @MethodSource("createExceptionPosition") - void 상은_행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { - assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_ELEPHANT_MOVE.getMessage(ELEPHANT_STRAIGHT_MOVE_DISTANCE, ELEPHANT_DIAGONAL_MOVE_DISTANCE)); - } -} diff --git a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java deleted file mode 100644 index 44ddb014b8..0000000000 --- a/src/test/java/janggi/domain/piece/strategy/HorseStrategyTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package janggi.domain.piece.strategy; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -public class HorseStrategyTest { - - private static final int HORSE_STRAIGHT_MOVE_DISTANCE = 1; - private static final int HORSE_DIAGONAL_MOVE_DISTANCE = 1; - - private final MoveStrategy strategy = new HorseStrategy(); - - private static Stream createPositionsAndPath() { - return Stream.of( - Arguments.of(new Position(6, 4), new Position(8, 5), - List.of( - new Position(7, 4), - new Position(8, 5) - )), - Arguments.of(new Position(6, 4), new Position(8, 3), - List.of( - new Position(7, 4), - new Position(8, 3) - )), - Arguments.of(new Position(6, 4), new Position(4, 3), - List.of( - new Position(5, 4), - new Position(4, 3) - )), - Arguments.of(new Position(6, 4), new Position(4, 5), - List.of( - new Position(5, 4), - new Position(4, 5) - )), - Arguments.of(new Position(6, 4), new Position(5, 2), - List.of( - new Position(6, 3), - new Position(5, 2) - )), - Arguments.of(new Position(6, 4), new Position(7, 2), - List.of( - new Position(6, 3), - new Position(7, 2) - )), - Arguments.of(new Position(6, 4), new Position(5, 6), - List.of( - new Position(6, 5), - new Position(5, 6) - )), - Arguments.of(new Position(6, 4), new Position(7, 6), - List.of( - new Position(6, 5), - new Position(7, 6) - )) - ); - } - - @ParameterizedTest - @MethodSource("createPositionsAndPath") - void 마는_직선_1칸_이동_후_대각선_1칸_이동한다(Position source, Position destination, List expectedPath) { - // when - List path = strategy.findPath(source, destination, Camp.HAN); - // then - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(expectedPath.size()); - assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); - }); - } - - private static Stream createExceptionPosition() { - return Stream.of( - Arguments.of(new Position(6, 4), new Position(5, 1)), - Arguments.of(new Position(6, 4), new Position(4, 2)), - Arguments.of(new Position(6, 4), new Position(6, 4)) - ); - } - - @ParameterizedTest - @MethodSource("createExceptionPosition") - void 마는_행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { - assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_HORSE_MOVE.getMessage(HORSE_STRAIGHT_MOVE_DISTANCE, HORSE_DIAGONAL_MOVE_DISTANCE)); - } -} diff --git a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java deleted file mode 100644 index 1b1923b1fe..0000000000 --- a/src/test/java/janggi/domain/piece/strategy/MultiStepStraightStrategyTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package janggi.domain.piece.strategy; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -public class MultiStepStraightStrategyTest { - - private final MoveStrategy strategy = new MultiStepStraightStrategy(); - - private static Stream createPositionsAndPath() { - return Stream.of( - Arguments.of(new Position(0, 0), - List.of( - new Position(1, 0), - new Position(2, 0), - new Position(3, 0), - new Position(4, 0), - new Position(5, 0), - new Position(6, 0), - new Position(7, 0), - new Position(8, 0), - new Position(9, 0) - )), - Arguments.of(new Position(0, 0), - List.of( - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4), - new Position(0, 5), - new Position(0, 6), - new Position(0, 7), - new Position(0, 8) - ) - ), - Arguments.of(new Position(0, 8), - List.of( - new Position(0, 7), - new Position(0, 6), - new Position(0, 5), - new Position(0, 4), - new Position(0, 3), - new Position(0, 2), - new Position(0, 1), - new Position(0, 0) - ) - ), - Arguments.of(new Position(9, 0), - List.of( - new Position(8, 0), - new Position(7, 0), - new Position(6, 0), - new Position(5, 0), - new Position(4, 0), - new Position(3, 0), - new Position(2, 0), - new Position(1, 0), - new Position(0, 0) - ) - ) - ); - } - - @ParameterizedTest - @MethodSource("createPositionsAndPath") - void 직선으로_1칸_이상_이동하는_경로를_계산한다(Position source, List expectedPath) { - // when - List path = strategy.findPath(source, expectedPath.getLast(), Camp.HAN); - // then - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(expectedPath.size()); - assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); - }); - } - - @Test - void 직선_이동이_아닌_경우_예외가_발생한다() { - assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(5, 5), Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); - } - - @Test - void 제자리_이동이면_예외가_발생한다() { - assertThatThrownBy(() -> strategy.findPath(new Position(0, 0), new Position(0, 0), Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.PIECE_MUST_MOVE.getMessage()); - } -} diff --git a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java deleted file mode 100644 index 1ce5c5f000..0000000000 --- a/src/test/java/janggi/domain/piece/strategy/SingleStepStraightStrategyTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package janggi.domain.piece.strategy; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class SingleStepStraightStrategyTest { - - private static final int SINGLE_STEP_DISTANCE = 1; - - private final MoveStrategy strategy = new SingleStepStraightStrategy(); - - private static Stream successMovePositions() { - return Stream.of( - Arguments.of(new Position(1, 4), new Position(1, 5)), - Arguments.of(new Position(1, 4), new Position(1, 3)), - Arguments.of(new Position(1, 4), new Position(0, 4)), - Arguments.of(new Position(1, 4), new Position(2, 4)) - ); - } - - @ParameterizedTest - @MethodSource("successMovePositions") - void 직선_방향으로_1칸만_이동한다(Position source, Position destination) { - //when - List path = strategy.findPath(source, destination, Camp.HAN); - //then - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(1); - assertSoftly.assertThat(path).containsExactly(destination); - }); - } - - @Test - void 직선_방향으로_1칸_만_이동하지_않으면_예외가_발생한다() { - assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.HAN)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_STRAIGHT_MOVE.getMessage(SINGLE_STEP_DISTANCE)); - } -} diff --git a/src/test/java/janggi/domain/piece/strategy/SingleStepStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SingleStepStrategyTest.java new file mode 100644 index 0000000000..8346d71d4d --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/SingleStepStrategyTest.java @@ -0,0 +1,112 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SingleStepStrategyTest { + + private static final int SINGLE_STEP_DISTANCE = 1; + + @DisplayName("전방향 1칸 이동(궁, 사) 테스트") + @Nested + class AllDirectionStep { + + private final MoveStrategy strategy = new SingleStepStrategy(false); + + private static Stream successMovePositions() { + return Stream.of( + Arguments.of(new Position(1, 4), new Position(1, 5)), + Arguments.of(new Position(1, 4), new Position(1, 3)), + Arguments.of(new Position(1, 4), new Position(0, 4)), + Arguments.of(new Position(1, 4), new Position(2, 4)) + ); + } + + @ParameterizedTest + @MethodSource("successMovePositions") + void 직선_방향으로_1칸만_이동한다(Position source, Position destination) { + List path = strategy.findPath(source, destination, Camp.HAN); + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(1); + assertSoftly.assertThat(path).containsExactly(destination); + }); + } + + @Test + void 직선_방향으로_1칸만_이동하지_않으면_예외가_발생한다() { + assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.HAN)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); + } + } + + @DisplayName("전진 및 좌우 1칸 이동(졸, 병) 테스트") + @Nested + class ForwardSideStep { + + private final MoveStrategy strategy = new SingleStepStrategy(true); + + private static Stream successMovePositions() { + return Stream.of( + Arguments.of(Camp.HAN, new Position(6, 0), new Position(5, 0)), + Arguments.of(Camp.HAN, new Position(6, 0), new Position(6, 1)), + Arguments.of(Camp.HAN, new Position(6, 1), new Position(6, 0)), + Arguments.of(Camp.CHO, new Position(3, 0), new Position(4, 0)), + Arguments.of(Camp.CHO, new Position(3, 0), new Position(3, 1)), + Arguments.of(Camp.CHO, new Position(3, 1), new Position(3, 0)) + ); + } + + @ParameterizedTest + @MethodSource("successMovePositions") + void 전진_또는_좌우_방향으로_1칸만_이동한다(Camp camp, Position source, Position destination) { + List path = strategy.findPath(source, destination, camp); + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(1); + assertSoftly.assertThat(path).containsExactly(destination); + }); + } + + private static Stream invalidDistancePositions() { + return Stream.of( + Arguments.of(Camp.HAN, new Position(6, 0), new Position(4, 0)), + Arguments.of(Camp.CHO, new Position(3, 0), new Position(3, 6)) + ); + } + + @ParameterizedTest + @MethodSource("invalidDistancePositions") + void 직선_방향으로_1칸만_이동하지_않으면_예외가_발생한다(Camp camp, Position source, Position destination) { + assertThatThrownBy(() -> strategy.findPath(source, destination, camp)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); + } + + private static Stream backwardMovePositions() { + return Stream.of( + Arguments.of(Camp.HAN, new Position(6, 0), new Position(7, 0)), + Arguments.of(Camp.CHO, new Position(3, 0), new Position(2, 0)) + ); + } + + @ParameterizedTest + @MethodSource("backwardMovePositions") + void 후진하는_경우_예외가_발생한다(Camp camp, Position source, Position destination) { + assertThatThrownBy(() -> strategy.findPath(source, destination, camp)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); + } + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java new file mode 100644 index 0000000000..07b8b54c20 --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java @@ -0,0 +1,51 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.piece.Camp; +import janggi.exception.ExceptionMessage; +import java.util.List; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +class SlidingStrategyTest { + + private final MoveStrategy strategy = new SlidingStrategy(); + + @Test + void 직선으로_여러_칸_이동한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(9, 0); + //when + List path = strategy.findPath(source, destination, Camp.CHO); + //then + SoftAssertions.assertSoftly(assertSoftly -> { + assertSoftly.assertThat(path).hasSize(9); + assertSoftly.assertThat(path.getLast()).isEqualTo(destination); + }); + } + + @Test + void 직선으로_이동하지_않으면_예외가_발생한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(3, 3); + //when & then + assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); + } + + @Test + void 제자리에_있으면_예외가_발생한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(0, 0); + //when & then + assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.PIECE_MUST_MOVE.getMessage()); + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java deleted file mode 100644 index 240878193c..0000000000 --- a/src/test/java/janggi/domain/piece/strategy/SoldierStrategyTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package janggi.domain.piece.strategy; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class SoldierStrategyTest { - - private static final int SINGLE_STEP_DISTANCE = 1; - - private final MoveStrategy strategy = new SoldierStrategy(); - - @DisplayName("병 행마법 테스트") - @Nested - class Byung { - private static Stream successMovePositions() { - return Stream.of( - Arguments.of(new Position(6, 0), new Position(5, 0)), - Arguments.of(new Position(5, 1), new Position(4, 1)), - Arguments.of(new Position(6, 0), new Position(6, 1)), - Arguments.of(new Position(6, 1), new Position(6, 0)) - ); - } - - private static Stream exceptionMovePositions() { - return Stream.of( - Arguments.of(new Position(6, 0), new Position(5, 1)), - Arguments.of(new Position(6, 0), new Position(6, 2)), - Arguments.of(new Position(3, 2), new Position(2, 3)) - ); - } - - @ParameterizedTest - @MethodSource("successMovePositions") - void 병의_1칸_이동_여부를_확인한다(Position source, Position destination) { - List path = strategy.findPath(source, destination, Camp.HAN); - - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(1); - assertSoftly.assertThat(path).containsExactly(destination); - }); - } - - @ParameterizedTest - @MethodSource("exceptionMovePositions") - void 병은_1칸_이동이_아니면_예외가_발생한다(Position source, Position destination) { - assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.HAN)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); - } - - @Test - void 병은_후진_시_예외가_발생한다() { - assertThatThrownBy(() -> strategy.findPath(new Position(6, 0), new Position(7, 0), Camp.HAN)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); - } - } - - @DisplayName("졸 행마법 테스트") - @Nested - class Zol { - @Test - void 졸의_1칸_이동_여부를_확인한다() { - Position source = new Position(3, 0); - Position destination = new Position(4, 0); - - List path = strategy.findPath(source, destination, Camp.CHO); - - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(1); - assertSoftly.assertThat(path).containsExactly(destination); - }); - } - - @Test - void 졸은_1칸_이동이_아니면_예외가_발생한다() { - assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_SOLDIER_MOVE.getMessage(SINGLE_STEP_DISTANCE)); - } - - @Test - void 졸은_후진_시_예외가_발생한다() { - assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(2, 0), Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); - } -} -} From c4d4de5a88ae1af20c4f9c106dfcdf591efc42b6 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Wed, 1 Apr 2026 15:34:05 +0900 Subject: [PATCH 61/68] =?UTF-8?q?refactor:=20BoardInitializer=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiGame.java | 11 +++-- src/main/java/janggi/domain/board/Board.java | 6 +-- .../janggi/domain/board/BoardInitializer.java | 10 ----- .../domain/board/InitialPiecePlacement.java | 12 +++++- .../board/StandardBoardInitializer.java | 29 ------------- .../java/janggi/domain/board/BoardTest.java | 6 +-- ...st.java => InitialPiecePlacementTest.java} | 26 ++++++------ .../java/janggi/domain/piece/PieceTest.java | 42 +++++++++---------- .../piece/condition/EmptyConditionTest.java | 11 ++--- .../OnePieceExistsConditionTest.java | 28 ++++--------- 10 files changed, 68 insertions(+), 113 deletions(-) delete mode 100644 src/main/java/janggi/domain/board/BoardInitializer.java delete mode 100644 src/main/java/janggi/domain/board/StandardBoardInitializer.java rename src/test/java/janggi/domain/board/{StandardBoardInitializerTest.java => InitialPiecePlacementTest.java} (79%) diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index ba0e5233e1..ac7275ae29 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -3,10 +3,9 @@ import janggi.domain.Position; import janggi.domain.Turn; import janggi.domain.board.Board; -import janggi.domain.board.BoardInitializer; import janggi.domain.board.ElephantFormation; import janggi.domain.board.ElephantSetUp; -import janggi.domain.board.StandardBoardInitializer; +import janggi.domain.board.InitialPiecePlacement; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.dto.CampDto; @@ -14,6 +13,7 @@ import janggi.util.RetryHandler; import janggi.view.InputView; import janggi.view.OutputView; +import janggi.view.format.ElephantSetUpFormat; import java.util.List; import java.util.Map; @@ -28,14 +28,13 @@ public void run() { private Board createBoard() { ElephantFormation hanElephantFormation = readElephantFormation(Camp.HAN); ElephantFormation choElephantFormation = readElephantFormation(Camp.CHO); - BoardInitializer initializer = new StandardBoardInitializer(hanElephantFormation, choElephantFormation); - return new Board(initializer); + return InitialPiecePlacement.initialize(hanElephantFormation, choElephantFormation); } private static ElephantFormation readElephantFormation(Camp camp) { return RetryHandler.retryOnInvalidInput(() -> { - ElephantSetUp elephantSetUp = InputView.readElephantSettingCommand(CampDto.from(camp)) - .getElephantSetUp(); + ElephantSetUpFormat elephantSetUpFormat = InputView.readElephantSettingCommand(CampDto.from(camp)); + ElephantSetUp elephantSetUp = elephantSetUpFormat.getElephantSetUp(); return new ElephantFormation(camp, elephantSetUp); } ); diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 4251a019d3..75e0779566 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -10,10 +10,10 @@ public class Board implements BoardChecker { - private final Map board = new HashMap<>(); + private final Map board; - public Board(BoardInitializer boardInitializer) { - board.putAll(boardInitializer.initialize()); + public Board(Map board) { + this.board = new HashMap<>(board); } @Override diff --git a/src/main/java/janggi/domain/board/BoardInitializer.java b/src/main/java/janggi/domain/board/BoardInitializer.java deleted file mode 100644 index 16da5717d7..0000000000 --- a/src/main/java/janggi/domain/board/BoardInitializer.java +++ /dev/null @@ -1,10 +0,0 @@ -package janggi.domain.board; - -import janggi.domain.Position; -import janggi.domain.piece.Piece; -import java.util.Map; - -public interface BoardInitializer { - - Map initialize(); -} diff --git a/src/main/java/janggi/domain/board/InitialPiecePlacement.java b/src/main/java/janggi/domain/board/InitialPiecePlacement.java index 84b56ea6a0..924b484cf6 100644 --- a/src/main/java/janggi/domain/board/InitialPiecePlacement.java +++ b/src/main/java/janggi/domain/board/InitialPiecePlacement.java @@ -4,6 +4,7 @@ import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; +import java.util.HashMap; import java.util.Map; public enum InitialPiecePlacement { @@ -42,7 +43,14 @@ public enum InitialPiecePlacement { this.piece = new Piece(pieceRule, camp); } - public void placeOn(Map board) { - board.put(position, piece); + public static Board initialize(ElephantFormation hanFormation, ElephantFormation choFormation) { + Map board = new HashMap<>(); + + for (InitialPiecePlacement placement : values()) { + board.put(placement.position, placement.piece); + } + board.putAll(hanFormation.placeElephantSetUpPieces()); + board.putAll(choFormation.placeElephantSetUpPieces()); + return new Board(board); } } diff --git a/src/main/java/janggi/domain/board/StandardBoardInitializer.java b/src/main/java/janggi/domain/board/StandardBoardInitializer.java deleted file mode 100644 index 9d3deed12a..0000000000 --- a/src/main/java/janggi/domain/board/StandardBoardInitializer.java +++ /dev/null @@ -1,29 +0,0 @@ -package janggi.domain.board; - -import janggi.domain.Position; -import janggi.domain.piece.Piece; -import java.util.HashMap; -import java.util.Map; - -public class StandardBoardInitializer implements BoardInitializer { - - private final ElephantFormation hanFormation; - private final ElephantFormation choFormation; - - public StandardBoardInitializer(ElephantFormation hanFormation, ElephantFormation choFormation) { - this.hanFormation = hanFormation; - this.choFormation = choFormation; - } - - @Override - public Map initialize() { - Map board = new HashMap<>(); - - for (InitialPiecePlacement placement : InitialPiecePlacement.values()) { - placement.placeOn(board); - } - board.putAll(hanFormation.placeElephantSetUpPieces()); - board.putAll(choFormation.placeElephantSetUpPieces()); - return board; - } -} diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 0ad08de1a4..b7224bb636 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -18,7 +18,7 @@ class BoardTest { Position source = new Position(7, 1); Position destination = new Position(0, 1); - Board board = new Board(() -> Map.of( + Board board = new Board(Map.of( new Position(4, 1), new Piece(PieceRule.SOLDIER, Camp.HAN), destination, new Piece(PieceRule.HORSE, Camp.CHO), source, new Piece(PieceRule.CANNON, Camp.HAN) @@ -42,7 +42,7 @@ source, new Piece(PieceRule.CANNON, Camp.HAN) Position destination = new Position(0, 1); // when - Board board = new Board(() -> Map.of( + Board board = new Board(Map.of( destination, new Piece(PieceRule.HORSE, Camp.CHO), source, new Piece(PieceRule.CANNON, Camp.CHO) )); @@ -58,7 +58,7 @@ source, new Piece(PieceRule.CANNON, Camp.CHO) Position source = new Position(7, 1); Position destination = new Position(0, 1); // when - Board board = new Board(Map::of); + Board board = new Board(Map.of()); // then Assertions.assertThatThrownBy(() -> board.movePiece(source, destination, Camp.HAN)) .isInstanceOf(IllegalArgumentException.class) diff --git a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java b/src/test/java/janggi/domain/board/InitialPiecePlacementTest.java similarity index 79% rename from src/test/java/janggi/domain/board/StandardBoardInitializerTest.java rename to src/test/java/janggi/domain/board/InitialPiecePlacementTest.java index b366d0cfd9..d3b37ce4e9 100644 --- a/src/test/java/janggi/domain/board/StandardBoardInitializerTest.java +++ b/src/test/java/janggi/domain/board/InitialPiecePlacementTest.java @@ -9,39 +9,39 @@ import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; -public class StandardBoardInitializerTest { +public class InitialPiecePlacementTest { @Test void 한_상마상마_초_마상마상_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer( - new ElephantFormation(Camp.HAN, ElephantSetUp.LEFT_ELEPHANT), - new ElephantFormation(Camp.CHO, ElephantSetUp.RIGHT_ELEPHANT)); + ElephantFormation hanElephantFormation = new ElephantFormation(Camp.HAN, ElephantSetUp.LEFT_ELEPHANT); + ElephantFormation choElephantFormation = new ElephantFormation(Camp.CHO, ElephantSetUp.RIGHT_ELEPHANT); + Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoLeftHanRightBoard()); // when - Map board = initializer.initialize(); + Board board = InitialPiecePlacement.initialize(hanElephantFormation, choElephantFormation); // then SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(board).hasSize(32); - assertSoftly.assertThat(board).isEqualTo(expectedBoard); + assertSoftly.assertThat(board.getBoard()).hasSize(32); + assertSoftly.assertThat(board.getBoard()).isEqualTo(expectedBoard); }); } @Test void 한_상마마상_초_마상상마_으로_보드를_초기화한다() { // given - BoardInitializer initializer = new StandardBoardInitializer( - new ElephantFormation(Camp.HAN, ElephantSetUp.OUTER_ELEPHANT), - new ElephantFormation(Camp.CHO, ElephantSetUp.INNER_ELEPHANT)); + ElephantFormation hanElephantFormation = new ElephantFormation(Camp.HAN, ElephantSetUp.OUTER_ELEPHANT); + ElephantFormation choElephantFormation = new ElephantFormation(Camp.CHO, ElephantSetUp.INNER_ELEPHANT); + Map expectedBoard = createExpectedBoard(); expectedBoard.putAll(createChoInnerHanOuterBoard()); // when - Map board = initializer.initialize(); + Board board = InitialPiecePlacement.initialize(hanElephantFormation, choElephantFormation); // then SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(board).hasSize(32); - assertSoftly.assertThat(board).isEqualTo(expectedBoard); + assertSoftly.assertThat(board.getBoard()).hasSize(32); + assertSoftly.assertThat(board.getBoard()).isEqualTo(expectedBoard); }); } diff --git a/src/test/java/janggi/domain/piece/PieceTest.java b/src/test/java/janggi/domain/piece/PieceTest.java index 2240bf9938..0aa985eb11 100644 --- a/src/test/java/janggi/domain/piece/PieceTest.java +++ b/src/test/java/janggi/domain/piece/PieceTest.java @@ -49,7 +49,7 @@ class General { void 궁은_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.GENERAL, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(1, 4), new Position(2, 4), board) @@ -60,7 +60,7 @@ class General { void 궁은_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.GENERAL, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(1, 4), new Position(3, 4), board)) @@ -76,7 +76,7 @@ class Guard { void 사는_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.GUARD, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(0, 3), new Position(0, 4), board) @@ -87,7 +87,7 @@ class Guard { void 사는_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.GUARD, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 3), new Position(0, 5), board)) @@ -103,7 +103,7 @@ class Horse { void 마는_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(0, 1), new Position(2, 2), board) @@ -114,7 +114,7 @@ class Horse { void 마는_이동_경로에_기물이_있으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); - BoardChecker board = new Board(() -> Map.of( + BoardChecker board = new Board(Map.of( new Position(1, 1), new Piece(PieceRule.CHARIOT, Camp.HAN) )); // when & then @@ -128,7 +128,7 @@ class Horse { void 마는_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.HORSE, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 1), new Position(0, 7), board)) @@ -144,7 +144,7 @@ class Elephant { void 상은_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.ELEPHANT, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(0, 6), new Position(3, 4), board) @@ -155,7 +155,7 @@ class Elephant { void 상은_행마법을_따르지_않으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.ELEPHANT, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); //when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 6), new Position(3, 3), board)) @@ -171,7 +171,7 @@ class Cannon { void 포는_이동_경로에_기물이_없으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); //when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(2, 1), new Position(8, 1), board)) @@ -183,7 +183,7 @@ class Cannon { void 포는_이동_경로에_기물이_2개_이상_있으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); - BoardChecker board = new Board(() -> Map.of( + BoardChecker board = new Board(Map.of( new Position(3, 1), new Piece(PieceRule.CHARIOT, Camp.HAN), new Position(5, 1), new Piece(PieceRule.CHARIOT, Camp.CHO) )); @@ -198,7 +198,7 @@ class Cannon { void 포는_이동_경로에_기물이_1개만_있으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.CANNON, Camp.CHO); - BoardChecker board = new Board(() -> Map.of( + BoardChecker board = new Board(Map.of( new Position(3, 1), new Piece(PieceRule.CHARIOT, Camp.HAN) )); // when & then @@ -215,7 +215,7 @@ class Chariot { void 차는_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(0, 0), new Position(9, 0), board) @@ -226,7 +226,7 @@ class Chariot { void 차는_이동_경로에_기물이_1개_이상_있으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); - BoardChecker board = new Board(() -> Map.of( + BoardChecker board = new Board(Map.of( new Position(1, 0), new Piece(PieceRule.CHARIOT, Camp.HAN), new Position(5, 1), new Piece(PieceRule.CHARIOT, Camp.CHO) @@ -242,7 +242,7 @@ class Chariot { void 차는_행마법을_따르지_않으면_예외가_발생한다() { //given Piece piece = new Piece(PieceRule.CHARIOT, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); //when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(0, 0), new Position(3, 3), board)) @@ -258,7 +258,7 @@ class SoldierCho { void 졸은_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(3, 0), new Position(4, 0), board) @@ -269,7 +269,7 @@ class SoldierCho { void 졸은_후진할_시_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(3, 0), new Position(2, 0), board)) @@ -281,7 +281,7 @@ class SoldierCho { void 졸은_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.CHO); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(3, 0), new Position(5, 0), board)) @@ -297,7 +297,7 @@ class SoldierHan { void 병은_이동_경로에_기물이_없으면_정상_이동한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then assertDoesNotThrow(() -> piece.validateMove(new Position(6, 0), new Position(5, 0), board) @@ -308,7 +308,7 @@ class SoldierHan { void 병은_후진할_시_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(6, 0), new Position(7, 0), board)) @@ -320,7 +320,7 @@ class SoldierHan { void 병은_행마법을_따르지_않으면_예외가_발생한다() { // given Piece piece = new Piece(PieceRule.SOLDIER, Camp.HAN); - BoardChecker board = new Board(Map::of); + BoardChecker board = new Board(Map.of()); // when & then Assertions.assertThatThrownBy( () -> piece.validateMove(new Position(6, 0), new Position(1, 0), board)) diff --git a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java index 29f0c8dd76..ea853b4656 100644 --- a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java @@ -4,7 +4,6 @@ import janggi.domain.Position; import janggi.domain.board.Board; -import janggi.domain.board.BoardInitializer; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; @@ -29,10 +28,9 @@ public class EmptyConditionTest { new Position(0, 5) ); Position blockingPosition = new Position(0, 4); - BoardInitializer boardInitializer = () -> Map.of( + Board board = new Board(Map.of( blockingPosition, new Piece(PieceRule.CHARIOT, Camp.HAN) - ); - Board board = new Board(boardInitializer); + )); //when & then assertThatThrownBy(() -> condition.checkPath(path, Camp.HAN, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) @@ -50,10 +48,9 @@ blockingPosition, new Piece(PieceRule.CHARIOT, Camp.HAN) new Position(0, 4) ); Position sameCampPiecePosition = new Position(0, 4); - BoardInitializer boardInitializer = () -> Map.of( + Board board = new Board(Map.of( sameCampPiecePosition, new Piece(PieceRule.CHARIOT, Camp.HAN) - ); - Board board = new Board(boardInitializer); + )); //when & then assertThatThrownBy(() -> condition.checkPath(path, Camp.HAN, board, PieceRule.CHARIOT)) .isInstanceOf(IllegalArgumentException.class) diff --git a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java index dab441bb18..a49707716e 100644 --- a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java +++ b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java @@ -4,7 +4,6 @@ import janggi.domain.Position; import janggi.domain.board.Board; -import janggi.domain.board.BoardInitializer; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceRule; @@ -32,8 +31,7 @@ public class OnePieceExistsConditionTest { ); Camp camp = Camp.HAN; - BoardInitializer boardInitializer = Map::of; - Board board = new Board(boardInitializer); + Board board = new Board(Map.of()); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) @@ -52,12 +50,10 @@ public class OnePieceExistsConditionTest { new Position(0, 5) ); Camp camp = Camp.HAN; - - BoardInitializer boardInitializer = () -> Map.of( + Board board = new Board(Map.of( new Position(0, 3), new Piece(PieceRule.CHARIOT, Camp.HAN), new Position(0, 4), new Piece(PieceRule.ELEPHANT, Camp.HAN) - ); - Board board = new Board(boardInitializer); + )); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) @@ -76,11 +72,9 @@ public class OnePieceExistsConditionTest { new Position(0, 5) ); Camp camp = Camp.HAN; - - BoardInitializer boardInitializer = () -> Map.of( + Board board = new Board(Map.of( new Position(0, 4), new Piece(PieceRule.CANNON, Camp.CHO) - ); - Board board = new Board(boardInitializer); + )); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) @@ -99,12 +93,10 @@ public class OnePieceExistsConditionTest { new Position(0, 5) ); Camp camp = Camp.HAN; - - BoardInitializer boardInitializer = () -> Map.of( + Board board = new Board(Map.of( new Position(0, 3), new Piece(PieceRule.SOLDIER, Camp.HAN), new Position(0, 5), new Piece(PieceRule.CHARIOT, Camp.HAN) - ); - Board board = new Board(boardInitializer); + )); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) @@ -123,12 +115,10 @@ public class OnePieceExistsConditionTest { new Position(0, 5) ); Camp camp = Camp.HAN; - - BoardInitializer boardInitializer = () -> Map.of( + Board board = new Board(Map.of( new Position(0, 3), new Piece(PieceRule.SOLDIER, Camp.HAN), new Position(0, 5), new Piece(PieceRule.CANNON, Camp.CHO) - ); - Board board = new Board(boardInitializer); + )); //when & then assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) .isInstanceOf(IllegalArgumentException.class) From 038079bde0f0b2b16699d86af7fd899b3c182a79 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Wed, 1 Apr 2026 16:01:59 +0900 Subject: [PATCH 62/68] =?UTF-8?q?refactor:=20elephantSetUpFormat=EC=97=90?= =?UTF-8?q?=EC=84=9C=20ElephantFormation=20=EC=83=9D=EC=84=B1=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiGame.java | 11 ++++------- .../java/janggi/domain/board/ElephantFormation.java | 2 -- src/main/java/janggi/dto/CampDto.java | 1 - src/main/java/janggi/dto/PiecePositionDto.java | 2 +- src/main/java/janggi/view/InputView.java | 2 +- src/main/java/janggi/view/OutputView.java | 2 +- .../java/janggi/view/format/ElephantSetUpFormat.java | 6 ++++-- 7 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index ac7275ae29..1a9c7a6d7c 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -4,7 +4,6 @@ import janggi.domain.Turn; import janggi.domain.board.Board; import janggi.domain.board.ElephantFormation; -import janggi.domain.board.ElephantSetUp; import janggi.domain.board.InitialPiecePlacement; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; @@ -31,13 +30,11 @@ private Board createBoard() { return InitialPiecePlacement.initialize(hanElephantFormation, choElephantFormation); } - private static ElephantFormation readElephantFormation(Camp camp) { + private ElephantFormation readElephantFormation(Camp camp) { return RetryHandler.retryOnInvalidInput(() -> { - ElephantSetUpFormat elephantSetUpFormat = InputView.readElephantSettingCommand(CampDto.from(camp)); - ElephantSetUp elephantSetUp = elephantSetUpFormat.getElephantSetUp(); - return new ElephantFormation(camp, elephantSetUp); - } - ); + ElephantSetUpFormat elephantSetUpFormat = InputView.readElephantSettingCommand(CampDto.from(camp)); + return elephantSetUpFormat.toElephantFormation(camp); + }); } private List toPiecePositions(Map boardState) { diff --git a/src/main/java/janggi/domain/board/ElephantFormation.java b/src/main/java/janggi/domain/board/ElephantFormation.java index 86fb4df30f..f400ba11df 100644 --- a/src/main/java/janggi/domain/board/ElephantFormation.java +++ b/src/main/java/janggi/domain/board/ElephantFormation.java @@ -37,6 +37,4 @@ private Map createByCamp(List settingCols) { } return map; } - - } diff --git a/src/main/java/janggi/dto/CampDto.java b/src/main/java/janggi/dto/CampDto.java index 7e51adbbb6..3a72116101 100644 --- a/src/main/java/janggi/dto/CampDto.java +++ b/src/main/java/janggi/dto/CampDto.java @@ -7,7 +7,6 @@ public record CampDto( String name, String color ) { - public static CampDto from(Camp camp) { CampFormat campFormat = CampFormat.from(camp); return new CampDto(campFormat.getName(), campFormat.getColor()); diff --git a/src/main/java/janggi/dto/PiecePositionDto.java b/src/main/java/janggi/dto/PiecePositionDto.java index 5572c56781..6e396b7e5a 100644 --- a/src/main/java/janggi/dto/PiecePositionDto.java +++ b/src/main/java/janggi/dto/PiecePositionDto.java @@ -6,7 +6,7 @@ public record PiecePositionDto( int row, - int col, + int column, String type, CampDto camp ) { diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index 82bd3e7c57..18cbb93296 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -17,7 +17,7 @@ private InputView() { } public static ElephantSetUpFormat readElephantSettingCommand(CampDto campDto) { - System.out.println("%s나라의 상차림을 선택해주세요.".formatted(campDto.name())); + System.out.println(LINE_SEPARATOR + "%s나라의 상차림을 선택해주세요.".formatted(campDto.name())); for (ElephantSetUpFormat elephantSetUpFormat : ElephantSetUpFormat.values()) { System.out.println(elephantSetUpFormat.getCommand() + ". " + elephantSetUpFormat.getDescription()); } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index 313979a8f9..512b1817ad 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -55,7 +55,7 @@ private static String renderHeader() { private static void applyPieces(String[][] board, List piecePositions) { for (PiecePositionDto piecePosition : piecePositions) { - board[piecePosition.row()][piecePosition.col()] = colorize(piecePosition); + board[piecePosition.row()][piecePosition.column()] = colorize(piecePosition); } } diff --git a/src/main/java/janggi/view/format/ElephantSetUpFormat.java b/src/main/java/janggi/view/format/ElephantSetUpFormat.java index f8fe82a482..516da371c9 100644 --- a/src/main/java/janggi/view/format/ElephantSetUpFormat.java +++ b/src/main/java/janggi/view/format/ElephantSetUpFormat.java @@ -1,6 +1,8 @@ package janggi.view.format; +import janggi.domain.board.ElephantFormation; import janggi.domain.board.ElephantSetUp; +import janggi.domain.piece.Camp; import janggi.exception.ExceptionMessage; import java.util.Arrays; @@ -37,7 +39,7 @@ public String getDescription() { return description; } - public ElephantSetUp getElephantSetUp() { - return elephantSetUp; + public ElephantFormation toElephantFormation(Camp camp) { + return new ElephantFormation(camp, elephantSetUp); } } From 121f2b8f24e29f11450dc4f14184df757ce61bd2 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Wed, 1 Apr 2026 16:48:46 +0900 Subject: [PATCH 63/68] =?UTF-8?q?refactor:=20CampDto=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiGame.java | 15 +++------------ src/main/java/janggi/dto/CampDto.java | 14 -------------- src/main/java/janggi/dto/PiecePositionDto.java | 5 +++-- src/main/java/janggi/view/InputView.java | 13 ++++++++----- src/main/java/janggi/view/OutputView.java | 4 +++- 5 files changed, 17 insertions(+), 34 deletions(-) delete mode 100644 src/main/java/janggi/dto/CampDto.java diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index 1a9c7a6d7c..f64b87a72e 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -7,7 +7,6 @@ import janggi.domain.board.InitialPiecePlacement; import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; -import janggi.dto.CampDto; import janggi.dto.PiecePositionDto; import janggi.util.RetryHandler; import janggi.view.InputView; @@ -32,7 +31,7 @@ private Board createBoard() { private ElephantFormation readElephantFormation(Camp camp) { return RetryHandler.retryOnInvalidInput(() -> { - ElephantSetUpFormat elephantSetUpFormat = InputView.readElephantSettingCommand(CampDto.from(camp)); + ElephantSetUpFormat elephantSetUpFormat = InputView.readElephantSettingCommand(camp); return elephantSetUpFormat.toElephantFormation(camp); }); } @@ -53,18 +52,10 @@ private void play(Board board) { private void playTurn(Board board, Turn turn) { Camp camp = turn.currentTurn(); - Position source = readSource(board, camp); + Position source = RetryHandler.retryOnInvalidInput(() -> Position.from(InputView.readSource(camp))); + board.validateCampTurn(source, camp); Position destination = RetryHandler.retryOnInvalidInput(() -> Position.from(InputView.readDestination())); board.movePiece(source, destination, camp); turn.finishTurn(); } - - private Position readSource(Board board, Camp camp) { - return RetryHandler.retryOnInvalidInput(() -> { - List rawPosition = InputView.readSource(CampDto.from(camp)); - Position source = Position.from(rawPosition); - board.validateCampTurn(source, camp); - return source; - }); - } } diff --git a/src/main/java/janggi/dto/CampDto.java b/src/main/java/janggi/dto/CampDto.java deleted file mode 100644 index 3a72116101..0000000000 --- a/src/main/java/janggi/dto/CampDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package janggi.dto; - -import janggi.domain.piece.Camp; -import janggi.view.format.CampFormat; - -public record CampDto( - String name, - String color -) { - public static CampDto from(Camp camp) { - CampFormat campFormat = CampFormat.from(camp); - return new CampDto(campFormat.getName(), campFormat.getColor()); - } -} diff --git a/src/main/java/janggi/dto/PiecePositionDto.java b/src/main/java/janggi/dto/PiecePositionDto.java index 6e396b7e5a..25726e279d 100644 --- a/src/main/java/janggi/dto/PiecePositionDto.java +++ b/src/main/java/janggi/dto/PiecePositionDto.java @@ -1,6 +1,7 @@ package janggi.dto; import janggi.domain.Position; +import janggi.domain.piece.Camp; import janggi.domain.piece.Piece; import janggi.view.format.PieceFormat; @@ -8,7 +9,7 @@ public record PiecePositionDto( int row, int column, String type, - CampDto camp + Camp camp ) { public static PiecePositionDto of(Position position, Piece piece) { PieceFormat pieceFormat = PieceFormat.from(piece); @@ -16,7 +17,7 @@ public static PiecePositionDto of(Position position, Piece piece) { position.row(), position.column(), pieceFormat.getFormat(), - CampDto.from(piece.camp()) + piece.camp() ); } } diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index 18cbb93296..d05949887a 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -1,8 +1,9 @@ package janggi.view; -import janggi.dto.CampDto; +import janggi.domain.piece.Camp; import janggi.exception.ExceptionMessage; import janggi.util.Parser; +import janggi.view.format.CampFormat; import janggi.view.format.ElephantSetUpFormat; import java.util.List; import java.util.Scanner; @@ -16,16 +17,18 @@ public final class InputView { private InputView() { } - public static ElephantSetUpFormat readElephantSettingCommand(CampDto campDto) { - System.out.println(LINE_SEPARATOR + "%s나라의 상차림을 선택해주세요.".formatted(campDto.name())); + public static ElephantSetUpFormat readElephantSettingCommand(Camp camp) { + CampFormat campFormat = CampFormat.from(camp); + System.out.println(LINE_SEPARATOR + "%s나라의 상차림을 선택해주세요.".formatted(campFormat.getName())); for (ElephantSetUpFormat elephantSetUpFormat : ElephantSetUpFormat.values()) { System.out.println(elephantSetUpFormat.getCommand() + ". " + elephantSetUpFormat.getDescription()); } return ElephantSetUpFormat.findElephantSettingBy(readLine()); } - public static List readSource(CampDto campDto) { - System.out.println(LINE_SEPARATOR + "%s나라 차례 입니다.".formatted(campDto.name())); + public static List readSource(Camp camp) { + CampFormat campFormat = CampFormat.from(camp); + System.out.println(LINE_SEPARATOR + "%s나라 차례 입니다.".formatted(campFormat.getName())); System.out.println("공격할 기물의 좌표를 행,열 순으로 입력해 주세요. (예: 9,8)"); return Parser.parseByDelimiter(DELIMITER, readLine()); } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index 512b1817ad..137e985c13 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -3,6 +3,7 @@ import static java.util.stream.Collectors.joining; import janggi.dto.PiecePositionDto; +import janggi.view.format.CampFormat; import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; @@ -70,7 +71,8 @@ private static String renderRow(int row, String[] cells) { } private static String colorize(PiecePositionDto piecePosition) { - return piecePosition.camp().color() + piecePosition.type() + RESET_COLOR; + CampFormat campFormat = CampFormat.from(piecePosition.camp()); + return campFormat.getColor() + piecePosition.type() + RESET_COLOR; } private static String fullWidthNumber(int number) { From 2b06742a534e0db7010b2575b4a44304abffd607 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Wed, 1 Apr 2026 18:28:45 +0900 Subject: [PATCH 64/68] =?UTF-8?q?refactor:=20=EC=B6=9C=EB=B0=9C=EC=A7=80,?= =?UTF-8?q?=20=EB=AA=A9=EC=A0=81=EC=A7=80=20=EA=B2=80=EC=A6=9D=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiGame.java | 21 ++++++++-- src/main/java/janggi/domain/board/Board.java | 38 +++++++++++-------- .../janggi/domain/board/BoardChecker.java | 2 +- .../piece/condition/EmptyCondition.java | 2 +- .../condition/OnePieceExistsCondition.java | 2 +- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/main/java/janggi/JanggiGame.java b/src/main/java/janggi/JanggiGame.java index f64b87a72e..dbb2f8ce31 100644 --- a/src/main/java/janggi/JanggiGame.java +++ b/src/main/java/janggi/JanggiGame.java @@ -52,10 +52,25 @@ private void play(Board board) { private void playTurn(Board board, Turn turn) { Camp camp = turn.currentTurn(); - Position source = RetryHandler.retryOnInvalidInput(() -> Position.from(InputView.readSource(camp))); - board.validateCampTurn(source, camp); - Position destination = RetryHandler.retryOnInvalidInput(() -> Position.from(InputView.readDestination())); + Position source = readSource(board, camp); + Position destination = readDestination(board, source, camp); board.movePiece(source, destination, camp); turn.finishTurn(); } + + private Position readSource(Board board, Camp camp) { + return RetryHandler.retryOnInvalidInput(() -> { + Position source = Position.from(InputView.readSource(camp)); + board.validateSource(source, camp); + return source; + }); + } + + private Position readDestination(Board board, Position source, Camp camp) { + return RetryHandler.retryOnInvalidInput(() -> { + Position destination = Position.from(InputView.readDestination()); + board.validateDestination(destination, source, camp); + return destination; + }); + } } diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 75e0779566..013900d4cf 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -22,41 +22,49 @@ public boolean hasPieceAt(Position position) { } @Override - public boolean isSameCampPieceAt(Position position, Camp camp) { - if (board.containsKey(position)) { - return board.get(position).isSameCamp(camp); + public boolean hasSameCampPieceAt(Position position, Camp camp) { + if (hasPieceAt(position)) { + Piece piece = board.get(position); + return piece.isSameCamp(camp); } return false; } @Override public boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule) { - if (board.containsKey(position)) { - Piece foundPiece = board.get(position); - return foundPiece.isSamePieceRule(pieceRule); + if (hasPieceAt(position)) { + Piece piece = board.get(position); + return piece.isSamePieceRule(pieceRule); } return false; } - public void movePiece(Position source, Position destination, Camp turn) { - validateCampTurn(source, turn); + public void movePiece(Position source, Position destination, Camp camp) { + validateSource(source, camp); + validateDestination(destination, source, camp); + Piece piece = board.get(source); piece.validateMove(source, destination, this); + board.put(destination, piece); board.remove(source); } - public void validateCampTurn(Position source, Camp turn) { - validateSource(source); - Piece piece = board.get(source); - if (!piece.isSameCamp(turn)) { + public void validateSource(Position source, Camp camp) { + if (!hasPieceAt(source)) { + throw new IllegalArgumentException(ExceptionMessage.SOURCE_NOT_EXISTS.getMessage()); + } + if (!hasSameCampPieceAt(source, camp)) { throw new IllegalArgumentException(ExceptionMessage.INVALID_CAMP_PIECE.getMessage()); } } - private void validateSource(Position source) { - if (!board.containsKey(source)) { - throw new IllegalArgumentException(ExceptionMessage.SOURCE_NOT_EXISTS.getMessage()); + public void validateDestination(Position destination, Position source, Camp camp) { + if (destination.equals(source)) { + throw new IllegalArgumentException(ExceptionMessage.PIECE_MUST_MOVE.getMessage()); + } + if (hasSameCampPieceAt(destination, camp)) { + throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } } diff --git a/src/main/java/janggi/domain/board/BoardChecker.java b/src/main/java/janggi/domain/board/BoardChecker.java index 9937109b3c..1805f496ee 100644 --- a/src/main/java/janggi/domain/board/BoardChecker.java +++ b/src/main/java/janggi/domain/board/BoardChecker.java @@ -8,7 +8,7 @@ public interface BoardChecker { boolean hasPieceAt(Position position); - boolean isSameCampPieceAt(Position position, Camp camp); + boolean hasSameCampPieceAt(Position position, Camp camp); boolean hasSamePieceRuleAt(Position position, PieceRule pieceRule); } diff --git a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java index e2d9c129ce..f83a5efba0 100644 --- a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java +++ b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java @@ -24,7 +24,7 @@ private void validateEmptyPosition(Position position, BoardChecker board) { } private void validateDestination(Position destination, Camp camp, BoardChecker board) { - if (board.isSameCampPieceAt(destination, camp)) { + if (board.hasSameCampPieceAt(destination, camp)) { throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } } diff --git a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java index 7fde51d0b9..bc8291afae 100644 --- a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java +++ b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java @@ -44,7 +44,7 @@ private void validateExactPieceCount(int countOfPiece) { } private void validateDestination(Position destination, Camp camp, BoardChecker board, PieceRule pieceRule) { - if (board.isSameCampPieceAt(destination, camp)) { + if (board.hasSameCampPieceAt(destination, camp)) { throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); } From d8188fc50aea29688120485ae785e1c006d5e47e Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Wed, 1 Apr 2026 21:03:46 +0900 Subject: [PATCH 65/68] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B1=B0=EB=A6=AC=20=EA=B5=AC=ED=95=98=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../janggi/domain/piece/strategy/DirectionInformation.java | 4 ++++ .../java/janggi/domain/piece/strategy/SingleStepStrategy.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java index 759618314f..ade16be26f 100644 --- a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java +++ b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java @@ -8,6 +8,10 @@ public DirectionInformation(Position source, Position destination) { this(destination.calculateRowDistance(source), destination.calculateColumnDistance(source)); } + public int calculateDistance() { + return calculateAbsRowDifference() + calculateAbsColDifference(); + } + public int calculateAbsRowDifference() { return Math.abs(rowDifference); } diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java index 80e9788d05..59de74d8ba 100644 --- a/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java @@ -29,7 +29,7 @@ public List findPath(Position source, Position destination, Camp camp) } private void validateMovement(DirectionInformation directionInfo) { - if (directionInfo.calculateAbsRowDifference() + directionInfo.calculateAbsColDifference() != SINGLE_STEP_DISTANCE) { + if (directionInfo.calculateDistance() != SINGLE_STEP_DISTANCE) { throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } From 619d82b6ed0932e2c87d37dc1bf9b8d2247d23d1 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Thu, 2 Apr 2026 13:37:23 +0900 Subject: [PATCH 66/68] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EC=A0=84=EB=9E=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/piece/Camp.java | 8 +-- .../piece/strategy/DiagonalStepStrategy.java | 54 ++++++++----------- .../piece/strategy/DirectionInformation.java | 30 ++++++----- .../piece/strategy/SingleStepStrategy.java | 21 +++++--- .../piece/strategy/SlidingStrategy.java | 45 ++++------------ .../java/janggi/domain/piece/CampTest.java | 12 ++--- .../strategy/DirectionInformationTest.java | 24 +++++++++ .../piece/strategy/SlidingStrategyTest.java | 11 ---- 8 files changed, 93 insertions(+), 112 deletions(-) diff --git a/src/main/java/janggi/domain/piece/Camp.java b/src/main/java/janggi/domain/piece/Camp.java index e9fced13d5..d0326ba964 100644 --- a/src/main/java/janggi/domain/piece/Camp.java +++ b/src/main/java/janggi/domain/piece/Camp.java @@ -1,7 +1,5 @@ package janggi.domain.piece; -import janggi.exception.ExceptionMessage; - public enum Camp { HAN(-1, 9), @@ -16,10 +14,8 @@ public enum Camp { this.startRowPosition = startRowPosition; } - public void validateForwardDirection(int rowDirection) { - if (forwardDirection != rowDirection && rowDirection != 0) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); - } + public boolean matchesForwardDirection(int direction) { + return forwardDirection == direction; } public int getStartRowPosition() { diff --git a/src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java b/src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java index 3714fe0f59..42427bb169 100644 --- a/src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java @@ -18,50 +18,40 @@ public DiagonalStepStrategy(int diagonalDistance) { @Override public List findPath(Position source, Position destination, Camp camp) { - DirectionInformation directionInfo = new DirectionInformation(source, destination); + DirectionInformation direction = new DirectionInformation(source, destination); + validateMovement(direction); - validateMovement(directionInfo); + Position first = moveStraightStep(source, direction); - if (directionInfo.isRowBiggerThanCol()) { - return createRowFirstPath(source, directionInfo); - } - return createColFirstPath(source, directionInfo); - } - - private void validateMovement(DirectionInformation directionInfo) { - int absRowDiff = directionInfo.calculateAbsRowDifference(); - int absColDiff = directionInfo.calculateAbsColDifference(); - int longDistance = STRAIGHT_DISTANCE + diagonalDistance; - int shortDistance = diagonalDistance; + List path = new ArrayList<>(); + path.add(first); + path.addAll(moveDiagonal(first, direction)); - boolean isValid = (absRowDiff == shortDistance && absColDiff == longDistance) - || (absRowDiff == longDistance && absColDiff == shortDistance); + return path; + } - if (!isValid) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, diagonalDistance)); + private void validateMovement(DirectionInformation direction) { + if (!direction.hasAbsDifferences(diagonalDistance, STRAIGHT_DISTANCE + diagonalDistance)) { + throw new IllegalArgumentException( + ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, diagonalDistance) + ); } } - private List createRowFirstPath(Position source, DirectionInformation directionInfo) { - List path = new ArrayList<>(); - source = source.moveRow(directionInfo.calculateRowDirection()); - path.add(source); - path.addAll(moveDiagonal(source, directionInfo)); - return path; + private Position moveStraightStep(Position source, DirectionInformation direction) { + if (direction.isRowBiggerThanCol()) { + return source.moveRow(direction.calculateRowDirection()); + } + return source.moveCol(direction.calculateColDirection()); } - private List createColFirstPath(Position source, DirectionInformation directionInfo) { + private List moveDiagonal(Position source, DirectionInformation direction) { List path = new ArrayList<>(); - source = source.moveCol(directionInfo.calculateColDirection()); - path.add(source); - path.addAll(moveDiagonal(source, directionInfo)); - return path; - } + int rowDirection = direction.calculateRowDirection(); + int colDirection = direction.calculateColDirection(); - private List moveDiagonal(Position source, DirectionInformation directionInfo) { - List path = new ArrayList<>(); for (int i = 0; i < diagonalDistance; i++) { - source = source.moveDiagonal(directionInfo.calculateRowDirection(), directionInfo.calculateColDirection()); + source = source.moveDiagonal(rowDirection, colDirection); path.add(source); } return path; diff --git a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java index ade16be26f..ff41e9cc74 100644 --- a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java +++ b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java @@ -8,18 +8,6 @@ public DirectionInformation(Position source, Position destination) { this(destination.calculateRowDistance(source), destination.calculateColumnDistance(source)); } - public int calculateDistance() { - return calculateAbsRowDifference() + calculateAbsColDifference(); - } - - public int calculateAbsRowDifference() { - return Math.abs(rowDifference); - } - - public int calculateAbsColDifference() { - return Math.abs(colDifference); - } - public int calculateRowDirection() { if (rowDifference == 0) { return 0; @@ -34,11 +22,25 @@ public int calculateColDirection() { return colDifference / Math.abs(colDifference); } + public int calculateDistance() { + return Math.abs(rowDifference) + Math.abs(colDifference); + } + public boolean isRowBiggerThanCol() { return Math.abs(rowDifference) > Math.abs(colDifference); } - public int addAllDifference() { - return rowDifference + colDifference; + public boolean isHorizontal() { + return rowDifference == 0; + } + + public boolean isVertical() { + return colDifference == 0; + } + + public boolean hasAbsDifferences(int difference1, int difference2) { + int absRow = Math.abs(rowDifference); + int absCol = Math.abs(colDifference); + return (absRow == difference1 && absCol == difference2) || (absRow == difference2 && absCol == difference1); } } diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java index 59de74d8ba..0fcb2c8eb6 100644 --- a/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java @@ -17,19 +17,26 @@ public SingleStepStrategy(boolean forwardOnly) { @Override public List findPath(Position source, Position destination, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(source, destination); + DirectionInformation direction = new DirectionInformation(source, destination); + validateMovement(direction); if (forwardOnly) { - camp.validateForwardDirection(directionInformation.calculateRowDirection()); - validateMovement(directionInformation); - return List.of(destination); + validateForwardDirection(direction.calculateRowDirection(), camp); } - validateMovement(directionInformation); return List.of(destination); } - private void validateMovement(DirectionInformation directionInfo) { - if (directionInfo.calculateDistance() != SINGLE_STEP_DISTANCE) { + private void validateForwardDirection(int rowDirection, Camp camp) { + boolean isRowMove = rowDirection != 0; + boolean isForward = camp.matchesForwardDirection(rowDirection); + + if (isRowMove && !isForward) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); + } + } + + private void validateMovement(DirectionInformation direction) { + if (direction.calculateDistance() != SINGLE_STEP_DISTANCE) { throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } } diff --git a/src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java b/src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java index 27a8590856..d2e7d66ae6 100644 --- a/src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java @@ -5,6 +5,7 @@ import janggi.exception.ExceptionMessage; import java.util.ArrayList; import java.util.List; +import java.util.function.BiFunction; public class SlidingStrategy implements MoveStrategy { @@ -12,48 +13,24 @@ public class SlidingStrategy implements MoveStrategy { public List findPath(Position source, Position destination, Camp camp) { DirectionInformation directionInformation = new DirectionInformation(source, destination); - validateStraightMove(directionInformation); - - if (directionInformation.isRowBiggerThanCol()) { - return createRowPath(source, directionInformation.rowDifference()); - } - return createColumnPath(source, directionInformation.colDifference()); - } - - private void validateStraightMove(DirectionInformation directionInformation) { - int sum = directionInformation.addAllDifference(); - int rowDifference = directionInformation.rowDifference(); - int colDifference = directionInformation.colDifference(); - - if (sum != rowDifference && sum != colDifference) { - throw new IllegalArgumentException(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); + if (directionInformation.isHorizontal()) { + return createPath(source, directionInformation.colDifference(), Position::moveCol); } - - if (rowDifference == 0 && colDifference == 0) { - throw new IllegalArgumentException(ExceptionMessage.PIECE_MUST_MOVE.getMessage()); + if (directionInformation.isVertical()) { + return createPath(source, directionInformation.rowDifference(), Position::moveRow); } + throw new IllegalArgumentException(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); } - private List createRowPath(Position source, int rowDifference) { + private List createPath(Position source, int difference, BiFunction move) { List path = new ArrayList<>(); - int rowDirection = rowDifference / Math.abs(rowDifference); - while (rowDifference != 0) { - source = source.moveRow(rowDirection); - path.add(source); - rowDifference -= rowDirection; - } - return path; - } - - private List createColumnPath(Position source, int colDifference) { - List path = new ArrayList<>(); + int direction = Integer.signum(difference); + int distance = Math.abs(difference); - int columnDirection = colDifference / Math.abs(colDifference); - while (colDifference != 0) { - source = source.moveCol(columnDirection); + for (int i = 0; i < distance; i++) { + source = move.apply(source, direction); path.add(source); - colDifference -= columnDirection; } return path; } diff --git a/src/test/java/janggi/domain/piece/CampTest.java b/src/test/java/janggi/domain/piece/CampTest.java index 8bb9470bec..686961477a 100644 --- a/src/test/java/janggi/domain/piece/CampTest.java +++ b/src/test/java/janggi/domain/piece/CampTest.java @@ -1,9 +1,7 @@ package janggi.domain.piece; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import janggi.exception.ExceptionMessage; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -23,12 +21,10 @@ class CampTest { @ParameterizedTest @CsvSource({ - "CHO, -1", - "HAN, 1" + "CHO, 1", + "HAN, -1" }) - void 후진하려고하면_예외가_발생한다(Camp camp, int rowDirection) { - assertThatThrownBy(() -> camp.validateForwardDirection(rowDirection)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); + void 전진_방향이_일치하는지_확인한다(Camp camp, int rowDirection) { + assertThat(camp.matchesForwardDirection(rowDirection)).isTrue(); } } diff --git a/src/test/java/janggi/domain/piece/strategy/DirectionInformationTest.java b/src/test/java/janggi/domain/piece/strategy/DirectionInformationTest.java index 0b854bf018..b5f2137124 100644 --- a/src/test/java/janggi/domain/piece/strategy/DirectionInformationTest.java +++ b/src/test/java/janggi/domain/piece/strategy/DirectionInformationTest.java @@ -36,4 +36,28 @@ class DirectionInformationTest { // then assertThat(result).isEqualTo(expectedResult); } + + @ParameterizedTest + @CsvSource({ + "2, 3, 2, 3, true", + "2, 3, 3, 2, true", + "-2, 3, 2, 3, true", + "2, -3, 3, 2, true", + "1, 2, 1, 2, true", + "1, 2, 2, 1, true", + "1, 2, 1, 3, false", + "1, 2, 3, 2, false" + }) + void 두_차이값이_행열_절대_차이값과_일치하는지_확인한다( + int rowDifference, int colDifference, + int difference1, int difference2, + boolean expectedResult + ) { + // given + DirectionInformation direction = new DirectionInformation(rowDifference, colDifference); + // when + boolean result = direction.hasAbsDifferences(difference1, difference2); + // then + assertThat(result).isEqualTo(expectedResult); + } } diff --git a/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java index 07b8b54c20..b40844ac4a 100644 --- a/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java @@ -37,15 +37,4 @@ class SlidingStrategyTest { .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); } - - @Test - void 제자리에_있으면_예외가_발생한다() { - //given - Position source = new Position(0, 0); - Position destination = new Position(0, 0); - //when & then - assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.PIECE_MUST_MOVE.getMessage()); - } } From 5518268ce248e736aebee3f41dbff30524c23053 Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Thu, 2 Apr 2026 20:07:30 +0900 Subject: [PATCH 67/68] =?UTF-8?q?refactor:=20=ED=96=89=EB=A7=88=EB=B2=95?= =?UTF-8?q?=20=EC=A0=84=EB=9E=B5=20=EC=B6=94=EC=83=81=ED=99=94=20=EC=88=98?= =?UTF-8?q?=EC=A4=80=20=EB=8F=99=EC=9D=BC=ED=95=98=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Position.java | 12 +- src/main/java/janggi/domain/piece/Piece.java | 4 +- .../java/janggi/domain/piece/PieceRule.java | 43 +++--- .../piece/condition/EmptyCondition.java | 31 ---- .../domain/piece/condition/MoveCondition.java | 12 -- .../condition/OnePieceExistsCondition.java | 55 ------- .../domain/piece/strategy/BaseMoveRule.java | 22 +++ ...tepStrategy.java => DiagonalStepRule.java} | 31 ++-- .../piece/strategy/DirectionInformation.java | 26 ++-- .../piece/strategy/EmptySlidingRule.java | 17 +++ .../piece/strategy/JumpingSlidingRule.java | 60 ++++++++ .../domain/piece/strategy/MoveRule.java | 11 ++ .../domain/piece/strategy/MoveStrategy.java | 10 -- ...eStepStrategy.java => SingleStepRule.java} | 24 +-- ...{SlidingStrategy.java => SlidingRule.java} | 13 +- .../piece/condition/EmptyConditionTest.java | 59 -------- .../OnePieceExistsConditionTest.java | 127 ---------------- .../piece/strategy/DiagonalStepRuleTest.java | 138 ++++++++++++++++++ .../strategy/DiagonalStepStrategyTest.java | 123 ---------------- .../piece/strategy/EmptySlidingRuleTest.java | 54 +++++++ .../strategy/JumpingSlidingRuleTest.java | 89 +++++++++++ ...ategyTest.java => SingleStepRuleTest.java} | 28 ++-- .../piece/strategy/SlidingStrategyTest.java | 40 ----- 23 files changed, 472 insertions(+), 557 deletions(-) delete mode 100644 src/main/java/janggi/domain/piece/condition/EmptyCondition.java delete mode 100644 src/main/java/janggi/domain/piece/condition/MoveCondition.java delete mode 100644 src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java create mode 100644 src/main/java/janggi/domain/piece/strategy/BaseMoveRule.java rename src/main/java/janggi/domain/piece/strategy/{DiagonalStepStrategy.java => DiagonalStepRule.java} (74%) create mode 100644 src/main/java/janggi/domain/piece/strategy/EmptySlidingRule.java create mode 100644 src/main/java/janggi/domain/piece/strategy/JumpingSlidingRule.java create mode 100644 src/main/java/janggi/domain/piece/strategy/MoveRule.java delete mode 100644 src/main/java/janggi/domain/piece/strategy/MoveStrategy.java rename src/main/java/janggi/domain/piece/strategy/{SingleStepStrategy.java => SingleStepRule.java} (73%) rename src/main/java/janggi/domain/piece/strategy/{SlidingStrategy.java => SlidingRule.java} (73%) delete mode 100644 src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java delete mode 100644 src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java create mode 100644 src/test/java/janggi/domain/piece/strategy/DiagonalStepRuleTest.java delete mode 100644 src/test/java/janggi/domain/piece/strategy/DiagonalStepStrategyTest.java create mode 100644 src/test/java/janggi/domain/piece/strategy/EmptySlidingRuleTest.java create mode 100644 src/test/java/janggi/domain/piece/strategy/JumpingSlidingRuleTest.java rename src/test/java/janggi/domain/piece/strategy/{SingleStepStrategyTest.java => SingleStepRuleTest.java} (77%) delete mode 100644 src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index 35e8b33d2f..c2d4417c7f 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -46,15 +46,15 @@ public int calculateColumnDistance(Position other) { return column - other.column; } - public Position moveRow(int direction) { - return new Position(row + direction, column); + public Position moveRow(int distance) { + return new Position(row + distance, column); } - public Position moveCol(int direction) { - return new Position(row, column + direction); + public Position moveCol(int distance) { + return new Position(row, column + distance); } - public Position moveDiagonal(int rowDirection, int colDirection) { - return new Position(row + rowDirection, column + colDirection); + public Position moveDiagonal(int rowDistance, int colDistance) { + return new Position(row + rowDistance, column + colDistance); } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 78f328baef..5aa846a9a4 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -2,13 +2,11 @@ import janggi.domain.Position; import janggi.domain.board.BoardChecker; -import java.util.List; public record Piece(PieceRule pieceRule, Camp camp) { public void validateMove(Position source, Position destination, BoardChecker board) { - List path = pieceRule.findPath(source, destination, camp); - pieceRule.checkPath(path, camp, board); + pieceRule.validateMove(source, destination, camp, board); } public boolean isSamePieceRule(PieceRule pieceRule) { diff --git a/src/main/java/janggi/domain/piece/PieceRule.java b/src/main/java/janggi/domain/piece/PieceRule.java index 7d10f1c1e4..ef41808015 100644 --- a/src/main/java/janggi/domain/piece/PieceRule.java +++ b/src/main/java/janggi/domain/piece/PieceRule.java @@ -2,38 +2,29 @@ import janggi.domain.Position; import janggi.domain.board.BoardChecker; -import janggi.domain.piece.condition.EmptyCondition; -import janggi.domain.piece.condition.MoveCondition; -import janggi.domain.piece.condition.OnePieceExistsCondition; -import janggi.domain.piece.strategy.DiagonalStepStrategy; -import janggi.domain.piece.strategy.MoveStrategy; -import janggi.domain.piece.strategy.SingleStepStrategy; -import janggi.domain.piece.strategy.SlidingStrategy; -import java.util.List; +import janggi.domain.piece.strategy.DiagonalStepRule; +import janggi.domain.piece.strategy.EmptySlidingRule; +import janggi.domain.piece.strategy.JumpingSlidingRule; +import janggi.domain.piece.strategy.MoveRule; +import janggi.domain.piece.strategy.SingleStepRule; public enum PieceRule { - GENERAL(new SingleStepStrategy(false), new EmptyCondition()), - CHARIOT(new SlidingStrategy(), new EmptyCondition()), - HORSE(new DiagonalStepStrategy(1), new EmptyCondition()), - CANNON(new SlidingStrategy(), new OnePieceExistsCondition()), - GUARD(new SingleStepStrategy(false), new EmptyCondition()), - ELEPHANT(new DiagonalStepStrategy(2), new EmptyCondition()), - SOLDIER(new SingleStepStrategy(true), new EmptyCondition()); + GENERAL(new SingleStepRule(false)), + CHARIOT(new EmptySlidingRule()), + HORSE(new DiagonalStepRule(1)), + CANNON(new JumpingSlidingRule()), + GUARD(new SingleStepRule(false)), + ELEPHANT(new DiagonalStepRule(2)), + SOLDIER(new SingleStepRule(true)); - private final MoveStrategy moveStrategy; - private final MoveCondition moveCondition; + private final MoveRule moveRule; - PieceRule(MoveStrategy moveStrategy, MoveCondition moveCondition) { - this.moveStrategy = moveStrategy; - this.moveCondition = moveCondition; + PieceRule(MoveRule moveRule) { + this.moveRule = moveRule; } - public List findPath(Position source, Position destination, Camp camp) { - return moveStrategy.findPath(source, destination, camp); - } - - public void checkPath(List path, Camp camp, BoardChecker board) { - moveCondition.checkPath(path, camp, board, this); + public void validateMove(Position source, Position destination, Camp camp, BoardChecker board) { + moveRule.validate(source, destination, camp, board, this); } } diff --git a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java b/src/main/java/janggi/domain/piece/condition/EmptyCondition.java deleted file mode 100644 index f83a5efba0..0000000000 --- a/src/main/java/janggi/domain/piece/condition/EmptyCondition.java +++ /dev/null @@ -1,31 +0,0 @@ -package janggi.domain.piece.condition; - -import janggi.domain.Position; -import janggi.domain.board.BoardChecker; -import janggi.domain.piece.Camp; -import janggi.domain.piece.PieceRule; -import janggi.exception.ExceptionMessage; -import java.util.List; - -public class EmptyCondition implements MoveCondition { - - @Override - public void checkPath(List path, Camp camp, BoardChecker board, PieceRule pieceRule) { - for (int i = 0; i < path.size() - 1; i++) { - validateEmptyPosition(path.get(i), board); - } - validateDestination(path.getLast(), camp, board); - } - - private void validateEmptyPosition(Position position, BoardChecker board) { - if (board.hasPieceAt(position)) { - throw new IllegalArgumentException(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); - } - } - - private void validateDestination(Position destination, Camp camp, BoardChecker board) { - if (board.hasSameCampPieceAt(destination, camp)) { - throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); - } - } -} diff --git a/src/main/java/janggi/domain/piece/condition/MoveCondition.java b/src/main/java/janggi/domain/piece/condition/MoveCondition.java deleted file mode 100644 index 9392c1f877..0000000000 --- a/src/main/java/janggi/domain/piece/condition/MoveCondition.java +++ /dev/null @@ -1,12 +0,0 @@ -package janggi.domain.piece.condition; - -import janggi.domain.Position; -import janggi.domain.board.BoardChecker; -import janggi.domain.piece.Camp; -import janggi.domain.piece.PieceRule; -import java.util.List; - -public interface MoveCondition { - - void checkPath(List path, Camp camp, BoardChecker board, PieceRule pieceRule); -} diff --git a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java b/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java deleted file mode 100644 index bc8291afae..0000000000 --- a/src/main/java/janggi/domain/piece/condition/OnePieceExistsCondition.java +++ /dev/null @@ -1,55 +0,0 @@ -package janggi.domain.piece.condition; - -import janggi.domain.Position; -import janggi.domain.board.BoardChecker; -import janggi.domain.piece.Camp; -import janggi.domain.piece.PieceRule; -import janggi.exception.ExceptionMessage; -import java.util.List; - -public class OnePieceExistsCondition implements MoveCondition { - - private static final int PASS_PIECE_COUNT = 1; - - @Override - public void checkPath(List path, Camp camp, BoardChecker board, PieceRule pieceRule) { - int countOfPiece = 0; - for (int i = 0; i < path.size() - 1; i++) { - Position position = path.get(i); - countOfPiece += countPieceAt(board, pieceRule, position); - } - - validateExactPieceCount(countOfPiece); - validateDestination(path.getLast(), camp, board, pieceRule); - } - - private int countPieceAt(BoardChecker board, PieceRule pieceRule, Position position) { - if (board.hasPieceAt(position)) { - validateDifferentPieceRule(board, pieceRule, position); - return 1; - } - return 0; - } - - private void validateDifferentPieceRule(BoardChecker board, PieceRule pieceRule, Position position) { - if (board.hasSamePieceRuleAt(position, pieceRule)) { - throw new IllegalArgumentException(ExceptionMessage.SAME_PIECE_TYPE_IN_PATH.getMessage()); - } - } - - private void validateExactPieceCount(int countOfPiece) { - if (countOfPiece != PASS_PIECE_COUNT) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(PASS_PIECE_COUNT)); - } - } - - private void validateDestination(Position destination, Camp camp, BoardChecker board, PieceRule pieceRule) { - if (board.hasSameCampPieceAt(destination, camp)) { - throw new IllegalArgumentException(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); - } - - if (board.hasSamePieceRuleAt(destination, pieceRule)) { - throw new IllegalArgumentException(ExceptionMessage.SAME_PIECE_TYPE_AT_DESTINATION.getMessage()); - } - } -} diff --git a/src/main/java/janggi/domain/piece/strategy/BaseMoveRule.java b/src/main/java/janggi/domain/piece/strategy/BaseMoveRule.java new file mode 100644 index 0000000000..43f252bbbf --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/BaseMoveRule.java @@ -0,0 +1,22 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.board.BoardChecker; +import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; +import java.util.List; + +public abstract class BaseMoveRule implements MoveRule { + + @Override + public abstract void validate(Position source, Position destination, Camp camp, BoardChecker board, PieceRule pieceRule); + + protected void validateEmptyPath(List path, BoardChecker board) { + for (int i = 0; i < path.size() - 1; i++) { + if (board.hasPieceAt(path.get(i))) { + throw new IllegalArgumentException(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); + } + } + } +} diff --git a/src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java b/src/main/java/janggi/domain/piece/strategy/DiagonalStepRule.java similarity index 74% rename from src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java rename to src/main/java/janggi/domain/piece/strategy/DiagonalStepRule.java index 42427bb169..6ecadddae9 100644 --- a/src/main/java/janggi/domain/piece/strategy/DiagonalStepStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/DiagonalStepRule.java @@ -1,36 +1,32 @@ package janggi.domain.piece.strategy; import janggi.domain.Position; +import janggi.domain.board.BoardChecker; import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; import janggi.exception.ExceptionMessage; import java.util.ArrayList; import java.util.List; -public class DiagonalStepStrategy implements MoveStrategy { +public class DiagonalStepRule extends BaseMoveRule { private static final int STRAIGHT_DISTANCE = 1; private final int diagonalDistance; - public DiagonalStepStrategy(int diagonalDistance) { + public DiagonalStepRule(int diagonalDistance) { this.diagonalDistance = diagonalDistance; } @Override - public List findPath(Position source, Position destination, Camp camp) { + public void validate(Position source, Position destination, Camp camp, BoardChecker board, PieceRule pieceRule) { DirectionInformation direction = new DirectionInformation(source, destination); - validateMovement(direction); - - Position first = moveStraightStep(source, direction); - - List path = new ArrayList<>(); - path.add(first); - path.addAll(moveDiagonal(first, direction)); - - return path; + validateDistance(direction); + List path = findPath(source, direction); + validateEmptyPath(path, board); } - private void validateMovement(DirectionInformation direction) { + private void validateDistance(DirectionInformation direction) { if (!direction.hasAbsDifferences(diagonalDistance, STRAIGHT_DISTANCE + diagonalDistance)) { throw new IllegalArgumentException( ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, diagonalDistance) @@ -38,6 +34,15 @@ private void validateMovement(DirectionInformation direction) { } } + private List findPath(Position source, DirectionInformation direction) { + List path = new ArrayList<>(); + + Position first = moveStraightStep(source, direction); + path.add(first); + path.addAll(moveDiagonal(first, direction)); + return path; + } + private Position moveStraightStep(Position source, DirectionInformation direction) { if (direction.isRowBiggerThanCol()) { return source.moveRow(direction.calculateRowDirection()); diff --git a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java index ff41e9cc74..838ce1e469 100644 --- a/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java +++ b/src/main/java/janggi/domain/piece/strategy/DirectionInformation.java @@ -2,45 +2,45 @@ import janggi.domain.Position; -public record DirectionInformation(int rowDifference, int colDifference) { +public record DirectionInformation(int rowDistance, int colDistance) { public DirectionInformation(Position source, Position destination) { this(destination.calculateRowDistance(source), destination.calculateColumnDistance(source)); } public int calculateRowDirection() { - if (rowDifference == 0) { + if (rowDistance == 0) { return 0; } - return rowDifference / Math.abs(rowDifference); + return rowDistance / Math.abs(rowDistance); } public int calculateColDirection() { - if (colDifference == 0) { + if (colDistance == 0) { return 0; } - return colDifference / Math.abs(colDifference); + return colDistance / Math.abs(colDistance); } public int calculateDistance() { - return Math.abs(rowDifference) + Math.abs(colDifference); + return Math.abs(rowDistance) + Math.abs(colDistance); } public boolean isRowBiggerThanCol() { - return Math.abs(rowDifference) > Math.abs(colDifference); + return Math.abs(rowDistance) > Math.abs(colDistance); } public boolean isHorizontal() { - return rowDifference == 0; + return rowDistance == 0; } public boolean isVertical() { - return colDifference == 0; + return colDistance == 0; } - public boolean hasAbsDifferences(int difference1, int difference2) { - int absRow = Math.abs(rowDifference); - int absCol = Math.abs(colDifference); - return (absRow == difference1 && absCol == difference2) || (absRow == difference2 && absCol == difference1); + public boolean hasAbsDifferences(int distance1, int distance2) { + int absRow = Math.abs(rowDistance); + int absCol = Math.abs(colDistance); + return (absRow == distance1 && absCol == distance2) || (absRow == distance2 && absCol == distance1); } } diff --git a/src/main/java/janggi/domain/piece/strategy/EmptySlidingRule.java b/src/main/java/janggi/domain/piece/strategy/EmptySlidingRule.java new file mode 100644 index 0000000000..ee46ad0481 --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/EmptySlidingRule.java @@ -0,0 +1,17 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.board.BoardChecker; +import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; +import java.util.List; + +public class EmptySlidingRule extends SlidingRule { + + @Override + public void validate(Position source, Position destination, Camp camp, BoardChecker board, PieceRule pieceRule) { + DirectionInformation directionInformation = new DirectionInformation(source, destination); + List path = findPath(source, directionInformation); + validateEmptyPath(path, board); + } +} diff --git a/src/main/java/janggi/domain/piece/strategy/JumpingSlidingRule.java b/src/main/java/janggi/domain/piece/strategy/JumpingSlidingRule.java new file mode 100644 index 0000000000..34e6943cbd --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/JumpingSlidingRule.java @@ -0,0 +1,60 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.board.BoardChecker; +import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; +import java.util.List; + +public class JumpingSlidingRule extends SlidingRule { + + private static final int REQUIRED_PIECE_COUNT = 1; + + @Override + public void validate(Position source, Position destination, Camp camp, BoardChecker board, PieceRule pieceRule) { + DirectionInformation directionInformation = new DirectionInformation(source, destination); + + List path = findPath(source, directionInformation); + checkJumpingPath(path, board, pieceRule); + } + + private void checkJumpingPath(List path, BoardChecker board, PieceRule pieceRule) { + int jumpedPieceCount = countJumpedPiece(path, board, pieceRule); + validateJumpedPieceCount(jumpedPieceCount); + validateDestination(board, pieceRule, path.getLast()); + } + + private int countJumpedPiece(List path, BoardChecker board, PieceRule pieceRule) { + int jumpedPieceCount = 0; + for (int i = 0; i < path.size() - 1; i++) { + Position position = path.get(i); + if (!board.hasPieceAt(position)) { + continue; + } + validateDifferentPieceRule(board, pieceRule, position); + jumpedPieceCount++; + } + return jumpedPieceCount; + } + + private void validateDifferentPieceRule(BoardChecker board, PieceRule pieceRule, Position position) { + if (board.hasSamePieceRuleAt(position, pieceRule)) { + throw new IllegalArgumentException(ExceptionMessage.SAME_PIECE_TYPE_IN_PATH.getMessage()); + } + } + + private void validateJumpedPieceCount(int jumpedPieceCount) { + if (jumpedPieceCount != REQUIRED_PIECE_COUNT) { + throw new IllegalArgumentException( + ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(REQUIRED_PIECE_COUNT) + ); + } + } + + private void validateDestination(BoardChecker board, PieceRule pieceRule, Position position) { + if (board.hasSamePieceRuleAt(position, pieceRule)) { + throw new IllegalArgumentException(ExceptionMessage.SAME_PIECE_TYPE_AT_DESTINATION.getMessage()); + } + } +} diff --git a/src/main/java/janggi/domain/piece/strategy/MoveRule.java b/src/main/java/janggi/domain/piece/strategy/MoveRule.java new file mode 100644 index 0000000000..fc28b5e4e7 --- /dev/null +++ b/src/main/java/janggi/domain/piece/strategy/MoveRule.java @@ -0,0 +1,11 @@ +package janggi.domain.piece.strategy; + +import janggi.domain.Position; +import janggi.domain.board.BoardChecker; +import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; + +public interface MoveRule { + + void validate(Position source, Position destination, Camp camp, BoardChecker board, PieceRule pieceRule); +} diff --git a/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java b/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java deleted file mode 100644 index 17e88a8b69..0000000000 --- a/src/main/java/janggi/domain/piece/strategy/MoveStrategy.java +++ /dev/null @@ -1,10 +0,0 @@ -package janggi.domain.piece.strategy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import java.util.List; - -public interface MoveStrategy { - - List findPath(Position source, Position destination, Camp camp); -} diff --git a/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java b/src/main/java/janggi/domain/piece/strategy/SingleStepRule.java similarity index 73% rename from src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java rename to src/main/java/janggi/domain/piece/strategy/SingleStepRule.java index 0fcb2c8eb6..b7c5a92ce6 100644 --- a/src/main/java/janggi/domain/piece/strategy/SingleStepStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SingleStepRule.java @@ -1,29 +1,35 @@ package janggi.domain.piece.strategy; import janggi.domain.Position; +import janggi.domain.board.BoardChecker; import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; import janggi.exception.ExceptionMessage; -import java.util.List; -public class SingleStepStrategy implements MoveStrategy { +public class SingleStepRule extends BaseMoveRule { private static final int SINGLE_STEP_DISTANCE = 1; private final boolean forwardOnly; - public SingleStepStrategy(boolean forwardOnly) { + public SingleStepRule(boolean forwardOnly) { this.forwardOnly = forwardOnly; } @Override - public List findPath(Position source, Position destination, Camp camp) { + public void validate(Position source, Position destination, Camp camp, BoardChecker board, PieceRule pieceRule) { DirectionInformation direction = new DirectionInformation(source, destination); - validateMovement(direction); + validateDistance(direction); if (forwardOnly) { validateForwardDirection(direction.calculateRowDirection(), camp); } - return List.of(destination); + } + + private void validateDistance(DirectionInformation direction) { + if (direction.calculateDistance() != SINGLE_STEP_DISTANCE) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); + } } private void validateForwardDirection(int rowDirection, Camp camp) { @@ -34,10 +40,4 @@ private void validateForwardDirection(int rowDirection, Camp camp) { throw new IllegalArgumentException(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); } } - - private void validateMovement(DirectionInformation direction) { - if (direction.calculateDistance() != SINGLE_STEP_DISTANCE) { - throw new IllegalArgumentException(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); - } - } } diff --git a/src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java b/src/main/java/janggi/domain/piece/strategy/SlidingRule.java similarity index 73% rename from src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java rename to src/main/java/janggi/domain/piece/strategy/SlidingRule.java index d2e7d66ae6..492c536f29 100644 --- a/src/main/java/janggi/domain/piece/strategy/SlidingStrategy.java +++ b/src/main/java/janggi/domain/piece/strategy/SlidingRule.java @@ -1,30 +1,25 @@ package janggi.domain.piece.strategy; import janggi.domain.Position; -import janggi.domain.piece.Camp; import janggi.exception.ExceptionMessage; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; -public class SlidingStrategy implements MoveStrategy { - - @Override - public List findPath(Position source, Position destination, Camp camp) { - DirectionInformation directionInformation = new DirectionInformation(source, destination); +public abstract class SlidingRule extends BaseMoveRule { + protected List findPath(Position source, DirectionInformation directionInformation) { if (directionInformation.isHorizontal()) { - return createPath(source, directionInformation.colDifference(), Position::moveCol); + return createPath(source, directionInformation.colDistance(), Position::moveCol); } if (directionInformation.isVertical()) { - return createPath(source, directionInformation.rowDifference(), Position::moveRow); + return createPath(source, directionInformation.rowDistance(), Position::moveRow); } throw new IllegalArgumentException(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); } private List createPath(Position source, int difference, BiFunction move) { List path = new ArrayList<>(); - int direction = Integer.signum(difference); int distance = Math.abs(difference); diff --git a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java b/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java deleted file mode 100644 index ea853b4656..0000000000 --- a/src/test/java/janggi/domain/piece/condition/EmptyConditionTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package janggi.domain.piece.condition; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.board.Board; -import janggi.domain.piece.Camp; -import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceRule; -import janggi.exception.ExceptionMessage; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; - -public class EmptyConditionTest { - - MoveCondition condition = new EmptyCondition(); - - @Test - void 이동하려는_경로에_기물이_존재하면_예외가_발생한다() { - //given - List path = List.of( - new Position(0, 0), - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4), - new Position(0, 5) - ); - Position blockingPosition = new Position(0, 4); - Board board = new Board(Map.of( - blockingPosition, new Piece(PieceRule.CHARIOT, Camp.HAN) - )); - //when & then - assertThatThrownBy(() -> condition.checkPath(path, Camp.HAN, board, PieceRule.CHARIOT)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); - } - - @Test - void 도착지점에_아군_기물이_존재하면_예외가_발생한다() { - //given - List path = List.of( - new Position(0, 0), - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4) - ); - Position sameCampPiecePosition = new Position(0, 4); - Board board = new Board(Map.of( - sameCampPiecePosition, new Piece(PieceRule.CHARIOT, Camp.HAN) - )); - //when & then - assertThatThrownBy(() -> condition.checkPath(path, Camp.HAN, board, PieceRule.CHARIOT)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); - } -} diff --git a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java b/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java deleted file mode 100644 index a49707716e..0000000000 --- a/src/test/java/janggi/domain/piece/condition/OnePieceExistsConditionTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package janggi.domain.piece.condition; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.board.Board; -import janggi.domain.piece.Camp; -import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceRule; -import janggi.exception.ExceptionMessage; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; - -public class OnePieceExistsConditionTest { - - private static final int PASS_PIECE_COUNT = 1; - - MoveCondition condition = new OnePieceExistsCondition(); - - @Test - void 이동하려는_경로에_기물이_존재하지_않으면_예외가_발생한다() { - //given - List path = List.of( - new Position(0, 0), - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4), - new Position(0, 5) - ); - Camp camp = Camp.HAN; - - Board board = new Board(Map.of()); - //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(PASS_PIECE_COUNT)); - } - - @Test - void 이동하려는_경로에_기물이_2개_이상_존재하면_예외가_발생한다() { - //given - List path = List.of( - new Position(0, 0), - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4), - new Position(0, 5) - ); - Camp camp = Camp.HAN; - Board board = new Board(Map.of( - new Position(0, 3), new Piece(PieceRule.CHARIOT, Camp.HAN), - new Position(0, 4), new Piece(PieceRule.ELEPHANT, Camp.HAN) - )); - //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(PASS_PIECE_COUNT)); - } - - @Test - void 이동하려는_경로에_있는_기물이_같은_타입이면_예외가_발생한다() { - //given - List path = List.of( - new Position(0, 0), - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4), - new Position(0, 5) - ); - Camp camp = Camp.HAN; - Board board = new Board(Map.of( - new Position(0, 4), new Piece(PieceRule.CANNON, Camp.CHO) - )); - //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.SAME_PIECE_TYPE_IN_PATH.getMessage()); - } - - @Test - void 목적지에_있는_기물이_아군이면_예외가_발생한다() { - //given - List path = List.of( - new Position(0, 0), - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4), - new Position(0, 5) - ); - Camp camp = Camp.HAN; - Board board = new Board(Map.of( - new Position(0, 3), new Piece(PieceRule.SOLDIER, Camp.HAN), - new Position(0, 5), new Piece(PieceRule.CHARIOT, Camp.HAN) - )); - //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.SAME_CAMP_PIECE_AT_DESTINATION.getMessage()); - } - - @Test - void 목적지에_있는_기물이_같은_타입이면_예외가_발생한다() { - //given - List path = List.of( - new Position(0, 0), - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4), - new Position(0, 5) - ); - Camp camp = Camp.HAN; - Board board = new Board(Map.of( - new Position(0, 3), new Piece(PieceRule.SOLDIER, Camp.HAN), - new Position(0, 5), new Piece(PieceRule.CANNON, Camp.CHO) - )); - //when & then - assertThatThrownBy(() -> condition.checkPath(path, camp, board, PieceRule.CANNON)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.SAME_PIECE_TYPE_AT_DESTINATION.getMessage()); - } -} diff --git a/src/test/java/janggi/domain/piece/strategy/DiagonalStepRuleTest.java b/src/test/java/janggi/domain/piece/strategy/DiagonalStepRuleTest.java new file mode 100644 index 0000000000..e78bb58cc8 --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/DiagonalStepRuleTest.java @@ -0,0 +1,138 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.board.BoardChecker; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class DiagonalStepRuleTest { + + @DisplayName("직선 1칸 후 대각선 1칸 이동(마) 테스트") + @Nested + class OneStepDiagonal { + + private static final int STRAIGHT_DISTANCE = 1; + private static final int DIAGONAL_DISTANCE = 1; + private final MoveRule moveRule = new DiagonalStepRule(DIAGONAL_DISTANCE); + + private static Stream successPaths() { + Position source = new Position(6, 4); + return Stream.of( + Arguments.of(source, new Position(8, 5)), + Arguments.of(source, new Position(8, 3)), + Arguments.of(source, new Position(4, 3)), + Arguments.of(source, new Position(4, 5)), + Arguments.of(source, new Position(5, 2)), + Arguments.of(source, new Position(7, 2)), + Arguments.of(source, new Position(5, 6)), + Arguments.of(source, new Position(7, 6)) + ); + } + + @ParameterizedTest + @MethodSource("successPaths") + void 직선_1칸_이동_후_대각선_1칸_이동한다(Position source, Position destination) { + assertThatNoException().isThrownBy(() -> moveRule.validate(source, destination, Camp.HAN, new Board(Map.of()), PieceRule.HORSE)); + } + + private static Stream invalidDistancePositions() { + return Stream.of( + Arguments.of(new Position(6, 4), new Position(5, 1)), + Arguments.of(new Position(6, 4), new Position(4, 2)), + Arguments.of(new Position(6, 4), new Position(6, 4)) + ); + } + + @ParameterizedTest + @MethodSource("invalidDistancePositions") + void 행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { + assertThatThrownBy(() -> moveRule.validate(source, destination, Camp.CHO, new Board(Map.of()), PieceRule.HORSE)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, DIAGONAL_DISTANCE)); + } + + @Test + void 이동하려는_경로에_기물이_존재하면_예외가_발생한다() { + BoardChecker blockingBoard = new Board(Map.of( + new Position(8, 2), new Piece(PieceRule.SOLDIER, Camp.CHO) + )); + Position source = new Position(9, 2); + Position destination = new Position(7, 3); + // when & then + assertThatThrownBy(() -> moveRule.validate(source, destination, Camp.HAN, blockingBoard, PieceRule.HORSE)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); + } + } + + @DisplayName("직선 1칸 후 대각선 2칸 이동(상) 테스트") + @Nested + class TwoStepDiagonal { + + private static final int STRAIGHT_DISTANCE = 1; + private static final int DIAGONAL_DISTANCE = 2; + private final MoveRule moveRule = new DiagonalStepRule(DIAGONAL_DISTANCE); + + private static Stream successPositions() { + Position source = new Position(6, 4); + return Stream.of( + Arguments.of(source, new Position(3, 2)), + Arguments.of(source, new Position(4, 1)), + Arguments.of(source, new Position(8, 1)), + Arguments.of(source, new Position(9, 2)), + Arguments.of(source, new Position(9, 6)), + Arguments.of(source, new Position(8, 7)), + Arguments.of(source, new Position(4, 7)), + Arguments.of(source, new Position(3, 6)) + ); + } + + @ParameterizedTest + @MethodSource("successPositions") + void 직선_1칸_이동_후_대각선_2칸_이동한다(Position source, Position destination) { + assertThatNoException().isThrownBy(() -> moveRule.validate(source, destination, Camp.HAN, new Board(Map.of()), PieceRule.ELEPHANT)); + } + + private static Stream invalidDistancePositions() { + return Stream.of( + Arguments.of(new Position(6, 4), new Position(3, 4)), + Arguments.of(new Position(6, 4), new Position(6, 4)) + ); + } + + @ParameterizedTest + @MethodSource("invalidDistancePositions") + void 행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { + assertThatThrownBy(() -> moveRule.validate(source, destination, Camp.CHO, new Board(Map.of()), PieceRule.ELEPHANT)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, DIAGONAL_DISTANCE)); + } + + @Test + void 이동하려는_경로에_기물이_존재하면_예외가_발생한다() { + BoardChecker blockingBoard = new Board(Map.of( + new Position(7, 2), new Piece(PieceRule.SOLDIER, Camp.CHO) + )); + Position source = new Position(9, 1); + Position destination = new Position(6, 3); + // when & then + assertThatThrownBy(() -> moveRule.validate(source, destination, Camp.HAN, blockingBoard, PieceRule.ELEPHANT)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); + } + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/DiagonalStepStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/DiagonalStepStrategyTest.java deleted file mode 100644 index 13f24b1eab..0000000000 --- a/src/test/java/janggi/domain/piece/strategy/DiagonalStepStrategyTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package janggi.domain.piece.strategy; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class DiagonalStepStrategyTest { - - @DisplayName("직선 1칸 후 대각선 1칸 이동(마) 테스트") - @Nested - class OneStepDiagonal { - - private static final int STRAIGHT_DISTANCE = 1; - private static final int DIAGONAL_DISTANCE = 1; - private final MoveStrategy strategy = new DiagonalStepStrategy(DIAGONAL_DISTANCE); - - private static Stream successPaths() { - Position source = new Position(6, 4); - return Stream.of( - Arguments.of(source, List.of(new Position(7, 4), new Position(8, 5))), - Arguments.of(source, List.of(new Position(7, 4), new Position(8, 3))), - Arguments.of(source, List.of(new Position(5, 4), new Position(4, 3))), - Arguments.of(source, List.of(new Position(5, 4), new Position(4, 5))), - Arguments.of(source, List.of(new Position(6, 3), new Position(5, 2))), - Arguments.of(source, List.of(new Position(6, 3), new Position(7, 2))), - Arguments.of(source, List.of(new Position(6, 5), new Position(5, 6))), - Arguments.of(source, List.of(new Position(6, 5), new Position(7, 6))) - ); - } - - @ParameterizedTest - @MethodSource("successPaths") - void 직선_1칸_이동_후_대각선_1칸_이동한다(Position source, List expectedPath) { - List path = strategy.findPath(source, expectedPath.getLast(), Camp.HAN); - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(expectedPath.size()); - assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); - }); - } - - private static Stream invalidDistancePositions() { - return Stream.of( - Arguments.of(new Position(6, 4), new Position(5, 1)), - Arguments.of(new Position(6, 4), new Position(4, 2)), - Arguments.of(new Position(6, 4), new Position(6, 4)) - ); - } - - @ParameterizedTest - @MethodSource("invalidDistancePositions") - void 행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { - assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, DIAGONAL_DISTANCE)); - } - } - - @DisplayName("직선 1칸 후 대각선 2칸 이동 테스트") - @Nested - class TwoStepDiagonal { - - private static final int STRAIGHT_DISTANCE = 1; - private static final int DIAGONAL_DISTANCE = 2; - private final MoveStrategy strategy = new DiagonalStepStrategy(DIAGONAL_DISTANCE); - - private static Stream createPaths() { - Position source = new Position(6, 4); - return Stream.of( - Arguments.of(source, - List.of(new Position(5, 4), new Position(4, 3), new Position(3, 2))), - Arguments.of(source, - List.of(new Position(6, 3), new Position(5, 2), new Position(4, 1))), - Arguments.of(source, - List.of(new Position(6, 3), new Position(7, 2), new Position(8, 1))), - Arguments.of(source, - List.of(new Position(7, 4), new Position(8, 3), new Position(9, 2))), - Arguments.of(source, - List.of(new Position(7, 4), new Position(8, 5), new Position(9, 6))), - Arguments.of(source, - List.of(new Position(6, 5), new Position(7, 6), new Position(8, 7))), - Arguments.of(source, - List.of(new Position(6, 5), new Position(5, 6), new Position(4, 7))), - Arguments.of(source, - List.of(new Position(5, 4), new Position(4, 5), new Position(3, 6))) - ); - } - - @ParameterizedTest - @MethodSource("createPaths") - void 직선_1칸_이동_후_대각선_2칸_이동한다(Position source, List expectedPath) { - List path = strategy.findPath(source, expectedPath.getLast(), Camp.HAN); - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(expectedPath.size()); - assertSoftly.assertThat(path).containsExactlyElementsOf(expectedPath); - }); - } - - private static Stream invalidDistancePositions() { - return Stream.of( - Arguments.of(new Position(6, 4), new Position(3, 4)), - Arguments.of(new Position(6, 4), new Position(6, 4)) - ); - } - - @ParameterizedTest - @MethodSource("invalidDistancePositions") - void 행마법_대로_움직이지_않으면_예외가_발생한다(Position source, Position destination) { - assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.INVALID_DIAGONAL_STEP_MOVE.getMessage(STRAIGHT_DISTANCE, DIAGONAL_DISTANCE)); - } - } -} diff --git a/src/test/java/janggi/domain/piece/strategy/EmptySlidingRuleTest.java b/src/test/java/janggi/domain/piece/strategy/EmptySlidingRuleTest.java new file mode 100644 index 0000000000..ddeda278aa --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/EmptySlidingRuleTest.java @@ -0,0 +1,54 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.board.BoardChecker; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class EmptySlidingRuleTest { + + private final MoveRule rule = new EmptySlidingRule(); + + @Test + void 직선으로_여러_칸_이동한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(9, 0); + BoardChecker board = new Board(Map.of()); + //when & then + assertThatNoException().isThrownBy(() -> rule.validate(source, destination, Camp.CHO, board, PieceRule.CHARIOT)); + } + + @Test + void 직선으로_이동하지_않으면_예외가_발생한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(3, 3); + BoardChecker board = new Board(Map.of()); + //when & then + assertThatThrownBy(() -> rule.validate(source, destination, Camp.CHO, board, PieceRule.CHARIOT)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); + } + + @Test + void 이동하려는_경로에_기물이_존재하면_예외가_발생한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(5, 0); + Position blockingPosition = new Position(3, 0); + BoardChecker board = new Board(Map.of(blockingPosition, new Piece(PieceRule.SOLDIER, Camp.CHO))); + //when & then + assertThatThrownBy(() -> rule.validate(source, destination, Camp.CHO, board, PieceRule.CHARIOT)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.PATH_NOT_EMPTY.getMessage()); + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/JumpingSlidingRuleTest.java b/src/test/java/janggi/domain/piece/strategy/JumpingSlidingRuleTest.java new file mode 100644 index 0000000000..b77a643edb --- /dev/null +++ b/src/test/java/janggi/domain/piece/strategy/JumpingSlidingRuleTest.java @@ -0,0 +1,89 @@ +package janggi.domain.piece.strategy; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Position; +import janggi.domain.board.Board; +import janggi.domain.board.BoardChecker; +import janggi.domain.piece.Camp; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceRule; +import janggi.exception.ExceptionMessage; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class JumpingSlidingRuleTest { + + private static final int REQUIRED_PIECE_COUNT = 1; + + private final MoveRule rule = new JumpingSlidingRule(); + + @Test + void 직선_방향으로_하나의_기물을_넘어_이동한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(0, 5); + BoardChecker board = new Board(Map.of( + new Position(0, 3), new Piece(PieceRule.SOLDIER, Camp.CHO) + )); + //when & then + assertThatNoException().isThrownBy(() -> rule.validate(source, destination, Camp.CHO, board, PieceRule.CANNON)); + } + + @Test + void 이동하려는_경로에_기물이_존재하지_않으면_예외가_발생한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(0, 5); + BoardChecker board = new Board(Map.of()); + //when & then + assertThatThrownBy(() -> rule.validate(source, destination, Camp.CHO, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(REQUIRED_PIECE_COUNT)); + } + + @Test + void 이동하려는_경로에_기물이_2개_이상_존재하면_예외가_발생한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(0, 5); + BoardChecker board = new Board(Map.of( + new Position(0, 3), new Piece(PieceRule.CHARIOT, Camp.CHO), + new Position(0, 4), new Piece(PieceRule.ELEPHANT, Camp.CHO) + )); + //when & then + assertThatThrownBy(() -> rule.validate(source, destination, Camp.CHO, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_JUMPED_PIECE_COUNT.getMessage(REQUIRED_PIECE_COUNT)); + } + + @Test + void 이동하려는_경로에_있는_기물이_같은_타입이면_예외가_발생한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(0, 5); + BoardChecker board = new Board(Map.of( + new Position(0, 4), new Piece(PieceRule.CANNON, Camp.CHO) + )); + //when & then + assertThatThrownBy(() -> rule.validate(source, destination, Camp.CHO, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.SAME_PIECE_TYPE_IN_PATH.getMessage()); + } + + @Test + void 목적지에_있는_기물이_같은_타입이면_예외가_발생한다() { + //given + Position source = new Position(0, 0); + Position destination = new Position(0, 5); + BoardChecker board = new Board(Map.of( + new Position(0, 3), new Piece(PieceRule.SOLDIER, Camp.CHO), + new Position(0, 5), new Piece(PieceRule.CANNON, Camp.CHO) + )); + //when & then + assertThatThrownBy(() -> rule.validate(source, destination, Camp.CHO, board, PieceRule.CANNON)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.SAME_PIECE_TYPE_AT_DESTINATION.getMessage()); + } +} diff --git a/src/test/java/janggi/domain/piece/strategy/SingleStepStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SingleStepRuleTest.java similarity index 77% rename from src/test/java/janggi/domain/piece/strategy/SingleStepStrategyTest.java rename to src/test/java/janggi/domain/piece/strategy/SingleStepRuleTest.java index 8346d71d4d..7cd3c1264e 100644 --- a/src/test/java/janggi/domain/piece/strategy/SingleStepStrategyTest.java +++ b/src/test/java/janggi/domain/piece/strategy/SingleStepRuleTest.java @@ -1,13 +1,13 @@ package janggi.domain.piece.strategy; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import janggi.domain.Position; import janggi.domain.piece.Camp; +import janggi.domain.piece.PieceRule; import janggi.exception.ExceptionMessage; -import java.util.List; import java.util.stream.Stream; -import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class SingleStepStrategyTest { +class SingleStepRuleTest { private static final int SINGLE_STEP_DISTANCE = 1; @@ -23,7 +23,7 @@ class SingleStepStrategyTest { @Nested class AllDirectionStep { - private final MoveStrategy strategy = new SingleStepStrategy(false); + private final MoveRule rule = new SingleStepRule(false); private static Stream successMovePositions() { return Stream.of( @@ -37,16 +37,12 @@ private static Stream successMovePositions() { @ParameterizedTest @MethodSource("successMovePositions") void 직선_방향으로_1칸만_이동한다(Position source, Position destination) { - List path = strategy.findPath(source, destination, Camp.HAN); - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(1); - assertSoftly.assertThat(path).containsExactly(destination); - }); + assertThatNoException().isThrownBy(() -> rule.validate(source, destination, Camp.HAN, null, PieceRule.GENERAL)); } @Test void 직선_방향으로_1칸만_이동하지_않으면_예외가_발생한다() { - assertThatThrownBy(() -> strategy.findPath(new Position(3, 0), new Position(5, 0), Camp.HAN)) + assertThatThrownBy(() -> rule.validate(new Position(3, 0), new Position(5, 0), Camp.HAN, null, PieceRule.GENERAL)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } @@ -56,7 +52,7 @@ private static Stream successMovePositions() { @Nested class ForwardSideStep { - private final MoveStrategy strategy = new SingleStepStrategy(true); + private final MoveRule rule = new SingleStepRule(true); private static Stream successMovePositions() { return Stream.of( @@ -72,11 +68,7 @@ private static Stream successMovePositions() { @ParameterizedTest @MethodSource("successMovePositions") void 전진_또는_좌우_방향으로_1칸만_이동한다(Camp camp, Position source, Position destination) { - List path = strategy.findPath(source, destination, camp); - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(1); - assertSoftly.assertThat(path).containsExactly(destination); - }); + assertThatNoException().isThrownBy(() -> rule.validate(source, destination, camp, null, PieceRule.SOLDIER)); } private static Stream invalidDistancePositions() { @@ -89,7 +81,7 @@ private static Stream invalidDistancePositions() { @ParameterizedTest @MethodSource("invalidDistancePositions") void 직선_방향으로_1칸만_이동하지_않으면_예외가_발생한다(Camp camp, Position source, Position destination) { - assertThatThrownBy(() -> strategy.findPath(source, destination, camp)) + assertThatThrownBy(() -> rule.validate(source, destination, camp, null, PieceRule.SOLDIER)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.INVALID_SINGLE_STEP_MOVE.getMessage(SINGLE_STEP_DISTANCE)); } @@ -104,7 +96,7 @@ private static Stream backwardMovePositions() { @ParameterizedTest @MethodSource("backwardMovePositions") void 후진하는_경우_예외가_발생한다(Camp camp, Position source, Position destination) { - assertThatThrownBy(() -> strategy.findPath(source, destination, camp)) + assertThatThrownBy(() -> rule.validate(source, destination, camp, null, PieceRule.SOLDIER)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(ExceptionMessage.INVALID_BACKWARD_MOVEMENT.getMessage()); } diff --git a/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java b/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java deleted file mode 100644 index b40844ac4a..0000000000 --- a/src/test/java/janggi/domain/piece/strategy/SlidingStrategyTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package janggi.domain.piece.strategy; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import janggi.domain.Position; -import janggi.domain.piece.Camp; -import janggi.exception.ExceptionMessage; -import java.util.List; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.Test; - -class SlidingStrategyTest { - - private final MoveStrategy strategy = new SlidingStrategy(); - - @Test - void 직선으로_여러_칸_이동한다() { - //given - Position source = new Position(0, 0); - Position destination = new Position(9, 0); - //when - List path = strategy.findPath(source, destination, Camp.CHO); - //then - SoftAssertions.assertSoftly(assertSoftly -> { - assertSoftly.assertThat(path).hasSize(9); - assertSoftly.assertThat(path.getLast()).isEqualTo(destination); - }); - } - - @Test - void 직선으로_이동하지_않으면_예외가_발생한다() { - //given - Position source = new Position(0, 0); - Position destination = new Position(3, 3); - //when & then - assertThatThrownBy(() -> strategy.findPath(source, destination, Camp.CHO)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ExceptionMessage.ONLY_STRAIGHT_MOVE_ALLOWED.getMessage()); - } -} From e1138ec5697fce1d763e786e554a2f42eb3e29aa Mon Sep 17 00:00:00 2001 From: Jiihyun Date: Thu, 2 Apr 2026 20:27:48 +0900 Subject: [PATCH 68/68] =?UTF-8?q?refactor:=20Position=20format=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/janggi/domain/PositionTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java index e221ad18d0..7b440f73e0 100644 --- a/src/test/java/janggi/domain/PositionTest.java +++ b/src/test/java/janggi/domain/PositionTest.java @@ -3,10 +3,13 @@ import static org.assertj.core.api.Assertions.assertThat; import janggi.exception.ExceptionMessage; +import java.util.List; +import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; public class PositionTest { @@ -14,6 +17,22 @@ public class PositionTest { private static final int MAX_ROW_INDEX = 9; private static final int MAX_COLUMN_INDEX = 8; + static Stream> createPositionFormat() { + return Stream.of( + List.of(), + List.of(1), + List.of(1, 2, 3) + ); + } + + @ParameterizedTest + @MethodSource("createPositionFormat") + void 포지션에_행과_열이_존재하지_않을_경우_예외가_발생한다(List position) { + Assertions.assertThatThrownBy(() -> Position.from(position)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessage.INVALID_INPUT_FORMAT.getMessage()); + } + @ParameterizedTest @CsvSource(value = { "-1,8", @@ -25,6 +44,7 @@ public class PositionTest { .hasMessage(ExceptionMessage.ROW_OUT_OF_RANGE.getMessage(MIN_POSITION_INDEX, MAX_ROW_INDEX)); } + @ParameterizedTest @CsvSource(value = { "0,9",