From 8ed7716aada1bb81733fbafb6907a7f448e9857d Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 14:32:48 +0900 Subject: [PATCH 01/92] =?UTF-8?q?docs(README):=201.1=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EB=A6=AC=EB=93=9C=EB=AF=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9775dda0ae..cb2eac2380 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,228 @@ -# java-janggi +# [장기] 사이클1 - 미션 (보드 초기화 + 기물 이동) -장기 미션 저장소 +## 목표 + +단순히 코드를 작성하는 것이 아니라, **무엇을 개발해야 하는지 먼저 정리하고** 개발을 진행하는 것이 핵심이다. + +- [x] 1.1단계를 시작하기 전에 **어떤 기능을 구현해야 하는지 정리**한다. +- [x] 1.1단계에서 정리한 내용을 README.md에 기능 목록으로 작성한다. +- [ ] 1.2단계를 시작하기 전에 **어떤 기능을 구현해야 하는지 정리**한다. +- [ ] 1.2단계에서 정리한 내용을 README.md에 기능 목록으로 작성한다. +- [ ] 개발 과정에서 **"나는 왜 이렇게 구현할까?"** 라는 고민을 기록한다. + +--- + +## 기능 요구 사항 + +### 1.1단계 - 보드 초기화 + +- 게임 시작 시 장기판과 전체 기물을 올바른 위치에 초기화한다. +- 1.1단계에서는 기물의 이동은 구현하지 않는다. + +### 1.2단계 - 기물 이동 + +- 각 기물의 이동 규칙을 구현한다. +- 기물의 이동 규칙은 직접 요구사항을 분석하여 정의한다. +- **궁성(宮城) 영역은 구현하지 않는다.** (사이클2에서 다룬다) + +--- + +## 기능 목록 + +### 위치(Position) +- [ ] 검증 + - [ ] x의 값은 0 이상 8 이하여야 한다. + - [ ] y의 값은 0 이상 9 이하여야 한다. + - [ ] x의 값이 0 이상 8 이하가 아니면 예외가 발생한다. + - [ ] y의 값이 0 이상 9 이하가 아니면 예외가 발생한다. +### 진영(Side) + - [ ] 진영은 초와 한만 가진다. +### 기물(Piece) + - [ ] 같은 진영 여부를 판단한다. +### 장기판(Board) + - [ ] 검증 + - [ ] 장기판은 올바른 위치에 기물을 초기화한다. + - [ ] 장기판은 올바른 위치에 기물을 초기화하지 않으면 예외가 발생한다. + +--- + +## 입출력예시 +1. 초나라 플레이어의 이름을 입력받는다. +2. 한나라 플레이어 이름을 입력받는다. +3. 초나라 플레이어의 기물 배치를 입력받는다. + + ``` + 기물 배치를 선택하세요. + 1. 상마상마 + 2. 마상마상 + 3. 상마마상 + 4. 마상상마 + ``` + +4. 한나라 플레이어의 기물 배치를 입력받는다. + + ``` + 기물 배치를 선택하세요. + 1. 상마상마 + 2. 마상마상 + 3. 상마마상 + 4. 마상상마 + ``` + +5. 초나라 플레이어부터 한 턴씩 진행 + + 1. 보드판 출력 + + 2. 플레이어가 선택할 수 있는 기물의 위치를 입력받는다. + + ``` + (보드판 출력) + 이동 시킬 기물의 위치를 입력하세요. + 궁: (x, y) + 차: (x, y), (x, y) + 포: (x, y), (x, y) + 마: (x, y), (x, y) + 상: (x, y), (x, y) + 사: (x, y), (x, y) + 졸: (x, y), (x, y), (x, y), (x, y), (x, y) + ``` + + 3. 선택한 기물의 목적지를 입력받는다. + + ``` + 선택한 기물이 이동할 수 있는 위치입니다. 이동할 위치를 입력하세요. + (x, y), (x, y), ... + ``` + + 4. 왕이 잡히면 게임 종료 + +--- + +## 프로그래밍 요구 사항 + +- [ ] 자바 코드 컨벤션을 지키면서 프로그래밍한다. + - [ ] 기본적으로[Java Style Guide](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java)을 원칙으로 한다. +- [ ] indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다. + - [ ] 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - [ ] 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +- [ ] 3항 연산자를 쓰지 않는다. +- [ ] else 예약어를 쓰지 않는다. + - [ ] else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. + - [ ] 힌트: if문에서 값을 반환하는 방식으로 구현하면 else 예약어를 사용하지 않아도 된다. +- [ ] 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외 + - [ ] 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다. + - [ ] UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다. +- [ ] 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. + - [ ] 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라. +- [ ] 배열 대신 컬렉션을 사용한다. +- [ ] 모든 원시 값과 문자열을 포장한다. +- [ ] 줄여 쓰지 않는다(축약 금지). +- [ ] 일급 컬렉션을 쓴다. +- [ ] 모든 엔티티를 작게 유지한다. +- [ ] 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. + +### 추가된 요구 사항 + +- [ ] 도메인의 의존성을 최소한으로 구현한다. +- [ ] 한 줄에 점을 하나만 찍는다. +- [ ] 게터/세터/프로퍼티를 쓰지 않는다. +- [ ] 모든 객체지향 생활 체조 원칙을 잘 지키며 구현한다. +- [ ] [프로그래밍 체크리스트](https://github.com/woowacourse/woowacourse-docs/blob/master/cleancode/pr_checklist.md)의 원칙을 지키면서 프로그래밍한다. + +--- + +## 팀 규칙 + +> [!abstract] ### **1. 상태 위치 규칙** +> +> (If-Then) 만약 기물의 위치가 이동, 잡힘, 배치 변경처럼 게임판 전체 상태 변화와 함께 바뀐다면 +> +> → 위치 정보는 보드가 관리하고, 기물은 자신의 위치를 직접 가지지 않는다 +> +> (기준) 상태 소유 기준: 변경의 책임이 있는 객체가 그 상태를 소유한다 +> +> (금지) 이번 미션에서 보드와 기물이 동시에 위치 상태를 가지지 않는다 +> +> --- +> +> ### **2. 불변 객체 기준** +> +> (If-Then) 만약 기물이 가지는 정보가 종류, 소속 팀, 이동 규칙처럼 게임 도중 바뀌지 않는 값이라면 +> +> → 기물 객체는 불변으로 만든다 +> +> (기준) 불변성 판단 기준: 게임 도중 변하지 않는 정보는 객체 내부에서 변경 불가능해야 한다 +> +> (금지) 이번 미션에서 기물의 종류, 팀, 이동 규칙을 생성 후 변경하지 않는다 +> +> --- +> +> ### **3. 캡슐화 기준** +> +> (If-Then) 만약 어떤 상태를 외부에서 직접 수정 시 규칙 위반이나 정합성 문제가 생긴다면 +> +> → 그 상태는 내부에 캡슐화하고, 책임 있는 객체만 변경할 수 있게 한다 +> +> (If-Then) 만약 보드의 내부 상태가 외부 로직에 그대로 노출되면 +> +> → 보드의 상태는 직접 수정 가능한 형태로 공개하지 않고, 필요한 동작만 메서드로 제공한다 +> +> (기준) 캡슐화 기준: 정합성을 깨뜨릴 수 있는 내부 표현과 수정 권한은 숨긴다 +> +> (금지) 이번 미션에서 보드의 내부 상태를 외부에서 직접 수정하지 않는다 +> +> --- +> +> ### **4. null 사용 기준** +> +> (If-Then) 만약 빈 칸도 보드 위의 의미 있는 상태라면 +> +> → 빈 칸은 null 대신 객체 또는 존재 여부가 드러나는 방식으로 표현한다 +> +> (If-Then) 만약 빈 칸 여부를 자주 검사해야 한다면 +> +> → null 검사에 의존하기보다, 빈 상태를 명시적으로 표현하는 구조를 우선한다 +> +> (기준) null 사용 기준: 없음도 의미 있는 상태라면 명시적으로 표현한다 +> +> (금지) 이번 미션에서 빈 칸을 단순 null 검사에만 의존하여 처리하지 않는다 +> +> --- +> +> ### **5. 조건문 대체 기준** +> +> (If-Then) 만약 조건문이 객체의 종류에 따라 동작을 나누기 위해 사용된다면 +> +> → 조건문 대신 다형성을 우선 고려하고, 각 객체가 자신의 규칙을 직접 구현한다 +> +> (If-Then) 만약 조건문이 보드 범위 확인, 장애물 확인, 같은 팀 여부 확인처럼 객체 종류와 무관한 공통 검증이라면 +> +> → 이런 조건문은 공통 로직으로 유지한다 +> +> (기준) 다형성 적용 기준: 같은 질문에 객체마다 다른 답을 해야 하면 다형성 후보로 본다 +> +> (금지) 이번 미션에서 기물 타입을 문자열, enum, switch/if로 반복 분기하여 이동 규칙을 처리하지 않는다 +> +> --- +> +> ### **6. 역할/인터페이스 설계 기준** +> +> (If-Then) 만약 여러 객체가 같은 행위를 해야 하지만 구현 방식은 서로 다르다면 +> +> → 공통 인터페이스를 정의해 무엇을 할 수 있는가를 통일한다. +> +> (기준) 인터페이스 설계 기준: 호출 방식은 통일하고, 구현 방식은 각 객체에 위임한다. +> +> (금지) 이번 미션에서 호출하는 쪽이 기물 종류를 먼저 판별한 뒤 각 규칙을 직접 실행하지 않는다. +> +> --- +> +> ### **7. 새 타입 추가 시 변경 범위 제한 규칙** +> +> (If-Then) 만약 새 기물이 추가될 때 기존 이동 로직을 여러 곳 수정해야 한다면 +> +> → 기물별 책임 분리가 부족한 것으로 보고 구조를 꼭 필히 다시 점검한다. +> +> (기준) 확장성 판단 기준: 새로운 기물 추가나 규칙 변경 시 기존 코드 수정 범위가 작아야 한다. +> +> (금지) 이번 미션에서 새 기물 추가를 위해 기존 분기문을 계속 늘리는 방식으로 확장하지 않는다 From d1fa8949f55647afcc2a4c4bb62b496d1642aa85 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 14:59:59 +0900 Subject: [PATCH 02/92] =?UTF-8?q?test(PositionTest):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/PositionTest.java | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/test/java/PositionTest.java diff --git a/src/test/java/PositionTest.java b/src/test/java/PositionTest.java new file mode 100644 index 0000000000..ebb009c2c8 --- /dev/null +++ b/src/test/java/PositionTest.java @@ -0,0 +1,43 @@ +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PositionTest { + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8}) + void x의_값은_0_이상_8_이하여야_한다(int value) { + int y = 0; + assertThatCode(() -> new Position(value, y)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + void y의_값은_0_이상_9_이하여야_한다(int value) { + int x = 0; + assertThatCode(() -> new Position(x, value)) + .doesNotThrowAnyException(); + } + + + @ParameterizedTest + @ValueSource(ints = {-1, -2, 9, 10, 100}) + void x의_값이_0_이상_8_이하가_아니면_예외가_발생한다(int value) { + int y = 0; + assertThatThrownBy(() -> new Position(value, y)) + .isInstanceOf(IllegalArgumentException.class); + + } + + @ParameterizedTest + @ValueSource(ints = {-1, -2, 10, 11, 100}) + void y의_값이_0_이상_9_이하가_아니면_예외가_발생한다(int value) { + int x = 0; + assertThatThrownBy(() -> new Position(x, value)) + .isInstanceOf(IllegalArgumentException.class); + + } +} From a9564561569a3fb917dc1e4676385ab04d58b12a Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 15:01:23 +0900 Subject: [PATCH 03/92] =?UTF-8?q?feat(Position):=20=EC=9C=84=EC=B9=98=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 --- README.md | 10 +++++----- src/main/java/Application.java | 5 +++++ src/main/java/Position.java | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 src/main/java/Application.java create mode 100644 src/main/java/Position.java diff --git a/README.md b/README.md index cb2eac2380..547d3dff97 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,11 @@ ## 기능 목록 ### 위치(Position) -- [ ] 검증 - - [ ] x의 값은 0 이상 8 이하여야 한다. - - [ ] y의 값은 0 이상 9 이하여야 한다. - - [ ] x의 값이 0 이상 8 이하가 아니면 예외가 발생한다. - - [ ] y의 값이 0 이상 9 이하가 아니면 예외가 발생한다. +- [x] 검증 + - [x] x의 값은 0 이상 8 이하여야 한다. + - [x] y의 값은 0 이상 9 이하여야 한다. + - [x] x의 값이 0 이상 8 이하가 아니면 예외가 발생한다. + - [x] y의 값이 0 이상 9 이하가 아니면 예외가 발생한다. ### 진영(Side) - [ ] 진영은 초와 한만 가진다. ### 기물(Piece) diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 0000000000..a24952e4b7 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,5 @@ +public class Application { + public static void main(String[] args) { + System.out.println("Hello 제이콥"); + } +} diff --git a/src/main/java/Position.java b/src/main/java/Position.java new file mode 100644 index 0000000000..72c074ec8b --- /dev/null +++ b/src/main/java/Position.java @@ -0,0 +1,21 @@ +public class Position { + private final int x; + private final int y; + + public Position(int x, int y) { + validate(x, y); + this.x = x; + this.y = y; + } + + private void validate(int x, int y) { + validateRange(x, 0, 8); + validateRange(y, 0, 9); + } + + private void validateRange(int number, int min, int max) { + if (number < min || number > max) { + throw new IllegalArgumentException("범위를 벗어난 좌표를 입력했습니다."); + } + } +} From c6fffa2ebcbc984d2e1ea0eef5a7ab2829048d77 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 16:38:40 +0900 Subject: [PATCH 04/92] =?UTF-8?q?test(SideTest):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/SideTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/test/java/SideTest.java diff --git a/src/test/java/SideTest.java b/src/test/java/SideTest.java new file mode 100644 index 0000000000..8abb4ef513 --- /dev/null +++ b/src/test/java/SideTest.java @@ -0,0 +1,14 @@ +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class SideTest { + + @Test + void 진영은_초와_한만_가진다() { + List sides = Arrays.stream(Side.values()).toList(); + assertThat(sides).containsExactlyInAnyOrder(Side.CHO, Side.HAN); + } +} From 2306d36a76be55fc6e1d962be873d31effc28a20 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 16:39:42 +0900 Subject: [PATCH 05/92] =?UTF-8?q?feat(Side):=20=EC=A7=84=EC=98=81=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 --- README.md | 2 +- src/main/java/Side.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/main/java/Side.java diff --git a/README.md b/README.md index 547d3dff97..cf218a30f4 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ - [x] x의 값이 0 이상 8 이하가 아니면 예외가 발생한다. - [x] y의 값이 0 이상 9 이하가 아니면 예외가 발생한다. ### 진영(Side) - - [ ] 진영은 초와 한만 가진다. + - [x] 진영은 초와 한만 가진다. ### 기물(Piece) - [ ] 같은 진영 여부를 판단한다. ### 장기판(Board) diff --git a/src/main/java/Side.java b/src/main/java/Side.java new file mode 100644 index 0000000000..8306ff7318 --- /dev/null +++ b/src/main/java/Side.java @@ -0,0 +1,5 @@ +public enum Side { + CHO, + HAN, + ; +} From 98b7af5676e4fe51e639ce953d2a20861c08b9f6 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 17:08:07 +0900 Subject: [PATCH 06/92] =?UTF-8?q?test(PieceTest):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/PieceTest.java | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/test/java/PieceTest.java diff --git a/src/test/java/PieceTest.java b/src/test/java/PieceTest.java new file mode 100644 index 0000000000..614aafb297 --- /dev/null +++ b/src/test/java/PieceTest.java @@ -0,0 +1,36 @@ +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +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; + +class PieceTest { + + @ParameterizedTest + @MethodSource("provideSideCase") + void 같은_진영_여부를_판단한다(Side side, Side ohter, boolean expected) { + Piece piece = new SubPiece(side); + + boolean actual = piece.isSameSideAs(ohter); + + assertThat(actual).isEqualTo(expected); + } + + static Stream provideSideCase() { + return Stream.of( + arguments(Side.CHO, Side.CHO, true), + arguments(Side.HAN, Side.HAN, true), + arguments(Side.CHO, Side.HAN, false), + arguments(Side.HAN, Side.CHO, false) + ); + } + + static class SubPiece extends Piece { + + public SubPiece(Side side) { + super(side); + } + } +} From 8c0e6b97f03b5a2f757353b884f7c6904e473cfc Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 17:09:58 +0900 Subject: [PATCH 07/92] =?UTF-8?q?feat(Piece):=20=EA=B8=B0=EB=AC=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 --- README.md | 2 +- src/main/java/Piece.java | 12 ++++++++++++ src/main/java/Side.java | 4 ++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/main/java/Piece.java diff --git a/README.md b/README.md index cf218a30f4..7318dac007 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ ### 진영(Side) - [x] 진영은 초와 한만 가진다. ### 기물(Piece) - - [ ] 같은 진영 여부를 판단한다. + - [x] 같은 진영 여부를 판단한다. ### 장기판(Board) - [ ] 검증 - [ ] 장기판은 올바른 위치에 기물을 초기화한다. diff --git a/src/main/java/Piece.java b/src/main/java/Piece.java new file mode 100644 index 0000000000..73d5553118 --- /dev/null +++ b/src/main/java/Piece.java @@ -0,0 +1,12 @@ +public abstract class Piece { + private final Side side; + + public Piece(Side side) { + this.side = side; + } + + + public boolean isSameSideAs(Side other) { + return side.isSameAs(other); + } +} diff --git a/src/main/java/Side.java b/src/main/java/Side.java index 8306ff7318..e4de8c9218 100644 --- a/src/main/java/Side.java +++ b/src/main/java/Side.java @@ -2,4 +2,8 @@ public enum Side { CHO, HAN, ; + + public boolean isSameAs(Side other) { + return this.equals(other); + } } From 58305355b6fb77a1f72b83b5a4362dd51dc3ecbf Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 21:52:08 +0900 Subject: [PATCH 08/92] =?UTF-8?q?feat(Pieces):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=93=A4=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/Cannon.java | 6 ++++++ src/main/java/Chariot.java | 6 ++++++ src/main/java/Elephant.java | 6 ++++++ src/main/java/General.java | 6 ++++++ src/main/java/Guard.java | 6 ++++++ src/main/java/Horse.java | 6 ++++++ src/main/java/Soldier.java | 6 ++++++ 7 files changed, 42 insertions(+) create mode 100644 src/main/java/Cannon.java create mode 100644 src/main/java/Chariot.java create mode 100644 src/main/java/Elephant.java create mode 100644 src/main/java/General.java create mode 100644 src/main/java/Guard.java create mode 100644 src/main/java/Horse.java create mode 100644 src/main/java/Soldier.java diff --git a/src/main/java/Cannon.java b/src/main/java/Cannon.java new file mode 100644 index 0000000000..3f9f4c8dc8 --- /dev/null +++ b/src/main/java/Cannon.java @@ -0,0 +1,6 @@ +public class Cannon extends Piece { + + public Cannon(Side side) { + super(side); + } +} diff --git a/src/main/java/Chariot.java b/src/main/java/Chariot.java new file mode 100644 index 0000000000..745f8cc7ee --- /dev/null +++ b/src/main/java/Chariot.java @@ -0,0 +1,6 @@ +public class Chariot extends Piece { + + public Chariot(Side side) { + super(side); + } +} diff --git a/src/main/java/Elephant.java b/src/main/java/Elephant.java new file mode 100644 index 0000000000..1b419758a3 --- /dev/null +++ b/src/main/java/Elephant.java @@ -0,0 +1,6 @@ +public class Elephant extends Piece { + + public Elephant(Side side) { + super(side); + } +} diff --git a/src/main/java/General.java b/src/main/java/General.java new file mode 100644 index 0000000000..ffa91760cd --- /dev/null +++ b/src/main/java/General.java @@ -0,0 +1,6 @@ +public class General extends Piece { + + public General(Side side) { + super(side); + } +} diff --git a/src/main/java/Guard.java b/src/main/java/Guard.java new file mode 100644 index 0000000000..e57f68266a --- /dev/null +++ b/src/main/java/Guard.java @@ -0,0 +1,6 @@ +public class Guard extends Piece { + + public Guard(Side side) { + super(side); + } +} diff --git a/src/main/java/Horse.java b/src/main/java/Horse.java new file mode 100644 index 0000000000..205cd55f0c --- /dev/null +++ b/src/main/java/Horse.java @@ -0,0 +1,6 @@ +public class Horse extends Piece { + + public Horse(Side side) { + super(side); + } +} diff --git a/src/main/java/Soldier.java b/src/main/java/Soldier.java new file mode 100644 index 0000000000..5dad6677e8 --- /dev/null +++ b/src/main/java/Soldier.java @@ -0,0 +1,6 @@ +public class Soldier extends Piece { + + public Soldier(Side side) { + super(side); + } +} From 07a7c918d820f83b159503a20e88c192d5c81b03 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 21:53:51 +0900 Subject: [PATCH 09/92] =?UTF-8?q?feat(Board):=20=EC=9E=A5=EA=B8=B0?= =?UTF-8?q?=ED=8C=90=20=EC=83=9D=EC=84=B1=20=EB=B6=80=EB=B6=84=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/Board.java | 8 ++++++++ src/test/java/BoardTest.java | 5 +++++ 2 files changed, 13 insertions(+) create mode 100644 src/main/java/Board.java create mode 100644 src/test/java/BoardTest.java diff --git a/src/main/java/Board.java b/src/main/java/Board.java new file mode 100644 index 0000000000..0b0d5f786c --- /dev/null +++ b/src/main/java/Board.java @@ -0,0 +1,8 @@ +import java.util.Map; + +public class Board { + private final Map board; + public Board(Map board) { + this.board = board; + } +} diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java new file mode 100644 index 0000000000..a720619d51 --- /dev/null +++ b/src/test/java/BoardTest.java @@ -0,0 +1,5 @@ +import static org.junit.jupiter.api.Assertions.*; + +class BoardTest { + +} From 151c12afa23a67c77028f2b0192ccba2b5ff194b Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Wed, 25 Mar 2026 21:54:28 +0900 Subject: [PATCH 10/92] =?UTF-8?q?feat(Game):=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Application.java | 6 +++- src/main/java/Game.java | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/main/java/Game.java diff --git a/src/main/java/Application.java b/src/main/java/Application.java index a24952e4b7..32287b309c 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,5 +1,9 @@ +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class Application { public static void main(String[] args) { - System.out.println("Hello 제이콥"); + } } diff --git a/src/main/java/Game.java b/src/main/java/Game.java new file mode 100644 index 0000000000..a68e30b186 --- /dev/null +++ b/src/main/java/Game.java @@ -0,0 +1,56 @@ +import java.util.HashMap; +import java.util.Map; + +public class Game { + + private final Board board; + + public Game() { + Map fixedPieces = generateInitialBoard(); + this.board = new Board(fixedPieces); + } + + private static Map generateInitialBoard() { + Map initialPieces = new HashMap<>(); + String choSelection = "1"; + if (choSelection.equals("1")) { + initialPieces.put(new Position(1, 0), new Elephant(Side.CHO)); + initialPieces.put(new Position(6, 0), new Elephant(Side.CHO)); + initialPieces.put(new Position(2, 0), new Horse(Side.CHO)); + initialPieces.put(new Position(7, 0), new Horse(Side.CHO)); + } + initialPieces.put(new Position(0, 0), new Chariot(Side.CHO)); + initialPieces.put(new Position(8, 0), new Chariot(Side.CHO)); + initialPieces.put(new Position(3, 0), new Guard(Side.CHO)); + initialPieces.put(new Position(5, 0), new Guard(Side.CHO)); + initialPieces.put(new Position(4, 1), new General(Side.CHO)); + initialPieces.put(new Position(1, 2), new Cannon(Side.CHO)); + initialPieces.put(new Position(7, 2), new Cannon(Side.CHO)); + initialPieces.put(new Position(0, 3), new Soldier(Side.CHO)); + initialPieces.put(new Position(2, 3), new Soldier(Side.CHO)); + initialPieces.put(new Position(4, 3), new Soldier(Side.CHO)); + initialPieces.put(new Position(6, 3), new Soldier(Side.CHO)); + initialPieces.put(new Position(8, 3), new Soldier(Side.CHO)); + + String hanSelection = "1"; + if (hanSelection.equals("1")) { + initialPieces.put(new Position(2, 9), new Elephant(Side.HAN)); + initialPieces.put(new Position(7, 9), new Elephant(Side.HAN)); + initialPieces.put(new Position(1, 9), new Horse(Side.HAN)); + initialPieces.put(new Position(6, 9), new Horse(Side.HAN)); + } + initialPieces.put(new Position(0, 9), new Chariot(Side.HAN)); + initialPieces.put(new Position(8, 9), new Chariot(Side.HAN)); + initialPieces.put(new Position(3, 9), new Guard(Side.HAN)); + initialPieces.put(new Position(5, 9), new Guard(Side.HAN)); + initialPieces.put(new Position(4, 8), new General(Side.HAN)); + initialPieces.put(new Position(1, 7), new Cannon(Side.HAN)); + initialPieces.put(new Position(7, 7), new Cannon(Side.HAN)); + initialPieces.put(new Position(0, 6), new Soldier(Side.HAN)); + initialPieces.put(new Position(2, 6), new Soldier(Side.HAN)); + initialPieces.put(new Position(4, 6), new Soldier(Side.HAN)); + initialPieces.put(new Position(6, 6), new Soldier(Side.HAN)); + initialPieces.put(new Position(8, 6), new Soldier(Side.HAN)); + return initialPieces; + } +} From 663a123a0bdd235997730e5818f2b21e8d69f575 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Thu, 26 Mar 2026 18:12:44 +0900 Subject: [PATCH 11/92] =?UTF-8?q?feat(InitialBoardFactory):=20=EA=B2=8C?= =?UTF-8?q?=EC=9E=84=20=EC=B4=88=EA=B8=B0=ED=99=94=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/Application.java | 10 ++-- src/main/java/Formation.java | 77 ++++++++++++++++++++++++++ src/main/java/Game.java | 56 ++----------------- src/main/java/GameManager.java | 21 +++++++ src/main/java/InitialBoardFactory.java | 50 +++++++++++++++++ src/main/java/InputView.java | 33 +++++++++++ src/main/java/OutputView.java | 2 + src/main/java/Player.java | 7 +++ src/main/java/Selection.java | 22 ++++++++ src/main/java/Side.java | 63 ++++++++++++++++++++- 10 files changed, 284 insertions(+), 57 deletions(-) create mode 100644 src/main/java/Formation.java create mode 100644 src/main/java/GameManager.java create mode 100644 src/main/java/InitialBoardFactory.java create mode 100644 src/main/java/InputView.java create mode 100644 src/main/java/OutputView.java create mode 100644 src/main/java/Player.java create mode 100644 src/main/java/Selection.java diff --git a/src/main/java/Application.java b/src/main/java/Application.java index 32287b309c..b266603dbf 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,9 +1,11 @@ -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Scanner; public class Application { public static void main(String[] args) { - + GameManager gameManager = new GameManager( + new InputView(new Scanner(System.in)), + new OutputView() + ); + gameManager.start(); } } diff --git a/src/main/java/Formation.java b/src/main/java/Formation.java new file mode 100644 index 0000000000..3c1acd802c --- /dev/null +++ b/src/main/java/Formation.java @@ -0,0 +1,77 @@ +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public enum Formation { + LEFT_ELEPHANT(Selection.FIRST) { + @Override + public void placeElephant(Map pieces, Side side) { + List orders = List.of( + new Elephant(side), + new Horse(side), + new Elephant(side), + new Horse(side) + ); + extracted(pieces, side, orders); + } + }, + RIGHT_ELEPHANT(Selection.SECOND) { + @Override + public void placeElephant(Map pieces, Side side) { + List orders = List.of( + new Horse(side), + new Elephant(side), + new Horse(side), + new Elephant(side) + ); + extracted(pieces, side, orders); + } + }, + OUTER_ELEPHANT(Selection.THIRD) { + @Override + public void placeElephant(Map pieces, Side side) { + List orders = List.of( + new Elephant(side), + new Horse(side), + new Horse(side), + new Elephant(side) + ); + extracted(pieces, side, orders); + } + }, + INNER_ELEPHANT(Selection.FOURTH) { + @Override + public void placeElephant(Map pieces, Side side) { + List orders = List.of( + new Horse(side), + new Elephant(side), + new Elephant(side), + new Horse(side) + ); + extracted(pieces, side, orders); + } + }, + ; + + private final Selection selection; + + Formation(Selection selection) { + this.selection = selection; + } + + public static Formation from(Selection selection ) { + return Arrays.stream(values()) + .filter(formation -> formation.selection.equals(selection)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("올바른 배치가 아닙니다.")); + } + + public abstract void placeElephant(Map pieces, Side side); + + private static void extracted(Map pieces, Side side, List leftElephant) { + List a = side.formationX(); + for (int i = 0; i < leftElephant.size(); i++) { + pieces.put(new Position(a.get(i), side.baseY()), leftElephant.get(i)); + } + } +} diff --git a/src/main/java/Game.java b/src/main/java/Game.java index a68e30b186..172d7fd075 100644 --- a/src/main/java/Game.java +++ b/src/main/java/Game.java @@ -1,56 +1,12 @@ -import java.util.HashMap; -import java.util.Map; - public class Game { - private final Board board; + private final Player choPlayer; + private final Player hanPlayer; - public Game() { - Map fixedPieces = generateInitialBoard(); - this.board = new Board(fixedPieces); + public Game(Board board, Player choPlayer, Player hanPlayer) { + this.board = board; + this.choPlayer = choPlayer; + this.hanPlayer = hanPlayer; } - private static Map generateInitialBoard() { - Map initialPieces = new HashMap<>(); - String choSelection = "1"; - if (choSelection.equals("1")) { - initialPieces.put(new Position(1, 0), new Elephant(Side.CHO)); - initialPieces.put(new Position(6, 0), new Elephant(Side.CHO)); - initialPieces.put(new Position(2, 0), new Horse(Side.CHO)); - initialPieces.put(new Position(7, 0), new Horse(Side.CHO)); - } - initialPieces.put(new Position(0, 0), new Chariot(Side.CHO)); - initialPieces.put(new Position(8, 0), new Chariot(Side.CHO)); - initialPieces.put(new Position(3, 0), new Guard(Side.CHO)); - initialPieces.put(new Position(5, 0), new Guard(Side.CHO)); - initialPieces.put(new Position(4, 1), new General(Side.CHO)); - initialPieces.put(new Position(1, 2), new Cannon(Side.CHO)); - initialPieces.put(new Position(7, 2), new Cannon(Side.CHO)); - initialPieces.put(new Position(0, 3), new Soldier(Side.CHO)); - initialPieces.put(new Position(2, 3), new Soldier(Side.CHO)); - initialPieces.put(new Position(4, 3), new Soldier(Side.CHO)); - initialPieces.put(new Position(6, 3), new Soldier(Side.CHO)); - initialPieces.put(new Position(8, 3), new Soldier(Side.CHO)); - - String hanSelection = "1"; - if (hanSelection.equals("1")) { - initialPieces.put(new Position(2, 9), new Elephant(Side.HAN)); - initialPieces.put(new Position(7, 9), new Elephant(Side.HAN)); - initialPieces.put(new Position(1, 9), new Horse(Side.HAN)); - initialPieces.put(new Position(6, 9), new Horse(Side.HAN)); - } - initialPieces.put(new Position(0, 9), new Chariot(Side.HAN)); - initialPieces.put(new Position(8, 9), new Chariot(Side.HAN)); - initialPieces.put(new Position(3, 9), new Guard(Side.HAN)); - initialPieces.put(new Position(5, 9), new Guard(Side.HAN)); - initialPieces.put(new Position(4, 8), new General(Side.HAN)); - initialPieces.put(new Position(1, 7), new Cannon(Side.HAN)); - initialPieces.put(new Position(7, 7), new Cannon(Side.HAN)); - initialPieces.put(new Position(0, 6), new Soldier(Side.HAN)); - initialPieces.put(new Position(2, 6), new Soldier(Side.HAN)); - initialPieces.put(new Position(4, 6), new Soldier(Side.HAN)); - initialPieces.put(new Position(6, 6), new Soldier(Side.HAN)); - initialPieces.put(new Position(8, 6), new Soldier(Side.HAN)); - return initialPieces; - } } diff --git a/src/main/java/GameManager.java b/src/main/java/GameManager.java new file mode 100644 index 0000000000..b4b28e39b2 --- /dev/null +++ b/src/main/java/GameManager.java @@ -0,0 +1,21 @@ +public class GameManager { + private final InputView inputView; + private final OutputView outputView; + + public GameManager(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void start() { + Player choPlayer = new Player(inputView.readChoPlayerName()); + Player hanPlayer = new Player(inputView.readHanPlayerName()); + + Formation choFormation = Formation.from(Selection.from(inputView.readChoFormation())); + Formation hanFormation = Formation.from(Selection.from(inputView.readHanFormation())); + + Board board = new InitialBoardFactory().create(choFormation, hanFormation); + + Game game = new Game(board, choPlayer, hanPlayer); + } +} diff --git a/src/main/java/InitialBoardFactory.java b/src/main/java/InitialBoardFactory.java new file mode 100644 index 0000000000..b333a87acc --- /dev/null +++ b/src/main/java/InitialBoardFactory.java @@ -0,0 +1,50 @@ +import java.util.HashMap; +import java.util.Map; + +public class InitialBoardFactory { + + public Board create(Formation choFormation, Formation hanFormation) { + Map pieces = new HashMap<>(); + + placeFixedPieces(pieces, Side.CHO); + choFormation.placeElephant(pieces, Side.CHO); + + placeFixedPieces(pieces, Side.HAN); + hanFormation.placeElephant(pieces, Side.HAN); + + return new Board(pieces); + } + + private void placeFixedPieces(Map pieces, Side side) { + placeGeneral(pieces, side); + placeChariots(pieces, side); + placeCannons(pieces, side); + placeGuards(pieces, side); + placeSoldiers(pieces, side); + } + + private void placeGeneral(Map pieces, Side side) { + pieces.put(new Position(4, side.generalY()), new General(side)); + } + + private void placeChariots(Map pieces, Side side) { + pieces.put(new Position(0, side.baseY()), new Chariot(side)); + pieces.put(new Position(8, side.baseY()), new Chariot(side)); + } + + private void placeCannons(Map pieces, Side side) { + pieces.put(new Position(1, side.cannonY()), new Cannon(side)); + pieces.put(new Position(7, side.cannonY()), new Cannon(side)); + } + + private void placeGuards(Map pieces, Side side) { + pieces.put(new Position(3, side.baseY()), new Guard(side)); + pieces.put(new Position(5, side.baseY()), new Guard(side)); + } + + private void placeSoldiers(Map pieces, Side side) { + for (int x = 0; x <= 8; x += 2) { + pieces.put(new Position(x, side.soldierY()), new Soldier(side)); + } + } +} diff --git a/src/main/java/InputView.java b/src/main/java/InputView.java new file mode 100644 index 0000000000..d81c1a09fe --- /dev/null +++ b/src/main/java/InputView.java @@ -0,0 +1,33 @@ +import java.util.Scanner; + +public class InputView { + private final Scanner scanner; + + public InputView(Scanner scanner) { + this.scanner = scanner; + } + + public String readChoPlayerName() { + System.out.println("초나라 플레이어 이름 입력: "); + return readLine(); + } + + public String readHanPlayerName() { + System.out.println("한나라 플레이어 이름 입력: "); + return readLine(); + } + + public String readChoFormation() { + System.out.println("초나라 플레이어 포지션 입력: "); + return readLine(); + } + + public String readHanFormation() { + System.out.println("한나라 플레이어 포지션 입력: "); + return readLine(); + } + + private String readLine() { + return scanner.nextLine(); + } +} diff --git a/src/main/java/OutputView.java b/src/main/java/OutputView.java new file mode 100644 index 0000000000..3c88321970 --- /dev/null +++ b/src/main/java/OutputView.java @@ -0,0 +1,2 @@ +public class OutputView { +} diff --git a/src/main/java/Player.java b/src/main/java/Player.java new file mode 100644 index 0000000000..085189568d --- /dev/null +++ b/src/main/java/Player.java @@ -0,0 +1,7 @@ +public class Player { + private final String name; + + public Player(String name) { + this.name = name; + } +} diff --git a/src/main/java/Selection.java b/src/main/java/Selection.java new file mode 100644 index 0000000000..f9a9d5a5e7 --- /dev/null +++ b/src/main/java/Selection.java @@ -0,0 +1,22 @@ +import java.util.Arrays; + +public enum Selection { + FIRST("1"), + SECOND("2"), + THIRD("3"), + FOURTH("4"), + ; + + private final String input; + + Selection(String input) { + this.input = input; + } + + public static Selection from(String input) { + return Arrays.stream(values()) + .filter(selection -> selection.input.equals(input)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("올바른 입력이 아닙니다.")); + } +} diff --git a/src/main/java/Side.java b/src/main/java/Side.java index e4de8c9218..b46f1944f0 100644 --- a/src/main/java/Side.java +++ b/src/main/java/Side.java @@ -1,7 +1,64 @@ +import java.util.List; + public enum Side { - CHO, - HAN, - ; + CHO { + @Override + public int baseY() { + return 0; + } + + @Override + public int generalY() { + return 1; + } + + @Override + public int cannonY() { + return 2; + } + + @Override + public int soldierY() { + return 3; + } + + @Override + public List formationX() { + return List.of(1, 2, 6, 7); + } + }, + HAN { + @Override + public int baseY() { + return 9; + } + + @Override + public int generalY() { + return 8; + } + + @Override + public int cannonY() { + return 7; + } + + @Override + public int soldierY() { + return 6; + } + + @Override + public List formationX() { + return List.of(7, 6, 2, 1); + } + }; + + public abstract int baseY(); + public abstract int generalY(); + public abstract int cannonY(); + public abstract int soldierY(); + public abstract List formationX(); public boolean isSameAs(Side other) { return this.equals(other); From b4c9502c00c7d51a4cca2bfe9d126d2d1d6cc03e Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Fri, 27 Mar 2026 19:27:09 +0900 Subject: [PATCH 12/92] =?UTF-8?q?feat(Position):=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=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 --- src/main/java/Position.java | 91 +++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/src/main/java/Position.java b/src/main/java/Position.java index 72c074ec8b..02d6bd0c70 100644 --- a/src/main/java/Position.java +++ b/src/main/java/Position.java @@ -1,4 +1,9 @@ +import java.util.Objects; + public class Position { + public static final int MIN = 0; + public static final int MAX_X = 8; + public static final int MAX_Y = 9; private final int x; private final int y; @@ -9,13 +14,91 @@ public Position(int x, int y) { } private void validate(int x, int y) { - validateRange(x, 0, 8); - validateRange(y, 0, 9); + validateRange(x, MAX_X); + validateRange(y, MAX_Y); } - private void validateRange(int number, int min, int max) { - if (number < min || number > max) { + private void validateRange(int number, int max) { + if (number < MIN || number > max) { throw new IllegalArgumentException("범위를 벗어난 좌표를 입력했습니다."); } } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) { + return false; + } + Position position = (Position) object; + return x == position.x && y == position.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + public Position up() { + return new Position(x, y + 1); + } + + public Position down() { + return new Position(x, y - 1); + } + + public Position left() { + return new Position(x - 1, y); + } + + public Position right() { + return new Position(x + 1, y); + } + + public Position leftUp() { + return new Position(x - 1, y + 1); + } + + public Position leftDown() { + return new Position(x - 1, y - 1); + } + + public Position rightUp() { + return new Position(x + 1, y + 1); + } + + public Position rightDown() { + return new Position(x + 1, y - 1); + } + + public boolean upPossible() { + return y + 1 <= MAX_Y; + } + + public boolean downPossible() { + return y - 1 >= MIN; + } + + public boolean leftPossible() { + return x - 1 >= MIN; + } + + public boolean rightPossible() { + return x + 1 <= MAX_X; + } + + public boolean leftUpPossible() { + return x - 1 >= MIN && y + 1 <= MAX_Y; + } + + public boolean leftDownPossible() { + return x - 1 >= MIN && y - 1 >= MIN; + } + + public boolean rightUpPossible() { + return x + 1 <= MAX_X && y + 1 <= MAX_Y; + } + + public boolean rightDownPossible() { + return x + 1 <= MAX_X && y - 1 >= MIN; + } } From 559f0f2c71760e20400ed30fd7da58f5f2c29843 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Fri, 27 Mar 2026 19:27:38 +0900 Subject: [PATCH 13/92] =?UTF-8?q?docs(README):=201.2=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7318dac007..e40715c703 100644 --- a/README.md +++ b/README.md @@ -28,22 +28,48 @@ --- ## 기능 목록 - -### 위치(Position) +### 1.1단계 +#### 위치(Position) - [x] 검증 - [x] x의 값은 0 이상 8 이하여야 한다. - [x] y의 값은 0 이상 9 이하여야 한다. - [x] x의 값이 0 이상 8 이하가 아니면 예외가 발생한다. - [x] y의 값이 0 이상 9 이하가 아니면 예외가 발생한다. -### 진영(Side) +#### 진영(Side) - [x] 진영은 초와 한만 가진다. -### 기물(Piece) +#### 기물(Piece) - [x] 같은 진영 여부를 판단한다. -### 장기판(Board) +#### 장기판(Board) - [ ] 검증 - [ ] 장기판은 올바른 위치에 기물을 초기화한다. - [ ] 장기판은 올바른 위치에 기물을 초기화하지 않으면 예외가 발생한다. +### 1.2단계 +#### 기물(Piece) +- [ ] 멱에 기물이 존재하면 이동할 수 없다 +- [ ] 목적지에 아군이 있으면 이동할 수 없다 +- [ ] 목적지에 적이 있으면 포획한다 + +#### General +- [ ] 직각방향 중 한칸 이동한다 +#### Guard +- [ ] 직각방향 중 한칸 이동한다 +#### Horse +- [ ] 직각방향 중 한칸 이동하고 이동한 방향의 대각방향으로 한칸 이동한다 +#### Elephant +- [ ] 직각방향 중 한칸 이동하고 이동한 방향의 대각방향으로 두칸 이동한다 +#### Chariot +- [ ] 직각방향 중 원하는 칸으로 이동한다 +#### Cannon +- [ ] 직각방향 중 가장 가까운 기물 이후의 칸으로 이동한다 + - [ ] 가장 가까운 기물이 존재하면 이동 가능하다 + - [ ] 가장 가까운 기물이 존재하지 않으면 이동 할 수 없다 + - [ ] 가장 가까운 기물이 포일 때 이동할 수 없다 + - [ ] 목적지에 있는 적 기물이 포인 경우 포획할 수 없다 +#### Soldier +- [ ] 직각방향 중 한칸 이동한다 +- [ ] 후진 이동 할 수 없다 + --- ## 입출력예시 From 8dfc831cd3a8cf3b4d77f61936a72919cb30e5e7 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Fri, 27 Mar 2026 19:27:38 +0900 Subject: [PATCH 14/92] =?UTF-8?q?docs(README):=201.2=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7318dac007..7ffcb16d4d 100644 --- a/README.md +++ b/README.md @@ -28,22 +28,87 @@ --- ## 기능 목록 - -### 위치(Position) +### 1.1단계 +#### 위치(domain.Position) - [x] 검증 - [x] x의 값은 0 이상 8 이하여야 한다. - [x] y의 값은 0 이상 9 이하여야 한다. - [x] x의 값이 0 이상 8 이하가 아니면 예외가 발생한다. - [x] y의 값이 0 이상 9 이하가 아니면 예외가 발생한다. -### 진영(Side) +#### 진영(domain.Side) - [x] 진영은 초와 한만 가진다. -### 기물(Piece) +#### 기물(domain.Piece) - [x] 같은 진영 여부를 판단한다. -### 장기판(Board) +#### 장기판(domain.Board) - [ ] 검증 - [ ] 장기판은 올바른 위치에 기물을 초기화한다. - [ ] 장기판은 올바른 위치에 기물을 초기화하지 않으면 예외가 발생한다. +### 1.2단계 +#### 기물(domain.Piece) +- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 +- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + +#### 궁(domain.General) +- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [ ] 상, 하, 좌, 우 1칸 모든 위치 반환 + - [ ] 보드 범위를 벗어나는 위치 제외 +- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [ ] 상, 하, 좌, 우 1칸 위치 반환 + - [ ] 해당 위치에 내 기물이 있으면 가지 못한다. +#### 사(domain.Guard) +- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [ ] 상, 하, 좌, 우 1칸 모든 위치 반환 + - [ ] 보드 범위를 벗어나는 위치 제외 +- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [ ] 상, 하, 좌, 우 1칸 위치 반환 + - [ ] 해당 위치에 내 기물이 있으면 가지 못한다. +#### 졸(domain.Soldier) +- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [ ] 초 기준 + - [ ] 상, 좌, 우 1칸 모든 위치 반환 + - [ ] 보드 범위를 벗어나는 위치 제외 + - [ ] 한 기준 + - [ ] 하, 좌, 우 1칸 모든 위치 반환 + - [ ] 보드 범위를 벗어나는 위치 제외 +- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [ ] 상, 하, 좌, 우 위치 반환 + - [ ] 해당 위치에 내 기물이 있으면 가지 못한다. +#### 마(domain.Horse) +- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [ ] 상하좌우 4개 및 날 일자 8개 위치 모두 반환 + - [ ] 보드 범위를 벗어나는 위치 제외 +- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [ ] 날 일자 8개의 목적지 모두 확인 + 1. 경유지에 기물이 없고, 날 일자 위치가 보드 범위를 벗어나지 않아야 한다. + 2. 1을 만족하면서 기물이 없거나 상대 기물이 있으면 해당 위치는 최종 목적지가 될 수 있다. +#### 상(domain.Elephant) +- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [ ] 상하좌우 4개, 날 일자 8개, 눈 목자 8개 위치 모두 반환 + - [ ] 보드 범위를 벗어나는 위치 제외 +- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [ ] 눈 목자 8개의 목적지 모두 확인 + 1. 경유지(직진 1칸, 날 일자 위치)에 기물이 없고, 눈 목자 위치가 보드 범위를 벗어나지 않아야 한다. + 2. 1을 만족하면서 기물이 없거나 상대 기물이 있으면 해당 위치는 최종 목적지가 될 수 있다. +#### 차(domain.Chariot) +- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [ ] 상하좌우 모든 직진 위치 반환 + - [ ] 보드 범위를 벗어나는 위치 제외 +- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [ ] 상하좌우 직진 위치 반환 + 1. 위치 탐색 중 내 기물이 있다면 기물 직전까지의 위치를 모두 반환한다. + 2. 위치 탐색 중 상대 기물이 있다면 기물까지의 위치를 모두 반환한다. +#### 포(domain.Cannon) +- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [ ] 상하좌우 모든 직진 위치 반환 + - [ ] 보드 범위를 벗어나는 위치 제외 +- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [ ] 상하좌우 직진 위치 반환 + 1. 위치 탐색 중 포가 아닌 기물을 만난 후부터의 위치를 반환한다. + 2. 포가 아닌 기물을 만난 이후의 위치 탐색 중 내 기물이 있다면 기물 직전까지의 위치를 모두 반환한다. + 3. 포가 아닌 기물을 만난 이후의 위치 탐색 중 상대의 포가 아닌 기물이 있다면 기물까지의 위치를 모두 반환한다. + 4. 포가 아닌 기물을 만난 이후의 위치 탐색 중 상대의 포 기물이 있다면 기물까지의 위치를 모두 반환한다. + --- ## 입출력예시 @@ -111,7 +176,7 @@ - [ ] 힌트: if문에서 값을 반환하는 방식으로 구현하면 else 예약어를 사용하지 않아도 된다. - [ ] 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외 - [ ] 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다. - - [ ] UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다. + - [ ] UI 로직을 view.InputView, ResultView와 같은 클래스를 추가해 분리한다. - [ ] 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. - [ ] 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라. - [ ] 배열 대신 컬렉션을 사용한다. From 4b70abd0238dd22d42649636f4c41461d30831ff Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:13:15 +0900 Subject: [PATCH 15/92] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/.gitkeep | 0 src/main/java/Application.java | 3 +++ src/main/java/{ => domain}/Board.java | 2 ++ src/main/java/{ => domain}/Cannon.java | 2 ++ src/main/java/{ => domain}/Chariot.java | 2 ++ src/main/java/{ => domain}/Elephant.java | 2 ++ src/main/java/{ => domain}/Formation.java | 2 ++ src/main/java/{ => domain}/Game.java | 2 ++ src/main/java/{ => domain}/GameManager.java | 5 +++++ src/main/java/{ => domain}/General.java | 2 ++ src/main/java/{ => domain}/Guard.java | 2 ++ src/main/java/{ => domain}/Horse.java | 2 ++ src/main/java/{ => domain}/InitialBoardFactory.java | 2 ++ src/main/java/{ => domain}/Piece.java | 2 ++ src/main/java/{ => domain}/Player.java | 2 ++ src/main/java/{ => domain}/Position.java | 2 ++ src/main/java/{ => domain}/Side.java | 2 ++ src/main/java/{ => domain}/Soldier.java | 2 ++ src/main/java/{ => view}/InputView.java | 2 ++ src/main/java/{ => view}/OutputView.java | 2 ++ src/test/java/.gitkeep | 0 src/test/java/PieceTest.java | 2 ++ src/test/java/PositionTest.java | 1 + src/test/java/SideTest.java | 1 + 24 files changed, 46 insertions(+) delete mode 100644 src/main/java/.gitkeep rename src/main/java/{ => domain}/Board.java (91%) rename src/main/java/{ => domain}/Cannon.java (85%) rename src/main/java/{ => domain}/Chariot.java (85%) rename src/main/java/{ => domain}/Elephant.java (85%) rename src/main/java/{ => domain}/Formation.java (99%) rename src/main/java/{ => domain}/Game.java (94%) rename src/main/java/{ => domain}/GameManager.java (92%) rename src/main/java/{ => domain}/General.java (85%) rename src/main/java/{ => domain}/Guard.java (84%) rename src/main/java/{ => domain}/Horse.java (84%) rename src/main/java/{ => domain}/InitialBoardFactory.java (99%) rename src/main/java/{ => domain}/Piece.java (92%) rename src/main/java/{ => domain}/Player.java (87%) rename src/main/java/{ => domain}/Position.java (99%) rename src/main/java/{ => domain}/Side.java (98%) rename src/main/java/{ => domain}/Soldier.java (85%) rename src/main/java/{ => view}/InputView.java (98%) rename src/main/java/{ => view}/OutputView.java (65%) delete mode 100644 src/test/java/.gitkeep diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/main/java/Application.java b/src/main/java/Application.java index b266603dbf..e3a5f760f5 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,4 +1,7 @@ +import domain.GameManager; import java.util.Scanner; +import view.InputView; +import view.OutputView; public class Application { public static void main(String[] args) { diff --git a/src/main/java/Board.java b/src/main/java/domain/Board.java similarity index 91% rename from src/main/java/Board.java rename to src/main/java/domain/Board.java index 0b0d5f786c..654c270212 100644 --- a/src/main/java/Board.java +++ b/src/main/java/domain/Board.java @@ -1,3 +1,5 @@ +package domain; + import java.util.Map; public class Board { diff --git a/src/main/java/Cannon.java b/src/main/java/domain/Cannon.java similarity index 85% rename from src/main/java/Cannon.java rename to src/main/java/domain/Cannon.java index 3f9f4c8dc8..68531641de 100644 --- a/src/main/java/Cannon.java +++ b/src/main/java/domain/Cannon.java @@ -1,3 +1,5 @@ +package domain; + public class Cannon extends Piece { public Cannon(Side side) { diff --git a/src/main/java/Chariot.java b/src/main/java/domain/Chariot.java similarity index 85% rename from src/main/java/Chariot.java rename to src/main/java/domain/Chariot.java index 745f8cc7ee..1a2668b8dd 100644 --- a/src/main/java/Chariot.java +++ b/src/main/java/domain/Chariot.java @@ -1,3 +1,5 @@ +package domain; + public class Chariot extends Piece { public Chariot(Side side) { diff --git a/src/main/java/Elephant.java b/src/main/java/domain/Elephant.java similarity index 85% rename from src/main/java/Elephant.java rename to src/main/java/domain/Elephant.java index 1b419758a3..5c6d117296 100644 --- a/src/main/java/Elephant.java +++ b/src/main/java/domain/Elephant.java @@ -1,3 +1,5 @@ +package domain; + public class Elephant extends Piece { public Elephant(Side side) { diff --git a/src/main/java/Formation.java b/src/main/java/domain/Formation.java similarity index 99% rename from src/main/java/Formation.java rename to src/main/java/domain/Formation.java index 3c1acd802c..04f05b3892 100644 --- a/src/main/java/Formation.java +++ b/src/main/java/domain/Formation.java @@ -1,3 +1,5 @@ +package domain; + import java.util.Arrays; import java.util.List; import java.util.Map; diff --git a/src/main/java/Game.java b/src/main/java/domain/Game.java similarity index 94% rename from src/main/java/Game.java rename to src/main/java/domain/Game.java index 172d7fd075..a2927810f3 100644 --- a/src/main/java/Game.java +++ b/src/main/java/domain/Game.java @@ -1,3 +1,5 @@ +package domain; + public class Game { private final Board board; private final Player choPlayer; diff --git a/src/main/java/GameManager.java b/src/main/java/domain/GameManager.java similarity index 92% rename from src/main/java/GameManager.java rename to src/main/java/domain/GameManager.java index b4b28e39b2..65fea21d75 100644 --- a/src/main/java/GameManager.java +++ b/src/main/java/domain/GameManager.java @@ -1,3 +1,8 @@ +package domain; + +import view.InputView; +import view.OutputView; + public class GameManager { private final InputView inputView; private final OutputView outputView; diff --git a/src/main/java/General.java b/src/main/java/domain/General.java similarity index 85% rename from src/main/java/General.java rename to src/main/java/domain/General.java index ffa91760cd..b035917010 100644 --- a/src/main/java/General.java +++ b/src/main/java/domain/General.java @@ -1,3 +1,5 @@ +package domain; + public class General extends Piece { public General(Side side) { diff --git a/src/main/java/Guard.java b/src/main/java/domain/Guard.java similarity index 84% rename from src/main/java/Guard.java rename to src/main/java/domain/Guard.java index e57f68266a..7f3b35c37d 100644 --- a/src/main/java/Guard.java +++ b/src/main/java/domain/Guard.java @@ -1,3 +1,5 @@ +package domain; + public class Guard extends Piece { public Guard(Side side) { diff --git a/src/main/java/Horse.java b/src/main/java/domain/Horse.java similarity index 84% rename from src/main/java/Horse.java rename to src/main/java/domain/Horse.java index 205cd55f0c..f2fd3e8c10 100644 --- a/src/main/java/Horse.java +++ b/src/main/java/domain/Horse.java @@ -1,3 +1,5 @@ +package domain; + public class Horse extends Piece { public Horse(Side side) { diff --git a/src/main/java/InitialBoardFactory.java b/src/main/java/domain/InitialBoardFactory.java similarity index 99% rename from src/main/java/InitialBoardFactory.java rename to src/main/java/domain/InitialBoardFactory.java index b333a87acc..3e8aae915d 100644 --- a/src/main/java/InitialBoardFactory.java +++ b/src/main/java/domain/InitialBoardFactory.java @@ -1,3 +1,5 @@ +package domain; + import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/Piece.java b/src/main/java/domain/Piece.java similarity index 92% rename from src/main/java/Piece.java rename to src/main/java/domain/Piece.java index 73d5553118..297fd19072 100644 --- a/src/main/java/Piece.java +++ b/src/main/java/domain/Piece.java @@ -1,3 +1,5 @@ +package domain; + public abstract class Piece { private final Side side; diff --git a/src/main/java/Player.java b/src/main/java/domain/Player.java similarity index 87% rename from src/main/java/Player.java rename to src/main/java/domain/Player.java index 085189568d..ce3a0bae9e 100644 --- a/src/main/java/Player.java +++ b/src/main/java/domain/Player.java @@ -1,3 +1,5 @@ +package domain; + public class Player { private final String name; diff --git a/src/main/java/Position.java b/src/main/java/domain/Position.java similarity index 99% rename from src/main/java/Position.java rename to src/main/java/domain/Position.java index 02d6bd0c70..3f55226a06 100644 --- a/src/main/java/Position.java +++ b/src/main/java/domain/Position.java @@ -1,3 +1,5 @@ +package domain; + import java.util.Objects; public class Position { diff --git a/src/main/java/Side.java b/src/main/java/domain/Side.java similarity index 98% rename from src/main/java/Side.java rename to src/main/java/domain/Side.java index b46f1944f0..55311edbce 100644 --- a/src/main/java/Side.java +++ b/src/main/java/domain/Side.java @@ -1,3 +1,5 @@ +package domain; + import java.util.List; public enum Side { diff --git a/src/main/java/Soldier.java b/src/main/java/domain/Soldier.java similarity index 85% rename from src/main/java/Soldier.java rename to src/main/java/domain/Soldier.java index 5dad6677e8..4a060f5bd9 100644 --- a/src/main/java/Soldier.java +++ b/src/main/java/domain/Soldier.java @@ -1,3 +1,5 @@ +package domain; + public class Soldier extends Piece { public Soldier(Side side) { diff --git a/src/main/java/InputView.java b/src/main/java/view/InputView.java similarity index 98% rename from src/main/java/InputView.java rename to src/main/java/view/InputView.java index d81c1a09fe..94a630946a 100644 --- a/src/main/java/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,3 +1,5 @@ +package view; + import java.util.Scanner; public class InputView { diff --git a/src/main/java/OutputView.java b/src/main/java/view/OutputView.java similarity index 65% rename from src/main/java/OutputView.java rename to src/main/java/view/OutputView.java index 3c88321970..d8f9743ccf 100644 --- a/src/main/java/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,2 +1,4 @@ +package view; + public class OutputView { } diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/test/java/PieceTest.java b/src/test/java/PieceTest.java index 614aafb297..65e0f8868a 100644 --- a/src/test/java/PieceTest.java +++ b/src/test/java/PieceTest.java @@ -1,6 +1,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; +import domain.Piece; +import domain.Side; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/src/test/java/PositionTest.java b/src/test/java/PositionTest.java index ebb009c2c8..236858a7e2 100644 --- a/src/test/java/PositionTest.java +++ b/src/test/java/PositionTest.java @@ -1,6 +1,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import domain.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; diff --git a/src/test/java/SideTest.java b/src/test/java/SideTest.java index 8abb4ef513..61f4c22338 100644 --- a/src/test/java/SideTest.java +++ b/src/test/java/SideTest.java @@ -1,5 +1,6 @@ import static org.assertj.core.api.Assertions.assertThat; +import domain.Side; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; From 80c4f315a3f7371f90c5afebc411afcdbf7834f0 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:18:49 +0900 Subject: [PATCH 16/92] =?UTF-8?q?feat(Piece):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=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 --- README.md | 4 ++-- src/main/java/domain/Piece.java | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7ffcb16d4d..7098449fe4 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ ### 1.2단계 #### 기물(domain.Piece) -- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 -- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 +- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 +- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 #### 궁(domain.General) - [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 diff --git a/src/main/java/domain/Piece.java b/src/main/java/domain/Piece.java index 297fd19072..e2f7dc5d6e 100644 --- a/src/main/java/domain/Piece.java +++ b/src/main/java/domain/Piece.java @@ -1,14 +1,20 @@ package domain; +import java.util.List; +import java.util.Map; + public abstract class Piece { - private final Side side; + protected final Side side; public Piece(Side side) { this.side = side; } - public boolean isSameSideAs(Side other) { return side.isSameAs(other); } + + public abstract List getAllPosition(Position position); + + public abstract List getPossibleDestinations(Position position, Map map); } From 7b112e08abc8795e581950a9fb68f53b34375de1 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:51:12 +0900 Subject: [PATCH 17/92] =?UTF-8?q?feat(General):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +++---- src/main/java/domain/General.java | 58 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7098449fe4..02edf8d2bb 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,12 @@ - [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 #### 궁(domain.General) -- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [ ] 상, 하, 좌, 우 1칸 모든 위치 반환 - - [ ] 보드 범위를 벗어나는 위치 제외 -- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [ ] 상, 하, 좌, 우 1칸 위치 반환 - - [ ] 해당 위치에 내 기물이 있으면 가지 못한다. +- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [x] 상, 하, 좌, 우 1칸 모든 위치 반환 + - [x] 보드 범위를 벗어나는 위치 제외 +- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [x] 상, 하, 좌, 우 1칸 위치 반환 + - [x] 해당 위치에 내 기물이 있으면 가지 못한다. #### 사(domain.Guard) - [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - [ ] 상, 하, 좌, 우 1칸 모든 위치 반환 diff --git a/src/main/java/domain/General.java b/src/main/java/domain/General.java index b035917010..256e158128 100644 --- a/src/main/java/domain/General.java +++ b/src/main/java/domain/General.java @@ -1,8 +1,66 @@ package domain; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class General extends Piece { + private final String name = "궁"; public General(Side side) { super(side); } + + @Override + public List getAllPosition(Position position) { + List positions = new ArrayList<>(); + + // 상 + if (position.upPossible()) { + positions.add(position.up()); + } + // 하 + if (position.downPossible()) { + positions.add(position.down()); + } + // 좌 + if (position.leftPossible()) { + positions.add(position.left()); + } + // 우 + if (position.rightPossible()) { + positions.add(position.right()); + } + + return positions; + } + + @Override + public List getPossibleDestinations(Position position, Map board) { + List destinations = new ArrayList<>(); + + // 상 + if (position.upPossible() && (!board.containsKey(position.up()) || !board.get(position.up()).isSameSideAs(side))) { + destinations.add(position.up()); + } + // 하 + if (position.downPossible() && (!board.containsKey(position.down()) || !board.get(position.down()).isSameSideAs(side))) { + destinations.add(position.down()); + } + // 좌 + if (position.leftPossible() && (!board.containsKey(position.left()) || !board.get(position.left()).isSameSideAs(side))) { + destinations.add(position.left()); + } + // 우 + if (position.rightPossible() && (!board.containsKey(position.right()) || !board.get(position.right()).isSameSideAs(side))) { + destinations.add(position.right()); + } + + return destinations; + } + + @Override + public String toString() { + return name; + } } From 6937ac25c62419a77326edf86b0d933ac6589136 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:52:56 +0900 Subject: [PATCH 18/92] =?UTF-8?q?feat(Guard):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +++---- src/main/java/domain/Guard.java | 58 +++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 02edf8d2bb..e50203589e 100644 --- a/README.md +++ b/README.md @@ -57,12 +57,12 @@ - [x] 상, 하, 좌, 우 1칸 위치 반환 - [x] 해당 위치에 내 기물이 있으면 가지 못한다. #### 사(domain.Guard) -- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [ ] 상, 하, 좌, 우 1칸 모든 위치 반환 - - [ ] 보드 범위를 벗어나는 위치 제외 -- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [ ] 상, 하, 좌, 우 1칸 위치 반환 - - [ ] 해당 위치에 내 기물이 있으면 가지 못한다. +- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [x] 상, 하, 좌, 우 1칸 모든 위치 반환 + - [x] 보드 범위를 벗어나는 위치 제외 +- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [x] 상, 하, 좌, 우 1칸 위치 반환 + - [x] 해당 위치에 내 기물이 있으면 가지 못한다. #### 졸(domain.Soldier) - [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - [ ] 초 기준 diff --git a/src/main/java/domain/Guard.java b/src/main/java/domain/Guard.java index 7f3b35c37d..42108b97ab 100644 --- a/src/main/java/domain/Guard.java +++ b/src/main/java/domain/Guard.java @@ -1,8 +1,66 @@ package domain; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class Guard extends Piece { + private final String name = "사"; public Guard(Side side) { super(side); } + + @Override + public List getAllPosition(Position position) { + List positions = new ArrayList<>(); + + // 상 + if (position.upPossible()) { + positions.add(position.up()); + } + // 하 + if (position.downPossible()) { + positions.add(position.down()); + } + // 좌 + if (position.leftPossible()) { + positions.add(position.left()); + } + // 우 + if (position.rightPossible()) { + positions.add(position.right()); + } + + return positions; + } + + @Override + public List getPossibleDestinations(Position position, Map board) { + List destinations = new ArrayList<>(); + + // 상 + if (position.upPossible() && (!board.containsKey(position.up()) || !board.get(position.up()).isSameSideAs(side))) { + destinations.add(position.up()); + } + // 하 + if (position.downPossible() && (!board.containsKey(position.down()) || !board.get(position.down()).isSameSideAs(side))) { + destinations.add(position.down()); + } + // 좌 + if (position.leftPossible() && (!board.containsKey(position.left()) || !board.get(position.left()).isSameSideAs(side))) { + destinations.add(position.left()); + } + // 우 + if (position.rightPossible() && (!board.containsKey(position.right()) || !board.get(position.right()).isSameSideAs(side))) { + destinations.add(position.right()); + } + + return destinations; + } + + @Override + public String toString() { + return name; + } } From 303e556e7d06ed59a89739fd2aa242a49a522c47 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:53:38 +0900 Subject: [PATCH 19/92] =?UTF-8?q?feat(Soldier):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++---- src/main/java/domain/Soldier.java | 81 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e50203589e..92d667da01 100644 --- a/README.md +++ b/README.md @@ -64,16 +64,16 @@ - [x] 상, 하, 좌, 우 1칸 위치 반환 - [x] 해당 위치에 내 기물이 있으면 가지 못한다. #### 졸(domain.Soldier) -- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [ ] 초 기준 - - [ ] 상, 좌, 우 1칸 모든 위치 반환 - - [ ] 보드 범위를 벗어나는 위치 제외 - - [ ] 한 기준 - - [ ] 하, 좌, 우 1칸 모든 위치 반환 - - [ ] 보드 범위를 벗어나는 위치 제외 -- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [ ] 상, 하, 좌, 우 위치 반환 - - [ ] 해당 위치에 내 기물이 있으면 가지 못한다. +- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [x] 초 기준 + - [x] 상, 좌, 우 1칸 모든 위치 반환 + - [x] 보드 범위를 벗어나는 위치 제외 + - [x] 한 기준 + - [x] 하, 좌, 우 1칸 모든 위치 반환 + - [x] 보드 범위를 벗어나는 위치 제외 +- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [x] 상, 하, 좌, 우 위치 반환 + - [x] 해당 위치에 내 기물이 있으면 가지 못한다. #### 마(domain.Horse) - [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - [ ] 상하좌우 4개 및 날 일자 8개 위치 모두 반환 diff --git a/src/main/java/domain/Soldier.java b/src/main/java/domain/Soldier.java index 4a060f5bd9..24d2db5b46 100644 --- a/src/main/java/domain/Soldier.java +++ b/src/main/java/domain/Soldier.java @@ -1,8 +1,89 @@ package domain; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class Soldier extends Piece { + private final String name = "졸"; public Soldier(Side side) { super(side); } + + @Override + public List getAllPosition(Position position) { + List positions = new ArrayList<>(); + + if (side.isCho()) { + // 상 + if (position.upPossible()) { + positions.add(position.up()); + } + // 좌 + if (position.leftPossible()) { + positions.add(position.left()); + } + // 우 + if (position.rightPossible()) { + positions.add(position.right()); + } + return positions; + } + + // 하 + if (position.downPossible()) { + positions.add(position.down()); + } + // 좌 + if (position.leftPossible()) { + positions.add(position.left()); + } + // 우 + if (position.rightPossible()) { + positions.add(position.right()); + } + return positions; + } + + @Override + public List getPossibleDestinations(Position position, Map map) { + List destinations = new ArrayList<>(); + + if (side.isCho()) { + // 상 + if (position.upPossible() && (!map.containsKey(position.up()) || !map.get(position.up()).isSameSideAs(side))) { + destinations.add(position.up()); + } + // 좌 + if (position.leftPossible() && (!map.containsKey(position.left()) || !map.get(position.left()).isSameSideAs(side))) { + destinations.add(position.left()); + } + // 우 + if (position.rightPossible() && (!map.containsKey(position.right()) || !map.get(position.right()).isSameSideAs(side))) { + destinations.add(position.right()); + } + return destinations; + } + + // 하 + if (position.downPossible() && (!map.containsKey(position.down()) || !map.get(position.down()).isSameSideAs(side))) { + destinations.add(position.down()); + } + // 좌 + if (position.leftPossible() && (!map.containsKey(position.left()) || !map.get(position.left()).isSameSideAs(side))) { + destinations.add(position.left()); + } + // 우 + if (position.rightPossible() && (!map.containsKey(position.right()) || !map.get(position.right()).isSameSideAs(side))) { + destinations.add(position.right()); + } + + return destinations; + } + + @Override + public String toString() { + return name; + } } From dd4f2ac7f88f03bc6c690dd1ec13597e5f891121 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:54:08 +0900 Subject: [PATCH 20/92] =?UTF-8?q?feat(Horse):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +-- src/main/java/domain/Horse.java | 121 ++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 92d667da01..81538f5e1e 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,11 @@ - [x] 상, 하, 좌, 우 위치 반환 - [x] 해당 위치에 내 기물이 있으면 가지 못한다. #### 마(domain.Horse) -- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [ ] 상하좌우 4개 및 날 일자 8개 위치 모두 반환 - - [ ] 보드 범위를 벗어나는 위치 제외 -- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [ ] 날 일자 8개의 목적지 모두 확인 +- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [x] 상하좌우 4개 및 날 일자 8개 위치 모두 반환 + - [x] 보드 범위를 벗어나는 위치 제외 +- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [x] 날 일자 8개의 목적지 모두 확인 1. 경유지에 기물이 없고, 날 일자 위치가 보드 범위를 벗어나지 않아야 한다. 2. 1을 만족하면서 기물이 없거나 상대 기물이 있으면 해당 위치는 최종 목적지가 될 수 있다. #### 상(domain.Elephant) diff --git a/src/main/java/domain/Horse.java b/src/main/java/domain/Horse.java index f2fd3e8c10..eb728dc3ac 100644 --- a/src/main/java/domain/Horse.java +++ b/src/main/java/domain/Horse.java @@ -1,8 +1,129 @@ package domain; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class Horse extends Piece { + private final String name = "마"; public Horse(Side side) { super(side); } + + @Override + public List getAllPosition(Position position) { + List positions = new ArrayList<>(); + + // 상 + if (position.upPossible()) { + Position curPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(curPosition); + if (curPosition.rightUpPossible()) { + positions.add(curPosition.rightUp()); + } + if (curPosition.leftUpPossible()) { + positions.add(curPosition.leftUp()); + } + } + // 하 + if (position.downPossible()) { + Position curPosition = position.down(); + positions.add(curPosition); + if (curPosition.rightDownPossible()) { + positions.add(curPosition.rightDown()); + } + if (curPosition.leftDownPossible()) { + positions.add(curPosition.leftDown()); + } + } + // 좌 + if (position.leftPossible()) { + Position curPosition = position.left(); + positions.add(curPosition); + if (curPosition.leftUpPossible()) { + positions.add(curPosition.leftUp()); + } + if (curPosition.leftDownPossible()) { + positions.add(curPosition.leftDown()); + } + } + // 우 + if (position.rightPossible()) { + Position curPosition = position.right(); + positions.add(curPosition); + if (curPosition.rightUpPossible()) { + positions.add(curPosition.rightUp()); + } + if (curPosition.rightDownPossible()) { + positions.add(curPosition.rightDown()); + } + } + + return positions; + } + + @Override + public List getPossibleDestinations(Position position, Map map) { + List destinations = new ArrayList<>(); + + // 상 + if (position.upPossible()) { + Position curPosition = position.up(); + if (!map.containsKey(curPosition)) { + if (curPosition.rightUpPossible() && (!map.containsKey(curPosition.rightUp()) || !map.get(curPosition.rightUp()).isSameSideAs(side))) { + destinations.add(curPosition.rightUp()); + } + if (curPosition.leftUpPossible() && (!map.containsKey(curPosition.leftUp()) || !map.get(curPosition.leftUp()).isSameSideAs(side))) { + destinations.add(curPosition.leftUp()); + } + } + } + + // 하 + if (position.downPossible()) { + Position curPosition = position.down(); + if (!map.containsKey(curPosition)) { + if (curPosition.rightDownPossible() && (!map.containsKey(curPosition.rightDown()) || !map.get(curPosition.rightDown()).isSameSideAs(side))) { + destinations.add(curPosition.rightDown()); + } + if (curPosition.leftDownPossible() && (!map.containsKey(curPosition.leftDown()) || !map.get(curPosition.leftDown()).isSameSideAs(side))) { + destinations.add(curPosition.leftDown()); + } + } + } + + // 좌 + if (position.leftPossible()) { + Position curPosition = position.left(); + if (!map.containsKey(curPosition)) { + if (curPosition.leftUpPossible() && (!map.containsKey(curPosition.leftUp()) || !map.get(curPosition.leftUp()).isSameSideAs(side))) { + destinations.add(curPosition.leftUp()); + } + if (curPosition.leftDownPossible() && (!map.containsKey(curPosition.leftDown()) || !map.get(curPosition.leftDown()).isSameSideAs(side))) { + destinations.add(curPosition.leftDown()); + } + } + } + + // 우 + if (position.rightPossible()) { + Position curPosition = position.right(); + if (!map.containsKey(curPosition)) { + if (curPosition.rightUpPossible() && (!map.containsKey(curPosition.rightUp()) || !map.get(curPosition.rightUp()).isSameSideAs(side))) { + destinations.add(curPosition.rightUp()); + } + if (curPosition.rightDownPossible() && (!map.containsKey(curPosition.rightDown()) || !map.get(curPosition.rightDown()).isSameSideAs(side))) { + destinations.add(curPosition.rightDown()); + } + } + } + + return destinations; + } + + @Override + public String toString() { + return name; + } } From 50f2eb928cf91d6345d612d76ab5df9c23558266 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:54:42 +0900 Subject: [PATCH 21/92] =?UTF-8?q?feat(Elephant):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- src/main/java/domain/Elephant.java | 166 +++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 81538f5e1e..fe649bd662 100644 --- a/README.md +++ b/README.md @@ -83,11 +83,11 @@ 1. 경유지에 기물이 없고, 날 일자 위치가 보드 범위를 벗어나지 않아야 한다. 2. 1을 만족하면서 기물이 없거나 상대 기물이 있으면 해당 위치는 최종 목적지가 될 수 있다. #### 상(domain.Elephant) -- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [ ] 상하좌우 4개, 날 일자 8개, 눈 목자 8개 위치 모두 반환 - - [ ] 보드 범위를 벗어나는 위치 제외 -- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [ ] 눈 목자 8개의 목적지 모두 확인 +- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [x] 상하좌우 4개, 날 일자 8개, 눈 목자 8개 위치 모두 반환 + - [x] 보드 범위를 벗어나는 위치 제외 +- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [x] 눈 목자 8개의 목적지 모두 확인 1. 경유지(직진 1칸, 날 일자 위치)에 기물이 없고, 눈 목자 위치가 보드 범위를 벗어나지 않아야 한다. 2. 1을 만족하면서 기물이 없거나 상대 기물이 있으면 해당 위치는 최종 목적지가 될 수 있다. #### 차(domain.Chariot) diff --git a/src/main/java/domain/Elephant.java b/src/main/java/domain/Elephant.java index 5c6d117296..7d2c99ec4b 100644 --- a/src/main/java/domain/Elephant.java +++ b/src/main/java/domain/Elephant.java @@ -1,8 +1,174 @@ package domain; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class Elephant extends Piece { + private final String name = "상"; public Elephant(Side side) { super(side); } + + @Override + public List getAllPosition(Position position) { + List positions = new ArrayList<>(); + + // 상 + if (position.upPossible()) { + Position curPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(curPosition); + if (curPosition.rightUpPossible()) { + Position rightUpPosition = curPosition.rightUp(); + positions.add(rightUpPosition); + if (rightUpPosition.rightUpPossible()) { + positions.add(rightUpPosition.rightUp()); + } + } + if (curPosition.leftUpPossible()) { + Position leftUPosition = curPosition.leftUp(); + positions.add(leftUPosition); + if (leftUPosition.leftUpPossible()) { + positions.add(leftUPosition.leftUp()); + } + } + } + // 하 + if (position.downPossible()) { + Position curPosition = position.down(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(curPosition); + if (curPosition.rightDownPossible()) { + Position rightDownPosition = curPosition.rightDown(); + positions.add(rightDownPosition); + if (rightDownPosition.rightDownPossible()) { + positions.add(rightDownPosition.rightDown()); + } + } + if (curPosition.leftDownPossible()) { + Position leftDownPosition = curPosition.leftDown(); + positions.add(leftDownPosition); + if (leftDownPosition.leftDownPossible()) { + positions.add(leftDownPosition.leftDown()); + } + } + } + // 좌 + if (position.leftPossible()) { + Position curPosition = position.left(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(curPosition); + if (curPosition.leftUpPossible()) { + Position leftUpPosition = curPosition.leftUp(); + positions.add(leftUpPosition); + if (leftUpPosition.leftUpPossible()) { + positions.add(leftUpPosition.leftUp()); + } + } + if (curPosition.leftDownPossible()) { + Position leftDownPosition = curPosition.leftDown(); + positions.add(leftDownPosition); + if (leftDownPosition.leftDownPossible()) { + positions.add(leftDownPosition.leftDown()); + } + } + } + // 우 + if (position.rightPossible()) { + Position curPosition = position.right(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(curPosition); + if (curPosition.rightUpPossible()) { + Position rightUpPosition = curPosition.rightUp(); + positions.add(rightUpPosition); + if (rightUpPosition.rightUpPossible()) { + positions.add(rightUpPosition.rightUp()); + } + } + if (curPosition.rightDownPossible()) { + Position rightDownPosition = curPosition.rightDown(); + positions.add(rightDownPosition); + if (rightDownPosition.rightDownPossible()) { + positions.add(rightDownPosition.rightDown()); + } + } + } + + return positions; + } + + @Override + public List getPossibleDestinations(Position position, Map map) { + List destinations = new ArrayList<>(); + + // 상 + if (position.upPossible() && !map.containsKey(position.up())) { + Position curPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + if (curPosition.rightUpPossible() && !map.containsKey(curPosition.rightUp())) { + Position rightUpPosition = curPosition.rightUp(); + if (rightUpPosition.rightUpPossible() && (!map.containsKey(rightUpPosition.rightUp()) || !map.get(rightUpPosition.rightUp()).isSameSideAs(side))) { + destinations.add(rightUpPosition.rightUp()); + } + } + if (curPosition.leftUpPossible() && !map.containsKey(curPosition.leftUp())) { + Position leftUpPosition = curPosition.leftUp(); + if (leftUpPosition.leftUpPossible() && (!map.containsKey(leftUpPosition.rightUp()) || !map.get(leftUpPosition.leftUp()).isSameSideAs(side))) { + destinations.add(leftUpPosition.leftUp()); + } + } + } + // 하 + if (position.downPossible() && !map.containsKey(position.down())) { + Position curPosition = position.down(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + if (curPosition.rightDownPossible() && !map.containsKey(curPosition.rightDown())) { + Position rightDownPosition = curPosition.rightDown(); + if (rightDownPosition.rightDownPossible() && (!map.containsKey(rightDownPosition.rightDown()) || !map.get(rightDownPosition.rightDown()).isSameSideAs(side))) { + destinations.add(rightDownPosition.rightDown()); + } + } + if (curPosition.leftDownPossible() && !map.containsKey(curPosition.leftDown())) { + Position leftDownPosition = curPosition.leftDown(); + if (leftDownPosition.leftDownPossible() && (!map.containsKey(leftDownPosition.leftDown()) || !map.get(leftDownPosition.leftDown()).isSameSideAs(side))) { + destinations.add(leftDownPosition.leftDown()); + } + } + } + // 좌 + if (position.leftPossible() && !map.containsKey(position.left())) { + Position curPosition = position.left(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + if (curPosition.leftUpPossible() && !map.containsKey(curPosition.leftUp())) { + Position leftUpPosition = curPosition.leftUp(); + if (leftUpPosition.leftUpPossible() && (!map.containsKey(leftUpPosition.leftUp()) || !map.get(leftUpPosition.leftUp()).isSameSideAs(side))) { + destinations.add(leftUpPosition.leftUp()); + } + } + if (curPosition.leftDownPossible() && !map.containsKey(curPosition.leftDown())) { + Position leftDownPosition = curPosition.leftDown(); + if (leftDownPosition.leftDownPossible() && (!map.containsKey(leftDownPosition.leftDown()) || !map.get(leftDownPosition.leftDown()).isSameSideAs(side))) { + destinations.add(leftDownPosition.leftDown()); + } + } + } + // 우 + if (position.rightPossible() && !map.containsKey(position.right())) { + Position curPosition = position.right(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + if (curPosition.rightUpPossible() && !map.containsKey(curPosition.rightUp())) { + Position rightUpPosition = curPosition.rightUp(); + if (rightUpPosition.rightUpPossible() && (!map.containsKey(rightUpPosition.rightUp()) || !map.get(rightUpPosition.rightUp()).isSameSideAs(side))) { + destinations.add(rightUpPosition.rightUp()); + } + } + if (curPosition.rightDownPossible() && !map.containsKey(curPosition.rightDown())) { + Position rightDownPosition = curPosition.rightDown(); + if (rightDownPosition.rightDownPossible() && (!map.containsKey(rightDownPosition.rightDown()) || !map.get(rightDownPosition.rightDown()).isSameSideAs(side))) { + destinations.add(rightDownPosition.rightDown()); + } + } + } + + return destinations; + } + + @Override + public String toString() { + return name; + } } From 21aa29e6f4db8767e80af489c34fdf6367a5a985 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:55:16 +0900 Subject: [PATCH 22/92] =?UTF-8?q?feat(Chariot):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +-- src/main/java/domain/Chariot.java | 103 ++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fe649bd662..c5b9ed97b3 100644 --- a/README.md +++ b/README.md @@ -91,11 +91,11 @@ 1. 경유지(직진 1칸, 날 일자 위치)에 기물이 없고, 눈 목자 위치가 보드 범위를 벗어나지 않아야 한다. 2. 1을 만족하면서 기물이 없거나 상대 기물이 있으면 해당 위치는 최종 목적지가 될 수 있다. #### 차(domain.Chariot) -- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [ ] 상하좌우 모든 직진 위치 반환 - - [ ] 보드 범위를 벗어나는 위치 제외 -- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [ ] 상하좌우 직진 위치 반환 +- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [x] 상하좌우 모든 직진 위치 반환 + - [x] 보드 범위를 벗어나는 위치 제외 +- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [x] 상하좌우 직진 위치 반환 1. 위치 탐색 중 내 기물이 있다면 기물 직전까지의 위치를 모두 반환한다. 2. 위치 탐색 중 상대 기물이 있다면 기물까지의 위치를 모두 반환한다. #### 포(domain.Cannon) diff --git a/src/main/java/domain/Chariot.java b/src/main/java/domain/Chariot.java index 1a2668b8dd..0602797147 100644 --- a/src/main/java/domain/Chariot.java +++ b/src/main/java/domain/Chariot.java @@ -1,8 +1,111 @@ package domain; +import domain.Position; +import domain.Side; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class Chariot extends Piece { + private final String name = "차"; public Chariot(Side side) { super(side); } + + @Override + public List getAllPosition(Position position) { + List positions = new ArrayList<>(); + // 상 + Position curPosition = position; + while (curPosition.upPossible()) { + curPosition = curPosition.up(); + positions.add(curPosition); + } + // 하 + curPosition = position; + while (curPosition.downPossible()) { + curPosition = curPosition.down(); + positions.add(curPosition); + } + // 좌 + curPosition = position; + while (curPosition.leftPossible()) { + curPosition = curPosition.left(); + positions.add(curPosition); + } + // 우 + curPosition = position; + while (curPosition.rightPossible()) { + curPosition = curPosition.right(); + positions.add(curPosition); + } + + return positions; + } + + @Override + public List getPossibleDestinations(Position position, Map board) { + List destinations = new ArrayList<>(); + + // 상 + Position curPosition = position; + while (curPosition.upPossible()) { + curPosition = curPosition.up(); + if (board.containsKey(curPosition)) { + Piece piece = board.get(curPosition); + if (!piece.isSameSideAs(side)) { + destinations.add(curPosition); + } + break; + } + destinations.add(curPosition); + } + // 하 + curPosition = position; + while (curPosition.downPossible()) { + curPosition = curPosition.down(); + if (board.containsKey(curPosition)) { + Piece piece = board.get(curPosition); + if (!piece.isSameSideAs(side)) { + destinations.add(curPosition); + } + break; + } + destinations.add(curPosition); + } + // 좌 + curPosition = position; + while (curPosition.leftPossible()) { + curPosition = curPosition.left(); + if (board.containsKey(curPosition)) { + Piece piece = board.get(curPosition); + if (!piece.isSameSideAs(side)) { + destinations.add(curPosition); + } + break; + } + destinations.add(curPosition); + } + // 우 + curPosition = position; + while (curPosition.rightPossible()) { + curPosition = curPosition.right(); + if (board.containsKey(curPosition)) { + Piece piece = board.get(curPosition); + if (!piece.isSameSideAs(side)) { + destinations.add(curPosition); + } + break; + } + destinations.add(curPosition); + } + + return destinations; + } + + @Override + public String toString() { + return name; + } } From da517e68eca823292abb98ec886f68b9ad58d51b Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 17:56:01 +0900 Subject: [PATCH 23/92] =?UTF-8?q?feat(Cannon):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- src/main/java/domain/Cannon.java | 153 +++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c5b9ed97b3..aba7e76ed9 100644 --- a/README.md +++ b/README.md @@ -99,11 +99,11 @@ 1. 위치 탐색 중 내 기물이 있다면 기물 직전까지의 위치를 모두 반환한다. 2. 위치 탐색 중 상대 기물이 있다면 기물까지의 위치를 모두 반환한다. #### 포(domain.Cannon) -- [ ] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [ ] 상하좌우 모든 직진 위치 반환 - - [ ] 보드 범위를 벗어나는 위치 제외 -- [ ] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [ ] 상하좌우 직진 위치 반환 +- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 + - [x] 상하좌우 모든 직진 위치 반환 + - [x] 보드 범위를 벗어나는 위치 제외 +- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 + - [x] 상하좌우 직진 위치 반환 1. 위치 탐색 중 포가 아닌 기물을 만난 후부터의 위치를 반환한다. 2. 포가 아닌 기물을 만난 이후의 위치 탐색 중 내 기물이 있다면 기물 직전까지의 위치를 모두 반환한다. 3. 포가 아닌 기물을 만난 이후의 위치 탐색 중 상대의 포가 아닌 기물이 있다면 기물까지의 위치를 모두 반환한다. diff --git a/src/main/java/domain/Cannon.java b/src/main/java/domain/Cannon.java index 68531641de..e466fee599 100644 --- a/src/main/java/domain/Cannon.java +++ b/src/main/java/domain/Cannon.java @@ -1,8 +1,161 @@ package domain; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class Cannon extends Piece { + private final String name = "포"; public Cannon(Side side) { super(side); } + + @Override + public List getAllPosition(Position position) { + List positions = new ArrayList<>(); + // 상 + Position curPosition = position; + while (curPosition.upPossible()) { + curPosition = curPosition.up(); + positions.add(curPosition); + } + // 하 + curPosition = position; + while (curPosition.downPossible()) { + curPosition = curPosition.down(); + positions.add(curPosition); + } + // 좌 + curPosition = position; + while (curPosition.leftPossible()) { + curPosition = curPosition.left(); + positions.add(curPosition); + } + // 우 + curPosition = position; + while (curPosition.rightPossible()) { + curPosition = curPosition.right(); + positions.add(curPosition); + } + + return positions; + } + + @Override + public List getPossibleDestinations(Position position, Map board) { + List destinations = new ArrayList<>(); + + // 상 + Position curPosition = position; + boolean canPut = false; + while (curPosition.upPossible()) { + curPosition = curPosition.up(); + if (board.containsKey(curPosition)) { + Piece piece = board.get(curPosition); + if (canPut) { + if (!piece.isSameSideAs(side) && !(piece instanceof Cannon)) { + destinations.add(curPosition); + } + break; + } + + if (piece instanceof Cannon) { + break; + } + + canPut = true; + continue; + } + + if (canPut) { + destinations.add(curPosition); + } + } + // 하 + curPosition = position; + canPut = false; + while (curPosition.downPossible()) { + curPosition = curPosition.down(); + if (board.containsKey(curPosition)) { + Piece piece = board.get(curPosition); + if (canPut) { + if (!piece.isSameSideAs(side) && !(piece instanceof Cannon)) { + destinations.add(curPosition); + } + break; + } + + if (piece instanceof Cannon) { + break; + } + + canPut = true; + continue; + } + + if (canPut) { + destinations.add(curPosition); + } + } + // 좌 + curPosition = position; + canPut = false; + while (curPosition.leftPossible()) { + curPosition = curPosition.left(); + if (board.containsKey(curPosition)) { + Piece piece = board.get(curPosition); + if (canPut) { + if (!piece.isSameSideAs(side) && !(piece instanceof Cannon)) { + destinations.add(curPosition); + } + break; + } + + if (piece instanceof Cannon) { + break; + } + + canPut = true; + continue; + } + + if (canPut) { + destinations.add(curPosition); + } + } + // 우 + curPosition = position; + canPut = false; + while (curPosition.rightPossible()) { + curPosition = curPosition.right(); + if (board.containsKey(curPosition)) { + Piece piece = board.get(curPosition); + if (canPut) { + if (!piece.isSameSideAs(side) && !(piece instanceof Cannon)) { + destinations.add(curPosition); + } + break; + } + + if (piece instanceof Cannon) { + break; + } + + canPut = true; + continue; + } + + if (canPut) { + destinations.add(curPosition); + } + } + + return destinations; + } + + @Override + public String toString() { + return name; + } } From 5cd420c64a307593bd2fb0c59fc2a2c7339a8825 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 18:06:00 +0900 Subject: [PATCH 24/92] =?UTF-8?q?feat(Side):=20=EC=A7=84=EC=98=81=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=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 --- src/main/java/domain/Side.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java index 55311edbce..a7d4d7fc86 100644 --- a/src/main/java/domain/Side.java +++ b/src/main/java/domain/Side.java @@ -65,4 +65,12 @@ public List formationX() { public boolean isSameAs(Side other) { return this.equals(other); } + + public boolean isCho() { + return this.equals(Side.CHO); + } + + public boolean isHan() { + return this.equals(Side.HAN); + } } From 1ed91118a1fa3a6395e6d5a09dc78fa3b4f82c82 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 18:07:18 +0900 Subject: [PATCH 25/92] =?UTF-8?q?refactor(Selection):=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/{ => domain}/Selection.java | 2 ++ 1 file changed, 2 insertions(+) rename src/main/java/{ => domain}/Selection.java (96%) diff --git a/src/main/java/Selection.java b/src/main/java/domain/Selection.java similarity index 96% rename from src/main/java/Selection.java rename to src/main/java/domain/Selection.java index f9a9d5a5e7..6fa6977f6f 100644 --- a/src/main/java/Selection.java +++ b/src/main/java/domain/Selection.java @@ -1,3 +1,5 @@ +package domain; + import java.util.Arrays; public enum Selection { From 72210f2a47da38babb254e2bd1d6ae7812719562 Mon Sep 17 00:00:00 2001 From: miniminjae92 Date: Sat, 28 Mar 2026 18:13:01 +0900 Subject: [PATCH 26/92] =?UTF-8?q?feat(Board):=20=EC=9E=A5=EA=B8=B0?= =?UTF-8?q?=ED=8C=90=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 --- README.md | 5 ++++- src/main/java/domain/Board.java | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aba7e76ed9..f38ec4100d 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,10 @@ 2. 포가 아닌 기물을 만난 이후의 위치 탐색 중 내 기물이 있다면 기물 직전까지의 위치를 모두 반환한다. 3. 포가 아닌 기물을 만난 이후의 위치 탐색 중 상대의 포가 아닌 기물이 있다면 기물까지의 위치를 모두 반환한다. 4. 포가 아닌 기물을 만난 이후의 위치 탐색 중 상대의 포 기물이 있다면 기물까지의 위치를 모두 반환한다. - +#### 장기판(domain.Board) +- [x] 위치가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 +- [x] 출발지와 목적지가 주어졌을 때 기물 이동 기능 +- [x] 게임 종료 여부 확인 기능 --- ## 입출력예시 diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 654c270212..654e690127 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -1,10 +1,46 @@ package domain; +import java.util.HashMap; +import java.util.List; import java.util.Map; public class Board { private final Map board; + public Board(Map board) { this.board = board; } + + public List getPossibleDestinations(Position position) { + Piece piece = board.get(position); + List positions = piece.getAllPosition(position); + Map map = makePiecesFromPositions(positions); + return piece.getPossibleDestinations(position, map); + } + + private Map makePiecesFromPositions(List positions) { + Map pieces = new HashMap<>(); + + for (Position position : positions) { + if (board.containsKey(position)) { + pieces.put(position, board.get(position)); + } + } + + return pieces; + } + + public void movePiece(Position from, Position to) { + board.put(to, board.remove(from)); + } + + public boolean isGameOver() { + return board.values().stream() + .filter(piece -> piece instanceof General) + .count() < 2; + } + + public Map getBoard() { + return board; + } } From 3e6002d5e8549c36f1e5ec78e7dcf5eea7783679 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 15:49:21 +0900 Subject: [PATCH 27/92] =?UTF-8?q?docs:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f38ec4100d..d47278a0eb 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ - [x] 1.1단계를 시작하기 전에 **어떤 기능을 구현해야 하는지 정리**한다. - [x] 1.1단계에서 정리한 내용을 README.md에 기능 목록으로 작성한다. -- [ ] 1.2단계를 시작하기 전에 **어떤 기능을 구현해야 하는지 정리**한다. -- [ ] 1.2단계에서 정리한 내용을 README.md에 기능 목록으로 작성한다. +- [x] 1.2단계를 시작하기 전에 **어떤 기능을 구현해야 하는지 정리**한다. +- [x] 1.2단계에서 정리한 내용을 README.md에 기능 목록으로 작성한다. - [ ] 개발 과정에서 **"나는 왜 이렇게 구현할까?"** 라는 고민을 기록한다. --- @@ -112,6 +112,30 @@ - [x] 위치가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - [x] 출발지와 목적지가 주어졌을 때 기물 이동 기능 - [x] 게임 종료 여부 확인 기능 + +#### 게임 +- [ ] 플레이어 턴을 진행한다. + - [ ] 기물 선택 + - [ ] 기물 이동 +- [ ] 플레이어 턴이 종료된다면 턴을 변경한다. +- [ ] 게임 종료 여부를 판단한다. + +## 예외 처리 +### 플레이어 입력 +- [x] 길이가 2~5 글자가 아닌 경우(사이 공백은 길이에 포함) +- [ ] 중복인 경우 +### 포메이션 입력 +- [x] 입력이 1,2,3,4 중 하나가 아닌 경우 +### 위치 입력 +- [x] `(x,y)` 형태가 아닌 경우 +### 기물 선택 +- [x] 올바른 범위가 아닌 위치를 입력한 경우 +- [ ] 올바른 기물 선택이 아닌 경우 → + - [x] 빈 좌표 선택 + - [ ] 상대 기물 +- [ ] 목적지 후보가 없는(어디로도 갈 수 없는) 기물의 위치를 입력한 경우 +- [ ] 주어진 목적지 후보에 없는 위치를 입력한 경우 + --- ## 입출력예시 @@ -145,20 +169,13 @@ ``` (보드판 출력) - 이동 시킬 기물의 위치를 입력하세요. - 궁: (x, y) - 차: (x, y), (x, y) - 포: (x, y), (x, y) - 마: (x, y), (x, y) - 상: (x, y), (x, y) - 사: (x, y), (x, y) - 졸: (x, y), (x, y), (x, y), (x, y), (x, y) + 이동 시킬 기물의 위치를 입력하세요. ex) (0, 3) ``` 3. 선택한 기물의 목적지를 입력받는다. ``` - 선택한 기물이 이동할 수 있는 위치입니다. 이동할 위치를 입력하세요. + 선택한 기물이 이동할 수 있는 위치입니다. 이동할 위치를 입력하세요. ex) (0, 3) (x, y), (x, y), ... ``` From d5f2ee75a011b6d6fd0b6969a3c412661cce5487 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 15:51:50 +0900 Subject: [PATCH 28/92] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EA=B2=BD=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/test/java/{ => domain}/PieceTest.java | 16 ++++++++++++++-- src/test/java/{ => domain}/PositionTest.java | 3 ++- src/test/java/{ => domain}/SideTest.java | 3 ++- 3 files changed, 18 insertions(+), 4 deletions(-) rename src/test/java/{ => domain}/PieceTest.java (75%) rename src/test/java/{ => domain}/PositionTest.java (98%) rename src/test/java/{ => domain}/SideTest.java (94%) diff --git a/src/test/java/PieceTest.java b/src/test/java/domain/PieceTest.java similarity index 75% rename from src/test/java/PieceTest.java rename to src/test/java/domain/PieceTest.java index 65e0f8868a..1e48e04c82 100644 --- a/src/test/java/PieceTest.java +++ b/src/test/java/domain/PieceTest.java @@ -1,8 +1,10 @@ +package domain; + import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; -import domain.Piece; -import domain.Side; +import java.util.List; +import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -34,5 +36,15 @@ static class SubPiece extends Piece { public SubPiece(Side side) { super(side); } + + @Override + public List getAllPosition(Position position) { + return List.of(); + } + + @Override + public List getPossibleDestinations(Position position, Map map) { + return List.of(); + } } } diff --git a/src/test/java/PositionTest.java b/src/test/java/domain/PositionTest.java similarity index 98% rename from src/test/java/PositionTest.java rename to src/test/java/domain/PositionTest.java index 236858a7e2..4fd37e5bc0 100644 --- a/src/test/java/PositionTest.java +++ b/src/test/java/domain/PositionTest.java @@ -1,7 +1,8 @@ +package domain; + import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import domain.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; diff --git a/src/test/java/SideTest.java b/src/test/java/domain/SideTest.java similarity index 94% rename from src/test/java/SideTest.java rename to src/test/java/domain/SideTest.java index 61f4c22338..b5aead2322 100644 --- a/src/test/java/SideTest.java +++ b/src/test/java/domain/SideTest.java @@ -1,6 +1,7 @@ +package domain; + import static org.assertj.core.api.Assertions.assertThat; -import domain.Side; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; From 5371e48eed6f622d8e0200c95eea166bcce9fa1e Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 15:53:10 +0900 Subject: [PATCH 29/92] =?UTF-8?q?test(Selection):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=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/domain/Selection.java | 2 +- src/test/java/domain/SelectionTest.java | 26 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/test/java/domain/SelectionTest.java diff --git a/src/main/java/domain/Selection.java b/src/main/java/domain/Selection.java index 6fa6977f6f..c6e8c657d6 100644 --- a/src/main/java/domain/Selection.java +++ b/src/main/java/domain/Selection.java @@ -17,7 +17,7 @@ public enum Selection { public static Selection from(String input) { return Arrays.stream(values()) - .filter(selection -> selection.input.equals(input)) + .filter(selection -> selection.input.equals(input.strip())) .findFirst() .orElseThrow(() -> new IllegalArgumentException("올바른 입력이 아닙니다.")); } diff --git a/src/test/java/domain/SelectionTest.java b/src/test/java/domain/SelectionTest.java new file mode 100644 index 0000000000..50b681c12a --- /dev/null +++ b/src/test/java/domain/SelectionTest.java @@ -0,0 +1,26 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class SelectionTest { + + @Test + void 문자열_입력을_Selection으로_변환한다() { + assertThat(Selection.from("1")).isEqualTo(Selection.FIRST); + assertThat(Selection.from(" 2 ")).isEqualTo(Selection.SECOND); + assertThat(Selection.from("3")).isEqualTo(Selection.THIRD); + assertThat(Selection.from("4")).isEqualTo(Selection.FOURTH); + } + + @Test + void 올바르지_않은_입력은_예외가_발생한다() { + assertThatThrownBy(() -> Selection.from("0")) + .isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy(() -> Selection.from("5")) + .isInstanceOf(IllegalArgumentException.class); + } +} From 2cbe86cc3780a8fa587b49c04c3a8f989c65f6b7 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 15:54:35 +0900 Subject: [PATCH 30/92] =?UTF-8?q?feat(Name):=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EB=B0=8F=20=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 --- src/main/java/domain/Name.java | 18 ++++++++++++++++++ src/test/java/domain/NameTest.java | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/main/java/domain/Name.java create mode 100644 src/test/java/domain/NameTest.java diff --git a/src/main/java/domain/Name.java b/src/main/java/domain/Name.java new file mode 100644 index 0000000000..19115a0f01 --- /dev/null +++ b/src/main/java/domain/Name.java @@ -0,0 +1,18 @@ +package domain; + +public record Name(String name) { + + public Name { + validate(name); + } + + public void validate(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("이름은 빈 값이 될 수 없습니다."); + } + + if (name.length() < 2 || name.length() > 5) { + throw new IllegalArgumentException("이름은 2~5글자 사이여야 합니다."); + } + } +} diff --git a/src/test/java/domain/NameTest.java b/src/test/java/domain/NameTest.java new file mode 100644 index 0000000000..ed2cb08fcc --- /dev/null +++ b/src/test/java/domain/NameTest.java @@ -0,0 +1,18 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class NameTest { + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" "}) + void 이름은_빈값이나_공백이_될수없다(String input) { + assertThatThrownBy(() -> new Name(input)) + .isInstanceOf(IllegalArgumentException.class); + } +} From ac6a56ba21b2b80c526984a83c5ee18c1d3392b0 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 15:55:44 +0900 Subject: [PATCH 31/92] =?UTF-8?q?test(Formation):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B4=EB=A6=84=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/domain/Formation.java | 14 ++++++------ src/test/java/domain/FormationTest.java | 30 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/test/java/domain/FormationTest.java diff --git a/src/main/java/domain/Formation.java b/src/main/java/domain/Formation.java index 04f05b3892..e9cfb8b70c 100644 --- a/src/main/java/domain/Formation.java +++ b/src/main/java/domain/Formation.java @@ -14,7 +14,7 @@ public void placeElephant(Map pieces, Side side) { new Elephant(side), new Horse(side) ); - extracted(pieces, side, orders); + place(pieces, side, orders); } }, RIGHT_ELEPHANT(Selection.SECOND) { @@ -26,7 +26,7 @@ public void placeElephant(Map pieces, Side side) { new Horse(side), new Elephant(side) ); - extracted(pieces, side, orders); + place(pieces, side, orders); } }, OUTER_ELEPHANT(Selection.THIRD) { @@ -38,7 +38,7 @@ public void placeElephant(Map pieces, Side side) { new Horse(side), new Elephant(side) ); - extracted(pieces, side, orders); + place(pieces, side, orders); } }, INNER_ELEPHANT(Selection.FOURTH) { @@ -50,7 +50,7 @@ public void placeElephant(Map pieces, Side side) { new Elephant(side), new Horse(side) ); - extracted(pieces, side, orders); + place(pieces, side, orders); } }, ; @@ -70,10 +70,10 @@ public static Formation from(Selection selection ) { public abstract void placeElephant(Map pieces, Side side); - private static void extracted(Map pieces, Side side, List leftElephant) { + private static void place(Map pieces, Side side, List orders) { List a = side.formationX(); - for (int i = 0; i < leftElephant.size(); i++) { - pieces.put(new Position(a.get(i), side.baseY()), leftElephant.get(i)); + for (int i = 0; i < orders.size(); i++) { + pieces.put(new Position(a.get(i), side.baseY()), orders.get(i)); } } } diff --git a/src/test/java/domain/FormationTest.java b/src/test/java/domain/FormationTest.java new file mode 100644 index 0000000000..a34e40e270 --- /dev/null +++ b/src/test/java/domain/FormationTest.java @@ -0,0 +1,30 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class FormationTest { + + @Test + void 선택값으로_포메이션을_찾는다() { + assertThat(Formation.from(Selection.FIRST)).isEqualTo(Formation.LEFT_ELEPHANT); + assertThat(Formation.from(Selection.SECOND)).isEqualTo(Formation.RIGHT_ELEPHANT); + assertThat(Formation.from(Selection.THIRD)).isEqualTo(Formation.OUTER_ELEPHANT); + assertThat(Formation.from(Selection.FOURTH)).isEqualTo(Formation.INNER_ELEPHANT); + } + + @Test + void LEFT_ELEPHANT은_상마상마로_배치한다() { + Map pieces = new HashMap<>(); + + Formation.LEFT_ELEPHANT.placeElephant(pieces, Side.CHO); + + assertThat(pieces.get(new Position(1, 0))).isInstanceOf(Elephant.class); + assertThat(pieces.get(new Position(2, 0))).isInstanceOf(Horse.class); + assertThat(pieces.get(new Position(6, 0))).isInstanceOf(Elephant.class); + assertThat(pieces.get(new Position(7, 0))).isInstanceOf(Horse.class); + } +} From 178cd126e27c3e59c0cc422e6f765f7003275f51 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 15:56:20 +0900 Subject: [PATCH 32/92] =?UTF-8?q?feat(Player):=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=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 --- src/main/java/domain/Player.java | 14 ++++++++++++-- src/test/java/domain/PlayerTest.java | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/test/java/domain/PlayerTest.java diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index ce3a0bae9e..17ad299fb1 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -1,9 +1,19 @@ package domain; public class Player { - private final String name; + private final Name name; + private TurnState turnState; - public Player(String name) { + public Player(Name name, TurnState turnState) { this.name = name; + this.turnState = turnState; + } + + public boolean isTurn() { + return turnState.isCurrent(); + } + + public void changeTurn() { + turnState = turnState.next(); } } diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java new file mode 100644 index 0000000000..9c39d59165 --- /dev/null +++ b/src/test/java/domain/PlayerTest.java @@ -0,0 +1,28 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class PlayerTest { + + @Test + void 현재_턴_상태를_반환한다() { + Player current = new Player(new Name("cho"), new CurrentTurn()); + Player notCurrent = new Player(new Name("han"), new NotCurrentTurn()); + + assertThat(current.isTurn()).isTrue(); + assertThat(notCurrent.isTurn()).isFalse(); + } + + @Test + void 턴_상태를_토글한다() { + Player player = new Player(new Name("cho"), new CurrentTurn()); + + player.changeTurn(); + assertThat(player.isTurn()).isFalse(); + + player.changeTurn(); + assertThat(player.isTurn()).isTrue(); + } +} From 2767292f4fb017b60a203085498305a4417bfff1 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 15:57:13 +0900 Subject: [PATCH 33/92] =?UTF-8?q?feat(TurnState):=20=ED=84=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=83=81=ED=83=9C=ED=8C=A8=ED=84=B4=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/CurrentTurn.java | 14 ++++++++++++++ src/main/java/domain/NotCurrentTurn.java | 14 ++++++++++++++ src/main/java/domain/TurnState.java | 6 ++++++ 3 files changed, 34 insertions(+) create mode 100644 src/main/java/domain/CurrentTurn.java create mode 100644 src/main/java/domain/NotCurrentTurn.java create mode 100644 src/main/java/domain/TurnState.java diff --git a/src/main/java/domain/CurrentTurn.java b/src/main/java/domain/CurrentTurn.java new file mode 100644 index 0000000000..145c028dad --- /dev/null +++ b/src/main/java/domain/CurrentTurn.java @@ -0,0 +1,14 @@ +package domain; + +public class CurrentTurn implements TurnState { + + @Override + public boolean isCurrent() { + return true; + } + + @Override + public TurnState next() { + return new NotCurrentTurn(); + } +} diff --git a/src/main/java/domain/NotCurrentTurn.java b/src/main/java/domain/NotCurrentTurn.java new file mode 100644 index 0000000000..7be47b2ec6 --- /dev/null +++ b/src/main/java/domain/NotCurrentTurn.java @@ -0,0 +1,14 @@ +package domain; + +public class NotCurrentTurn implements TurnState { + + @Override + public boolean isCurrent() { + return false; + } + + @Override + public TurnState next() { + return new CurrentTurn(); + } +} diff --git a/src/main/java/domain/TurnState.java b/src/main/java/domain/TurnState.java new file mode 100644 index 0000000000..a2e49397a6 --- /dev/null +++ b/src/main/java/domain/TurnState.java @@ -0,0 +1,6 @@ +package domain; + +public interface TurnState { + boolean isCurrent(); + TurnState next(); +} From 92d1e8a8cec700de03740bee6684e5b3ef4eb18a Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 16:01:47 +0900 Subject: [PATCH 34/92] =?UTF-8?q?test(Board):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +-- src/main/java/domain/Board.java | 28 +++++------- src/test/java/BoardTest.java | 5 --- src/test/java/domain/BoardTest.java | 69 +++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 25 deletions(-) delete mode 100644 src/test/java/BoardTest.java create mode 100644 src/test/java/domain/BoardTest.java diff --git a/README.md b/README.md index d47278a0eb..40862a3f70 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ #### 기물(domain.Piece) - [x] 같은 진영 여부를 판단한다. #### 장기판(domain.Board) - - [ ] 검증 - - [ ] 장기판은 올바른 위치에 기물을 초기화한다. - - [ ] 장기판은 올바른 위치에 기물을 초기화하지 않으면 예외가 발생한다. + - [x] 검증 + - [x] 장기판은 올바른 위치에 기물을 초기화한다. + - [x] 장기판은 올바른 위치에 기물을 초기화하지 않으면 예외가 발생한다. ### 1.2단계 #### 기물(domain.Piece) diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 654e690127..5cd23ff708 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -1,33 +1,31 @@ package domain; -import java.util.HashMap; import java.util.List; import java.util.Map; public class Board { private final Map board; - public Board(Map board) { this.board = board; } + public Map getBoard() { + return board; + } + public List getPossibleDestinations(Position position) { + if (!board.containsKey(position)) { + throw new IllegalArgumentException("기물이 존재하지 않는 위치입니다."); + } Piece piece = board.get(position); + // TODO: 상대 기물인 경우 에외발생 - 현재 누구 차례인지 정보 필요 List positions = piece.getAllPosition(position); - Map map = makePiecesFromPositions(positions); + Map map = func(positions); return piece.getPossibleDestinations(position, map); } - private Map makePiecesFromPositions(List positions) { - Map pieces = new HashMap<>(); - - for (Position position : positions) { - if (board.containsKey(position)) { - pieces.put(position, board.get(position)); - } - } - - return pieces; + private Map func(List positions) { + return Map.of(); } public void movePiece(Position from, Position to) { @@ -39,8 +37,4 @@ public boolean isGameOver() { .filter(piece -> piece instanceof General) .count() < 2; } - - public Map getBoard() { - return board; - } } diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java deleted file mode 100644 index a720619d51..0000000000 --- a/src/test/java/BoardTest.java +++ /dev/null @@ -1,5 +0,0 @@ -import static org.junit.jupiter.api.Assertions.*; - -class BoardTest { - -} diff --git a/src/test/java/domain/BoardTest.java b/src/test/java/domain/BoardTest.java new file mode 100644 index 0000000000..b32fe77f67 --- /dev/null +++ b/src/test/java/domain/BoardTest.java @@ -0,0 +1,69 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class BoardTest { + + @Test + void 장기판은_올바른_위치에_기물을_초기화한다() { + Board board = new InitialBoardFactory().create(Formation.LEFT_ELEPHANT, Formation.RIGHT_ELEPHANT); + Map actual = board.getBoard(); + + assertThat(actual).hasSize(32); + assertThat(actual.get(new Position(4, 1))).isInstanceOf(General.class); + assertThat(actual.get(new Position(4, 8))).isInstanceOf(General.class); + + assertThat(actual.get(new Position(0, 0))).isInstanceOf(Chariot.class); + assertThat(actual.get(new Position(8, 0))).isInstanceOf(Chariot.class); + assertThat(actual.get(new Position(0, 9))).isInstanceOf(Chariot.class); + assertThat(actual.get(new Position(8, 9))).isInstanceOf(Chariot.class); + + assertThat(actual.get(new Position(1, 0))).isInstanceOf(domain.Elephant.class); + assertThat(actual.get(new Position(2, 0))).isInstanceOf(domain.Horse.class); + assertThat(actual.get(new Position(6, 0))).isInstanceOf(domain.Elephant.class); + assertThat(actual.get(new Position(7, 0))).isInstanceOf(domain.Horse.class); + + assertThat(actual.get(new Position(7, 9))).isInstanceOf(domain.Horse.class); + assertThat(actual.get(new Position(6, 9))).isInstanceOf(domain.Elephant.class); + assertThat(actual.get(new Position(2, 9))).isInstanceOf(domain.Horse.class); + assertThat(actual.get(new Position(1, 9))).isInstanceOf(domain.Elephant.class); + } + + @Test + void 기물을_이동하면_출발_위치는_비고_도착_위치로_옮겨진다() { + Map pieces = new HashMap<>(); + Position from = new Position(0, 3); + Position to = new Position(0, 4); + Piece soldier = new Soldier(Side.CHO); + pieces.put(from, soldier); + Board board = new Board(pieces); + + board.movePiece(from, to); + + assertThat(board.getBoard()).doesNotContainKey(from); + assertThat(board.getBoard().get(to)).isSameAs(soldier); + } + + @Test + void 양쪽_장군이_모두_있으면_게임은_종료되지_않는다() { + Map pieces = new HashMap<>(); + pieces.put(new Position(4, 1), new General(Side.CHO)); + pieces.put(new Position(4, 8), new General(Side.HAN)); + Board board = new Board(pieces); + + assertThat(board.isGameOver()).isFalse(); + } + + @Test + void 장군이_하나만_남으면_게임이_종료된다() { + Map pieces = new HashMap<>(); + pieces.put(new Position(4, 1), new General(Side.CHO)); + Board board = new Board(pieces); + + assertThat(board.isGameOver()).isTrue(); + } +} From 78870f02a661df2560371ff42705599a94ed0592 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 16:02:19 +0900 Subject: [PATCH 35/92] =?UTF-8?q?feat(view):=20=EC=9E=85=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=20=EB=B6=80=EB=B6=84=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/domain/Piece.java | 4 ++ src/main/java/domain/Position.java | 6 +++ src/main/java/domain/Side.java | 14 +++++- src/main/java/view/InputView.java | 75 ++++++++++++++++++++++++++---- src/main/java/view/OutputView.java | 44 ++++++++++++++++++ 5 files changed, 132 insertions(+), 11 deletions(-) diff --git a/src/main/java/domain/Piece.java b/src/main/java/domain/Piece.java index e2f7dc5d6e..f3ffd69292 100644 --- a/src/main/java/domain/Piece.java +++ b/src/main/java/domain/Piece.java @@ -14,6 +14,10 @@ public boolean isSameSideAs(Side other) { return side.isSameAs(other); } + public Side getSide() { + return side; + } + public abstract List getAllPosition(Position position); public abstract List getPossibleDestinations(Position position, Map map); diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index 3f55226a06..b9e3193c6c 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -6,6 +6,7 @@ public class Position { public static final int MIN = 0; public static final int MAX_X = 8; public static final int MAX_Y = 9; + private final int x; private final int y; @@ -40,6 +41,11 @@ public int hashCode() { return Objects.hash(x, y); } + @Override + public String toString() { + return "(" + x + ", " + y + ")"; + } + public Position up() { return new Position(x, y + 1); } diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java index a7d4d7fc86..c4ee2d2d84 100644 --- a/src/main/java/domain/Side.java +++ b/src/main/java/domain/Side.java @@ -3,7 +3,7 @@ import java.util.List; public enum Side { - CHO { + CHO("초") { @Override public int baseY() { return 0; @@ -29,7 +29,7 @@ public List formationX() { return List.of(1, 2, 6, 7); } }, - HAN { + HAN("한") { @Override public int baseY() { return 9; @@ -56,6 +56,12 @@ public List formationX() { } }; + private final String name; + + Side(String name) { + this.name = name; + } + public abstract int baseY(); public abstract int generalY(); public abstract int cannonY(); @@ -73,4 +79,8 @@ public boolean isCho() { public boolean isHan() { return this.equals(Side.HAN); } + + public String getName() { + return name; + } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 94a630946a..0d14ef14ed 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,8 +1,14 @@ package view; +import domain.Piece; +import domain.Position; +import domain.Side; +import java.util.List; +import java.util.Map; import java.util.Scanner; public class InputView { + private final Scanner scanner; public InputView(Scanner scanner) { @@ -10,26 +16,77 @@ public InputView(Scanner scanner) { } public String readChoPlayerName() { - System.out.println("초나라 플레이어 이름 입력: "); - return readLine(); + System.out.print("초나라 플레이어 이름 입력: "); + return scanner.nextLine(); } public String readHanPlayerName() { - System.out.println("한나라 플레이어 이름 입력: "); - return readLine(); + System.out.print("한나라 플레이어 이름 입력: "); + return scanner.nextLine(); } public String readChoFormation() { - System.out.println("초나라 플레이어 포지션 입력: "); - return readLine(); + System.out.println("초나라 플레이어 포메이션 입력"); + printFormations(); + return scanner.nextLine(); } public String readHanFormation() { - System.out.println("한나라 플레이어 포지션 입력: "); - return readLine(); + System.out.println("한나라 플레이어 포메이션 입력"); + printFormations(); + return scanner.nextLine(); + } + + private void printFormations() { + System.out.println("1. 상마상마"); + System.out.println("2. 마상마상"); + System.out.println("3. 상마마상"); + System.out.println("4. 마상상마"); + } + + public String readPlayerPieceSelection(Side side, Map board) { + System.out.println(side.getName() + "나라 플레이어 차례입니다. 이동 시킬 기물의 위치를 입력하세요."); + /*System.out.printf("궁: %s\n", String.join(", ", + board.keySet().stream() + .filter(position -> board.get(position) instanceof General) + .map(Object::toString) + .toList())); + System.out.printf("차: %s\n", String.join(", ", + board.keySet().stream() + .filter(position -> board.get(position) instanceof Chariot) + .map(Object::toString) + .toList())); + System.out.printf("포: %s\n", String.join(", ", + board.keySet().stream() + .filter(position -> board.get(position) instanceof Cannon) + .map(Object::toString) + .toList())); + System.out.printf("마: %s\n", String.join(", ", + board.keySet().stream() + .filter(position -> board.get(position) instanceof Horse) + .map(Object::toString) + .toList())); + System.out.printf("상: %s\n", String.join(", ", + board.keySet().stream() + .filter(position -> board.get(position) instanceof Elephant) + .map(Object::toString) + .toList())); + System.out.printf("사: %s\n", String.join(", ", + board.keySet().stream() + .filter(position -> board.get(position) instanceof Guard) + .map(Object::toString) + .toList())); + System.out.printf("졸: %s\n", String.join(", ", + board.keySet().stream() + .filter(position -> board.get(position) instanceof Soldier) + .map(Object::toString) + .toList()));*/ + return scanner.nextLine(); } - private String readLine() { + public String readDestination(List destinations) { + System.out.println("선택한 기물이 이동할 수 있는 위치입니다. 이동할 위치를 입력하세요."); + System.out.println(destinations); return scanner.nextLine(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index d8f9743ccf..36ee4fd4b1 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,4 +1,48 @@ package view; +import domain.Piece; +import domain.Position; +import domain.Side; +import java.util.Map; + public class OutputView { + + private static final String RED = "\u001B[31m"; + private static final String BLUE = "\u001B[34m"; + private static final String RESET = "\u001B[0m"; + + public void printBoard(Map board) { + System.out.println(); + System.out.println(" 0 1 2 3 4 5 6 7 8"); + + for (int y = 9; y >= 0; y--) { + System.out.print(" " + y + " "); + for (int x = 0; x <= 8; x++) { + Position position = new Position(x, y); + if (board.containsKey(position)) { + printPiece(board.get(position)); + System.out.print(RESET); + continue; + } + System.out.printf("%-3s", "+"); + } + System.out.println(); + } + } + + private void printPiece(Piece piece) { + String color = getColor(piece.getSide()); + System.out.printf(color + "%-3s", piece); + } + + private String getColor(Side side) { + if (side == Side.HAN) { + return RED; + } + return BLUE; + } + + public void printError(String message) { + System.out.println(message); + } } From 193056f6b95a21f6abe5ec878fccee8a31e8a509 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 16:02:53 +0900 Subject: [PATCH 36/92] =?UTF-8?q?feat(InputParser):=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=20=ED=8C=8C=EC=84=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B0=8F?= =?UTF-8?q?=20=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 --- src/main/java/domain/InputParser.java | 38 +++++++++++++++++++++++ src/test/java/domain/InputParserTest.java | 24 ++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/main/java/domain/InputParser.java create mode 100644 src/test/java/domain/InputParserTest.java diff --git a/src/main/java/domain/InputParser.java b/src/main/java/domain/InputParser.java new file mode 100644 index 0000000000..758a643ba2 --- /dev/null +++ b/src/main/java/domain/InputParser.java @@ -0,0 +1,38 @@ +package domain; + + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class InputParser { + public static final Pattern POSITION_PATTERN = Pattern.compile(" *\\( *\\d+ *, *\\d+ *\\) *"); + + public static Player parsePlayer(String input) { + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("빈 값은 입력할 수 없습니다."); + } + return new Player(new Name(input.strip()), new CurrentTurn()); + } + + public static Position parsePosition(String input) { + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("빈 값은 입력할 수 없습니다."); + } + + if (!POSITION_PATTERN.matcher(input).matches()) { + throw new IllegalArgumentException("질못된 입력 형식입니다."); + } + + return getPosition(input); + } + + private static Position getPosition(String input) { + List coordinate = Arrays.stream(input.strip().substring(1, input.length() - 1).split(",")) + .map(String::strip) + .map(Integer::parseInt) + .toList(); + + return new Position(coordinate.get(0), coordinate.get(1)); + } +} diff --git a/src/test/java/domain/InputParserTest.java b/src/test/java/domain/InputParserTest.java new file mode 100644 index 0000000000..202913a324 --- /dev/null +++ b/src/test/java/domain/InputParserTest.java @@ -0,0 +1,24 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class InputParserTest { + + @ParameterizedTest + @ValueSource(strings = {"(0,3)", "(3, 9)"}) + void 올바른_좌표가_입력되는_경우(String input) { + assertThatCode(() -> InputParser.parsePosition(input)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @ValueSource(strings = {"", "0,3", "a,b"}) + void 올바른_좌표가_입력되지_않는_경우(String input) { + assertThatThrownBy(() -> InputParser.parsePosition(input)) + .isInstanceOf(IllegalArgumentException.class); + } +} From c95e53385201c73754725d18caca018190898cd5 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 16:03:36 +0900 Subject: [PATCH 37/92] =?UTF-8?q?test(GameTest):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80,=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/Game.java | 36 +++++++++++++ src/main/java/domain/GameManager.java | 57 +++++++++++++++++++-- src/main/java/domain/Players.java | 13 +++++ src/test/java/domain/GameTest.java | 74 +++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 src/main/java/domain/Players.java create mode 100644 src/test/java/domain/GameTest.java diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index a2927810f3..236c671449 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -1,6 +1,10 @@ package domain; +import java.util.List; +import java.util.Map; + public class Game { + private final Board board; private final Player choPlayer; private final Player hanPlayer; @@ -11,4 +15,36 @@ public Game(Board board, Player choPlayer, Player hanPlayer) { this.hanPlayer = hanPlayer; } + public Map getBoard() { + return board.getBoard(); + } + + public boolean isOver() { + return board.isGameOver(); + } + + public List getPossibleDestinations(Position position) { + // TODO: Players에게 현재 턴인 플레이어의 진영을 물어보고, 사용자가 선택한 position의 진영을 비교해서 다르면 에러 발생 + List destinations = board.getPossibleDestinations(position); + if (destinations.isEmpty()) { + throw new IllegalArgumentException("갈 수 있는 목적지가 없습니다. 다른 기물을 선택하세요."); + } + return destinations; + } + + public void movePiece(Position from, Position to) { + board.movePiece(from, to); + } + + public String nextTurn() { + if (choPlayer.isTurn()) { + choPlayer.changeTurn(); + hanPlayer.changeTurn(); + return Side.HAN.getName(); + } + + choPlayer.changeTurn(); + hanPlayer.changeTurn(); + return Side.CHO.getName(); + } } diff --git a/src/main/java/domain/GameManager.java b/src/main/java/domain/GameManager.java index 65fea21d75..4fd75a5e25 100644 --- a/src/main/java/domain/GameManager.java +++ b/src/main/java/domain/GameManager.java @@ -1,11 +1,15 @@ package domain; +import java.util.List; +import java.util.function.Supplier; import view.InputView; import view.OutputView; public class GameManager { private final InputView inputView; private final OutputView outputView; + private Side side; + private Position from; public GameManager(InputView inputView, OutputView outputView) { this.inputView = inputView; @@ -13,14 +17,59 @@ public GameManager(InputView inputView, OutputView outputView) { } public void start() { - Player choPlayer = new Player(inputView.readChoPlayerName()); - Player hanPlayer = new Player(inputView.readHanPlayerName()); + Player choPlayer = retry(() -> InputParser.parsePlayer(inputView.readChoPlayerName())); + Player hanPlayer = retry(() -> InputParser.parsePlayer(inputView.readChoPlayerName())); Formation choFormation = Formation.from(Selection.from(inputView.readChoFormation())); Formation hanFormation = Formation.from(Selection.from(inputView.readHanFormation())); - Board board = new InitialBoardFactory().create(choFormation, hanFormation); - Game game = new Game(board, choPlayer, hanPlayer); + + side = Side.CHO; + while (!game.isOver()) { + outputView.printBoard(game.getBoard()); + + List destinations = retry(() -> { + from = InputParser.parsePosition(inputView.readPlayerPieceSelection(side, game.getBoard())); + return game.getPossibleDestinations(from); + }); + + Position to = retry(() -> { + Position position = InputParser.parsePosition(inputView.readDestination(destinations)); + if (!destinations.contains(position)) { + throw new IllegalArgumentException("선택할 수 없는 위치입니다."); + } + return position; + }); + game.movePiece(from, to); + + side = nextTurn(); + } + } + + public Side nextTurn() { + if (side.isCho()) return Side.HAN; + return Side.CHO; + } + + private T retry(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + } + } + } + + private void retry(Runnable action) { + while (true) { + try { + action.run(); + return; + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + } + } } } diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java new file mode 100644 index 0000000000..20d82362e0 --- /dev/null +++ b/src/main/java/domain/Players.java @@ -0,0 +1,13 @@ +package domain; + +import java.util.List; + +public class Players { + List players; + + public Players(List players) { + this.players = players; + } + // get current player + // toggle player +} diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java new file mode 100644 index 0000000000..fb9b915495 --- /dev/null +++ b/src/test/java/domain/GameTest.java @@ -0,0 +1,74 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GameTest { + private Player choPlayer; + private Player hanPlayer; + + @BeforeEach + void setUp() { + choPlayer = new Player(new Name("cho"), new CurrentTurn()); + hanPlayer = new Player(new Name("han"), new NotCurrentTurn()); + } + + @Test + void nextTurn은_플레이어_턴을_교체하고_다음_진영명을_반환한다() { + Game game = new Game( + new Board(new HashMap<>()), + choPlayer, + hanPlayer + ); + + String first = game.nextTurn(); + String second = game.nextTurn(); + + assertThat(first).isEqualTo("한"); + assertThat(second).isEqualTo("초"); + } + + @Test + void movePiece는_보드의_기물을_이동시킨다() { + Position from = new Position(0, 3); + Position to = new Position(0, 4); + Soldier soldier = new Soldier(Side.CHO); + Game game = createGameWithPieces(Map.of(from, soldier)); + + game.movePiece(from, to); + + assertThat(game.getBoard()).doesNotContainKey(from); + assertThat(game.getBoard().get(to)).isSameAs(soldier); + } + + @Test + void 올바른_범위가_아닌_위치를_입력한_경우() { + Position from = new Position(0, 3); + Position to = new Position(0, 4); + Soldier soldier = new Soldier(Side.CHO); + Game game = createGameWithPieces(Map.of(from, soldier)); + + assertThatThrownBy(() -> game.getPossibleDestinations(to)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 장군이_하나면_게임은_종료상태다() { + Game game = createGameWithPieces(Map.of(new Position(4, 1), new General(Side.CHO))); + + assertThat(game.isOver()).isTrue(); + } + + private Game createGameWithPieces(Map pieces) { + return new Game( + new Board(new HashMap<>(pieces)), + new Player(new Name("cho"), new CurrentTurn()), + new Player(new Name("han"), new NotCurrentTurn()) + ); + } +} From 6567b11422523d33922be5fd106d79d2d45c592f Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 20:04:05 +0900 Subject: [PATCH 38/92] =?UTF-8?q?refactor(Players):=20TurnState=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=B2=B4=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20Players=20=EC=83=9D=EC=84=B1=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 --- README.md | 2 +- .../{CurrentTurn.java => ActiveTurn.java} | 4 +- ...{NotCurrentTurn.java => InactiveTurn.java} | 4 +- src/main/java/domain/Player.java | 22 +++++++++-- src/main/java/domain/Players.java | 39 +++++++++++++++++-- src/test/java/domain/PlayerTest.java | 18 ++++----- 6 files changed, 68 insertions(+), 21 deletions(-) rename src/main/java/domain/{CurrentTurn.java => ActiveTurn.java} (63%) rename src/main/java/domain/{NotCurrentTurn.java => InactiveTurn.java} (63%) diff --git a/README.md b/README.md index 40862a3f70..40d436c17a 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ ## 예외 처리 ### 플레이어 입력 - [x] 길이가 2~5 글자가 아닌 경우(사이 공백은 길이에 포함) -- [ ] 중복인 경우 +- [x] 중복인 경우 ### 포메이션 입력 - [x] 입력이 1,2,3,4 중 하나가 아닌 경우 ### 위치 입력 diff --git a/src/main/java/domain/CurrentTurn.java b/src/main/java/domain/ActiveTurn.java similarity index 63% rename from src/main/java/domain/CurrentTurn.java rename to src/main/java/domain/ActiveTurn.java index 145c028dad..4892cb4d64 100644 --- a/src/main/java/domain/CurrentTurn.java +++ b/src/main/java/domain/ActiveTurn.java @@ -1,6 +1,6 @@ package domain; -public class CurrentTurn implements TurnState { +public class ActiveTurn implements TurnState { @Override public boolean isCurrent() { @@ -9,6 +9,6 @@ public boolean isCurrent() { @Override public TurnState next() { - return new NotCurrentTurn(); + return new InactiveTurn(); } } diff --git a/src/main/java/domain/NotCurrentTurn.java b/src/main/java/domain/InactiveTurn.java similarity index 63% rename from src/main/java/domain/NotCurrentTurn.java rename to src/main/java/domain/InactiveTurn.java index 7be47b2ec6..b4dd7215c1 100644 --- a/src/main/java/domain/NotCurrentTurn.java +++ b/src/main/java/domain/InactiveTurn.java @@ -1,6 +1,6 @@ package domain; -public class NotCurrentTurn implements TurnState { +public class InactiveTurn implements TurnState { @Override public boolean isCurrent() { @@ -9,6 +9,6 @@ public boolean isCurrent() { @Override public TurnState next() { - return new CurrentTurn(); + return new ActiveTurn(); } } diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 17ad299fb1..78fa8dabbc 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -2,18 +2,34 @@ public class Player { private final Name name; + private final Side side; private TurnState turnState; - public Player(Name name, TurnState turnState) { + public Player(Name name, Side side, TurnState turnState) { this.name = name; + this.side = side; this.turnState = turnState; } - public boolean isTurn() { + public void validateAlly(Piece piece) { + if (piece.getSide() != side) { + throw new IllegalArgumentException("상대방의 기물은 움직일 수 없습니다."); + } + } + + public boolean isCurrentTurn() { return turnState.isCurrent(); } - public void changeTurn() { + public void toggleTurn() { turnState = turnState.next(); } + + public Side getSide() { + return side; + } + + public String getName() { + return name.name(); + } } diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 20d82362e0..1e94f2b961 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -5,9 +5,40 @@ public class Players { List players; - public Players(List players) { - this.players = players; + private Players(Player cho, Player han) { + this.players = List.of(cho, han); + } + + public static Players createInitial(Name choName, Name hanName) { + validateDuplicateName(choName, hanName); + return new Players( + new Player(choName, Side.CHO, new ActiveTurn()), + new Player(hanName, Side.HAN, new InactiveTurn()) + ); + } + + private static void validateDuplicateName(Name choName, Name hanName) { + if (choName.equals(hanName)) { + throw new IllegalArgumentException("동일한 플레이어 이름을 사용할 수 없습니다."); + } + } + + public Player getCurrentPlayer() { + return players.stream() + .filter(Player::isCurrentTurn) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("현재 턴인 플레이어가 없습니다.")); + } + + public Side getCurrentSide() { + return players.stream() + .filter(Player::isCurrentTurn) + .map(Player::getSide) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("현재 턴인 플레이어의 진영이 존재하지 않습니다.")); + } + + public void switchPlayer() { + players.forEach(Player::toggleTurn); } - // get current player - // toggle player } diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 9c39d59165..46547c28be 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -8,21 +8,21 @@ class PlayerTest { @Test void 현재_턴_상태를_반환한다() { - Player current = new Player(new Name("cho"), new CurrentTurn()); - Player notCurrent = new Player(new Name("han"), new NotCurrentTurn()); + Player current = new Player(new Name("cho"), Side.CHO, new ActiveTurn()); + Player notCurrent = new Player(new Name("han"),Side.HAN, new InactiveTurn()); - assertThat(current.isTurn()).isTrue(); - assertThat(notCurrent.isTurn()).isFalse(); + assertThat(current.isCurrentTurn()).isTrue(); + assertThat(notCurrent.isCurrentTurn()).isFalse(); } @Test void 턴_상태를_토글한다() { - Player player = new Player(new Name("cho"), new CurrentTurn()); + Player player = new Player(new Name("cho"), Side.CHO, new ActiveTurn()); - player.changeTurn(); - assertThat(player.isTurn()).isFalse(); + player.toggleTurn(); + assertThat(player.isCurrentTurn()).isFalse(); - player.changeTurn(); - assertThat(player.isTurn()).isTrue(); + player.toggleTurn(); + assertThat(player.isCurrentTurn()).isTrue(); } } From e4c45587c5c021402f5737f574a680bcf42102a5 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 20:07:50 +0900 Subject: [PATCH 39/92] =?UTF-8?q?feat(domain):=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20=EC=98=AC=EB=B0=94=EB=A5=B8=20=ED=84=B4=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EB=8A=94=20=EC=A7=84=ED=96=89=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 --- README.md | 18 ++--- src/main/java/Application.java | 2 +- src/main/java/domain/Board.java | 20 ++++-- src/main/java/domain/Game.java | 59 +++++++++------- src/main/java/domain/GameManager.java | 67 ++++++++++--------- src/main/java/domain/InitialBoardFactory.java | 17 +++-- src/main/java/domain/InputParser.java | 4 +- src/main/java/view/InputView.java | 60 ++--------------- src/main/java/view/OutputView.java | 9 +++ src/test/java/domain/BoardTest.java | 2 +- src/test/java/domain/GameTest.java | 62 +++++++---------- 11 files changed, 149 insertions(+), 171 deletions(-) diff --git a/README.md b/README.md index 40d436c17a..3c5da330d6 100644 --- a/README.md +++ b/README.md @@ -114,11 +114,11 @@ - [x] 게임 종료 여부 확인 기능 #### 게임 -- [ ] 플레이어 턴을 진행한다. - - [ ] 기물 선택 - - [ ] 기물 이동 -- [ ] 플레이어 턴이 종료된다면 턴을 변경한다. -- [ ] 게임 종료 여부를 판단한다. +- [x] 플레이어 턴을 진행한다. + - [x] 기물 선택 + - [x] 기물 이동 +- [x] 플레이어 턴이 종료된다면 턴을 변경한다. +- [x] 게임 종료 여부를 판단한다. ## 예외 처리 ### 플레이어 입력 @@ -130,11 +130,11 @@ - [x] `(x,y)` 형태가 아닌 경우 ### 기물 선택 - [x] 올바른 범위가 아닌 위치를 입력한 경우 -- [ ] 올바른 기물 선택이 아닌 경우 → +- [x] 올바른 기물 선택이 아닌 경우 → - [x] 빈 좌표 선택 - - [ ] 상대 기물 -- [ ] 목적지 후보가 없는(어디로도 갈 수 없는) 기물의 위치를 입력한 경우 -- [ ] 주어진 목적지 후보에 없는 위치를 입력한 경우 + - [x] 상대 기물 +- [x] 목적지 후보가 없는(어디로도 갈 수 없는) 기물의 위치를 입력한 경우 +- [x] 주어진 목적지 후보에 없는 위치를 입력한 경우 --- diff --git a/src/main/java/Application.java b/src/main/java/Application.java index e3a5f760f5..1350f30072 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -9,6 +9,6 @@ public static void main(String[] args) { new InputView(new Scanner(System.in)), new OutputView() ); - gameManager.start(); + gameManager.play(); } } diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 5cd23ff708..63c73dfb67 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -1,5 +1,6 @@ package domain; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,14 +19,21 @@ public List getPossibleDestinations(Position position) { throw new IllegalArgumentException("기물이 존재하지 않는 위치입니다."); } Piece piece = board.get(position); - // TODO: 상대 기물인 경우 에외발생 - 현재 누구 차례인지 정보 필요 List positions = piece.getAllPosition(position); - Map map = func(positions); + Map map = findPiecesAt(positions); return piece.getPossibleDestinations(position, map); } - private Map func(List positions) { - return Map.of(); + private Map findPiecesAt(List positions) { + Map pieces = new HashMap<>(); + + for (Position position : positions) { + if (board.containsKey(position)) { + pieces.put(position, board.get(position)); + } + } + + return pieces; } public void movePiece(Position from, Position to) { @@ -37,4 +45,8 @@ public boolean isGameOver() { .filter(piece -> piece instanceof General) .count() < 2; } + + public Piece getPiece(Position position) { + return board.get(position); + } } diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index 236c671449..450510d6ec 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -4,47 +4,54 @@ import java.util.Map; public class Game { - private final Board board; - private final Player choPlayer; - private final Player hanPlayer; + private final Players players; - public Game(Board board, Player choPlayer, Player hanPlayer) { + public Game(Board board, Players players) { this.board = board; - this.choPlayer = choPlayer; - this.hanPlayer = hanPlayer; + this.players = players; } - public Map getBoard() { - return board.getBoard(); + public Side getCurrentSide() { + return players.getCurrentSide(); } - public boolean isOver() { - return board.isGameOver(); + public List selectSource(Position position) { + Piece piece = board.getPiece(position); + players.getCurrentPlayer().validateAlly(piece); + return getPossibleDestinations(position); } - public List getPossibleDestinations(Position position) { - // TODO: Players에게 현재 턴인 플레이어의 진영을 물어보고, 사용자가 선택한 position의 진영을 비교해서 다르면 에러 발생 - List destinations = board.getPossibleDestinations(position); - if (destinations.isEmpty()) { - throw new IllegalArgumentException("갈 수 있는 목적지가 없습니다. 다른 기물을 선택하세요."); + public void move(Position from, Position to) { + validateDestinations(selectSource(from), to); + movePiece(from, to); + players.switchPlayer(); + } + + private void validateDestinations(List positions, Position target) { + if (!positions.contains(target)) { + throw new IllegalArgumentException("선택할 수 없는 위치입니다."); } - return destinations; } - public void movePiece(Position from, Position to) { + private List getPossibleDestinations(Position position) { + return board.getPossibleDestinations(position); + } + + private void movePiece(Position from, Position to) { board.movePiece(from, to); } - public String nextTurn() { - if (choPlayer.isTurn()) { - choPlayer.changeTurn(); - hanPlayer.changeTurn(); - return Side.HAN.getName(); - } + public Map getBoard() { + return board.getBoard(); + } + + public boolean isOver() { + return board.isGameOver(); + } - choPlayer.changeTurn(); - hanPlayer.changeTurn(); - return Side.CHO.getName(); + public String getWinner() { + players.switchPlayer(); + return players.getCurrentPlayer().getName(); } } diff --git a/src/main/java/domain/GameManager.java b/src/main/java/domain/GameManager.java index 4fd75a5e25..067c680aad 100644 --- a/src/main/java/domain/GameManager.java +++ b/src/main/java/domain/GameManager.java @@ -8,48 +8,55 @@ public class GameManager { private final InputView inputView; private final OutputView outputView; - private Side side; - private Position from; public GameManager(InputView inputView, OutputView outputView) { this.inputView = inputView; this.outputView = outputView; } - public void start() { - Player choPlayer = retry(() -> InputParser.parsePlayer(inputView.readChoPlayerName())); - Player hanPlayer = retry(() -> InputParser.parsePlayer(inputView.readChoPlayerName())); - - Formation choFormation = Formation.from(Selection.from(inputView.readChoFormation())); - Formation hanFormation = Formation.from(Selection.from(inputView.readHanFormation())); - Board board = new InitialBoardFactory().create(choFormation, hanFormation); - Game game = new Game(board, choPlayer, hanPlayer); - - side = Side.CHO; + public void play() { + Game game = initializeGame(); + outputView.printBoard(game.getBoard()); while (!game.isOver()) { - outputView.printBoard(game.getBoard()); + playTurn(game); + } + outputView.printWinner(game.getWinner()); + } - List destinations = retry(() -> { - from = InputParser.parsePosition(inputView.readPlayerPieceSelection(side, game.getBoard())); - return game.getPossibleDestinations(from); - }); + private Game initializeGame() { + Name choName = getPlayerName(Side.CHO); + Players players = retry(() -> { + Name hanName = getPlayerName(Side.HAN); + return Players.createInitial(choName, hanName); + }); + Board board = InitialBoardFactory.create(getFormation(Side.CHO), getFormation(Side.HAN)); + return new Game(board, players); + } - Position to = retry(() -> { - Position position = InputParser.parsePosition(inputView.readDestination(destinations)); - if (!destinations.contains(position)) { - throw new IllegalArgumentException("선택할 수 없는 위치입니다."); - } - return position; - }); - game.movePiece(from, to); + private Name getPlayerName(Side side) { + return retry(() -> InputParser.parseName(inputView.readPlayerName(side))); + } - side = nextTurn(); - } + private Formation getFormation(Side side) { + return retry(() -> Formation.from(Selection.from(inputView.readFormation(side)))); + } + + private void playTurn(Game game) { + Position from = selectPiecePosition(game); + retry(() -> { + Position to = InputParser.parsePosition(inputView.readDestination()); + game.move(from, to); + }); + outputView.printBoard(game.getBoard()); } - public Side nextTurn() { - if (side.isCho()) return Side.HAN; - return Side.CHO; + private Position selectPiecePosition(Game game) { + return retry(() -> { + Position position = InputParser.parsePosition(inputView.readPlayerPieceSelection(game.getCurrentSide())); + List destinations = game.selectSource(position); + outputView.printDestinations(destinations); + return position; + }); } private T retry(Supplier supplier) { diff --git a/src/main/java/domain/InitialBoardFactory.java b/src/main/java/domain/InitialBoardFactory.java index 3e8aae915d..84602a575a 100644 --- a/src/main/java/domain/InitialBoardFactory.java +++ b/src/main/java/domain/InitialBoardFactory.java @@ -5,7 +5,10 @@ public class InitialBoardFactory { - public Board create(Formation choFormation, Formation hanFormation) { + private InitialBoardFactory() { + } + + public static Board create(Formation choFormation, Formation hanFormation) { Map pieces = new HashMap<>(); placeFixedPieces(pieces, Side.CHO); @@ -17,7 +20,7 @@ public Board create(Formation choFormation, Formation hanFormation) { return new Board(pieces); } - private void placeFixedPieces(Map pieces, Side side) { + private static void placeFixedPieces(Map pieces, Side side) { placeGeneral(pieces, side); placeChariots(pieces, side); placeCannons(pieces, side); @@ -25,26 +28,26 @@ private void placeFixedPieces(Map pieces, Side side) { placeSoldiers(pieces, side); } - private void placeGeneral(Map pieces, Side side) { + private static void placeGeneral(Map pieces, Side side) { pieces.put(new Position(4, side.generalY()), new General(side)); } - private void placeChariots(Map pieces, Side side) { + private static void placeChariots(Map pieces, Side side) { pieces.put(new Position(0, side.baseY()), new Chariot(side)); pieces.put(new Position(8, side.baseY()), new Chariot(side)); } - private void placeCannons(Map pieces, Side side) { + private static void placeCannons(Map pieces, Side side) { pieces.put(new Position(1, side.cannonY()), new Cannon(side)); pieces.put(new Position(7, side.cannonY()), new Cannon(side)); } - private void placeGuards(Map pieces, Side side) { + private static void placeGuards(Map pieces, Side side) { pieces.put(new Position(3, side.baseY()), new Guard(side)); pieces.put(new Position(5, side.baseY()), new Guard(side)); } - private void placeSoldiers(Map pieces, Side side) { + private static void placeSoldiers(Map pieces, Side side) { for (int x = 0; x <= 8; x += 2) { pieces.put(new Position(x, side.soldierY()), new Soldier(side)); } diff --git a/src/main/java/domain/InputParser.java b/src/main/java/domain/InputParser.java index 758a643ba2..9750970201 100644 --- a/src/main/java/domain/InputParser.java +++ b/src/main/java/domain/InputParser.java @@ -8,11 +8,11 @@ public class InputParser { public static final Pattern POSITION_PATTERN = Pattern.compile(" *\\( *\\d+ *, *\\d+ *\\) *"); - public static Player parsePlayer(String input) { + public static Name parseName(String input) { if (input == null || input.isBlank()) { throw new IllegalArgumentException("빈 값은 입력할 수 없습니다."); } - return new Player(new Name(input.strip()), new CurrentTurn()); + return new Name(input.strip()); } public static Position parsePosition(String input) { diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 0d14ef14ed..0deebe03eb 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -8,31 +8,19 @@ import java.util.Scanner; public class InputView { - private final Scanner scanner; public InputView(Scanner scanner) { this.scanner = scanner; } - public String readChoPlayerName() { - System.out.print("초나라 플레이어 이름 입력: "); - return scanner.nextLine(); - } - - public String readHanPlayerName() { - System.out.print("한나라 플레이어 이름 입력: "); - return scanner.nextLine(); - } - - public String readChoFormation() { - System.out.println("초나라 플레이어 포메이션 입력"); - printFormations(); + public String readPlayerName(Side side) { + System.out.printf("%s나라 플레이어 이름 입력: ", side.getName()); return scanner.nextLine(); } - public String readHanFormation() { - System.out.println("한나라 플레이어 포메이션 입력"); + public String readFormation(Side side) { + System.out.printf("%s나라 플레이어 포메이션 입력%n", side.getName()); printFormations(); return scanner.nextLine(); } @@ -44,49 +32,13 @@ private void printFormations() { System.out.println("4. 마상상마"); } - public String readPlayerPieceSelection(Side side, Map board) { + public String readPlayerPieceSelection(Side side) { System.out.println(side.getName() + "나라 플레이어 차례입니다. 이동 시킬 기물의 위치를 입력하세요."); - /*System.out.printf("궁: %s\n", String.join(", ", - board.keySet().stream() - .filter(position -> board.get(position) instanceof General) - .map(Object::toString) - .toList())); - System.out.printf("차: %s\n", String.join(", ", - board.keySet().stream() - .filter(position -> board.get(position) instanceof Chariot) - .map(Object::toString) - .toList())); - System.out.printf("포: %s\n", String.join(", ", - board.keySet().stream() - .filter(position -> board.get(position) instanceof Cannon) - .map(Object::toString) - .toList())); - System.out.printf("마: %s\n", String.join(", ", - board.keySet().stream() - .filter(position -> board.get(position) instanceof Horse) - .map(Object::toString) - .toList())); - System.out.printf("상: %s\n", String.join(", ", - board.keySet().stream() - .filter(position -> board.get(position) instanceof Elephant) - .map(Object::toString) - .toList())); - System.out.printf("사: %s\n", String.join(", ", - board.keySet().stream() - .filter(position -> board.get(position) instanceof Guard) - .map(Object::toString) - .toList())); - System.out.printf("졸: %s\n", String.join(", ", - board.keySet().stream() - .filter(position -> board.get(position) instanceof Soldier) - .map(Object::toString) - .toList()));*/ return scanner.nextLine(); } - public String readDestination(List destinations) { + public String readDestination() { System.out.println("선택한 기물이 이동할 수 있는 위치입니다. 이동할 위치를 입력하세요."); - System.out.println(destinations); return scanner.nextLine(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 36ee4fd4b1..fa1ead05a9 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -3,6 +3,7 @@ import domain.Piece; import domain.Position; import domain.Side; +import java.util.List; import java.util.Map; public class OutputView { @@ -45,4 +46,12 @@ private String getColor(Side side) { public void printError(String message) { System.out.println(message); } + + public void printDestinations(List destinations) { + System.out.println(destinations); + } + + public void printWinner(String winner) { + System.out.printf("%s가 승리했습니다.%n", winner); + } } diff --git a/src/test/java/domain/BoardTest.java b/src/test/java/domain/BoardTest.java index b32fe77f67..7e9918c274 100644 --- a/src/test/java/domain/BoardTest.java +++ b/src/test/java/domain/BoardTest.java @@ -10,7 +10,7 @@ class BoardTest { @Test void 장기판은_올바른_위치에_기물을_초기화한다() { - Board board = new InitialBoardFactory().create(Formation.LEFT_ELEPHANT, Formation.RIGHT_ELEPHANT); + Board board = InitialBoardFactory.create(Formation.LEFT_ELEPHANT, Formation.RIGHT_ELEPHANT); Map actual = board.getBoard(); assertThat(actual).hasSize(32); diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index fb9b915495..467ce81712 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -3,72 +3,60 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class GameTest { - private Player choPlayer; - private Player hanPlayer; + private Players players; + private Game game; @BeforeEach void setUp() { - choPlayer = new Player(new Name("cho"), new CurrentTurn()); - hanPlayer = new Player(new Name("han"), new NotCurrentTurn()); + players = Players.createInitial(new Name("cho"), new Name("han")); + Formation choFormation = Formation.from(Selection.from("1")); + Formation hanFormation = Formation.from(Selection.from("1")); + Board board = InitialBoardFactory.create(choFormation, hanFormation); + game = new Game(board, players); } @Test - void nextTurn은_플레이어_턴을_교체하고_다음_진영명을_반환한다() { - Game game = new Game( - new Board(new HashMap<>()), - choPlayer, - hanPlayer - ); - - String first = game.nextTurn(); - String second = game.nextTurn(); + void 턴이_바뀌면_진영이_바뀐다() { + Side first = game.getCurrentSide(); + players.switchPlayer(); + Side second = game.getCurrentSide(); - assertThat(first).isEqualTo("한"); - assertThat(second).isEqualTo("초"); + assertThat(first).isEqualTo(Side.CHO); + assertThat(second).isEqualTo(Side.HAN); } @Test - void movePiece는_보드의_기물을_이동시킨다() { + void 보드의_기물을_이동이_가능하다() { Position from = new Position(0, 3); - Position to = new Position(0, 4); - Soldier soldier = new Soldier(Side.CHO); - Game game = createGameWithPieces(Map.of(from, soldier)); - - game.movePiece(from, to); + Piece expected = game.getBoard().get(from); + Position to = new Position(1, 3); + game.move(from, to); assertThat(game.getBoard()).doesNotContainKey(from); - assertThat(game.getBoard().get(to)).isSameAs(soldier); + assertThat(game.getBoard().get(to)).isEqualTo(expected); } @Test - void 올바른_범위가_아닌_위치를_입력한_경우() { + void 존재하지_않는_목적지로_이동하는_경우_예외가_발생한다() { Position from = new Position(0, 3); - Position to = new Position(0, 4); - Soldier soldier = new Soldier(Side.CHO); - Game game = createGameWithPieces(Map.of(from, soldier)); + Position to = new Position(1, 4); - assertThatThrownBy(() -> game.getPossibleDestinations(to)) + assertThatThrownBy(() -> game.move(from, to)) .isInstanceOf(IllegalArgumentException.class); } @Test void 장군이_하나면_게임은_종료상태다() { - Game game = createGameWithPieces(Map.of(new Position(4, 1), new General(Side.CHO))); + Game game = new Game( + new Board(Map.of(new Position(4, 1), new General(Side.CHO))), + Players.createInitial(new Name("cho"), new Name("han")) + ); assertThat(game.isOver()).isTrue(); } - - private Game createGameWithPieces(Map pieces) { - return new Game( - new Board(new HashMap<>(pieces)), - new Player(new Name("cho"), new CurrentTurn()), - new Player(new Name("han"), new NotCurrentTurn()) - ); - } } From a6a65706e65d3942d330d7dbdba994d92f7056d5 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 20:20:11 +0900 Subject: [PATCH 40/92] =?UTF-8?q?test(Piece):=20=EA=B0=81=20=EA=B8=B0?= =?UTF-8?q?=EB=AC=BC=EB=93=A4=20=EC=9D=B4=EB=8F=99=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/domain/CannonTest.java | 44 +++++++++++++++++++ src/test/java/domain/ChariotTest.java | 61 ++++++++++++++++++++++++++ src/test/java/domain/ElephantTest.java | 50 +++++++++++++++++++++ src/test/java/domain/GeneralTest.java | 44 +++++++++++++++++++ src/test/java/domain/GuardTest.java | 44 +++++++++++++++++++ src/test/java/domain/HorseTest.java | 50 +++++++++++++++++++++ src/test/java/domain/SoldierTest.java | 55 +++++++++++++++++++++++ 7 files changed, 348 insertions(+) create mode 100644 src/test/java/domain/CannonTest.java create mode 100644 src/test/java/domain/ChariotTest.java create mode 100644 src/test/java/domain/ElephantTest.java create mode 100644 src/test/java/domain/GeneralTest.java create mode 100644 src/test/java/domain/GuardTest.java create mode 100644 src/test/java/domain/HorseTest.java create mode 100644 src/test/java/domain/SoldierTest.java diff --git a/src/test/java/domain/CannonTest.java b/src/test/java/domain/CannonTest.java new file mode 100644 index 0000000000..4a1cd6ad2b --- /dev/null +++ b/src/test/java/domain/CannonTest.java @@ -0,0 +1,44 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class CannonTest { + + @Test + void 중간에_넘을_기물이_없으면_이동할_수_없다() { + Cannon cannon = new Cannon(Side.CHO); + Position from = new Position(4, 4); + + List destinations = cannon.getPossibleDestinations(from, new HashMap<>()); + + assertThat(destinations).isEmpty(); + } + + @Test + void 한_기물을_넘은_뒤_이동하고_포는_잡을_수_없다() { + Cannon cannon = new Cannon(Side.CHO); + Position from = new Position(4, 4); + Map board = new HashMap<>(); + board.put(new Position(4, 5), new Soldier(Side.HAN)); + board.put(new Position(4, 8), new Soldier(Side.HAN)); + board.put(new Position(4, 3), new Soldier(Side.HAN)); + board.put(new Position(4, 2), new Soldier(Side.CHO)); + board.put(new Position(3, 4), new Cannon(Side.HAN)); + board.put(new Position(5, 4), new Soldier(Side.HAN)); + board.put(new Position(7, 4), new Cannon(Side.HAN)); + + List destinations = cannon.getPossibleDestinations(from, board); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 6), + new Position(4, 7), + new Position(4, 8), + new Position(6, 4) + ); + } +} diff --git a/src/test/java/domain/ChariotTest.java b/src/test/java/domain/ChariotTest.java new file mode 100644 index 0000000000..69ce2dca10 --- /dev/null +++ b/src/test/java/domain/ChariotTest.java @@ -0,0 +1,61 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ChariotTest { + + @Test + void 빈_보드에서는_직선_모든_칸으로_이동한다() { + Chariot chariot = new Chariot(Side.CHO); + Position from = new Position(4, 4); + + List destinations = chariot.getPossibleDestinations(from, new HashMap<>()); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 5), + new Position(4, 6), + new Position(4, 7), + new Position(4, 8), + new Position(4, 9), + new Position(4, 3), + new Position(4, 2), + new Position(4, 1), + new Position(4, 0), + new Position(3, 4), + new Position(2, 4), + new Position(1, 4), + new Position(0, 4), + new Position(5, 4), + new Position(6, 4), + new Position(7, 4), + new Position(8, 4) + ); + } + + @Test + void 기물이_있으면_해당_칸까지만_이동하고_더_이상_가지_못한다() { + Chariot chariot = new Chariot(Side.CHO); + Position from = new Position(4, 4); + Map board = new HashMap<>(); + board.put(new Position(4, 6), new Soldier(Side.CHO)); + board.put(new Position(4, 2), new Soldier(Side.HAN)); + board.put(new Position(2, 4), new Soldier(Side.CHO)); + board.put(new Position(6, 4), new Soldier(Side.HAN)); + + List destinations = chariot.getPossibleDestinations(from, board); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 5), + new Position(4, 3), + new Position(4, 2), + new Position(3, 4), + new Position(5, 4), + new Position(6, 4) + ); + } +} diff --git a/src/test/java/domain/ElephantTest.java b/src/test/java/domain/ElephantTest.java new file mode 100644 index 0000000000..2bb75439d5 --- /dev/null +++ b/src/test/java/domain/ElephantTest.java @@ -0,0 +1,50 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ElephantTest { + + @Test + void 빈_보드에서는_8곳으로_이동한다() { + Elephant elephant = new Elephant(Side.CHO); + Position from = new Position(4, 4); + + List destinations = elephant.getPossibleDestinations(from, new HashMap<>()); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(6, 7), + new Position(2, 7), + new Position(6, 1), + new Position(2, 1), + new Position(1, 6), + new Position(1, 2), + new Position(7, 6), + new Position(7, 2) + ); + } + + @Test + void 경로가_막힌_방향으로는_이동하지_못한다() { + Elephant elephant = new Elephant(Side.CHO); + Position from = new Position(4, 4); + Map board = new HashMap<>(); + board.put(new Position(4, 3), new Soldier(Side.HAN)); + board.put(new Position(5, 6), new Soldier(Side.HAN)); + board.put(new Position(1, 6), new Soldier(Side.CHO)); + board.put(new Position(1, 2), new Soldier(Side.HAN)); + + List destinations = elephant.getPossibleDestinations(from, board); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(2, 7), + new Position(1, 2), + new Position(7, 6), + new Position(7, 2) + ); + } +} diff --git a/src/test/java/domain/GeneralTest.java b/src/test/java/domain/GeneralTest.java new file mode 100644 index 0000000000..b4a7a81e11 --- /dev/null +++ b/src/test/java/domain/GeneralTest.java @@ -0,0 +1,44 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class GeneralTest { + + @Test + void 빈_보드에서는_상하좌우로_이동한다() { + General general = new General(Side.CHO); + Position from = new Position(4, 4); + + List destinations = general.getPossibleDestinations(from, new HashMap<>()); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 5), + new Position(4, 3), + new Position(3, 4), + new Position(5, 4) + ); + } + + @Test + void 같은_진영_기물이_있는_칸으로는_이동하지_못한다() { + General general = new General(Side.CHO); + Position from = new Position(4, 4); + Map board = new HashMap<>(); + board.put(new Position(4, 5), new Soldier(Side.CHO)); + board.put(new Position(4, 3), new Soldier(Side.HAN)); + board.put(new Position(3, 4), new Soldier(Side.CHO)); + board.put(new Position(5, 4), new Soldier(Side.HAN)); + + List destinations = general.getPossibleDestinations(from, board); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 3), + new Position(5, 4) + ); + } +} diff --git a/src/test/java/domain/GuardTest.java b/src/test/java/domain/GuardTest.java new file mode 100644 index 0000000000..5cb77419ea --- /dev/null +++ b/src/test/java/domain/GuardTest.java @@ -0,0 +1,44 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class GuardTest { + + @Test + void 빈_보드에서는_상하좌우로_이동한다() { + Guard guard = new Guard(Side.HAN); + Position from = new Position(4, 4); + + List destinations = guard.getPossibleDestinations(from, new HashMap<>()); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 5), + new Position(4, 3), + new Position(3, 4), + new Position(5, 4) + ); + } + + @Test + void 같은_진영_기물이_있는_칸으로는_이동하지_못한다() { + Guard guard = new Guard(Side.HAN); + Position from = new Position(4, 4); + Map board = new HashMap<>(); + board.put(new Position(4, 5), new Soldier(Side.HAN)); + board.put(new Position(4, 3), new Soldier(Side.CHO)); + board.put(new Position(3, 4), new Soldier(Side.HAN)); + board.put(new Position(5, 4), new Soldier(Side.CHO)); + + List destinations = guard.getPossibleDestinations(from, board); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 3), + new Position(5, 4) + ); + } +} diff --git a/src/test/java/domain/HorseTest.java b/src/test/java/domain/HorseTest.java new file mode 100644 index 0000000000..b86a027f09 --- /dev/null +++ b/src/test/java/domain/HorseTest.java @@ -0,0 +1,50 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class HorseTest { + + @Test + void 빈_보드에서는_8곳으로_이동한다() { + Horse horse = new Horse(Side.CHO); + Position from = new Position(4, 4); + + List destinations = horse.getPossibleDestinations(from, new HashMap<>()); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(5, 6), + new Position(3, 6), + new Position(5, 2), + new Position(3, 2), + new Position(2, 5), + new Position(2, 3), + new Position(6, 5), + new Position(6, 3) + ); + } + + @Test + void 다리_칸이_막히면_해당_방향으로_이동하지_못한다() { + Horse horse = new Horse(Side.CHO); + Position from = new Position(4, 4); + Map board = new HashMap<>(); + board.put(new Position(4, 5), new Soldier(Side.HAN)); + board.put(new Position(6, 5), new Soldier(Side.CHO)); + board.put(new Position(2, 3), new Soldier(Side.HAN)); + + List destinations = horse.getPossibleDestinations(from, board); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(5, 2), + new Position(3, 2), + new Position(2, 5), + new Position(2, 3), + new Position(6, 3) + ); + } +} diff --git a/src/test/java/domain/SoldierTest.java b/src/test/java/domain/SoldierTest.java new file mode 100644 index 0000000000..20dab71db4 --- /dev/null +++ b/src/test/java/domain/SoldierTest.java @@ -0,0 +1,55 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class SoldierTest { + + @Test + void 초_졸은_위_좌_우로_이동한다() { + Soldier soldier = new Soldier(Side.CHO); + Position from = new Position(4, 4); + + List destinations = soldier.getPossibleDestinations(from, new HashMap<>()); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 5), + new Position(3, 4), + new Position(5, 4) + ); + } + + @Test + void 한_졸은_아래_좌_우로_이동한다() { + Soldier soldier = new Soldier(Side.HAN); + Position from = new Position(4, 4); + + List destinations = soldier.getPossibleDestinations(from, new HashMap<>()); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(4, 3), + new Position(3, 4), + new Position(5, 4) + ); + } + + @Test + void 같은_진영_기물이_있는_칸으로는_이동하지_못한다() { + Soldier soldier = new Soldier(Side.CHO); + Position from = new Position(4, 4); + Map board = new HashMap<>(); + board.put(new Position(4, 5), new Soldier(Side.CHO)); + board.put(new Position(3, 4), new Soldier(Side.HAN)); + board.put(new Position(5, 4), new Soldier(Side.CHO)); + + List destinations = soldier.getPossibleDestinations(from, board); + + assertThat(destinations).containsExactlyInAnyOrder( + new Position(3, 4) + ); + } +} From 3e24b68e68cf844bf517b7666910774d6a67c6da Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Mon, 30 Mar 2026 20:28:58 +0900 Subject: [PATCH 41/92] =?UTF-8?q?style:=20=EC=B6=95=EC=95=BD=ED=95=9C=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=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/domain/Cannon.java | 88 +++++++++++++-------------- src/main/java/domain/Chariot.java | 90 ++++++++++++++-------------- src/main/java/domain/Elephant.java | 88 +++++++++++++-------------- src/main/java/domain/Horse.java | 96 +++++++++++++++--------------- 4 files changed, 180 insertions(+), 182 deletions(-) diff --git a/src/main/java/domain/Cannon.java b/src/main/java/domain/Cannon.java index e466fee599..67530c5854 100644 --- a/src/main/java/domain/Cannon.java +++ b/src/main/java/domain/Cannon.java @@ -15,28 +15,28 @@ public Cannon(Side side) { public List getAllPosition(Position position) { List positions = new ArrayList<>(); // 상 - Position curPosition = position; - while (curPosition.upPossible()) { - curPosition = curPosition.up(); - positions.add(curPosition); + Position currentPosition = position; + while (currentPosition.upPossible()) { + currentPosition = currentPosition.up(); + positions.add(currentPosition); } // 하 - curPosition = position; - while (curPosition.downPossible()) { - curPosition = curPosition.down(); - positions.add(curPosition); + currentPosition = position; + while (currentPosition.downPossible()) { + currentPosition = currentPosition.down(); + positions.add(currentPosition); } // 좌 - curPosition = position; - while (curPosition.leftPossible()) { - curPosition = curPosition.left(); - positions.add(curPosition); + currentPosition = position; + while (currentPosition.leftPossible()) { + currentPosition = currentPosition.left(); + positions.add(currentPosition); } // 우 - curPosition = position; - while (curPosition.rightPossible()) { - curPosition = curPosition.right(); - positions.add(curPosition); + currentPosition = position; + while (currentPosition.rightPossible()) { + currentPosition = currentPosition.right(); + positions.add(currentPosition); } return positions; @@ -47,15 +47,15 @@ public List getPossibleDestinations(Position position, Map destinations = new ArrayList<>(); // 상 - Position curPosition = position; + Position currentPosition = position; boolean canPut = false; - while (curPosition.upPossible()) { - curPosition = curPosition.up(); - if (board.containsKey(curPosition)) { - Piece piece = board.get(curPosition); + while (currentPosition.upPossible()) { + currentPosition = currentPosition.up(); + if (board.containsKey(currentPosition)) { + Piece piece = board.get(currentPosition); if (canPut) { if (!piece.isSameSideAs(side) && !(piece instanceof Cannon)) { - destinations.add(curPosition); + destinations.add(currentPosition); } break; } @@ -69,19 +69,19 @@ public List getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getAllPosition(Position position) { List positions = new ArrayList<>(); // 상 - Position curPosition = position; - while (curPosition.upPossible()) { - curPosition = curPosition.up(); - positions.add(curPosition); + Position currentPosition = position; + while (currentPosition.upPossible()) { + currentPosition = currentPosition.up(); + positions.add(currentPosition); } // 하 - curPosition = position; - while (curPosition.downPossible()) { - curPosition = curPosition.down(); - positions.add(curPosition); + currentPosition = position; + while (currentPosition.downPossible()) { + currentPosition = currentPosition.down(); + positions.add(currentPosition); } // 좌 - curPosition = position; - while (curPosition.leftPossible()) { - curPosition = curPosition.left(); - positions.add(curPosition); + currentPosition = position; + while (currentPosition.leftPossible()) { + currentPosition = currentPosition.left(); + positions.add(currentPosition); } // 우 - curPosition = position; - while (curPosition.rightPossible()) { - curPosition = curPosition.right(); - positions.add(curPosition); + currentPosition = position; + while (currentPosition.rightPossible()) { + currentPosition = currentPosition.right(); + positions.add(currentPosition); } return positions; @@ -49,56 +47,56 @@ public List getPossibleDestinations(Position position, Map destinations = new ArrayList<>(); // 상 - Position curPosition = position; - while (curPosition.upPossible()) { - curPosition = curPosition.up(); - if (board.containsKey(curPosition)) { - Piece piece = board.get(curPosition); + Position currentPosition = position; + while (currentPosition.upPossible()) { + currentPosition = currentPosition.up(); + if (board.containsKey(currentPosition)) { + Piece piece = board.get(currentPosition); if (!piece.isSameSideAs(side)) { - destinations.add(curPosition); + destinations.add(currentPosition); } break; } - destinations.add(curPosition); + destinations.add(currentPosition); } // 하 - curPosition = position; - while (curPosition.downPossible()) { - curPosition = curPosition.down(); - if (board.containsKey(curPosition)) { - Piece piece = board.get(curPosition); + currentPosition = position; + while (currentPosition.downPossible()) { + currentPosition = currentPosition.down(); + if (board.containsKey(currentPosition)) { + Piece piece = board.get(currentPosition); if (!piece.isSameSideAs(side)) { - destinations.add(curPosition); + destinations.add(currentPosition); } break; } - destinations.add(curPosition); + destinations.add(currentPosition); } // 좌 - curPosition = position; - while (curPosition.leftPossible()) { - curPosition = curPosition.left(); - if (board.containsKey(curPosition)) { - Piece piece = board.get(curPosition); + currentPosition = position; + while (currentPosition.leftPossible()) { + currentPosition = currentPosition.left(); + if (board.containsKey(currentPosition)) { + Piece piece = board.get(currentPosition); if (!piece.isSameSideAs(side)) { - destinations.add(curPosition); + destinations.add(currentPosition); } break; } - destinations.add(curPosition); + destinations.add(currentPosition); } // 우 - curPosition = position; - while (curPosition.rightPossible()) { - curPosition = curPosition.right(); - if (board.containsKey(curPosition)) { - Piece piece = board.get(curPosition); + currentPosition = position; + while (currentPosition.rightPossible()) { + currentPosition = currentPosition.right(); + if (board.containsKey(currentPosition)) { + Piece piece = board.get(currentPosition); if (!piece.isSameSideAs(side)) { - destinations.add(curPosition); + destinations.add(currentPosition); } break; } - destinations.add(curPosition); + destinations.add(currentPosition); } return destinations; diff --git a/src/main/java/domain/Elephant.java b/src/main/java/domain/Elephant.java index 7d2c99ec4b..39b8803e93 100644 --- a/src/main/java/domain/Elephant.java +++ b/src/main/java/domain/Elephant.java @@ -17,17 +17,17 @@ public List getAllPosition(Position position) { // 상 if (position.upPossible()) { - Position curPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(curPosition); - if (curPosition.rightUpPossible()) { - Position rightUpPosition = curPosition.rightUp(); + Position currentPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(currentPosition); + if (currentPosition.rightUpPossible()) { + Position rightUpPosition = currentPosition.rightUp(); positions.add(rightUpPosition); if (rightUpPosition.rightUpPossible()) { positions.add(rightUpPosition.rightUp()); } } - if (curPosition.leftUpPossible()) { - Position leftUPosition = curPosition.leftUp(); + if (currentPosition.leftUpPossible()) { + Position leftUPosition = currentPosition.leftUp(); positions.add(leftUPosition); if (leftUPosition.leftUpPossible()) { positions.add(leftUPosition.leftUp()); @@ -36,17 +36,17 @@ public List getAllPosition(Position position) { } // 하 if (position.downPossible()) { - Position curPosition = position.down(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(curPosition); - if (curPosition.rightDownPossible()) { - Position rightDownPosition = curPosition.rightDown(); + Position currentPosition = position.down(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(currentPosition); + if (currentPosition.rightDownPossible()) { + Position rightDownPosition = currentPosition.rightDown(); positions.add(rightDownPosition); if (rightDownPosition.rightDownPossible()) { positions.add(rightDownPosition.rightDown()); } } - if (curPosition.leftDownPossible()) { - Position leftDownPosition = curPosition.leftDown(); + if (currentPosition.leftDownPossible()) { + Position leftDownPosition = currentPosition.leftDown(); positions.add(leftDownPosition); if (leftDownPosition.leftDownPossible()) { positions.add(leftDownPosition.leftDown()); @@ -55,17 +55,17 @@ public List getAllPosition(Position position) { } // 좌 if (position.leftPossible()) { - Position curPosition = position.left(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(curPosition); - if (curPosition.leftUpPossible()) { - Position leftUpPosition = curPosition.leftUp(); + Position currentPosition = position.left(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(currentPosition); + if (currentPosition.leftUpPossible()) { + Position leftUpPosition = currentPosition.leftUp(); positions.add(leftUpPosition); if (leftUpPosition.leftUpPossible()) { positions.add(leftUpPosition.leftUp()); } } - if (curPosition.leftDownPossible()) { - Position leftDownPosition = curPosition.leftDown(); + if (currentPosition.leftDownPossible()) { + Position leftDownPosition = currentPosition.leftDown(); positions.add(leftDownPosition); if (leftDownPosition.leftDownPossible()) { positions.add(leftDownPosition.leftDown()); @@ -74,17 +74,17 @@ public List getAllPosition(Position position) { } // 우 if (position.rightPossible()) { - Position curPosition = position.right(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(curPosition); - if (curPosition.rightUpPossible()) { - Position rightUpPosition = curPosition.rightUp(); + Position currentPosition = position.right(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(currentPosition); + if (currentPosition.rightUpPossible()) { + Position rightUpPosition = currentPosition.rightUp(); positions.add(rightUpPosition); if (rightUpPosition.rightUpPossible()) { positions.add(rightUpPosition.rightUp()); } } - if (curPosition.rightDownPossible()) { - Position rightDownPosition = curPosition.rightDown(); + if (currentPosition.rightDownPossible()) { + Position rightDownPosition = currentPosition.rightDown(); positions.add(rightDownPosition); if (rightDownPosition.rightDownPossible()) { positions.add(rightDownPosition.rightDown()); @@ -101,15 +101,15 @@ public List getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getAllPosition(Position position) { // 상 if (position.upPossible()) { - Position curPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(curPosition); - if (curPosition.rightUpPossible()) { - positions.add(curPosition.rightUp()); + Position currentPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! + positions.add(currentPosition); + if (currentPosition.rightUpPossible()) { + positions.add(currentPosition.rightUp()); } - if (curPosition.leftUpPossible()) { - positions.add(curPosition.leftUp()); + if (currentPosition.leftUpPossible()) { + positions.add(currentPosition.leftUp()); } } // 하 if (position.downPossible()) { - Position curPosition = position.down(); - positions.add(curPosition); - if (curPosition.rightDownPossible()) { - positions.add(curPosition.rightDown()); + Position currentPosition = position.down(); + positions.add(currentPosition); + if (currentPosition.rightDownPossible()) { + positions.add(currentPosition.rightDown()); } - if (curPosition.leftDownPossible()) { - positions.add(curPosition.leftDown()); + if (currentPosition.leftDownPossible()) { + positions.add(currentPosition.leftDown()); } } // 좌 if (position.leftPossible()) { - Position curPosition = position.left(); - positions.add(curPosition); - if (curPosition.leftUpPossible()) { - positions.add(curPosition.leftUp()); + Position currentPosition = position.left(); + positions.add(currentPosition); + if (currentPosition.leftUpPossible()) { + positions.add(currentPosition.leftUp()); } - if (curPosition.leftDownPossible()) { - positions.add(curPosition.leftDown()); + if (currentPosition.leftDownPossible()) { + positions.add(currentPosition.leftDown()); } } // 우 if (position.rightPossible()) { - Position curPosition = position.right(); - positions.add(curPosition); - if (curPosition.rightUpPossible()) { - positions.add(curPosition.rightUp()); + Position currentPosition = position.right(); + positions.add(currentPosition); + if (currentPosition.rightUpPossible()) { + positions.add(currentPosition.rightUp()); } - if (curPosition.rightDownPossible()) { - positions.add(curPosition.rightDown()); + if (currentPosition.rightDownPossible()) { + positions.add(currentPosition.rightDown()); } } @@ -69,52 +69,52 @@ public List getPossibleDestinations(Position position, Map Date: Mon, 30 Mar 2026 23:45:47 +0900 Subject: [PATCH 42/92] =?UTF-8?q?docs:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 253 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 144 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 3c5da330d6..8cb2e6757b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - [x] 1.1단계에서 정리한 내용을 README.md에 기능 목록으로 작성한다. - [x] 1.2단계를 시작하기 전에 **어떤 기능을 구현해야 하는지 정리**한다. - [x] 1.2단계에서 정리한 내용을 README.md에 기능 목록으로 작성한다. -- [ ] 개발 과정에서 **"나는 왜 이렇게 구현할까?"** 라는 고민을 기록한다. +- [x] 개발 과정에서 **"나는 왜 이렇게 구현할까?"** 라는 고민을 기록한다. --- @@ -27,114 +27,101 @@ --- -## 기능 목록 -### 1.1단계 -#### 위치(domain.Position) -- [x] 검증 - - [x] x의 값은 0 이상 8 이하여야 한다. - - [x] y의 값은 0 이상 9 이하여야 한다. - - [x] x의 값이 0 이상 8 이하가 아니면 예외가 발생한다. - - [x] y의 값이 0 이상 9 이하가 아니면 예외가 발생한다. -#### 진영(domain.Side) - - [x] 진영은 초와 한만 가진다. -#### 기물(domain.Piece) - - [x] 같은 진영 여부를 판단한다. -#### 장기판(domain.Board) - - [x] 검증 - - [x] 장기판은 올바른 위치에 기물을 초기화한다. - - [x] 장기판은 올바른 위치에 기물을 초기화하지 않으면 예외가 발생한다. - -### 1.2단계 -#### 기물(domain.Piece) -- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 -- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - -#### 궁(domain.General) -- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [x] 상, 하, 좌, 우 1칸 모든 위치 반환 - - [x] 보드 범위를 벗어나는 위치 제외 -- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [x] 상, 하, 좌, 우 1칸 위치 반환 - - [x] 해당 위치에 내 기물이 있으면 가지 못한다. -#### 사(domain.Guard) -- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [x] 상, 하, 좌, 우 1칸 모든 위치 반환 - - [x] 보드 범위를 벗어나는 위치 제외 -- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [x] 상, 하, 좌, 우 1칸 위치 반환 - - [x] 해당 위치에 내 기물이 있으면 가지 못한다. -#### 졸(domain.Soldier) -- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [x] 초 기준 - - [x] 상, 좌, 우 1칸 모든 위치 반환 - - [x] 보드 범위를 벗어나는 위치 제외 - - [x] 한 기준 - - [x] 하, 좌, 우 1칸 모든 위치 반환 - - [x] 보드 범위를 벗어나는 위치 제외 -- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [x] 상, 하, 좌, 우 위치 반환 - - [x] 해당 위치에 내 기물이 있으면 가지 못한다. -#### 마(domain.Horse) -- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [x] 상하좌우 4개 및 날 일자 8개 위치 모두 반환 - - [x] 보드 범위를 벗어나는 위치 제외 -- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [x] 날 일자 8개의 목적지 모두 확인 - 1. 경유지에 기물이 없고, 날 일자 위치가 보드 범위를 벗어나지 않아야 한다. - 2. 1을 만족하면서 기물이 없거나 상대 기물이 있으면 해당 위치는 최종 목적지가 될 수 있다. -#### 상(domain.Elephant) -- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [x] 상하좌우 4개, 날 일자 8개, 눈 목자 8개 위치 모두 반환 - - [x] 보드 범위를 벗어나는 위치 제외 -- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [x] 눈 목자 8개의 목적지 모두 확인 - 1. 경유지(직진 1칸, 날 일자 위치)에 기물이 없고, 눈 목자 위치가 보드 범위를 벗어나지 않아야 한다. - 2. 1을 만족하면서 기물이 없거나 상대 기물이 있으면 해당 위치는 최종 목적지가 될 수 있다. -#### 차(domain.Chariot) -- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [x] 상하좌우 모든 직진 위치 반환 - - [x] 보드 범위를 벗어나는 위치 제외 -- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [x] 상하좌우 직진 위치 반환 - 1. 위치 탐색 중 내 기물이 있다면 기물 직전까지의 위치를 모두 반환한다. - 2. 위치 탐색 중 상대 기물이 있다면 기물까지의 위치를 모두 반환한다. -#### 포(domain.Cannon) -- [x] 위치가 주어졌을 때 갈 수 있는 모든 경유지와 목적지 목록 반환 기능 - - [x] 상하좌우 모든 직진 위치 반환 - - [x] 보드 범위를 벗어나는 위치 제외 -- [x] 위치에 대한 기물의 정보가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 - - [x] 상하좌우 직진 위치 반환 - 1. 위치 탐색 중 포가 아닌 기물을 만난 후부터의 위치를 반환한다. - 2. 포가 아닌 기물을 만난 이후의 위치 탐색 중 내 기물이 있다면 기물 직전까지의 위치를 모두 반환한다. - 3. 포가 아닌 기물을 만난 이후의 위치 탐색 중 상대의 포가 아닌 기물이 있다면 기물까지의 위치를 모두 반환한다. - 4. 포가 아닌 기물을 만난 이후의 위치 탐색 중 상대의 포 기물이 있다면 기물까지의 위치를 모두 반환한다. -#### 장기판(domain.Board) -- [x] 위치가 주어졌을 때 갈 수 있는 목적지 목록 반환 기능 -- [x] 출발지와 목적지가 주어졌을 때 기물 이동 기능 -- [x] 게임 종료 여부 확인 기능 - -#### 게임 -- [x] 플레이어 턴을 진행한다. - - [x] 기물 선택 - - [x] 기물 이동 -- [x] 플레이어 턴이 종료된다면 턴을 변경한다. -- [x] 게임 종료 여부를 판단한다. - -## 예외 처리 -### 플레이어 입력 -- [x] 길이가 2~5 글자가 아닌 경우(사이 공백은 길이에 포함) -- [x] 중복인 경우 -### 포메이션 입력 -- [x] 입력이 1,2,3,4 중 하나가 아닌 경우 -### 위치 입력 -- [x] `(x,y)` 형태가 아닌 경우 -### 기물 선택 -- [x] 올바른 범위가 아닌 위치를 입력한 경우 -- [x] 올바른 기물 선택이 아닌 경우 → - - [x] 빈 좌표 선택 - - [x] 상대 기물 -- [x] 목적지 후보가 없는(어디로도 갈 수 없는) 기물의 위치를 입력한 경우 -- [x] 주어진 목적지 후보에 없는 위치를 입력한 경우 +## 기능 목록 (테스트 명세) + +### 1.1단계: 보드 및 기본 도메인 초기화 + +#### 위치 (Position) +- [ ] x 좌표가 0 미만이거나 8 초과이면 예외가 발생한다 +- [ ] y 좌표가 0 미만이거나 9 초과이면 예외가 발생한다 +- [ ] 올바른 x, y 좌표가 주어지면 객체가 정상적으로 생성된다 + +#### 진영 (Side) +- [ ] 진영은 초와 한만 가진다 + +#### 플레이어 (Player) +- [ ] 플레이어는 상대방의 기물을 선택하면 예외가 발생한다 +- [ ] 턴 상태를 토글하면 현재 턴 여부가 반전된다 + +#### 플레이어들 (Players) +- [ ] 초기 플레이어 생성 시 두 플레이어의 이름이 같으면 예외가 발생한다 + +#### 기물 공통 (Piece) +- [ ] 주어진 진영과 자신의 진영이 같은지 올바르게 판별한다 + +#### 장기판 초기화 (BoardFactory / Board) +- [ ] 선택한 포메이션에 맞게 32개의 기물이 초기 위치에 정확히 배치된다 +- [ ] 양 진영의 궁이 지정된 궁성 위치에 정상적으로 배치된다 + +### 1.2단계: 기물 이동 및 게임 상태 + +#### 궁 (General) +- [ ] 궁은 상 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) +- [ ] 궁은 목적지가 보드 범위를 벗어나면 이동할 수 없다 +- [ ] 궁은 목적지에 아군 기물이 있으면 이동할 수 없다 + +#### 사 (Guard) +- [ ] 사는 상 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) +- [ ] 사는 목적지에 아군 기물이 있으면 이동할 수 없다 + +#### 졸 (Soldier) +- [ ] 초나라 졸은 상 좌 우 1칸 이동할 수 있다 (OneStepStrategy) +- [ ] 한나라 졸은 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) +- [ ] 졸은 목적지에 아군 기물이 있으면 이동할 수 없다 + +#### 마 (Horse) +- [ ] 마는 직선 1칸 이동 후 대각선 1칸 방향으로 이동할 수 있다 (SequenceStrategy) +- [ ] 마는 직선 1칸 경로에 장애물 기물이 존재하면 해당 방향으로 이동할 수 없다 +- [ ] 마는 목적지에 아군 기물이 있으면 이동할 수 없다 + +#### 상 (Elephant) +- [ ] 상은 직선 1칸 이동 후 대각선 2칸 방향으로 이동할 수 있다 (SequenceStrategy) +- [ ] 상은 직선 1칸 또는 대각선 1칸 경로 중 하나라도 장애물 기물이 존재하면 이동할 수 없다 +- [ ] 상은 목적지에 아군 기물이 있으면 이동할 수 없다 + +#### 차 (Chariot) +- [ ] 차는 상 하 좌 우 방향으로 장애물을 만날 때까지 연속해서 이동할 수 있다 (SlideStrategy) +- [ ] 차는 이동 경로 중 아군 기물을 만나면 그 직전 위치까지만 이동할 수 있다 +- [ ] 차는 이동 경로 중 적군 기물을 만나면 해당 위치까지 이동하여 잡을 수 있다 + +#### 포 (Cannon) +- [ ] 포는 상 하 좌 우 방향으로 기물 하나를 뛰어넘어 빈칸으로 이동할 수 있다 (JumpSlideStrategy) +- [ ] 포는 이동 경로에서 포 기물을 뛰어넘을 수 없다 +- [ ] 포는 기물을 뛰어넘은 후 적군 기물을 만나면 해당 위치까지 이동하여 잡을 수 있다 +- [ ] 포는 목적지에 아군 기물이 있으면 이동할 수 없다 +- [ ] 포는 뛰어넘은 후 만난 적군 기물이 포일 경우 잡을 수 없다 + +#### 장기판 이동 (Board) +- [ ] 출발지와 목적지가 주어지면 기물이 이동된 새로운 불변 보드 객체를 반환한다 +- [ ] 목적지에 적군 기물이 있으면 보드에서 해당 기물을 제거하고 이동한다 +- [ ] 기물이 존재하지 않는 빈 좌표를 출발지로 입력하면 예외가 발생한다 + +#### 게임 상태 및 흐름 (Game) +- [ ] 기물 이동이 완료되면 턴이 상대방 진영으로 변경된다 +- [ ] 이동할 수 있는 목적지가 전혀 없는 기물을 선택하면 예외가 발생한다 +- [ ] 선택한 기물이 이동할 수 없는 위치를 목적지로 입력하면 예외가 발생한다 +- [ ] 보드에 궁이 하나만 남게 되면 게임은 종료 상태가 된다 + +### 1.3단계: 사용자 입력 및 파서 검증 + +#### 플레이어 이름 입력 +- [ ] 이름이 2글자 미만이거나 5글자를 초과하면 예외가 발생한다 +- [ ] 이름에 빈 값이나 공백만 입력되면 예외가 발생한다 + +#### 포메이션(마, 상) 옵션 입력 +- [ ] 포메이션 입력이 1, 2, 3, 4 중 하나가 아니면 예외가 발생한다 + +#### 위치 입력 +- [ ] 위치 입력 포맷이 `(x, y)` 형태가 아니면 예외가 발생한다 +- [ ] 위치 입력 포맷 내의 좌표에 숫자가 아닌 값이 포함되면 예외가 발생한다 + +## 구조 및 설계 + +- [ ] 이동 방향(Direction) 분리: 상, 하, 좌, 우 등 x/y 증감값을 갖는 Enum 구현 +- [ ] 이동 전략(Strategy) 분리: `OneStepStrategy`, `SlideStrategy`, `JumpSlideStrategy`, `SequenceStrategy` 구현 및 각 기물에 주입 +- [ ] 플라이웨이트 팩토리(PieceFactory): 위치 상태가 없는 불변 기물 객체를 싱글톤/캐싱하여 반환 +- [ ] 보드 캡슐화(BoardReader): 기물이 보드의 맵 자료구조에 직접 접근하지 못하도록 읽기 전용 인터페이스 도입 +- [ ] 불변 맵 복사: 보드 이동 시 `Map.copyOf()`를 사용하여 불변 맵 반환 --- @@ -180,6 +167,9 @@ ``` 4. 왕이 잡히면 게임 종료 + ``` + 제이콥(이/가) 승리했습니다. + ``` --- @@ -311,3 +301,48 @@ > (기준) 확장성 판단 기준: 새로운 기물 추가나 규칙 변경 시 기존 코드 수정 범위가 작아야 한다. > > (금지) 이번 미션에서 새 기물 추가를 위해 기존 분기문을 계속 늘리는 방식으로 확장하지 않는다 + + +### 👨‍💻 고민과 결정 (나는 왜 이렇게 구현할까?) + +**1. 기물과 보드의 협력 설계: 내부 자료구조 노출 방지와 책임 할당** + +- **고민:** 기물이 이동 가능 위치를 찾으려면 보드의 상태(빈칸, 아군/적군 위치)를 알아야 한다. 기존처럼 `Map`를 기물에게 직접 넘기면 보드의 내부 구현이 노출되고 캡슐화가 깨진다. 반대로, 기물이 보드 상태를 아예 모른 채 '이동 경로(Route)' 데이터만 반환하고 외부 심판(Validator)이 이를 판정하게 만드는 것이 맞을까? + +- **결정:** 보드 내부 구조를 추상화한 `BoardReader` 인터페이스를 기물에게 주입하는 방식을 선택했다. + +- **근거:** 첫째, 기물이 `Map`이라는 보드의 세부 구현에 강하게 의존하는 구조를 탈피하고 싶었다. 둘째, 외부 심판(Validator) 방식을 도입하기에는 마(馬), 상(象)처럼 기물마다 확인해야 할 경유지(멱)의 개수가 다르고 포(包)처럼 예외적인 규칙도 많다. 이 모든 경우의 수를 외부 심판 하나에 위임하면 심판 객체의 책임이 너무 무거워지고 비대해진다. `BoardReader`를 도입하면 보드의 실제 자료구조는 완벽히 숨기면서도, 각 기물이 자신의 고유한 이동 규칙을 객체지향적으로 응집도 있게 캡슐화할 수 있다 + +**2. 상태 변경과 불변성: `move` 시 가변 Map 갱신 vs 새로운 Board 반환** + +- **고민:** 기물이 이동할 때 기존 `Board` 내부의 Map을 `put/remove`로 갱신하는 것이 직관적이다. 하지만 상태가 가변적이면 사이드 이펙트가 우려된다. + +- **결정:** `Map.copyOf()`를 활용하여 기존 상태와 연결고리가 끊어진 불변 맵을 만들고, 이를 가진 새로운 Board 객체를 반환하도록 구현한다. + +- **근거:** 가변 상태를 사용할 경우 부수효과(Side-effect)로 인해 예상치 못한 곳에서 데이터가 변경되는 버그가 발생하거나, 동시성 문제까지 신경 써야 하는 복잡성이 생긴다. 상태가 변할 때마다 기존 상태를 수정하는 대신 새로운 불변 상태를 생성하여 반환하도록 설계하면, 동작의 예측 가능성이 높아지고 이런 부작용에 대한 우려를 원천적으로 차단할 수 있다. + +**3. 기물 객체의 라이프사이클: 매번 `new` 생성 vs 기물 인스턴스 캐싱 재사용** + +- **고민:** 게임 초기화 시 32개의 기물을 배치할 때, `new Horse(Side.CHO)` 형태로 매번 인스턴스를 생성하는 것이 자원 낭비는 아닐까? + +- **결정:** `PieceFactory`를 도입하여 기물 인스턴스를 캐싱하고 재사용한다. + +- **근거:** 팀 규칙에 따라 기물은 자신의 '위치(Position)'를 상태로 갖지 않는 완벽한 불변 객체다. 즉, 보드판 위에 있는 2개의 '초나라 마'는 메모리 상에서 완전히 동일한 상태를 갖는다. 이를 싱글톤처럼 1개만 생성하여 공유함으로써 메모리를 절약한다. + + +**4. 다채로운 이동 규칙의 중복 제거: 하드코딩 vs 전략(Strategy) 패턴** + +- **고민:** 기물 내부에 상, 하, 좌, 우 방향에 따라 `if`문과 `while`문이 산재해 있어 코드 중복이 매우 심하다. 이 반복적이고 고정적인 로직들을 어떻게 관리해야 재사용성과 응집도를 높일 수 있을까? + +- **결정:** 상, 하, 좌, 우 등의 좌표 증감값을 `Direction` Enum으로 도출하고, 고정적인 공통 탐색 알고리즘을 `OneStepStrategy`, `SlideStrategy`, `JumpSlideStrategy`, `SequenceStrategy`로 추상화하여 기물에 주입한다. + +- **근거:** 상하좌우 방향마다 고정되고 반복적으로 발생하는 탐색 로직(`if`, `while`)을 다형성으로 추상화하면 코드의 재사용성이 크게 향상된다. 기물은 '자신이 갈 수 있는 방향'이라는 순수 도메인 지식만 가지고, 실제 보드를 탐색하는 복잡한 절차는 전략 객체가 담당하게 역할을 분리함으로써, 규칙 변경이나 확장 시 해당 전략만 수정하면 되는 높은 응집도를 확보할 수 있다. + + +**5. 다형성의 활용: 상태 패턴(State) vs 전략 패턴(Strategy)의 분리 적용** + +- **고민:** `if/else` 분기문을 제거하고 다형성을 활용해야 하는 상황이 많다. 기물마다 이동하는 규칙이 다르고, 플레이어의 턴(현재 턴, 대기 턴) 상태도 다르다. 이들을 구조가 비슷하다는 이유로 모두 동일한 디자인 패턴으로 묶어서 처리하는 것이 맞을까? + +- **결정:** 런타임에 빈번하게 교체되는 플레이어의 턴 관리에는 상태 패턴(State Pattern)을 적용하고, 객체 생성 시 한 번 정해지면 변하지 않는 기물의 이동 규칙에는 전략 패턴(Strategy Pattern)을 적용하여 용도를 분리했다. + +- **근거:** 두 패턴은 클래스 다이어그램 구조가 거의 동일하지만 설계의 '의도(Intent)'가 다르다. 턴 상태(`ActiveTurn`, `InactiveTurn`)는 게임 진행에 따라 매 턴마다 동적으로 상태가 교체되며 스스로 책임을 넘기는 행위가 중요하므로 상태 패턴이 적합하다. 반면, 기물의 이동 규칙(`OneStepStrategy`, `SlideStrategy`)은 기물 생성 시 알고리즘이 한 번 주입되면 게임이 끝날 때까지 변경될 일이 없으므로 전략 패턴이 부합한다. '상태 변경의 빈도'와 '의도'를 기준으로 패턴을 다르게 적용하여 객체지향적 설계의 목적을 코드에 명확히 드러냈다. From 50546c539a9ff1e0a83d41e643f67cd4331a0eb6 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 01:01:08 +0900 Subject: [PATCH 43/92] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=B0=8F=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 28 ++++++++++--------- ...ialBoardFactory.java => BoardFactory.java} | 4 +-- src/main/java/domain/Cannon.java | 8 +++--- src/main/java/domain/Chariot.java | 8 +++--- src/main/java/domain/Elephant.java | 16 +++++------ src/main/java/domain/Formation.java | 18 ++++++------ .../{Selection.java => FormationCommand.java} | 8 +++--- src/main/java/domain/GameManager.java | 8 +++--- src/main/java/domain/General.java | 8 +++--- src/main/java/domain/Guard.java | 8 +++--- src/main/java/domain/Horse.java | 16 +++++------ src/main/java/domain/Piece.java | 4 +-- src/main/java/domain/Side.java | 2 +- src/main/java/domain/Soldier.java | 12 ++++---- src/main/java/view/InputView.java | 25 +++++++---------- src/test/java/domain/BoardTest.java | 2 +- .../java/domain/FormationCommandTest.java | 26 +++++++++++++++++ src/test/java/domain/FormationTest.java | 8 +++--- src/test/java/domain/GameTest.java | 6 ++-- src/test/java/domain/InputParserTest.java | 8 ++++-- src/test/java/domain/NameTest.java | 16 +++++++++++ src/test/java/domain/PieceTest.java | 25 ++--------------- src/test/java/domain/PlayerTest.java | 21 +++++++------- src/test/java/domain/PlayersTest.java | 14 ++++++++++ src/test/java/domain/PositionTest.java | 16 +++++------ src/test/java/domain/SelectionTest.java | 26 ----------------- 26 files changed, 176 insertions(+), 165 deletions(-) rename src/main/java/domain/{InitialBoardFactory.java => BoardFactory.java} (96%) rename src/main/java/domain/{Selection.java => FormationCommand.java} (65%) create mode 100644 src/test/java/domain/FormationCommandTest.java create mode 100644 src/test/java/domain/PlayersTest.java delete mode 100644 src/test/java/domain/SelectionTest.java diff --git a/README.md b/README.md index 8cb2e6757b..b6f7576fa1 100644 --- a/README.md +++ b/README.md @@ -32,22 +32,22 @@ ### 1.1단계: 보드 및 기본 도메인 초기화 #### 위치 (Position) -- [ ] x 좌표가 0 미만이거나 8 초과이면 예외가 발생한다 -- [ ] y 좌표가 0 미만이거나 9 초과이면 예외가 발생한다 -- [ ] 올바른 x, y 좌표가 주어지면 객체가 정상적으로 생성된다 +- [x] x 좌표가 0 미만이거나 8 초과이면 예외가 발생한다 +- [x] y 좌표가 0 미만이거나 9 초과이면 예외가 발생한다 +- [x] 올바른 x, y 좌표가 주어지면 객체가 정상적으로 생성된다 #### 진영 (Side) -- [ ] 진영은 초와 한만 가진다 +- [x] 진영은 초와 한만 가진다 #### 플레이어 (Player) -- [ ] 플레이어는 상대방의 기물을 선택하면 예외가 발생한다 -- [ ] 턴 상태를 토글하면 현재 턴 여부가 반전된다 +- [x] 턴 상태를 토글하면 현재 턴 여부가 반전된다 +- [x] 플레이어는 상대방의 기물을 선택하면 예외가 발생한다 #### 플레이어들 (Players) -- [ ] 초기 플레이어 생성 시 두 플레이어의 이름이 같으면 예외가 발생한다 +- [x] 초기 플레이어 생성 시 두 플레이어의 이름이 같으면 예외가 발생한다 #### 기물 공통 (Piece) -- [ ] 주어진 진영과 자신의 진영이 같은지 올바르게 판별한다 +- [x] 주어진 진영과 자신의 진영이 같은지 올바르게 판별한다 #### 장기판 초기화 (BoardFactory / Board) - [ ] 선택한 포메이션에 맞게 32개의 기물이 초기 위치에 정확히 배치된다 @@ -105,15 +105,17 @@ ### 1.3단계: 사용자 입력 및 파서 검증 #### 플레이어 이름 입력 -- [ ] 이름이 2글자 미만이거나 5글자를 초과하면 예외가 발생한다 -- [ ] 이름에 빈 값이나 공백만 입력되면 예외가 발생한다 +- [x] 이름이 2글자 미만이거나 5글자를 초과하면 예외가 발생한다 +- [x] 이름에 빈 값이나 공백만 입력되면 예외가 발생한다 #### 포메이션(마, 상) 옵션 입력 -- [ ] 포메이션 입력이 1, 2, 3, 4 중 하나가 아니면 예외가 발생한다 +- [x] 포메이션 입력이 1, 2, 3, 4 일 때 정상 동작한다 +- [x] 포메이션 입력이 1, 2, 3, 4 중 하나가 아니면 예외가 발생한다 #### 위치 입력 -- [ ] 위치 입력 포맷이 `(x, y)` 형태가 아니면 예외가 발생한다 -- [ ] 위치 입력 포맷 내의 좌표에 숫자가 아닌 값이 포함되면 예외가 발생한다 +- [x] 올바른 좌표 형식이 입력되는 경우 정상 동작한다 +- [x] 위치 입력 포맷이 `(x, y)` 형태가 아니면 예외가 발생한다 +- [x] 위치 입력 포맷 내의 좌표에 숫자가 아닌 값이 포함되면 예외가 발생한다 ## 구조 및 설계 diff --git a/src/main/java/domain/InitialBoardFactory.java b/src/main/java/domain/BoardFactory.java similarity index 96% rename from src/main/java/domain/InitialBoardFactory.java rename to src/main/java/domain/BoardFactory.java index 84602a575a..bcb496e1c4 100644 --- a/src/main/java/domain/InitialBoardFactory.java +++ b/src/main/java/domain/BoardFactory.java @@ -3,9 +3,9 @@ import java.util.HashMap; import java.util.Map; -public class InitialBoardFactory { +public class BoardFactory { - private InitialBoardFactory() { + private BoardFactory() { } public static Board create(Formation choFormation, Formation hanFormation) { diff --git a/src/main/java/domain/Cannon.java b/src/main/java/domain/Cannon.java index 67530c5854..639299392e 100644 --- a/src/main/java/domain/Cannon.java +++ b/src/main/java/domain/Cannon.java @@ -54,7 +54,7 @@ public List getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map pieces, Side side) { List orders = List.of( @@ -17,7 +17,7 @@ public void placeElephant(Map pieces, Side side) { place(pieces, side, orders); } }, - RIGHT_ELEPHANT(Selection.SECOND) { + RIGHT_ELEPHANT(FormationCommand.SECOND) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( @@ -29,7 +29,7 @@ public void placeElephant(Map pieces, Side side) { place(pieces, side, orders); } }, - OUTER_ELEPHANT(Selection.THIRD) { + OUTER_ELEPHANT(FormationCommand.THIRD) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( @@ -41,7 +41,7 @@ public void placeElephant(Map pieces, Side side) { place(pieces, side, orders); } }, - INNER_ELEPHANT(Selection.FOURTH) { + INNER_ELEPHANT(FormationCommand.FOURTH) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( @@ -55,15 +55,15 @@ public void placeElephant(Map pieces, Side side) { }, ; - private final Selection selection; + private final FormationCommand formationCommand; - Formation(Selection selection) { - this.selection = selection; + Formation(FormationCommand formationCommand) { + this.formationCommand = formationCommand; } - public static Formation from(Selection selection ) { + public static Formation from(FormationCommand formationCommand) { return Arrays.stream(values()) - .filter(formation -> formation.selection.equals(selection)) + .filter(formation -> formation.formationCommand.equals(formationCommand)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("올바른 배치가 아닙니다.")); } diff --git a/src/main/java/domain/Selection.java b/src/main/java/domain/FormationCommand.java similarity index 65% rename from src/main/java/domain/Selection.java rename to src/main/java/domain/FormationCommand.java index c6e8c657d6..6a8520eaf2 100644 --- a/src/main/java/domain/Selection.java +++ b/src/main/java/domain/FormationCommand.java @@ -2,7 +2,7 @@ import java.util.Arrays; -public enum Selection { +public enum FormationCommand { FIRST("1"), SECOND("2"), THIRD("3"), @@ -11,13 +11,13 @@ public enum Selection { private final String input; - Selection(String input) { + FormationCommand(String input) { this.input = input; } - public static Selection from(String input) { + public static FormationCommand from(String input) { return Arrays.stream(values()) - .filter(selection -> selection.input.equals(input.strip())) + .filter(command -> command.input.equals(input.strip())) .findFirst() .orElseThrow(() -> new IllegalArgumentException("올바른 입력이 아닙니다.")); } diff --git a/src/main/java/domain/GameManager.java b/src/main/java/domain/GameManager.java index 067c680aad..b55be7ed28 100644 --- a/src/main/java/domain/GameManager.java +++ b/src/main/java/domain/GameManager.java @@ -29,7 +29,7 @@ private Game initializeGame() { Name hanName = getPlayerName(Side.HAN); return Players.createInitial(choName, hanName); }); - Board board = InitialBoardFactory.create(getFormation(Side.CHO), getFormation(Side.HAN)); + Board board = BoardFactory.create(getFormation(Side.CHO), getFormation(Side.HAN)); return new Game(board, players); } @@ -38,13 +38,13 @@ private Name getPlayerName(Side side) { } private Formation getFormation(Side side) { - return retry(() -> Formation.from(Selection.from(inputView.readFormation(side)))); + return retry(() -> Formation.from(FormationCommand.from(inputView.readFormation(side)))); } private void playTurn(Game game) { Position from = selectPiecePosition(game); retry(() -> { - Position to = InputParser.parsePosition(inputView.readDestination()); + Position to = InputParser.parsePosition(inputView.readTargetPosition()); game.move(from, to); }); outputView.printBoard(game.getBoard()); @@ -52,7 +52,7 @@ private void playTurn(Game game) { private Position selectPiecePosition(Game game) { return retry(() -> { - Position position = InputParser.parsePosition(inputView.readPlayerPieceSelection(game.getCurrentSide())); + Position position = InputParser.parsePosition(inputView.readSourcePosition(game.getCurrentSide())); List destinations = game.selectSource(position); outputView.printDestinations(destinations); return position; diff --git a/src/main/java/domain/General.java b/src/main/java/domain/General.java index 256e158128..e0be28df96 100644 --- a/src/main/java/domain/General.java +++ b/src/main/java/domain/General.java @@ -40,19 +40,19 @@ public List getPossibleDestinations(Position position, Map destinations = new ArrayList<>(); // 상 - if (position.upPossible() && (!board.containsKey(position.up()) || !board.get(position.up()).isSameSideAs(side))) { + if (position.upPossible() && (!board.containsKey(position.up()) || !board.get(position.up()).isAlly(side))) { destinations.add(position.up()); } // 하 - if (position.downPossible() && (!board.containsKey(position.down()) || !board.get(position.down()).isSameSideAs(side))) { + if (position.downPossible() && (!board.containsKey(position.down()) || !board.get(position.down()).isAlly(side))) { destinations.add(position.down()); } // 좌 - if (position.leftPossible() && (!board.containsKey(position.left()) || !board.get(position.left()).isSameSideAs(side))) { + if (position.leftPossible() && (!board.containsKey(position.left()) || !board.get(position.left()).isAlly(side))) { destinations.add(position.left()); } // 우 - if (position.rightPossible() && (!board.containsKey(position.right()) || !board.get(position.right()).isSameSideAs(side))) { + if (position.rightPossible() && (!board.containsKey(position.right()) || !board.get(position.right()).isAlly(side))) { destinations.add(position.right()); } diff --git a/src/main/java/domain/Guard.java b/src/main/java/domain/Guard.java index 42108b97ab..bf41c3fa3e 100644 --- a/src/main/java/domain/Guard.java +++ b/src/main/java/domain/Guard.java @@ -40,19 +40,19 @@ public List getPossibleDestinations(Position position, Map destinations = new ArrayList<>(); // 상 - if (position.upPossible() && (!board.containsKey(position.up()) || !board.get(position.up()).isSameSideAs(side))) { + if (position.upPossible() && (!board.containsKey(position.up()) || !board.get(position.up()).isAlly(side))) { destinations.add(position.up()); } // 하 - if (position.downPossible() && (!board.containsKey(position.down()) || !board.get(position.down()).isSameSideAs(side))) { + if (position.downPossible() && (!board.containsKey(position.down()) || !board.get(position.down()).isAlly(side))) { destinations.add(position.down()); } // 좌 - if (position.leftPossible() && (!board.containsKey(position.left()) || !board.get(position.left()).isSameSideAs(side))) { + if (position.leftPossible() && (!board.containsKey(position.left()) || !board.get(position.left()).isAlly(side))) { destinations.add(position.left()); } // 우 - if (position.rightPossible() && (!board.containsKey(position.right()) || !board.get(position.right()).isSameSideAs(side))) { + if (position.rightPossible() && (!board.containsKey(position.right()) || !board.get(position.right()).isAlly(side))) { destinations.add(position.right()); } diff --git a/src/main/java/domain/Horse.java b/src/main/java/domain/Horse.java index d634497a91..a325ee9972 100644 --- a/src/main/java/domain/Horse.java +++ b/src/main/java/domain/Horse.java @@ -71,10 +71,10 @@ public List getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map getPossibleDestinations(Position position, Map formationX() { public abstract int soldierY(); public abstract List formationX(); - public boolean isSameAs(Side other) { + public boolean isAlly(Side other) { return this.equals(other); } diff --git a/src/main/java/domain/Soldier.java b/src/main/java/domain/Soldier.java index 24d2db5b46..a507bf0b48 100644 --- a/src/main/java/domain/Soldier.java +++ b/src/main/java/domain/Soldier.java @@ -52,30 +52,30 @@ public List getPossibleDestinations(Position position, Map actual = board.getBoard(); assertThat(actual).hasSize(32); diff --git a/src/test/java/domain/FormationCommandTest.java b/src/test/java/domain/FormationCommandTest.java new file mode 100644 index 0000000000..b1c52e1149 --- /dev/null +++ b/src/test/java/domain/FormationCommandTest.java @@ -0,0 +1,26 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class FormationCommandTest { + + @Test + void 포메이션_입력이_1_2_3_4_일때_정상_동작한다() { + assertThat(FormationCommand.from("1")).isEqualTo(FormationCommand.FIRST); + assertThat(FormationCommand.from(" 2 ")).isEqualTo(FormationCommand.SECOND); + assertThat(FormationCommand.from("3")).isEqualTo(FormationCommand.THIRD); + assertThat(FormationCommand.from("4")).isEqualTo(FormationCommand.FOURTH); + } + + @Test + void 포메이션_입력이_1_2_3_4_중_하나가_아니면_예외가_발생한다() { + assertThatThrownBy(() -> FormationCommand.from("0")) + .isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy(() -> FormationCommand.from("5")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/domain/FormationTest.java b/src/test/java/domain/FormationTest.java index a34e40e270..c07c31919b 100644 --- a/src/test/java/domain/FormationTest.java +++ b/src/test/java/domain/FormationTest.java @@ -10,10 +10,10 @@ class FormationTest { @Test void 선택값으로_포메이션을_찾는다() { - assertThat(Formation.from(Selection.FIRST)).isEqualTo(Formation.LEFT_ELEPHANT); - assertThat(Formation.from(Selection.SECOND)).isEqualTo(Formation.RIGHT_ELEPHANT); - assertThat(Formation.from(Selection.THIRD)).isEqualTo(Formation.OUTER_ELEPHANT); - assertThat(Formation.from(Selection.FOURTH)).isEqualTo(Formation.INNER_ELEPHANT); + assertThat(Formation.from(FormationCommand.FIRST)).isEqualTo(Formation.LEFT_ELEPHANT); + assertThat(Formation.from(FormationCommand.SECOND)).isEqualTo(Formation.RIGHT_ELEPHANT); + assertThat(Formation.from(FormationCommand.THIRD)).isEqualTo(Formation.OUTER_ELEPHANT); + assertThat(Formation.from(FormationCommand.FOURTH)).isEqualTo(Formation.INNER_ELEPHANT); } @Test diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index 467ce81712..d1dd7cdd65 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -14,9 +14,9 @@ class GameTest { @BeforeEach void setUp() { players = Players.createInitial(new Name("cho"), new Name("han")); - Formation choFormation = Formation.from(Selection.from("1")); - Formation hanFormation = Formation.from(Selection.from("1")); - Board board = InitialBoardFactory.create(choFormation, hanFormation); + Formation choFormation = Formation.from(FormationCommand.from("1")); + Formation hanFormation = Formation.from(FormationCommand.from("1")); + Board board = BoardFactory.create(choFormation, hanFormation); game = new Game(board, players); } diff --git a/src/test/java/domain/InputParserTest.java b/src/test/java/domain/InputParserTest.java index 202913a324..0bd87d0741 100644 --- a/src/test/java/domain/InputParserTest.java +++ b/src/test/java/domain/InputParserTest.java @@ -4,20 +4,22 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; class InputParserTest { @ParameterizedTest @ValueSource(strings = {"(0,3)", "(3, 9)"}) - void 올바른_좌표가_입력되는_경우(String input) { + void 올바른_좌표_형식이_입력되는_경우_정상_동작한다(String input) { assertThatCode(() -> InputParser.parsePosition(input)) .doesNotThrowAnyException(); } @ParameterizedTest - @ValueSource(strings = {"", "0,3", "a,b"}) - void 올바른_좌표가_입력되지_않는_경우(String input) { + @NullAndEmptySource + @ValueSource(strings = {" ", "0,3", "a,b", "(a,b)", "()", "(,)"}) + void 위치_입력_포맷이_올바른_형태가_아니면_예외가_발생한다(String input) { assertThatThrownBy(() -> InputParser.parsePosition(input)) .isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/domain/NameTest.java b/src/test/java/domain/NameTest.java index ed2cb08fcc..d967f3bfa0 100644 --- a/src/test/java/domain/NameTest.java +++ b/src/test/java/domain/NameTest.java @@ -1,5 +1,6 @@ package domain; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.params.ParameterizedTest; @@ -15,4 +16,19 @@ class NameTest { assertThatThrownBy(() -> new Name(input)) .isInstanceOf(IllegalArgumentException.class); } + + @ParameterizedTest + @ValueSource(strings = {"12", "12345"}) + void 이름이_2글자_이상이거나_5글자를_이하이면_정상_생성된다(String input) { + assertThatCode(() -> new Name(input)) + .doesNotThrowAnyException(); + + + } + @ParameterizedTest + @ValueSource(strings = {"1", "123456"}) + void 이름이_2글자_미만이거나_5글자를_초과하면_예외가_발생한다(String input) { + assertThatThrownBy(() -> new Name(input)) + .isInstanceOf(IllegalArgumentException.class); + } } diff --git a/src/test/java/domain/PieceTest.java b/src/test/java/domain/PieceTest.java index 1e48e04c82..ee3e397642 100644 --- a/src/test/java/domain/PieceTest.java +++ b/src/test/java/domain/PieceTest.java @@ -3,8 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; -import java.util.List; -import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -14,10 +12,10 @@ class PieceTest { @ParameterizedTest @MethodSource("provideSideCase") - void 같은_진영_여부를_판단한다(Side side, Side ohter, boolean expected) { - Piece piece = new SubPiece(side); + void 주어진_진영과_자신의_진영이_같은지_올바르게_판별한다(Side side, Side ohter, boolean expected) { + Piece piece = new Soldier(side); - boolean actual = piece.isSameSideAs(ohter); + boolean actual = piece.isAlly(ohter); assertThat(actual).isEqualTo(expected); } @@ -30,21 +28,4 @@ static Stream provideSideCase() { arguments(Side.HAN, Side.CHO, false) ); } - - static class SubPiece extends Piece { - - public SubPiece(Side side) { - super(side); - } - - @Override - public List getAllPosition(Position position) { - return List.of(); - } - - @Override - public List getPossibleDestinations(Position position, Map map) { - return List.of(); - } - } } diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 46547c28be..2e8d838170 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -1,22 +1,14 @@ package domain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; class PlayerTest { @Test - void 현재_턴_상태를_반환한다() { - Player current = new Player(new Name("cho"), Side.CHO, new ActiveTurn()); - Player notCurrent = new Player(new Name("han"),Side.HAN, new InactiveTurn()); - - assertThat(current.isCurrentTurn()).isTrue(); - assertThat(notCurrent.isCurrentTurn()).isFalse(); - } - - @Test - void 턴_상태를_토글한다() { + void 턴_상태를_토글하면_현재_턴_여부가_반전된다() { Player player = new Player(new Name("cho"), Side.CHO, new ActiveTurn()); player.toggleTurn(); @@ -25,4 +17,13 @@ class PlayerTest { player.toggleTurn(); assertThat(player.isCurrentTurn()).isTrue(); } + + @Test + void 플레이어는_상대방의_기물을_선택하면_예외가_발생한다() { + Player choPlayer = new Player(new Name("cho"), Side.CHO, new ActiveTurn()); + Piece hanPiece = new Soldier(Side.HAN); + + assertThatThrownBy(() -> choPlayer.validateAlly(hanPiece)) + .isInstanceOf(IllegalArgumentException.class); + } } diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java new file mode 100644 index 0000000000..4838176480 --- /dev/null +++ b/src/test/java/domain/PlayersTest.java @@ -0,0 +1,14 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class PlayersTest { + + @Test + void 초기_플레이어_생성_시_두_플레이어의_이름이_같으면_예외가_발생한다() { + assertThatThrownBy(() -> Players.createInitial(new Name("whale"), new Name("whale"))) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/domain/PositionTest.java b/src/test/java/domain/PositionTest.java index 4fd37e5bc0..a6a42027c1 100644 --- a/src/test/java/domain/PositionTest.java +++ b/src/test/java/domain/PositionTest.java @@ -10,35 +10,35 @@ class PositionTest { @ParameterizedTest @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8}) - void x의_값은_0_이상_8_이하여야_한다(int value) { + void x의_값은_0_이상_8_이하여야_한다(int validX) { int y = 0; - assertThatCode(() -> new Position(value, y)) + assertThatCode(() -> new Position(validX, y)) .doesNotThrowAnyException(); } @ParameterizedTest @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) - void y의_값은_0_이상_9_이하여야_한다(int value) { + void y의_값은_0_이상_9_이하여야_한다(int validY) { int x = 0; - assertThatCode(() -> new Position(x, value)) + assertThatCode(() -> new Position(x, validY)) .doesNotThrowAnyException(); } @ParameterizedTest @ValueSource(ints = {-1, -2, 9, 10, 100}) - void x의_값이_0_이상_8_이하가_아니면_예외가_발생한다(int value) { + void x_좌표가_0_미만이거나_8_초과이면_예외가_발생한다(int invalidX) { int y = 0; - assertThatThrownBy(() -> new Position(value, y)) + assertThatThrownBy(() -> new Position(invalidX, y)) .isInstanceOf(IllegalArgumentException.class); } @ParameterizedTest @ValueSource(ints = {-1, -2, 10, 11, 100}) - void y의_값이_0_이상_9_이하가_아니면_예외가_발생한다(int value) { + void y_좌표가_0_미만이거나_9_초과이면_예외가_발생한다(int invalidY) { int x = 0; - assertThatThrownBy(() -> new Position(x, value)) + assertThatThrownBy(() -> new Position(x, invalidY)) .isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/domain/SelectionTest.java b/src/test/java/domain/SelectionTest.java deleted file mode 100644 index 50b681c12a..0000000000 --- a/src/test/java/domain/SelectionTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package domain; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.api.Test; - -class SelectionTest { - - @Test - void 문자열_입력을_Selection으로_변환한다() { - assertThat(Selection.from("1")).isEqualTo(Selection.FIRST); - assertThat(Selection.from(" 2 ")).isEqualTo(Selection.SECOND); - assertThat(Selection.from("3")).isEqualTo(Selection.THIRD); - assertThat(Selection.from("4")).isEqualTo(Selection.FOURTH); - } - - @Test - void 올바르지_않은_입력은_예외가_발생한다() { - assertThatThrownBy(() -> Selection.from("0")) - .isInstanceOf(IllegalArgumentException.class); - - assertThatThrownBy(() -> Selection.from("5")) - .isInstanceOf(IllegalArgumentException.class); - } -} From b4df2ef7efe06521c5558aa710e9af8ee49e95fa Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 05:55:23 +0900 Subject: [PATCH 44/92] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC,=20=EC=9E=A5?= =?UTF-8?q?=EA=B8=B0=ED=8C=90=20=EC=83=9D=EC=84=B1=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= =?UTF-8?q?,=20=EC=9D=B4=EB=8F=99=20=EC=A0=84=EB=9E=B5=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=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/domain/BoardFactory.java | 19 ++++---- src/main/java/domain/BoardReader.java | 7 +++ src/main/java/domain/ContinuousStrategy.java | 35 ++++++++++++++ src/main/java/domain/Formation.java | 34 +++++++------- src/main/java/domain/MovementStrategy.java | 7 +++ src/main/java/domain/OneStepStrategy.java | 23 +++++++++ src/main/java/domain/Path.java | 33 +++++++++++++ src/main/java/domain/Piece.java | 49 +++++++++++++++++--- src/main/java/domain/PieceFactory.java | 49 ++++++++++++++++++++ src/main/java/domain/SequenceStrategy.java | 38 +++++++++++++++ 10 files changed, 260 insertions(+), 34 deletions(-) create mode 100644 src/main/java/domain/BoardReader.java create mode 100644 src/main/java/domain/ContinuousStrategy.java create mode 100644 src/main/java/domain/MovementStrategy.java create mode 100644 src/main/java/domain/OneStepStrategy.java create mode 100644 src/main/java/domain/Path.java create mode 100644 src/main/java/domain/PieceFactory.java create mode 100644 src/main/java/domain/SequenceStrategy.java diff --git a/src/main/java/domain/BoardFactory.java b/src/main/java/domain/BoardFactory.java index bcb496e1c4..91aa4174b8 100644 --- a/src/main/java/domain/BoardFactory.java +++ b/src/main/java/domain/BoardFactory.java @@ -10,13 +10,10 @@ private BoardFactory() { public static Board create(Formation choFormation, Formation hanFormation) { Map pieces = new HashMap<>(); - placeFixedPieces(pieces, Side.CHO); choFormation.placeElephant(pieces, Side.CHO); - placeFixedPieces(pieces, Side.HAN); hanFormation.placeElephant(pieces, Side.HAN); - return new Board(pieces); } @@ -29,27 +26,27 @@ private static void placeFixedPieces(Map pieces, Side side) { } private static void placeGeneral(Map pieces, Side side) { - pieces.put(new Position(4, side.generalY()), new General(side)); + pieces.put(Position.of(4, side.generalY()), PieceFactory.createGeneral(side)); } private static void placeChariots(Map pieces, Side side) { - pieces.put(new Position(0, side.baseY()), new Chariot(side)); - pieces.put(new Position(8, side.baseY()), new Chariot(side)); + pieces.put(Position.of(0, side.baseY()), PieceFactory.createChariot(side)); + pieces.put(Position.of(8, side.baseY()), PieceFactory.createChariot(side)); } private static void placeCannons(Map pieces, Side side) { - pieces.put(new Position(1, side.cannonY()), new Cannon(side)); - pieces.put(new Position(7, side.cannonY()), new Cannon(side)); + pieces.put(Position.of(1, side.cannonY()), PieceFactory.createCannon(side)); + pieces.put(Position.of(7, side.cannonY()), PieceFactory.createCannon(side)); } private static void placeGuards(Map pieces, Side side) { - pieces.put(new Position(3, side.baseY()), new Guard(side)); - pieces.put(new Position(5, side.baseY()), new Guard(side)); + pieces.put(Position.of(3, side.baseY()), PieceFactory.createGuard(side)); + pieces.put(Position.of(5, side.baseY()), PieceFactory.createGuard(side)); } private static void placeSoldiers(Map pieces, Side side) { for (int x = 0; x <= 8; x += 2) { - pieces.put(new Position(x, side.soldierY()), new Soldier(side)); + pieces.put(Position.of(x, side.soldierY()), PieceFactory.createSoldier(side)); } } } diff --git a/src/main/java/domain/BoardReader.java b/src/main/java/domain/BoardReader.java new file mode 100644 index 0000000000..b05bf99cc9 --- /dev/null +++ b/src/main/java/domain/BoardReader.java @@ -0,0 +1,7 @@ +package domain; + +public interface BoardReader { + boolean isWithinRange(Position position); + boolean isEmpty(Position position); + Piece getPiece(Position position); +} diff --git a/src/main/java/domain/ContinuousStrategy.java b/src/main/java/domain/ContinuousStrategy.java new file mode 100644 index 0000000000..4a9835d12a --- /dev/null +++ b/src/main/java/domain/ContinuousStrategy.java @@ -0,0 +1,35 @@ +package domain; + +import java.util.ArrayList; +import java.util.List; + +public class ContinuousStrategy implements MovementStrategy { + private final List directions; + + public ContinuousStrategy(List directions) { + this.directions = directions; + } + + @Override + public List generatePaths(Position current, BoardReader board) { + List paths = new ArrayList<>(); + for (Direction direction : directions) { + Path path = createPath(current, direction); + if (!path.isEmpty()) { + paths.add(path); + } + } + return paths; + } + + private Path createPath(Position current, Direction direction) { + List positions = new ArrayList<>(); + Position pos = current; + + while (pos.canMove(direction)) { + pos = pos.move(direction); + positions.add(pos); + } + return new Path(positions); + } +} diff --git a/src/main/java/domain/Formation.java b/src/main/java/domain/Formation.java index 727ad44009..ebfdab3c1a 100644 --- a/src/main/java/domain/Formation.java +++ b/src/main/java/domain/Formation.java @@ -9,10 +9,10 @@ public enum Formation { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( - new Elephant(side), - new Horse(side), - new Elephant(side), - new Horse(side) + PieceFactory.createElephant(side), + PieceFactory.createHorse(side), + PieceFactory.createElephant(side), + PieceFactory.createHorse(side) ); place(pieces, side, orders); } @@ -21,10 +21,10 @@ public void placeElephant(Map pieces, Side side) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( - new Horse(side), - new Elephant(side), - new Horse(side), - new Elephant(side) + PieceFactory.createHorse(side), + PieceFactory.createElephant(side), + PieceFactory.createHorse(side), + PieceFactory.createElephant(side) ); place(pieces, side, orders); } @@ -33,10 +33,10 @@ public void placeElephant(Map pieces, Side side) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( - new Elephant(side), - new Horse(side), - new Horse(side), - new Elephant(side) + PieceFactory.createElephant(side), + PieceFactory.createHorse(side), + PieceFactory.createHorse(side), + PieceFactory.createElephant(side) ); place(pieces, side, orders); } @@ -45,10 +45,10 @@ public void placeElephant(Map pieces, Side side) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( - new Horse(side), - new Elephant(side), - new Elephant(side), - new Horse(side) + PieceFactory.createHorse(side), + PieceFactory.createElephant(side), + PieceFactory.createElephant(side), + PieceFactory.createHorse(side) ); place(pieces, side, orders); } @@ -73,7 +73,7 @@ public static Formation from(FormationCommand formationCommand) { private static void place(Map pieces, Side side, List orders) { List a = side.formationX(); for (int i = 0; i < orders.size(); i++) { - pieces.put(new Position(a.get(i), side.baseY()), orders.get(i)); + pieces.put(Position.of(a.get(i), side.baseY()), orders.get(i)); } } } diff --git a/src/main/java/domain/MovementStrategy.java b/src/main/java/domain/MovementStrategy.java new file mode 100644 index 0000000000..d64d210ad1 --- /dev/null +++ b/src/main/java/domain/MovementStrategy.java @@ -0,0 +1,7 @@ +package domain; + +import java.util.List; + +public interface MovementStrategy { + List generatePaths(Position current, BoardReader board); +} diff --git a/src/main/java/domain/OneStepStrategy.java b/src/main/java/domain/OneStepStrategy.java new file mode 100644 index 0000000000..0fe2a4460b --- /dev/null +++ b/src/main/java/domain/OneStepStrategy.java @@ -0,0 +1,23 @@ +package domain; + +import java.util.ArrayList; +import java.util.List; + +public class OneStepStrategy implements MovementStrategy { + private final List directions; + + public OneStepStrategy(List directions) { + this.directions = directions; + } + + @Override + public List generatePaths(Position current, BoardReader board) { + List paths = new ArrayList<>(); + for (Direction direction : directions) { + if (current.canMove(direction)) { + paths.add(new Path(List.of(current.move(direction)))); + } + } + return paths; + } +} diff --git a/src/main/java/domain/Path.java b/src/main/java/domain/Path.java new file mode 100644 index 0000000000..f4aa858279 --- /dev/null +++ b/src/main/java/domain/Path.java @@ -0,0 +1,33 @@ +package domain; + +import java.util.List; + +public class Path { + private final List positions; + + public Path(List positions) { + this.positions = List.copyOf(positions); + } + + public boolean isEmpty() { + return positions.isEmpty(); + } + + public Position getDestination() { + if (isEmpty()) { + throw new IllegalStateException("경로가 존재하지 않습니다."); + } + return positions.getLast(); + } + + public List getObstacles() { + if (isEmpty()) { + return List.of(); + } + return positions.subList(0, positions.size() - 1); + } + + public List getPositions() { + return positions; + } +} diff --git a/src/main/java/domain/Piece.java b/src/main/java/domain/Piece.java index 25dcb7ff17..cd19b6e68f 100644 --- a/src/main/java/domain/Piece.java +++ b/src/main/java/domain/Piece.java @@ -1,24 +1,61 @@ package domain; +import java.util.ArrayList; import java.util.List; -import java.util.Map; public abstract class Piece { - protected final Side side; + private final Side side; + private final MovementStrategy movementStrategy; - public Piece(Side side) { + protected Piece(Side side, MovementStrategy movementStrategy) { this.side = side; + this.movementStrategy = movementStrategy; + } + + public MovablePositions findMovablePositions(Position current, BoardReader board) { + List paths = movementStrategy.generatePaths(current, board); + List validDestinations = filterValidPositions(current, paths, board); + return new MovablePositions(validDestinations); + } + + protected abstract List filterValidPositions(Position current, List paths, BoardReader board); + + protected List filterStandardPaths(List paths, BoardReader board) { + List valid = new ArrayList<>(); + for (Path path : paths) { + if (isObstaclesClear(path, board) && isValidDestination(path.getDestination(), board)) { + valid.add(path.getDestination()); + } + } + return valid; + } + + private boolean isObstaclesClear(Path path, BoardReader board) { + for (Position obstacle : path.getObstacles()) { + if (!board.isEmpty(obstacle)) { + return false; + } + } + return true; + } + + protected boolean isValidDestination(Position dest, BoardReader board) { + return board.isEmpty(dest) || !board.getPiece(dest).isAlly(side); } public boolean isAlly(Side other) { - return side.isAlly(other); + return this.side == other; } public Side getSide() { return side; } - public abstract List getAllPosition(Position position); + public boolean isGeneral() { + return false; + } - public abstract List getPossibleDestinations(Position position, Map map); + public boolean isCannon() { + return false; + } } diff --git a/src/main/java/domain/PieceFactory.java b/src/main/java/domain/PieceFactory.java new file mode 100644 index 0000000000..adc049421b --- /dev/null +++ b/src/main/java/domain/PieceFactory.java @@ -0,0 +1,49 @@ +package domain; + +import java.util.Map; + +public class PieceFactory { + private static final MovementStrategy LINEAR_ONE_STEP = new OneStepStrategy(Direction.linear()); + private static final MovementStrategy HORSE_STRATEGY = new SequenceStrategy(Direction.horseSequences()); + private static final MovementStrategy ELEPHANT_STRATEGY = new SequenceStrategy(Direction.elephantSequences()); + private static final MovementStrategy CONTINUOUS_STRATEGY = new ContinuousStrategy(Direction.linear()); + + private static final Map GENERALS = Map.of( + Side.CHO, new General(Side.CHO, LINEAR_ONE_STEP), + Side.HAN, new General(Side.HAN, LINEAR_ONE_STEP) + ); + private static final Map GUARDS = Map.of( + Side.CHO, new Guard(Side.CHO, LINEAR_ONE_STEP), + Side.HAN, new Guard(Side.HAN, LINEAR_ONE_STEP) + ); + private static final Map SOLDIERS = Map.of( + Side.CHO, new Soldier(Side.CHO, LINEAR_ONE_STEP), + Side.HAN, new Soldier(Side.HAN, LINEAR_ONE_STEP) + ); + private static final Map HORSES = Map.of( + Side.CHO, new Horse(Side.CHO, HORSE_STRATEGY), + Side.HAN, new Horse(Side.HAN, HORSE_STRATEGY) + ); + private static final Map ELEPHANTS = Map.of( + Side.CHO, new Elephant(Side.CHO, ELEPHANT_STRATEGY), + Side.HAN, new Elephant(Side.HAN, ELEPHANT_STRATEGY) + ); + private static final Map CHARIOTS = Map.of( + Side.CHO, new Chariot(Side.CHO, CONTINUOUS_STRATEGY), + Side.HAN, new Chariot(Side.HAN, CONTINUOUS_STRATEGY) + ); + private static final Map CANNONS = Map.of( + Side.CHO, new Cannon(Side.CHO, CONTINUOUS_STRATEGY), + Side.HAN, new Cannon(Side.HAN, CONTINUOUS_STRATEGY) + ); + + private PieceFactory() {} + + public static General createGeneral(Side side) { return GENERALS.get(side); } + public static Guard createGuard(Side side) { return GUARDS.get(side); } + public static Horse createHorse(Side side) { return HORSES.get(side); } + public static Elephant createElephant(Side side) { return ELEPHANTS.get(side); } + public static Chariot createChariot(Side side) { return CHARIOTS.get(side); } + public static Cannon createCannon(Side side) { return CANNONS.get(side); } + public static Soldier createSoldier(Side side) { return SOLDIERS.get(side); } +} diff --git a/src/main/java/domain/SequenceStrategy.java b/src/main/java/domain/SequenceStrategy.java new file mode 100644 index 0000000000..db0f0c3147 --- /dev/null +++ b/src/main/java/domain/SequenceStrategy.java @@ -0,0 +1,38 @@ +package domain; + +import java.util.ArrayList; +import java.util.List; + +public class SequenceStrategy implements MovementStrategy { + private final List> sequences; + + public SequenceStrategy(List> sequences) { + this.sequences = sequences; + } + + @Override + public List generatePaths(Position current, BoardReader board) { + List paths = new ArrayList<>(); + for (List sequence : sequences) { + Path path = createPath(current, sequence); + if (!path.isEmpty()) { + paths.add(path); + } + } + return paths; + } + + private Path createPath(Position current, List sequence) { + List positions = new ArrayList<>(); + Position pos = current; + + for (Direction direction : sequence) { + if (!pos.canMove(direction)) { + return new Path(List.of()); + } + pos = pos.move(direction); + positions.add(pos); + } + return new Path(positions); + } +} From 96a44ffd5dbf4cdc82812ed2d18cf24b9aa45d69 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 05:57:17 +0900 Subject: [PATCH 45/92] =?UTF-8?q?feat:=20=EB=B0=A9=ED=96=A5=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98,=20=EA=B2=8C=EC=9E=84,=20=EA=B2=8C=EC=9E=84=EB=A7=A4?= =?UTF-8?q?=EB=8B=88=EC=A0=80=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/domain/Board.java | 55 +++++----- src/main/java/domain/Direction.java | 52 +++++++++ src/main/java/domain/Game.java | 21 ++-- src/main/java/domain/GameManager.java | 2 +- src/main/java/domain/InputParser.java | 2 +- src/main/java/domain/MovablePositions.java | 21 ++++ src/main/java/domain/Position.java | 116 +++++++-------------- src/main/java/view/OutputView.java | 2 +- 8 files changed, 154 insertions(+), 117 deletions(-) create mode 100644 src/main/java/domain/Direction.java create mode 100644 src/main/java/domain/MovablePositions.java diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 63c73dfb67..8072299ca6 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -1,52 +1,59 @@ package domain; import java.util.HashMap; -import java.util.List; import java.util.Map; -public class Board { +public class Board implements BoardReader{ private final Map board; + public Board(Map board) { - this.board = board; + this.board = Map.copyOf(board); } - public Map getBoard() { - return board; + public MovablePositions findMovablePositions(Position position) { + validatePieceExists(position); + Piece piece = board.get(position); + return piece.findMovablePositions(position, this); } - public List getPossibleDestinations(Position position) { + private void validatePieceExists(Position position) { if (!board.containsKey(position)) { throw new IllegalArgumentException("기물이 존재하지 않는 위치입니다."); } - Piece piece = board.get(position); - List positions = piece.getAllPosition(position); - Map map = findPiecesAt(positions); - return piece.getPossibleDestinations(position, map); } - private Map findPiecesAt(List positions) { - Map pieces = new HashMap<>(); - - for (Position position : positions) { - if (board.containsKey(position)) { - pieces.put(position, board.get(position)); - } + public Board movePiece(Position from, Position to) { + Map nextBoardMap = new HashMap<>(this.board); + Piece movingPiece = nextBoardMap.remove(from); + if (movingPiece == null) { + throw new IllegalArgumentException("출발지에 기물이 없습니다."); } - - return pieces; - } - - public void movePiece(Position from, Position to) { - board.put(to, board.remove(from)); + nextBoardMap.put(to, movingPiece); + return new Board(nextBoardMap); } public boolean isGameOver() { return board.values().stream() - .filter(piece -> piece instanceof General) + .filter(Piece::isGeneral) .count() < 2; } + @Override + public boolean isWithinRange(Position position) { + return Position.isWithinRange(position.getX(), position.getY()); + } + + @Override + public boolean isEmpty(Position position) { + return !board.containsKey(position); + } + + @Override public Piece getPiece(Position position) { return board.get(position); } + + public Map getBoard() { + return board; + } } diff --git a/src/main/java/domain/Direction.java b/src/main/java/domain/Direction.java new file mode 100644 index 0000000000..e85573f4c4 --- /dev/null +++ b/src/main/java/domain/Direction.java @@ -0,0 +1,52 @@ +package domain; + +import java.util.List; + +public enum Direction { + N(0, 1), + S(0, -1), + E(1, 0), + W(-1, 0), + NE(1, 1), + NW(-1, 1), + SE(1, -1), + SW(-1, -1); + + private final int dx; + private final int dy; + + Direction(int dx, int dy) { + this.dx = dx; + this.dy = dy; + } + + public static List linear() { + return List.of(N, S, E, W); + } + + public static List> horseSequences() { + return List.of( + List.of(N, NW), List.of(N, NE), + List.of(S, SW), List.of(S, SE), + List.of(E, NE), List.of(E, SE), + List.of(W, NW), List.of(W, SW) + ); + } + + public static List> elephantSequences() { + return List.of( + List.of(N, NW, NW), List.of(N, NE, NE), + List.of(S, SW, SW), List.of(S, SE, SE), + List.of(E, NE, NE), List.of(E, SE, SE), + List.of(W, NW, NW), List.of(W, SW, SW) + ); + } + + public int getDx() { + return dx; + } + + public int getDy() { + return dy; + } +} diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index 450510d6ec..1d3ee309f0 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -1,10 +1,9 @@ package domain; -import java.util.List; import java.util.Map; public class Game { - private final Board board; + private Board board; private final Players players; public Game(Board board, Players players) { @@ -16,30 +15,24 @@ public Side getCurrentSide() { return players.getCurrentSide(); } - public List selectSource(Position position) { + public MovablePositions selectSource(Position position) { Piece piece = board.getPiece(position); players.getCurrentPlayer().validateAlly(piece); - return getPossibleDestinations(position); + return findMovablePositions(position); } public void move(Position from, Position to) { - validateDestinations(selectSource(from), to); + selectSource(from).validateDestinations(to); movePiece(from, to); players.switchPlayer(); } - private void validateDestinations(List positions, Position target) { - if (!positions.contains(target)) { - throw new IllegalArgumentException("선택할 수 없는 위치입니다."); - } - } - - private List getPossibleDestinations(Position position) { - return board.getPossibleDestinations(position); + private MovablePositions findMovablePositions(Position position) { + return board.findMovablePositions(position); } private void movePiece(Position from, Position to) { - board.movePiece(from, to); + this.board = board.movePiece(from, to); } public Map getBoard() { diff --git a/src/main/java/domain/GameManager.java b/src/main/java/domain/GameManager.java index b55be7ed28..4b3dcadebb 100644 --- a/src/main/java/domain/GameManager.java +++ b/src/main/java/domain/GameManager.java @@ -53,7 +53,7 @@ private void playTurn(Game game) { private Position selectPiecePosition(Game game) { return retry(() -> { Position position = InputParser.parsePosition(inputView.readSourcePosition(game.getCurrentSide())); - List destinations = game.selectSource(position); + List destinations = game.selectSource(position).getPositions(); outputView.printDestinations(destinations); return position; }); diff --git a/src/main/java/domain/InputParser.java b/src/main/java/domain/InputParser.java index 9750970201..eb1ce70e2e 100644 --- a/src/main/java/domain/InputParser.java +++ b/src/main/java/domain/InputParser.java @@ -33,6 +33,6 @@ private static Position getPosition(String input) { .map(Integer::parseInt) .toList(); - return new Position(coordinate.get(0), coordinate.get(1)); + return Position.of(coordinate.get(0), coordinate.get(1)); } } diff --git a/src/main/java/domain/MovablePositions.java b/src/main/java/domain/MovablePositions.java new file mode 100644 index 0000000000..d1f5e52f1b --- /dev/null +++ b/src/main/java/domain/MovablePositions.java @@ -0,0 +1,21 @@ +package domain; + +import java.util.List; + +public class MovablePositions { + private final List positions; + + public MovablePositions(List positions) { + this.positions = List.copyOf(positions); + } + + public List getPositions() { + return positions; + } + + public void validateDestinations(Position target) { + if (!positions.contains(target)) { + throw new IllegalArgumentException("선택할 수 없는 기물입니다."); + } + } +} diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index b9e3193c6c..774bed4f53 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -1,8 +1,12 @@ package domain; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; public class Position { + private static final Map CACHE = new HashMap<>(); + public static final int MIN = 0; public static final int MAX_X = 8; public static final int MAX_Y = 9; @@ -10,103 +14,63 @@ public class Position { private final int x; private final int y; - public Position(int x, int y) { - validate(x, y); - this.x = x; - this.y = y; - } - - private void validate(int x, int y) { - validateRange(x, MAX_X); - validateRange(y, MAX_Y); - } - - private void validateRange(int number, int max) { - if (number < MIN || number > max) { - throw new IllegalArgumentException("범위를 벗어난 좌표를 입력했습니다."); - } - } - - @Override - public boolean equals(Object object) { - if (object == null || getClass() != object.getClass()) { - return false; + static { + for (int x = MIN; x <= MAX_X; x++) { + for (int y = MIN; y <= MAX_Y; y++) { + CACHE.put(generateKey(x, y), new Position(x, y)); + } } - Position position = (Position) object; - return x == position.x && y == position.y; - } - - @Override - public int hashCode() { - return Objects.hash(x, y); - } - - @Override - public String toString() { - return "(" + x + ", " + y + ")"; } - public Position up() { - return new Position(x, y + 1); - } - - public Position down() { - return new Position(x, y - 1); - } - - public Position left() { - return new Position(x - 1, y); - } - - public Position right() { - return new Position(x + 1, y); - } - - public Position leftUp() { - return new Position(x - 1, y + 1); - } - - public Position leftDown() { - return new Position(x - 1, y - 1); + private Position(int x, int y) { + this.x = x; + this.y = y; } - public Position rightUp() { - return new Position(x + 1, y + 1); + public static Position of(int x, int y) { + validateRange(x, y); + return CACHE.get(generateKey(x, y)); } - public Position rightDown() { - return new Position(x + 1, y - 1); + private static void validateRange(int x, int y) { + if (!isWithinRange(x, y)) { + throw new IllegalArgumentException(String.format("범위를 벗어난 좌표입니다: (%d, %d)", x, y)); + } } - public boolean upPossible() { - return y + 1 <= MAX_Y; + public boolean canMove(Direction direction) { + return isWithinRange(this.x + direction.getDx(), this.y + direction.getDy()); } - public boolean downPossible() { - return y - 1 >= MIN; + public Position move(Direction direction) { + return Position.of(this.x + direction.getDx(), this.y + direction.getDy()); } - public boolean leftPossible() { - return x - 1 >= MIN; + public static boolean isWithinRange(int x, int y) { + return x >= MIN && x <= MAX_X && y >= MIN && y <= MAX_Y; } - public boolean rightPossible() { - return x + 1 <= MAX_X; + private static int generateKey(int x, int y) { + return x * 31 + y; } - public boolean leftUpPossible() { - return x - 1 >= MIN && y + 1 <= MAX_Y; - } + public int getX() { return x; } + public int getY() { return y; } - public boolean leftDownPossible() { - return x - 1 >= MIN && y - 1 >= MIN; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Position position)) return false; + return x == position.x && y == position.y; } - public boolean rightUpPossible() { - return x + 1 <= MAX_X && y + 1 <= MAX_Y; + @Override + public int hashCode() { + return Objects.hash(x, y); } - public boolean rightDownPossible() { - return x + 1 <= MAX_X && y - 1 >= MIN; + @Override + public String toString() { + return String.format("(%d, %d)", x, y); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index fa1ead05a9..5b393cbce9 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -19,7 +19,7 @@ public void printBoard(Map board) { for (int y = 9; y >= 0; y--) { System.out.print(" " + y + " "); for (int x = 0; x <= 8; x++) { - Position position = new Position(x, y); + Position position = Position.of(x, y); if (board.containsKey(position)) { printPiece(board.get(position)); System.out.print(RESET); From fd6dfac1447866c4373cd7f9c01b2fa954b13ff2 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 05:58:06 +0900 Subject: [PATCH 46/92] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EC=A0=84=EB=9E=B5=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 65 ++++----- src/main/java/domain/Cannon.java | 177 +++++++----------------- src/main/java/domain/Chariot.java | 112 ++++----------- src/main/java/domain/Elephant.java | 165 +--------------------- src/main/java/domain/General.java | 56 +------- src/main/java/domain/Guard.java | 57 +------- src/main/java/domain/Horse.java | 120 +--------------- src/main/java/domain/Soldier.java | 84 +++-------- src/test/java/domain/BoardTest.java | 117 +++++++++++----- src/test/java/domain/CannonTest.java | 101 ++++++++++---- src/test/java/domain/ChariotTest.java | 93 +++++++------ src/test/java/domain/ElephantTest.java | 80 ++++++----- src/test/java/domain/FormationTest.java | 8 +- src/test/java/domain/GameTest.java | 61 ++++---- src/test/java/domain/GeneralTest.java | 41 ++---- src/test/java/domain/GuardTest.java | 40 ++---- src/test/java/domain/HorseTest.java | 71 +++++----- src/test/java/domain/PieceTest.java | 6 +- src/test/java/domain/PlayerTest.java | 26 +++- src/test/java/domain/PositionTest.java | 8 +- src/test/java/domain/SoldierTest.java | 56 ++------ 21 files changed, 542 insertions(+), 1002 deletions(-) diff --git a/README.md b/README.md index b6f7576fa1..6b2b999977 100644 --- a/README.md +++ b/README.md @@ -50,57 +50,50 @@ - [x] 주어진 진영과 자신의 진영이 같은지 올바르게 판별한다 #### 장기판 초기화 (BoardFactory / Board) -- [ ] 선택한 포메이션에 맞게 32개의 기물이 초기 위치에 정확히 배치된다 -- [ ] 양 진영의 궁이 지정된 궁성 위치에 정상적으로 배치된다 +- [x] 선택한 포메이션에 맞게 32개의 기물이 초기 위치에 정확히 배치된다 +- [x] 양 진영의 궁이 지정된 궁성 위치에 정상적으로 배치된다 ### 1.2단계: 기물 이동 및 게임 상태 #### 궁 (General) -- [ ] 궁은 상 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) -- [ ] 궁은 목적지가 보드 범위를 벗어나면 이동할 수 없다 -- [ ] 궁은 목적지에 아군 기물이 있으면 이동할 수 없다 +- [x] 궁은 상 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) #### 사 (Guard) -- [ ] 사는 상 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) -- [ ] 사는 목적지에 아군 기물이 있으면 이동할 수 없다 +- [x] 사는 상 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) #### 졸 (Soldier) -- [ ] 초나라 졸은 상 좌 우 1칸 이동할 수 있다 (OneStepStrategy) -- [ ] 한나라 졸은 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) -- [ ] 졸은 목적지에 아군 기물이 있으면 이동할 수 없다 +- [x] 초나라 졸은 상 좌 우 1칸 이동할 수 있다 (OneStepStrategy) +- [x] 한나라 졸은 하 좌 우 1칸 이동할 수 있다 (OneStepStrategy) #### 마 (Horse) -- [ ] 마는 직선 1칸 이동 후 대각선 1칸 방향으로 이동할 수 있다 (SequenceStrategy) -- [ ] 마는 직선 1칸 경로에 장애물 기물이 존재하면 해당 방향으로 이동할 수 없다 -- [ ] 마는 목적지에 아군 기물이 있으면 이동할 수 없다 +- [x] 마는 직선 1칸 이동 후 대각선 1칸 방향으로 이동할 수 있다 (SequenceStrategy) +- [x] 마는 직선 1칸 경로에 장애물 기물이 존재하면 해당 방향으로 이동할 수 없다 #### 상 (Elephant) -- [ ] 상은 직선 1칸 이동 후 대각선 2칸 방향으로 이동할 수 있다 (SequenceStrategy) -- [ ] 상은 직선 1칸 또는 대각선 1칸 경로 중 하나라도 장애물 기물이 존재하면 이동할 수 없다 -- [ ] 상은 목적지에 아군 기물이 있으면 이동할 수 없다 +- [x] 상은 직선 1칸 이동 후 대각선 2칸 방향으로 이동할 수 있다 (SequenceStrategy) +- [x] 상은 직선 1칸 또는 대각선 1칸 경로 중 하나라도 장애물 기물이 존재하면 이동할 수 없다 #### 차 (Chariot) -- [ ] 차는 상 하 좌 우 방향으로 장애물을 만날 때까지 연속해서 이동할 수 있다 (SlideStrategy) -- [ ] 차는 이동 경로 중 아군 기물을 만나면 그 직전 위치까지만 이동할 수 있다 -- [ ] 차는 이동 경로 중 적군 기물을 만나면 해당 위치까지 이동하여 잡을 수 있다 +- [x] 차는 상 하 좌 우 방향으로 장애물을 만날 때까지 연속해서 이동할 수 있다 (ContinuousStrategy) +- [x] 차는 이동 경로 중 아군 기물을 만나면 그 직전 위치까지만 이동할 수 있다 +- [x] 차는 이동 경로 중 적군 기물을 만나면 해당 위치까지 이동하여 잡을 수 있다 #### 포 (Cannon) -- [ ] 포는 상 하 좌 우 방향으로 기물 하나를 뛰어넘어 빈칸으로 이동할 수 있다 (JumpSlideStrategy) -- [ ] 포는 이동 경로에서 포 기물을 뛰어넘을 수 없다 -- [ ] 포는 기물을 뛰어넘은 후 적군 기물을 만나면 해당 위치까지 이동하여 잡을 수 있다 -- [ ] 포는 목적지에 아군 기물이 있으면 이동할 수 없다 -- [ ] 포는 뛰어넘은 후 만난 적군 기물이 포일 경우 잡을 수 없다 +- [x] 포는 상 하 좌 우 방향으로 기물 하나를 뛰어넘어 빈칸으로 이동할 수 있다 +- [x] 포는 이동 경로에서 포 기물을 뛰어넘을 수 없다 +- [x] 포는 기물을 뛰어넘은 후 적군 기물을 만나면 해당 위치까지 이동하여 잡을 수 있다 (ContinuousStrategy) +- [x] 포는 뛰어넘은 후 만난 적군 기물이 포일 경우 잡을 수 없다 #### 장기판 이동 (Board) -- [ ] 출발지와 목적지가 주어지면 기물이 이동된 새로운 불변 보드 객체를 반환한다 -- [ ] 목적지에 적군 기물이 있으면 보드에서 해당 기물을 제거하고 이동한다 -- [ ] 기물이 존재하지 않는 빈 좌표를 출발지로 입력하면 예외가 발생한다 +- [x] 목적지에 적군 기물이 있으면 보드에서 해당 기물을 제거하고 이동한다 +- [x] 목적지에 아군 기물이 있으면 이동할 수 없다 +- [x] 기물이 존재하지 않는 빈 좌표를 출발지로 입력하면 예외가 발생한다 #### 게임 상태 및 흐름 (Game) -- [ ] 기물 이동이 완료되면 턴이 상대방 진영으로 변경된다 -- [ ] 이동할 수 있는 목적지가 전혀 없는 기물을 선택하면 예외가 발생한다 -- [ ] 선택한 기물이 이동할 수 없는 위치를 목적지로 입력하면 예외가 발생한다 -- [ ] 보드에 궁이 하나만 남게 되면 게임은 종료 상태가 된다 +- [x] 기물 이동이 완료되면 턴이 상대방 진영으로 변경된다 +- [x] 이동할 수 있는 목적지가 전혀 없는 기물을 선택하면 예외가 발생한다 +- [x] 선택한 기물이 이동할 수 없는 위치를 목적지로 입력하면 예외가 발생한다 +- [x] 보드에 궁이 하나만 남게 되면 게임은 종료 상태가 된다 ### 1.3단계: 사용자 입력 및 파서 검증 @@ -119,11 +112,11 @@ ## 구조 및 설계 -- [ ] 이동 방향(Direction) 분리: 상, 하, 좌, 우 등 x/y 증감값을 갖는 Enum 구현 -- [ ] 이동 전략(Strategy) 분리: `OneStepStrategy`, `SlideStrategy`, `JumpSlideStrategy`, `SequenceStrategy` 구현 및 각 기물에 주입 -- [ ] 플라이웨이트 팩토리(PieceFactory): 위치 상태가 없는 불변 기물 객체를 싱글톤/캐싱하여 반환 -- [ ] 보드 캡슐화(BoardReader): 기물이 보드의 맵 자료구조에 직접 접근하지 못하도록 읽기 전용 인터페이스 도입 -- [ ] 불변 맵 복사: 보드 이동 시 `Map.copyOf()`를 사용하여 불변 맵 반환 +- [x] 이동 방향(Direction) 분리: 상, 하, 좌, 우 등 x/y 증감값을 갖는 Enum 구현 +- [x] 이동 전략(Strategy) 분리: `OneStepStrategy`, `ContinuousStrategy`, `SequenceStrategy` 구현 및 각 기물에 주입 +- [x] 플라이웨이트 팩토리(PieceFactory): 위치 상태가 없는 불변 기물 객체를 싱글톤/캐싱하여 반환 +- [x] 보드 캡슐화(BoardReader): 기물이 보드의 맵 자료구조에 직접 접근하지 못하도록 읽기 전용 인터페이스 도입 +- [x] 불변 맵 복사: 보드 이동 시 `Map.copyOf()`를 사용하여 불변 맵 반환 --- diff --git a/src/main/java/domain/Cannon.java b/src/main/java/domain/Cannon.java index 639299392e..a68573244a 100644 --- a/src/main/java/domain/Cannon.java +++ b/src/main/java/domain/Cannon.java @@ -2,160 +2,81 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; public class Cannon extends Piece { - private final String name = "포"; - - public Cannon(Side side) { - super(side); + public Cannon(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override - public List getAllPosition(Position position) { - List positions = new ArrayList<>(); - // 상 - Position currentPosition = position; - while (currentPosition.upPossible()) { - currentPosition = currentPosition.up(); - positions.add(currentPosition); - } - // 하 - currentPosition = position; - while (currentPosition.downPossible()) { - currentPosition = currentPosition.down(); - positions.add(currentPosition); - } - // 좌 - currentPosition = position; - while (currentPosition.leftPossible()) { - currentPosition = currentPosition.left(); - positions.add(currentPosition); - } - // 우 - currentPosition = position; - while (currentPosition.rightPossible()) { - currentPosition = currentPosition.right(); - positions.add(currentPosition); + protected List filterValidPositions(Position current, List paths, BoardReader board) { + List valid = new ArrayList<>(); + for (Path path : paths) { + addJumpPathPositions(valid, path, board); } - - return positions; + return valid; } - @Override - public List getPossibleDestinations(Position position, Map board) { - List destinations = new ArrayList<>(); - - // 상 - Position currentPosition = position; - boolean canPut = false; - while (currentPosition.upPossible()) { - currentPosition = currentPosition.up(); - if (board.containsKey(currentPosition)) { - Piece piece = board.get(currentPosition); - if (canPut) { - if (!piece.isAlly(side) && !(piece instanceof Cannon)) { - destinations.add(currentPosition); - } - break; - } + private void addJumpPathPositions(List valid, Path path, BoardReader board) { + List positions = path.getPositions(); + int bridgeIndex = findBridgeIndex(positions, board); - if (piece instanceof Cannon) { - break; - } - - canPut = true; - continue; - } - - if (canPut) { - destinations.add(currentPosition); - } + if (isInvalidBridge(bridgeIndex, positions, board)) { + return; } - // 하 - currentPosition = position; - canPut = false; - while (currentPosition.downPossible()) { - currentPosition = currentPosition.down(); - if (board.containsKey(currentPosition)) { - Piece piece = board.get(currentPosition); - if (canPut) { - if (!piece.isAlly(side) && !(piece instanceof Cannon)) { - destinations.add(currentPosition); - } - break; - } - - if (piece instanceof Cannon) { - break; - } - canPut = true; - continue; - } + collectValidDestinations(valid, positions, bridgeIndex + 1, board); + } - if (canPut) { - destinations.add(currentPosition); + private int findBridgeIndex(List positions, BoardReader board) { + for (int i = 0; i < positions.size(); i++) { + if (!board.isEmpty(positions.get(i))) { + return i; } } - // 좌 - currentPosition = position; - canPut = false; - while (currentPosition.leftPossible()) { - currentPosition = currentPosition.left(); - if (board.containsKey(currentPosition)) { - Piece piece = board.get(currentPosition); - if (canPut) { - if (!piece.isAlly(side) && !(piece instanceof Cannon)) { - destinations.add(currentPosition); - } - break; - } - - if (piece instanceof Cannon) { - break; - } + return -1; + } - canPut = true; - continue; - } + private boolean isInvalidBridge(int index, List positions, BoardReader board) { + if (index == -1) { + return true; + } + Piece bridge = board.getPiece(positions.get(index)); + return bridge.isCannon(); + } - if (canPut) { - destinations.add(currentPosition); + private void collectValidDestinations(List valid, List positions, int startIndex, BoardReader board) { + for (int i = startIndex; i < positions.size(); i++) { + if (processPositionAfterJump(valid, positions.get(i), board)) { + break; } } - // 우 - currentPosition = position; - canPut = false; - while (currentPosition.rightPossible()) { - currentPosition = currentPosition.right(); - if (board.containsKey(currentPosition)) { - Piece piece = board.get(currentPosition); - if (canPut) { - if (!piece.isAlly(side) && !(piece instanceof Cannon)) { - destinations.add(currentPosition); - } - break; - } + } - if (piece instanceof Cannon) { - break; - } + private boolean processPositionAfterJump(List valid, Position pos, BoardReader board) { + if (board.isEmpty(pos)) { + valid.add(pos); + return false; + } - canPut = true; - continue; - } + addIfCatchable(valid, pos, board); + return true; + } - if (canPut) { - destinations.add(currentPosition); - } + private void addIfCatchable(List valid, Position pos, BoardReader board) { + Piece target = board.getPiece(pos); + if (!target.isCannon() && !target.isAlly(getSide())) { + valid.add(pos); } + } - return destinations; + @Override + public boolean isCannon() { + return true; } @Override public String toString() { - return name; + return "포"; } } diff --git a/src/main/java/domain/Chariot.java b/src/main/java/domain/Chariot.java index becb4daba2..b791e570d2 100644 --- a/src/main/java/domain/Chariot.java +++ b/src/main/java/domain/Chariot.java @@ -2,108 +2,48 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; public class Chariot extends Piece { - private final String name = "차"; - - public Chariot(Side side) { - super(side); + public Chariot(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override - public List getAllPosition(Position position) { - List positions = new ArrayList<>(); - // 상 - Position currentPosition = position; - while (currentPosition.upPossible()) { - currentPosition = currentPosition.up(); - positions.add(currentPosition); - } - // 하 - currentPosition = position; - while (currentPosition.downPossible()) { - currentPosition = currentPosition.down(); - positions.add(currentPosition); - } - // 좌 - currentPosition = position; - while (currentPosition.leftPossible()) { - currentPosition = currentPosition.left(); - positions.add(currentPosition); + protected List filterValidPositions(Position current, List paths, BoardReader board) { + List valid = new ArrayList<>(); + for (Path path : paths) { + collectPathPositions(valid, path, board); } - // 우 - currentPosition = position; - while (currentPosition.rightPossible()) { - currentPosition = currentPosition.right(); - positions.add(currentPosition); - } - - return positions; + return valid; } - @Override - public List getPossibleDestinations(Position position, Map board) { - List destinations = new ArrayList<>(); - - // 상 - Position currentPosition = position; - while (currentPosition.upPossible()) { - currentPosition = currentPosition.up(); - if (board.containsKey(currentPosition)) { - Piece piece = board.get(currentPosition); - if (!piece.isAlly(side)) { - destinations.add(currentPosition); - } - break; - } - destinations.add(currentPosition); - } - // 하 - currentPosition = position; - while (currentPosition.downPossible()) { - currentPosition = currentPosition.down(); - if (board.containsKey(currentPosition)) { - Piece piece = board.get(currentPosition); - if (!piece.isAlly(side)) { - destinations.add(currentPosition); - } + private void collectPathPositions(List valid, Path path, BoardReader board) { + for (Position pos : path.getPositions()) { + if (processPosition(valid, pos, board)) { break; } - destinations.add(currentPosition); } - // 좌 - currentPosition = position; - while (currentPosition.leftPossible()) { - currentPosition = currentPosition.left(); - if (board.containsKey(currentPosition)) { - Piece piece = board.get(currentPosition); - if (!piece.isAlly(side)) { - destinations.add(currentPosition); - } - break; - } - destinations.add(currentPosition); - } - // 우 - currentPosition = position; - while (currentPosition.rightPossible()) { - currentPosition = currentPosition.right(); - if (board.containsKey(currentPosition)) { - Piece piece = board.get(currentPosition); - if (!piece.isAlly(side)) { - destinations.add(currentPosition); - } - break; - } - destinations.add(currentPosition); + } + + private boolean processPosition(List valid, Position pos, BoardReader board) { + if (board.isEmpty(pos)) { + valid.add(pos); + return false; } - return destinations; + addIfEnemy(valid, pos, board); + return true; + } + + private void addIfEnemy(List valid, Position pos, BoardReader board) { + Piece target = board.getPiece(pos); + if (!target.isAlly(getSide())) { + valid.add(pos); + } } @Override public String toString() { - return name; + return "차"; } } diff --git a/src/main/java/domain/Elephant.java b/src/main/java/domain/Elephant.java index 8ece14038f..54df243de4 100644 --- a/src/main/java/domain/Elephant.java +++ b/src/main/java/domain/Elephant.java @@ -1,174 +1,19 @@ package domain; -import java.util.ArrayList; import java.util.List; -import java.util.Map; public class Elephant extends Piece { - private final String name = "상"; - - public Elephant(Side side) { - super(side); - } - - @Override - public List getAllPosition(Position position) { - List positions = new ArrayList<>(); - - // 상 - if (position.upPossible()) { - Position currentPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(currentPosition); - if (currentPosition.rightUpPossible()) { - Position rightUpPosition = currentPosition.rightUp(); - positions.add(rightUpPosition); - if (rightUpPosition.rightUpPossible()) { - positions.add(rightUpPosition.rightUp()); - } - } - if (currentPosition.leftUpPossible()) { - Position leftUPosition = currentPosition.leftUp(); - positions.add(leftUPosition); - if (leftUPosition.leftUpPossible()) { - positions.add(leftUPosition.leftUp()); - } - } - } - // 하 - if (position.downPossible()) { - Position currentPosition = position.down(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(currentPosition); - if (currentPosition.rightDownPossible()) { - Position rightDownPosition = currentPosition.rightDown(); - positions.add(rightDownPosition); - if (rightDownPosition.rightDownPossible()) { - positions.add(rightDownPosition.rightDown()); - } - } - if (currentPosition.leftDownPossible()) { - Position leftDownPosition = currentPosition.leftDown(); - positions.add(leftDownPosition); - if (leftDownPosition.leftDownPossible()) { - positions.add(leftDownPosition.leftDown()); - } - } - } - // 좌 - if (position.leftPossible()) { - Position currentPosition = position.left(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(currentPosition); - if (currentPosition.leftUpPossible()) { - Position leftUpPosition = currentPosition.leftUp(); - positions.add(leftUpPosition); - if (leftUpPosition.leftUpPossible()) { - positions.add(leftUpPosition.leftUp()); - } - } - if (currentPosition.leftDownPossible()) { - Position leftDownPosition = currentPosition.leftDown(); - positions.add(leftDownPosition); - if (leftDownPosition.leftDownPossible()) { - positions.add(leftDownPosition.leftDown()); - } - } - } - // 우 - if (position.rightPossible()) { - Position currentPosition = position.right(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(currentPosition); - if (currentPosition.rightUpPossible()) { - Position rightUpPosition = currentPosition.rightUp(); - positions.add(rightUpPosition); - if (rightUpPosition.rightUpPossible()) { - positions.add(rightUpPosition.rightUp()); - } - } - if (currentPosition.rightDownPossible()) { - Position rightDownPosition = currentPosition.rightDown(); - positions.add(rightDownPosition); - if (rightDownPosition.rightDownPossible()) { - positions.add(rightDownPosition.rightDown()); - } - } - } - - return positions; + public Elephant(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override - public List getPossibleDestinations(Position position, Map map) { - List destinations = new ArrayList<>(); - - // 상 - if (position.upPossible() && !map.containsKey(position.up())) { - Position currentPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - if (currentPosition.rightUpPossible() && !map.containsKey(currentPosition.rightUp())) { - Position rightUpPosition = currentPosition.rightUp(); - if (rightUpPosition.rightUpPossible() && (!map.containsKey(rightUpPosition.rightUp()) || !map.get(rightUpPosition.rightUp()).isAlly(side))) { - destinations.add(rightUpPosition.rightUp()); - } - } - if (currentPosition.leftUpPossible() && !map.containsKey(currentPosition.leftUp())) { - Position leftUpPosition = currentPosition.leftUp(); - if (leftUpPosition.leftUpPossible() && (!map.containsKey(leftUpPosition.rightUp()) || !map.get(leftUpPosition.leftUp()).isAlly(side))) { - destinations.add(leftUpPosition.leftUp()); - } - } - } - // 하 - if (position.downPossible() && !map.containsKey(position.down())) { - Position currentPosition = position.down(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - if (currentPosition.rightDownPossible() && !map.containsKey(currentPosition.rightDown())) { - Position rightDownPosition = currentPosition.rightDown(); - if (rightDownPosition.rightDownPossible() && (!map.containsKey(rightDownPosition.rightDown()) || !map.get(rightDownPosition.rightDown()).isAlly(side))) { - destinations.add(rightDownPosition.rightDown()); - } - } - if (currentPosition.leftDownPossible() && !map.containsKey(currentPosition.leftDown())) { - Position leftDownPosition = currentPosition.leftDown(); - if (leftDownPosition.leftDownPossible() && (!map.containsKey(leftDownPosition.leftDown()) || !map.get(leftDownPosition.leftDown()).isAlly(side))) { - destinations.add(leftDownPosition.leftDown()); - } - } - } - // 좌 - if (position.leftPossible() && !map.containsKey(position.left())) { - Position currentPosition = position.left(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - if (currentPosition.leftUpPossible() && !map.containsKey(currentPosition.leftUp())) { - Position leftUpPosition = currentPosition.leftUp(); - if (leftUpPosition.leftUpPossible() && (!map.containsKey(leftUpPosition.leftUp()) || !map.get(leftUpPosition.leftUp()).isAlly(side))) { - destinations.add(leftUpPosition.leftUp()); - } - } - if (currentPosition.leftDownPossible() && !map.containsKey(currentPosition.leftDown())) { - Position leftDownPosition = currentPosition.leftDown(); - if (leftDownPosition.leftDownPossible() && (!map.containsKey(leftDownPosition.leftDown()) || !map.get(leftDownPosition.leftDown()).isAlly(side))) { - destinations.add(leftDownPosition.leftDown()); - } - } - } - // 우 - if (position.rightPossible() && !map.containsKey(position.right())) { - Position currentPosition = position.right(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - if (currentPosition.rightUpPossible() && !map.containsKey(currentPosition.rightUp())) { - Position rightUpPosition = currentPosition.rightUp(); - if (rightUpPosition.rightUpPossible() && (!map.containsKey(rightUpPosition.rightUp()) || !map.get(rightUpPosition.rightUp()).isAlly(side))) { - destinations.add(rightUpPosition.rightUp()); - } - } - if (currentPosition.rightDownPossible() && !map.containsKey(currentPosition.rightDown())) { - Position rightDownPosition = currentPosition.rightDown(); - if (rightDownPosition.rightDownPossible() && (!map.containsKey(rightDownPosition.rightDown()) || !map.get(rightDownPosition.rightDown()).isAlly(side))) { - destinations.add(rightDownPosition.rightDown()); - } - } - } - - return destinations; + protected List filterValidPositions(Position current, List paths, BoardReader board) { + return filterStandardPaths(paths, board); } @Override public String toString() { - return name; + return "상"; } } diff --git a/src/main/java/domain/General.java b/src/main/java/domain/General.java index e0be28df96..5b05b40b87 100644 --- a/src/main/java/domain/General.java +++ b/src/main/java/domain/General.java @@ -1,66 +1,24 @@ package domain; -import java.util.ArrayList; import java.util.List; -import java.util.Map; public class General extends Piece { - private final String name = "궁"; - - public General(Side side) { - super(side); + public General(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override - public List getAllPosition(Position position) { - List positions = new ArrayList<>(); - - // 상 - if (position.upPossible()) { - positions.add(position.up()); - } - // 하 - if (position.downPossible()) { - positions.add(position.down()); - } - // 좌 - if (position.leftPossible()) { - positions.add(position.left()); - } - // 우 - if (position.rightPossible()) { - positions.add(position.right()); - } - - return positions; + protected List filterValidPositions(Position current, List paths, BoardReader board) { + return filterStandardPaths(paths, board); } @Override - public List getPossibleDestinations(Position position, Map board) { - List destinations = new ArrayList<>(); - - // 상 - if (position.upPossible() && (!board.containsKey(position.up()) || !board.get(position.up()).isAlly(side))) { - destinations.add(position.up()); - } - // 하 - if (position.downPossible() && (!board.containsKey(position.down()) || !board.get(position.down()).isAlly(side))) { - destinations.add(position.down()); - } - // 좌 - if (position.leftPossible() && (!board.containsKey(position.left()) || !board.get(position.left()).isAlly(side))) { - destinations.add(position.left()); - } - // 우 - if (position.rightPossible() && (!board.containsKey(position.right()) || !board.get(position.right()).isAlly(side))) { - destinations.add(position.right()); - } - - return destinations; + public boolean isGeneral() { + return true; } @Override public String toString() { - return name; + return "궁"; } } diff --git a/src/main/java/domain/Guard.java b/src/main/java/domain/Guard.java index bf41c3fa3e..e6b6902db3 100644 --- a/src/main/java/domain/Guard.java +++ b/src/main/java/domain/Guard.java @@ -1,66 +1,19 @@ package domain; -import java.util.ArrayList; import java.util.List; -import java.util.Map; public class Guard extends Piece { - private final String name = "사"; - - public Guard(Side side) { - super(side); - } - - @Override - public List getAllPosition(Position position) { - List positions = new ArrayList<>(); - - // 상 - if (position.upPossible()) { - positions.add(position.up()); - } - // 하 - if (position.downPossible()) { - positions.add(position.down()); - } - // 좌 - if (position.leftPossible()) { - positions.add(position.left()); - } - // 우 - if (position.rightPossible()) { - positions.add(position.right()); - } - - return positions; + public Guard(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override - public List getPossibleDestinations(Position position, Map board) { - List destinations = new ArrayList<>(); - - // 상 - if (position.upPossible() && (!board.containsKey(position.up()) || !board.get(position.up()).isAlly(side))) { - destinations.add(position.up()); - } - // 하 - if (position.downPossible() && (!board.containsKey(position.down()) || !board.get(position.down()).isAlly(side))) { - destinations.add(position.down()); - } - // 좌 - if (position.leftPossible() && (!board.containsKey(position.left()) || !board.get(position.left()).isAlly(side))) { - destinations.add(position.left()); - } - // 우 - if (position.rightPossible() && (!board.containsKey(position.right()) || !board.get(position.right()).isAlly(side))) { - destinations.add(position.right()); - } - - return destinations; + protected List filterValidPositions(Position current, List paths, BoardReader board) { + return filterStandardPaths(paths, board); } @Override public String toString() { - return name; + return "사"; } } diff --git a/src/main/java/domain/Horse.java b/src/main/java/domain/Horse.java index a325ee9972..a704da8dc0 100644 --- a/src/main/java/domain/Horse.java +++ b/src/main/java/domain/Horse.java @@ -1,129 +1,19 @@ package domain; -import java.util.ArrayList; import java.util.List; -import java.util.Map; public class Horse extends Piece { - private final String name = "마"; - - public Horse(Side side) { - super(side); - } - - @Override - public List getAllPosition(Position position) { - List positions = new ArrayList<>(); - - // 상 - if (position.upPossible()) { - Position currentPosition = position.up(); // 애초에 넘길때 가능한지 체크하고 만들면 조건문 하나 줄일 수 있지! - positions.add(currentPosition); - if (currentPosition.rightUpPossible()) { - positions.add(currentPosition.rightUp()); - } - if (currentPosition.leftUpPossible()) { - positions.add(currentPosition.leftUp()); - } - } - // 하 - if (position.downPossible()) { - Position currentPosition = position.down(); - positions.add(currentPosition); - if (currentPosition.rightDownPossible()) { - positions.add(currentPosition.rightDown()); - } - if (currentPosition.leftDownPossible()) { - positions.add(currentPosition.leftDown()); - } - } - // 좌 - if (position.leftPossible()) { - Position currentPosition = position.left(); - positions.add(currentPosition); - if (currentPosition.leftUpPossible()) { - positions.add(currentPosition.leftUp()); - } - if (currentPosition.leftDownPossible()) { - positions.add(currentPosition.leftDown()); - } - } - // 우 - if (position.rightPossible()) { - Position currentPosition = position.right(); - positions.add(currentPosition); - if (currentPosition.rightUpPossible()) { - positions.add(currentPosition.rightUp()); - } - if (currentPosition.rightDownPossible()) { - positions.add(currentPosition.rightDown()); - } - } - - return positions; + public Horse(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override - public List getPossibleDestinations(Position position, Map map) { - List destinations = new ArrayList<>(); - - // 상 - if (position.upPossible()) { - Position currentPosition = position.up(); - if (!map.containsKey(currentPosition)) { - if (currentPosition.rightUpPossible() && (!map.containsKey(currentPosition.rightUp()) || !map.get(currentPosition.rightUp()).isAlly(side))) { - destinations.add(currentPosition.rightUp()); - } - if (currentPosition.leftUpPossible() && (!map.containsKey(currentPosition.leftUp()) || !map.get(currentPosition.leftUp()).isAlly(side))) { - destinations.add(currentPosition.leftUp()); - } - } - } - - // 하 - if (position.downPossible()) { - Position currentPosition = position.down(); - if (!map.containsKey(currentPosition)) { - if (currentPosition.rightDownPossible() && (!map.containsKey(currentPosition.rightDown()) || !map.get(currentPosition.rightDown()).isAlly(side))) { - destinations.add(currentPosition.rightDown()); - } - if (currentPosition.leftDownPossible() && (!map.containsKey(currentPosition.leftDown()) || !map.get(currentPosition.leftDown()).isAlly(side))) { - destinations.add(currentPosition.leftDown()); - } - } - } - - // 좌 - if (position.leftPossible()) { - Position currentPosition = position.left(); - if (!map.containsKey(currentPosition)) { - if (currentPosition.leftUpPossible() && (!map.containsKey(currentPosition.leftUp()) || !map.get(currentPosition.leftUp()).isAlly(side))) { - destinations.add(currentPosition.leftUp()); - } - if (currentPosition.leftDownPossible() && (!map.containsKey(currentPosition.leftDown()) || !map.get(currentPosition.leftDown()).isAlly(side))) { - destinations.add(currentPosition.leftDown()); - } - } - } - - // 우 - if (position.rightPossible()) { - Position currentPosition = position.right(); - if (!map.containsKey(currentPosition)) { - if (currentPosition.rightUpPossible() && (!map.containsKey(currentPosition.rightUp()) || !map.get(currentPosition.rightUp()).isAlly(side))) { - destinations.add(currentPosition.rightUp()); - } - if (currentPosition.rightDownPossible() && (!map.containsKey(currentPosition.rightDown()) || !map.get(currentPosition.rightDown()).isAlly(side))) { - destinations.add(currentPosition.rightDown()); - } - } - } - - return destinations; + protected List filterValidPositions(Position current, List paths, BoardReader board) { + return filterStandardPaths(paths, board); } @Override public String toString() { - return name; + return "마"; } } diff --git a/src/main/java/domain/Soldier.java b/src/main/java/domain/Soldier.java index a507bf0b48..41415de0c4 100644 --- a/src/main/java/domain/Soldier.java +++ b/src/main/java/domain/Soldier.java @@ -2,88 +2,36 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; public class Soldier extends Piece { - private final String name = "졸"; - - public Soldier(Side side) { - super(side); + public Soldier(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override - public List getAllPosition(Position position) { - List positions = new ArrayList<>(); - - if (side.isCho()) { - // 상 - if (position.upPossible()) { - positions.add(position.up()); - } - // 좌 - if (position.leftPossible()) { - positions.add(position.left()); + protected List filterValidPositions(Position current, List paths, BoardReader board) { + List valid = new ArrayList<>(); + for (Path path : paths) { + Position dest = path.getDestination(); + if (isBackward(current, dest)) { + continue; } - // 우 - if (position.rightPossible()) { - positions.add(position.right()); + if (isValidDestination(dest, board)) { + valid.add(dest); } - return positions; - } - - // 하 - if (position.downPossible()) { - positions.add(position.down()); } - // 좌 - if (position.leftPossible()) { - positions.add(position.left()); - } - // 우 - if (position.rightPossible()) { - positions.add(position.right()); - } - return positions; + return valid; } - @Override - public List getPossibleDestinations(Position position, Map map) { - List destinations = new ArrayList<>(); - - if (side.isCho()) { - // 상 - if (position.upPossible() && (!map.containsKey(position.up()) || !map.get(position.up()).isAlly(side))) { - destinations.add(position.up()); - } - // 좌 - if (position.leftPossible() && (!map.containsKey(position.left()) || !map.get(position.left()).isAlly(side))) { - destinations.add(position.left()); - } - // 우 - if (position.rightPossible() && (!map.containsKey(position.right()) || !map.get(position.right()).isAlly(side))) { - destinations.add(position.right()); - } - return destinations; - } - - // 하 - if (position.downPossible() && (!map.containsKey(position.down()) || !map.get(position.down()).isAlly(side))) { - destinations.add(position.down()); - } - // 좌 - if (position.leftPossible() && (!map.containsKey(position.left()) || !map.get(position.left()).isAlly(side))) { - destinations.add(position.left()); + private boolean isBackward(Position current, Position dest) { + if (getSide().isCho()) { + return dest.getY() < current.getY(); } - // 우 - if (position.rightPossible() && (!map.containsKey(position.right()) || !map.get(position.right()).isAlly(side))) { - destinations.add(position.right()); - } - - return destinations; + return dest.getY() > current.getY(); } @Override public String toString() { - return name; + return "졸"; } } diff --git a/src/test/java/domain/BoardTest.java b/src/test/java/domain/BoardTest.java index 7d921d3650..993f284499 100644 --- a/src/test/java/domain/BoardTest.java +++ b/src/test/java/domain/BoardTest.java @@ -1,58 +1,51 @@ package domain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.HashMap; import java.util.Map; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class BoardTest { @Test - void 장기판은_올바른_위치에_기물을_초기화한다() { + void 선택한_포메이션에_맞게_32개의_기물이_초기_위치에_정확히_배치된다() { + // LEFT(상마상마), RIGHT(마상마상) 포메이션으로 초기화 Board board = BoardFactory.create(Formation.LEFT_ELEPHANT, Formation.RIGHT_ELEPHANT); Map actual = board.getBoard(); assertThat(actual).hasSize(32); - assertThat(actual.get(new Position(4, 1))).isInstanceOf(General.class); - assertThat(actual.get(new Position(4, 8))).isInstanceOf(General.class); - - assertThat(actual.get(new Position(0, 0))).isInstanceOf(Chariot.class); - assertThat(actual.get(new Position(8, 0))).isInstanceOf(Chariot.class); - assertThat(actual.get(new Position(0, 9))).isInstanceOf(Chariot.class); - assertThat(actual.get(new Position(8, 9))).isInstanceOf(Chariot.class); - - assertThat(actual.get(new Position(1, 0))).isInstanceOf(domain.Elephant.class); - assertThat(actual.get(new Position(2, 0))).isInstanceOf(domain.Horse.class); - assertThat(actual.get(new Position(6, 0))).isInstanceOf(domain.Elephant.class); - assertThat(actual.get(new Position(7, 0))).isInstanceOf(domain.Horse.class); - - assertThat(actual.get(new Position(7, 9))).isInstanceOf(domain.Horse.class); - assertThat(actual.get(new Position(6, 9))).isInstanceOf(domain.Elephant.class); - assertThat(actual.get(new Position(2, 9))).isInstanceOf(domain.Horse.class); - assertThat(actual.get(new Position(1, 9))).isInstanceOf(domain.Elephant.class); - } - @Test - void 기물을_이동하면_출발_위치는_비고_도착_위치로_옮겨진다() { - Map pieces = new HashMap<>(); - Position from = new Position(0, 3); - Position to = new Position(0, 4); - Piece soldier = new Soldier(Side.CHO); - pieces.put(from, soldier); - Board board = new Board(pieces); + // 궁성 중앙 (4,1), (4,8)에 궁(General) 배치 확인 + assertThat(actual.get(Position.of(4, 1))).isInstanceOf(General.class); + assertThat(actual.get(Position.of(4, 8))).isInstanceOf(General.class); + + // 네 귀퉁이에 차(Chariot) 배치 확인 + assertThat(actual.get(Position.of(0, 0))).isInstanceOf(Chariot.class); + assertThat(actual.get(Position.of(8, 0))).isInstanceOf(Chariot.class); + assertThat(actual.get(Position.of(0, 9))).isInstanceOf(Chariot.class); + assertThat(actual.get(Position.of(8, 9))).isInstanceOf(Chariot.class); - board.movePiece(from, to); + // 초나라(y=0) LEFT_ELEPHANT: 상(1), 마(2), 상(6), 마(7) + assertThat(actual.get(Position.of(1, 0))).isInstanceOf(Elephant.class); + assertThat(actual.get(Position.of(2, 0))).isInstanceOf(Horse.class); + assertThat(actual.get(Position.of(6, 0))).isInstanceOf(Elephant.class); + assertThat(actual.get(Position.of(7, 0))).isInstanceOf(Horse.class); - assertThat(board.getBoard()).doesNotContainKey(from); - assertThat(board.getBoard().get(to)).isSameAs(soldier); + // 한나라(y=9) RIGHT_ELEPHANT: 마(2), 상(1), 마(7), 상(6) + assertThat(actual.get(Position.of(2, 9))).isInstanceOf(Horse.class); + assertThat(actual.get(Position.of(1, 9))).isInstanceOf(Elephant.class); + assertThat(actual.get(Position.of(7, 9))).isInstanceOf(Horse.class); + assertThat(actual.get(Position.of(6, 9))).isInstanceOf(Elephant.class); } @Test void 양쪽_장군이_모두_있으면_게임은_종료되지_않는다() { Map pieces = new HashMap<>(); - pieces.put(new Position(4, 1), new General(Side.CHO)); - pieces.put(new Position(4, 8), new General(Side.HAN)); + pieces.put(Position.of(4, 1), PieceFactory.createGeneral(Side.CHO)); + pieces.put(Position.of(4, 8), PieceFactory.createGeneral(Side.HAN)); Board board = new Board(pieces); assertThat(board.isGameOver()).isFalse(); @@ -61,9 +54,67 @@ class BoardTest { @Test void 장군이_하나만_남으면_게임이_종료된다() { Map pieces = new HashMap<>(); - pieces.put(new Position(4, 1), new General(Side.CHO)); + pieces.put(Position.of(4, 1), PieceFactory.createGeneral(Side.CHO)); Board board = new Board(pieces); assertThat(board.isGameOver()).isTrue(); } + + @Test + @DisplayName("목적지에 적군 기물이 있으면 보드에서 해당 기물을 제거하고 이동한다") + void captureEnemy() { + // Given: (0, 3)에 초나라 졸, (0, 4)에 한나라 졸 배치 + Position from = Position.of(0, 3); + Position to = Position.of(0, 4); + Piece choSoldier = PieceFactory.createSoldier(Side.CHO); + Piece hanSoldier = PieceFactory.createSoldier(Side.HAN); + + Board board = new Board(Map.of( + from, choSoldier, + to, hanSoldier + )); + + // When: (0, 3)의 초나라 졸이 (0, 4)의 한나라 졸을 잡음 + Board movedBoard = board.movePiece(from, to); + + // Then: 출발지는 비어있고, 도착지에는 초나라 졸이 위치함 + assertThat(movedBoard.isEmpty(from)).isTrue(); + assertThat(movedBoard.getPiece(to)).isSameAs(choSoldier); + } + + @Test + @DisplayName("기물이 존재하지 않는 빈 좌표를 출발지로 입력하면 예외가 발생한다") + void moveEmptySource() { + // Given: 비어있는 보드 + Board board = new Board(Map.of()); + Position emptyPos = Position.of(0, 0); + Position to = Position.of(0, 1); + + // When & Then: 빈 좌표를 선택하여 이동을 시도하거나 이동 가능한 위치를 찾을 때 예외 발생 + assertThatThrownBy(() -> board.findMovablePositions(emptyPos)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("기물이 존재하지 않는 위치입니다."); + + assertThatThrownBy(() -> board.movePiece(emptyPos, to)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("출발지에 기물이 없습니다."); + } + + @Test + @DisplayName("목적지에 아군 기물이 있는지는 이동 가능 경로 탐색 단계에서 필터링된다") + void validateAllyAtDestination() { + // Given: (0, 0)에 초나라 차, (0, 1)에 초나라 졸 배치 + Position chariotPos = Position.of(0, 0); + Position allyPos = Position.of(0, 1); + Board board = new Board(Map.of( + chariotPos, PieceFactory.createChariot(Side.CHO), + allyPos, PieceFactory.createSoldier(Side.CHO) + )); + + // When: 차(Chariot)의 이동 가능 위치 탐색 + MovablePositions movablePositions = board.findMovablePositions(chariotPos); + + // Then: 아군이 있는 (0, 1)은 목적지 목록에 포함되지 않음 + assertThat(movablePositions.getPositions()).doesNotContain(allyPos); + } } diff --git a/src/test/java/domain/CannonTest.java b/src/test/java/domain/CannonTest.java index 4a1cd6ad2b..2e0699c3a1 100644 --- a/src/test/java/domain/CannonTest.java +++ b/src/test/java/domain/CannonTest.java @@ -2,43 +2,92 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class CannonTest { @Test - void 중간에_넘을_기물이_없으면_이동할_수_없다() { - Cannon cannon = new Cannon(Side.CHO); - Position from = new Position(4, 4); + @DisplayName("포는 상하좌우 방향으로 기물 하나를 뛰어넘어 빈칸으로 이동할 수 있다") + void jumpOverBridge() { + // Given: (1, 1)에 포, (1, 3)에 다리(졸) 배치 + Position current = Position.of(1, 1); + Position bridge = Position.of(1, 3); + Map pieces = Map.of( + current, PieceFactory.createCannon(Side.CHO), + bridge, PieceFactory.createSoldier(Side.CHO) + ); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 다리(1, 3) 이전인 (1, 2)는 못 가고, 다리 너머인 (1, 4)부터 끝까지 이동 가능 + assertThat(movable.getPositions()).doesNotContain(Position.of(1, 2)); + assertThat(movable.getPositions()).contains(Position.of(1, 4), Position.of(1, 9)); + } - List destinations = cannon.getPossibleDestinations(from, new HashMap<>()); + @Test + @DisplayName("포는 이동 경로에서 포 기물을 뛰어넘을 수 없다") + void cannotJumpOverAnotherCannon() { + // Given: (1, 1)에 포, (1, 3)에 다른 포(다리 역할 시도) 배치 + Position current = Position.of(1, 1); + Position cannonBridge = Position.of(1, 3); + Map pieces = Map.of( + current, PieceFactory.createCannon(Side.CHO), + cannonBridge, PieceFactory.createCannon(Side.HAN) + ); + Board board = new Board(pieces); - assertThat(destinations).isEmpty(); + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 다리가 포(Cannon)인 경우 뛰어넘을 수 없으므로 이동 가능한 좌표가 없어야 함 + assertThat(movable.getPositions()).isEmpty(); } @Test - void 한_기물을_넘은_뒤_이동하고_포는_잡을_수_없다() { - Cannon cannon = new Cannon(Side.CHO); - Position from = new Position(4, 4); - Map board = new HashMap<>(); - board.put(new Position(4, 5), new Soldier(Side.HAN)); - board.put(new Position(4, 8), new Soldier(Side.HAN)); - board.put(new Position(4, 3), new Soldier(Side.HAN)); - board.put(new Position(4, 2), new Soldier(Side.CHO)); - board.put(new Position(3, 4), new Cannon(Side.HAN)); - board.put(new Position(5, 4), new Soldier(Side.HAN)); - board.put(new Position(7, 4), new Cannon(Side.HAN)); - - List destinations = cannon.getPossibleDestinations(from, board); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 6), - new Position(4, 7), - new Position(4, 8), - new Position(6, 4) + @DisplayName("포는 기물을 뛰어넘은 후 적군 기물을 만나면 해당 위치까지 이동하여 잡을 수 있다") + void captureEnemy() { + // Given: (1, 1)에 포, (1, 3)에 다리, (1, 5)에 적군(졸) 배치 + Position current = Position.of(1, 1); + Position bridge = Position.of(1, 3); + Position enemy = Position.of(1, 5); + Map pieces = Map.of( + current, PieceFactory.createCannon(Side.CHO), + bridge, PieceFactory.createSoldier(Side.CHO), + enemy, PieceFactory.createSoldier(Side.HAN) ); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 적군(1, 5)까지는 갈 수 있지만, 그 너머(1, 6)는 갈 수 없음 + assertThat(movable.getPositions()).contains(Position.of(1, 4), Position.of(1, 5)); + assertThat(movable.getPositions()).doesNotContain(Position.of(1, 6)); + } + + @Test + @DisplayName("포는 뛰어넘은 후 만난 적군 기물이 포일 경우 잡을 수 없다") + void cannotCaptureEnemyCannon() { + // Given: (1, 1)에 포, (1, 3)에 다리, (1, 5)에 적군 포 배치 + Position current = Position.of(1, 1); + Position bridge = Position.of(1, 3); + Position enemyCannon = Position.of(1, 5); + Map pieces = Map.of( + current, PieceFactory.createCannon(Side.CHO), + bridge, PieceFactory.createSoldier(Side.CHO), + enemyCannon, PieceFactory.createCannon(Side.HAN) + ); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 적군 포(1, 5) 직전인 (1, 4)까지만 갈 수 있고 (1, 5)는 포함되지 않음 + assertThat(movable.getPositions()).contains(Position.of(1, 4)); + assertThat(movable.getPositions()).doesNotContain(Position.of(1, 5)); } } diff --git a/src/test/java/domain/ChariotTest.java b/src/test/java/domain/ChariotTest.java index 69ce2dca10..2425b90cdc 100644 --- a/src/test/java/domain/ChariotTest.java +++ b/src/test/java/domain/ChariotTest.java @@ -2,60 +2,65 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class ChariotTest { @Test - void 빈_보드에서는_직선_모든_칸으로_이동한다() { - Chariot chariot = new Chariot(Side.CHO); - Position from = new Position(4, 4); - - List destinations = chariot.getPossibleDestinations(from, new HashMap<>()); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 5), - new Position(4, 6), - new Position(4, 7), - new Position(4, 8), - new Position(4, 9), - new Position(4, 3), - new Position(4, 2), - new Position(4, 1), - new Position(4, 0), - new Position(3, 4), - new Position(2, 4), - new Position(1, 4), - new Position(0, 4), - new Position(5, 4), - new Position(6, 4), - new Position(7, 4), - new Position(8, 4) + @DisplayName("차는 상하좌우 방향으로 장애물을 만날 때까지 연속해서 이동할 수 있다") + void move() { + // Given: 빈 보드의 (0, 0)에 차 배치 + Position current = Position.of(0, 0); + Map pieces = Map.of(current, PieceFactory.createChariot(Side.CHO)); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 같은 행(0, 1~9)과 같은 열(1~8, 0)의 모든 위치가 포함되어야 함 (총 9+8=17개) + assertThat(movable.getPositions()).hasSize(17); + assertThat(movable.getPositions()).contains(Position.of(0, 9), Position.of(8, 0)); + } + + @Test + @DisplayName("차는 이동 경로 중 아군 기물을 만나면 그 직전 위치까지만 이동할 수 있다") + void allyObstacle() { + // Given: (0, 0)에 차, (0, 3)에 아군 배치 + Position current = Position.of(0, 0); + Position ally = Position.of(0, 3); + Map pieces = Map.of( + current, PieceFactory.createChariot(Side.CHO), + ally, PieceFactory.createSoldier(Side.CHO) ); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: (0, 1), (0, 2)는 가능하지만 (0, 3)과 그 너머(0, 4)는 불가능해야 함 + assertThat(movable.getPositions()).contains(Position.of(0, 1), Position.of(0, 2)); + assertThat(movable.getPositions()).doesNotContain(Position.of(0, 3), Position.of(0, 4)); } @Test - void 기물이_있으면_해당_칸까지만_이동하고_더_이상_가지_못한다() { - Chariot chariot = new Chariot(Side.CHO); - Position from = new Position(4, 4); - Map board = new HashMap<>(); - board.put(new Position(4, 6), new Soldier(Side.CHO)); - board.put(new Position(4, 2), new Soldier(Side.HAN)); - board.put(new Position(2, 4), new Soldier(Side.CHO)); - board.put(new Position(6, 4), new Soldier(Side.HAN)); - - List destinations = chariot.getPossibleDestinations(from, board); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 5), - new Position(4, 3), - new Position(4, 2), - new Position(3, 4), - new Position(5, 4), - new Position(6, 4) + @DisplayName("차는 이동 경로 중 적군 기물을 만나면 해당 위치까지 이동하여 잡을 수 있다") + void enemyCapture() { + // Given: (0, 0)에 차, (5, 0)에 적군 배치 + Position current = Position.of(0, 0); + Position enemy = Position.of(5, 0); + Map pieces = Map.of( + current, PieceFactory.createChariot(Side.CHO), + enemy, PieceFactory.createSoldier(Side.HAN) ); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 적군이 있는 (5, 0)까지는 이동 가능하지만, 그 너머(6, 0)는 불가능해야 함 + assertThat(movable.getPositions()).contains(Position.of(1, 0), Position.of(4, 0), Position.of(5, 0)); + assertThat(movable.getPositions()).doesNotContain(Position.of(6, 0)); } } diff --git a/src/test/java/domain/ElephantTest.java b/src/test/java/domain/ElephantTest.java index 2bb75439d5..e973358283 100644 --- a/src/test/java/domain/ElephantTest.java +++ b/src/test/java/domain/ElephantTest.java @@ -2,49 +2,63 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class ElephantTest { @Test - void 빈_보드에서는_8곳으로_이동한다() { - Elephant elephant = new Elephant(Side.CHO); - Position from = new Position(4, 4); - - List destinations = elephant.getPossibleDestinations(from, new HashMap<>()); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(6, 7), - new Position(2, 7), - new Position(6, 1), - new Position(2, 1), - new Position(1, 6), - new Position(1, 2), - new Position(7, 6), - new Position(7, 2) + @DisplayName("상은 직선 1칸 이동 후 같은 방향 대각선으로 2칸 이동할 수 있다") + void move() { + // Given: (4, 4)에 상 배치 (장애물 없는 상태) + Position current = Position.of(4, 4); + Map pieces = Map.of(current, PieceFactory.createElephant(Side.CHO)); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 8방향의 최종 목적지 확인 + assertThat(movable.getPositions()).containsExactlyInAnyOrder( + Position.of(6, 7), Position.of(2, 7), // 북쪽 기반 + Position.of(7, 6), Position.of(7, 2), // 동쪽 기반 + Position.of(6, 1), Position.of(2, 1), // 남쪽 기반 + Position.of(1, 6), Position.of(1, 2) // 서쪽 기반 ); } @Test - void 경로가_막힌_방향으로는_이동하지_못한다() { - Elephant elephant = new Elephant(Side.CHO); - Position from = new Position(4, 4); - Map board = new HashMap<>(); - board.put(new Position(4, 3), new Soldier(Side.HAN)); - board.put(new Position(5, 6), new Soldier(Side.HAN)); - board.put(new Position(1, 6), new Soldier(Side.CHO)); - board.put(new Position(1, 2), new Soldier(Side.HAN)); - - List destinations = elephant.getPossibleDestinations(from, board); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(2, 7), - new Position(1, 2), - new Position(7, 6), - new Position(7, 2) + @DisplayName("상은 직선 1칸 또는 대각선 1칸 경로(멱) 중 하나라도 기물이 존재하면 이동할 수 없다") + void bridge() { + // Given: (4, 4)에 상 배치 + Position current = Position.of(4, 4); + + // 1. 직선 1칸 멱(4, 5)에 장애물 배치 -> 북쪽 기반 (6, 7)과 (2, 7) 경로 차단 + // 2. 대각선 1칸 멱(5, 2)에 장애물 배치 -> 남동쪽 목적지 (6, 1) 경로 차단 + Map pieces = Map.of( + current, PieceFactory.createElephant(Side.CHO), + Position.of(4, 5), PieceFactory.createSoldier(Side.HAN), // 북쪽 직진 멱 + Position.of(5, 2), PieceFactory.createSoldier(Side.HAN) // 남동쪽 대각선 멱 (수정됨) ); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then + // 1. 북쪽 직진 멱이 막혔으므로 (6, 7)과 (2, 7)은 없어야 함 + assertThat(movable.getPositions()).doesNotContain( + Position.of(6, 7), + Position.of(2, 7) + ); + + // 2. 대각선 멱(5, 2)이 막혔으므로 (6, 1)은 없어야 함 + assertThat(movable.getPositions()).doesNotContain( + Position.of(6, 1) + ); + + // 장애물이 없는 다른 방향(예: 서쪽 기반 1, 6 등)은 유지되어야 함 + assertThat(movable.getPositions()).contains(Position.of(1, 6)); } } diff --git a/src/test/java/domain/FormationTest.java b/src/test/java/domain/FormationTest.java index c07c31919b..fd1e4028dd 100644 --- a/src/test/java/domain/FormationTest.java +++ b/src/test/java/domain/FormationTest.java @@ -22,9 +22,9 @@ class FormationTest { Formation.LEFT_ELEPHANT.placeElephant(pieces, Side.CHO); - assertThat(pieces.get(new Position(1, 0))).isInstanceOf(Elephant.class); - assertThat(pieces.get(new Position(2, 0))).isInstanceOf(Horse.class); - assertThat(pieces.get(new Position(6, 0))).isInstanceOf(Elephant.class); - assertThat(pieces.get(new Position(7, 0))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(1, 0))).isInstanceOf(Elephant.class); + assertThat(pieces.get(Position.of(2, 0))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(6, 0))).isInstanceOf(Elephant.class); + assertThat(pieces.get(Position.of(7, 0))).isInstanceOf(Horse.class); } } diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index d1dd7cdd65..4883eaa8be 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -14,49 +14,60 @@ class GameTest { @BeforeEach void setUp() { players = Players.createInitial(new Name("cho"), new Name("han")); - Formation choFormation = Formation.from(FormationCommand.from("1")); - Formation hanFormation = Formation.from(FormationCommand.from("1")); - Board board = BoardFactory.create(choFormation, hanFormation); + Board board = BoardFactory.create(Formation.from(FormationCommand.FIRST), Formation.from(FormationCommand.FIRST)); game = new Game(board, players); } @Test - void 턴이_바뀌면_진영이_바뀐다() { - Side first = game.getCurrentSide(); - players.switchPlayer(); - Side second = game.getCurrentSide(); + void 기물_이동이_완료되면_턴이_상대방_진영으로_변경된다() { + // Given: 초나라 졸(0,3)을 (0,4)로 전진 + Position from = Position.of(0, 3); + Position to = Position.of(0, 4); - assertThat(first).isEqualTo(Side.CHO); - assertThat(second).isEqualTo(Side.HAN); + // When + game.move(from, to); + + // Then + assertThat(game.getCurrentSide()).isEqualTo(Side.HAN); } @Test - void 보드의_기물을_이동이_가능하다() { - Position from = new Position(0, 3); - Piece expected = game.getBoard().get(from); - Position to = new Position(1, 3); - game.move(from, to); + void 이동할_수_있는_목적지가_전혀_없는_기물을_선택하면_예외가_발생한다() { + // Given: 사(Guard)를 기물들로 사방을 포위하여 이동 경로가 0개인 상황 연출 + Position guardPos = Position.of(4, 0); + Map blockedMap = Map.of( + guardPos, PieceFactory.createGuard(Side.CHO), + Position.of(3, 0), PieceFactory.createChariot(Side.CHO), + Position.of(5, 0), PieceFactory.createChariot(Side.CHO), + Position.of(4, 1), PieceFactory.createChariot(Side.CHO) + ); + Game blockedGame = new Game(new Board(blockedMap), players); - assertThat(game.getBoard()).doesNotContainKey(from); - assertThat(game.getBoard().get(to)).isEqualTo(expected); + // When & Then: selectSource 내부에서 movablePositions.isEmpty() 체크 시 예외 발생 + assertThatThrownBy(() -> blockedGame.selectSource(guardPos)) + .isInstanceOf(IllegalArgumentException.class); } @Test - void 존재하지_않는_목적지로_이동하는_경우_예외가_발생한다() { - Position from = new Position(0, 3); - Position to = new Position(1, 4); + void 선택한_기물이_이동할_수_없는_위치를_목적지로_입력하면_예외가_발생한다() { + // Given: 초나라 졸(0,3) 선택 (졸은 대각선 이동 불가) + Position from = Position.of(0, 3); + Position invalidTo = Position.of(1, 4); - assertThatThrownBy(() -> game.move(from, to)) + // When & Then: validateDestinations(to) 호출 시 예외 발생 + assertThatThrownBy(() -> game.move(from, invalidTo)) .isInstanceOf(IllegalArgumentException.class); } @Test - void 장군이_하나면_게임은_종료상태다() { - Game game = new Game( - new Board(Map.of(new Position(4, 1), new General(Side.CHO))), - Players.createInitial(new Name("cho"), new Name("han")) + void 보드에_궁이_하나만_남게_되면_게임은_종료_상태가_된다() { + // Given: 한나라 궁(General)이 잡히고 초나라 궁만 남은 보드 상황 + Map oneGeneralMap = Map.of( + Position.of(4, 1), PieceFactory.createGeneral(Side.CHO) ); + Game gameOverGame = new Game(new Board(oneGeneralMap), players); - assertThat(game.isOver()).isTrue(); + // When & Then + assertThat(gameOverGame.isOver()).isTrue(); } } diff --git a/src/test/java/domain/GeneralTest.java b/src/test/java/domain/GeneralTest.java index b4a7a81e11..8314f9ce5b 100644 --- a/src/test/java/domain/GeneralTest.java +++ b/src/test/java/domain/GeneralTest.java @@ -2,43 +2,26 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class GeneralTest { - @Test - void 빈_보드에서는_상하좌우로_이동한다() { - General general = new General(Side.CHO); - Position from = new Position(4, 4); - - List destinations = general.getPossibleDestinations(from, new HashMap<>()); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 5), - new Position(4, 3), - new Position(3, 4), - new Position(5, 4) + @DisplayName("궁은 상하좌우 1칸 이동하며, 범위를 벗어나거나 아군이 있으면 이동할 수 없다") + void move() { + // (4,1)에 궁, (4,2)에 아군 사 배치 -> (4,2) 이동 불가, (4,0), (3,1), (5,1) 이동 가능 + Position current = Position.of(4, 1); + Map pieces = Map.of( + current, PieceFactory.createGeneral(Side.CHO), + Position.of(4, 2), PieceFactory.createGuard(Side.CHO) ); - } - - @Test - void 같은_진영_기물이_있는_칸으로는_이동하지_못한다() { - General general = new General(Side.CHO); - Position from = new Position(4, 4); - Map board = new HashMap<>(); - board.put(new Position(4, 5), new Soldier(Side.CHO)); - board.put(new Position(4, 3), new Soldier(Side.HAN)); - board.put(new Position(3, 4), new Soldier(Side.CHO)); - board.put(new Position(5, 4), new Soldier(Side.HAN)); + Board board = new Board(pieces); - List destinations = general.getPossibleDestinations(from, board); + MovablePositions movable = board.findMovablePositions(current); - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 3), - new Position(5, 4) + assertThat(movable.getPositions()).containsExactlyInAnyOrder( + Position.of(4, 0), Position.of(3, 1), Position.of(5, 1) ); } } diff --git a/src/test/java/domain/GuardTest.java b/src/test/java/domain/GuardTest.java index 5cb77419ea..e9e80bad8b 100644 --- a/src/test/java/domain/GuardTest.java +++ b/src/test/java/domain/GuardTest.java @@ -2,43 +2,25 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class GuardTest { - @Test - void 빈_보드에서는_상하좌우로_이동한다() { - Guard guard = new Guard(Side.HAN); - Position from = new Position(4, 4); - - List destinations = guard.getPossibleDestinations(from, new HashMap<>()); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 5), - new Position(4, 3), - new Position(3, 4), - new Position(5, 4) + @DisplayName("사는 상하좌우 1칸 이동하며 아군 기물이 있으면 이동할 수 없다") + void move() { + Position current = Position.of(3, 1); + Map pieces = Map.of( + current, PieceFactory.createGuard(Side.CHO), + Position.of(3, 2), PieceFactory.createChariot(Side.CHO) ); - } - - @Test - void 같은_진영_기물이_있는_칸으로는_이동하지_못한다() { - Guard guard = new Guard(Side.HAN); - Position from = new Position(4, 4); - Map board = new HashMap<>(); - board.put(new Position(4, 5), new Soldier(Side.HAN)); - board.put(new Position(4, 3), new Soldier(Side.CHO)); - board.put(new Position(3, 4), new Soldier(Side.HAN)); - board.put(new Position(5, 4), new Soldier(Side.CHO)); + Board board = new Board(pieces); - List destinations = guard.getPossibleDestinations(from, board); + MovablePositions movable = board.findMovablePositions(current); - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 3), - new Position(5, 4) + assertThat(movable.getPositions()).containsExactlyInAnyOrder( + Position.of(4, 1), Position.of(2, 1), Position.of(3,0) ); } } diff --git a/src/test/java/domain/HorseTest.java b/src/test/java/domain/HorseTest.java index b86a027f09..e37288aeba 100644 --- a/src/test/java/domain/HorseTest.java +++ b/src/test/java/domain/HorseTest.java @@ -2,49 +2,54 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class HorseTest { @Test - void 빈_보드에서는_8곳으로_이동한다() { - Horse horse = new Horse(Side.CHO); - Position from = new Position(4, 4); - - List destinations = horse.getPossibleDestinations(from, new HashMap<>()); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(5, 6), - new Position(3, 6), - new Position(5, 2), - new Position(3, 2), - new Position(2, 5), - new Position(2, 3), - new Position(6, 5), - new Position(6, 3) + @DisplayName("마는 직선 1칸 이동 후 대각선 1칸 방향으로 이동할 수 있다") + void move() { + // Given: (4, 4)에 마 배치 (장애물 없는 상태) + Position current = Position.of(4, 4); + Map pieces = Map.of(current, PieceFactory.createHorse(Side.CHO)); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 8방향의 L자형 목적지 확인 + assertThat(movable.getPositions()).containsExactlyInAnyOrder( + Position.of(3, 6), Position.of(5, 6), // 북쪽 방향 2개 + Position.of(6, 5), Position.of(6, 3), // 동쪽 방향 2개 + Position.of(5, 2), Position.of(3, 2), // 남쪽 방향 2개 + Position.of(2, 3), Position.of(2, 5) // 서쪽 방향 2개 ); } @Test - void 다리_칸이_막히면_해당_방향으로_이동하지_못한다() { - Horse horse = new Horse(Side.CHO); - Position from = new Position(4, 4); - Map board = new HashMap<>(); - board.put(new Position(4, 5), new Soldier(Side.HAN)); - board.put(new Position(6, 5), new Soldier(Side.CHO)); - board.put(new Position(2, 3), new Soldier(Side.HAN)); - - List destinations = horse.getPossibleDestinations(from, board); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(5, 2), - new Position(3, 2), - new Position(2, 5), - new Position(2, 3), - new Position(6, 3) + @DisplayName("마는 직선 1칸 경로(멱)에 기물이 존재하면 해당 방향으로 이동할 수 없다") + void bridge() { + // Given: (4, 4)에 마 배치, 북쪽 멱(4, 5)에 장애물 배치 + Position current = Position.of(4, 4); + Position northBridge = Position.of(4, 5); + Map pieces = Map.of( + current, PieceFactory.createHorse(Side.CHO), + northBridge, PieceFactory.createSoldier(Side.HAN) ); + Board board = new Board(pieces); + + // When + MovablePositions movable = board.findMovablePositions(current); + + // Then: 북쪽 멱이 막혔으므로 북쪽 대각선 목적지인 (3, 6)과 (5, 6)은 제외되어야 함 + assertThat(movable.getPositions()).doesNotContain( + Position.of(3, 6), + Position.of(5, 6) + ); + + // 나머지 6개 방향은 정상 이동 가능해야 함 + assertThat(movable.getPositions()).hasSize(6); } } diff --git a/src/test/java/domain/PieceTest.java b/src/test/java/domain/PieceTest.java index ee3e397642..77b0c74897 100644 --- a/src/test/java/domain/PieceTest.java +++ b/src/test/java/domain/PieceTest.java @@ -12,10 +12,10 @@ class PieceTest { @ParameterizedTest @MethodSource("provideSideCase") - void 주어진_진영과_자신의_진영이_같은지_올바르게_판별한다(Side side, Side ohter, boolean expected) { - Piece piece = new Soldier(side); + void 주어진_진영과_자신의_진영이_같은지_올바르게_판별한다(Side side, Side other, boolean expected) { + Piece piece = PieceFactory.createSoldier(side); - boolean actual = piece.isAlly(ohter); + boolean actual = piece.isAlly(other); assertThat(actual).isEqualTo(expected); } diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 2e8d838170..d53fc63c59 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -9,21 +9,41 @@ class PlayerTest { @Test void 턴_상태를_토글하면_현재_턴_여부가_반전된다() { + // Given: 초나라 플레이어가 자신의 턴(ActiveTurn)인 상태로 생성 Player player = new Player(new Name("cho"), Side.CHO, new ActiveTurn()); + // When: 턴을 한 번 토글 (Active -> Waiting) player.toggleTurn(); + // Then assertThat(player.isCurrentTurn()).isFalse(); + // When: 턴을 다시 토글 (Waiting -> Active) player.toggleTurn(); + // Then assertThat(player.isCurrentTurn()).isTrue(); } @Test - void 플레이어는_상대방의_기물을_선택하면_예외가_발생한다() { + void 플레이어는_자신의_기물이_아닌_상대방의_기물을_검증하면_예외가_발생한다() { + // Given: 초나라 플레이어와 한나라 졸(Soldier) Player choPlayer = new Player(new Name("cho"), Side.CHO, new ActiveTurn()); - Piece hanPiece = new Soldier(Side.HAN); + // PieceFactory를 사용하여 실제 도메인과 동일한 기물 생성 (전략 주입 포함) + Piece hanPiece = PieceFactory.createSoldier(Side.HAN); + + // When & Then: 초나라 플레이어가 한나라 기물을 validateAlly 할 때 예외 발생 assertThatThrownBy(() -> choPlayer.validateAlly(hanPiece)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("자신의 기물만"); + } + + @Test + void 플레이어는_자신의_기물을_검증하면_예외가_발생하지_않는다() { + // Given: 초나라 플레이어와 초나라 졸(Soldier) + Player choPlayer = new Player(new Name("cho"), Side.CHO, new ActiveTurn()); + Piece choPiece = PieceFactory.createSoldier(Side.CHO); + + // When & Then: 예외 없이 통과해야 함 + choPlayer.validateAlly(choPiece); } } diff --git a/src/test/java/domain/PositionTest.java b/src/test/java/domain/PositionTest.java index a6a42027c1..c4178aa13f 100644 --- a/src/test/java/domain/PositionTest.java +++ b/src/test/java/domain/PositionTest.java @@ -12,7 +12,7 @@ class PositionTest { @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8}) void x의_값은_0_이상_8_이하여야_한다(int validX) { int y = 0; - assertThatCode(() -> new Position(validX, y)) + assertThatCode(() -> Position.of(validX, y)) .doesNotThrowAnyException(); } @@ -20,7 +20,7 @@ class PositionTest { @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) void y의_값은_0_이상_9_이하여야_한다(int validY) { int x = 0; - assertThatCode(() -> new Position(x, validY)) + assertThatCode(() -> Position.of(x, validY)) .doesNotThrowAnyException(); } @@ -29,7 +29,7 @@ class PositionTest { @ValueSource(ints = {-1, -2, 9, 10, 100}) void x_좌표가_0_미만이거나_8_초과이면_예외가_발생한다(int invalidX) { int y = 0; - assertThatThrownBy(() -> new Position(invalidX, y)) + assertThatThrownBy(() -> Position.of(invalidX, y)) .isInstanceOf(IllegalArgumentException.class); } @@ -38,7 +38,7 @@ class PositionTest { @ValueSource(ints = {-1, -2, 10, 11, 100}) void y_좌표가_0_미만이거나_9_초과이면_예외가_발생한다(int invalidY) { int x = 0; - assertThatThrownBy(() -> new Position(x, invalidY)) + assertThatThrownBy(() -> Position.of(x, invalidY)) .isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/domain/SoldierTest.java b/src/test/java/domain/SoldierTest.java index 20dab71db4..580593a31c 100644 --- a/src/test/java/domain/SoldierTest.java +++ b/src/test/java/domain/SoldierTest.java @@ -2,54 +2,26 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class SoldierTest { - - @Test - void 초_졸은_위_좌_우로_이동한다() { - Soldier soldier = new Soldier(Side.CHO); - Position from = new Position(4, 4); - - List destinations = soldier.getPossibleDestinations(from, new HashMap<>()); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 5), - new Position(3, 4), - new Position(5, 4) - ); - } - @Test - void 한_졸은_아래_좌_우로_이동한다() { - Soldier soldier = new Soldier(Side.HAN); - Position from = new Position(4, 4); - - List destinations = soldier.getPossibleDestinations(from, new HashMap<>()); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(4, 3), - new Position(3, 4), - new Position(5, 4) + @DisplayName("초나라 졸은 상/좌/우 이동이 가능하고 한나라 졸은 하/좌/우 이동이 가능하다") + void moveBySide() { + Position choPos = Position.of(4, 3); + Position hanPos = Position.of(4, 6); + Board board = new Board(Map.of( + choPos, PieceFactory.createSoldier(Side.CHO), + hanPos, PieceFactory.createSoldier(Side.HAN) + )); + + assertThat(board.findMovablePositions(choPos).getPositions()).containsExactlyInAnyOrder( + Position.of(4, 4), Position.of(3, 3), Position.of(5, 3) ); - } - - @Test - void 같은_진영_기물이_있는_칸으로는_이동하지_못한다() { - Soldier soldier = new Soldier(Side.CHO); - Position from = new Position(4, 4); - Map board = new HashMap<>(); - board.put(new Position(4, 5), new Soldier(Side.CHO)); - board.put(new Position(3, 4), new Soldier(Side.HAN)); - board.put(new Position(5, 4), new Soldier(Side.CHO)); - - List destinations = soldier.getPossibleDestinations(from, board); - - assertThat(destinations).containsExactlyInAnyOrder( - new Position(3, 4) + assertThat(board.findMovablePositions(hanPos).getPositions()).containsExactlyInAnyOrder( + Position.of(4, 5), Position.of(3, 6), Position.of(5, 6) ); } } From e2e879a83cdfc85c1f612362e28737d6b422fd5a Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 14:07:58 +0900 Subject: [PATCH 47/92] =?UTF-8?q?refactor:=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EC=84=B8=EB=B6=80=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=93=A4=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/Game.java | 3 +++ src/main/java/domain/GameManager.java | 6 ++++++ src/main/java/domain/InputParser.java | 1 + src/main/java/domain/MovementStrategy.java | 7 ------- src/main/java/domain/Position.java | 1 + src/main/java/domain/{ => board}/Board.java | 5 ++++- src/main/java/domain/{ => board}/BoardFactory.java | 6 +++++- src/main/java/domain/{ => board}/BoardReader.java | 6 ++++-- src/main/java/domain/{ => board}/Formation.java | 6 +++++- .../java/domain/{ => board}/FormationCommand.java | 2 +- src/main/java/domain/{ => piece}/Cannon.java | 7 ++++++- src/main/java/domain/{ => piece}/Chariot.java | 7 ++++++- src/main/java/domain/{ => piece}/Elephant.java | 7 ++++++- src/main/java/domain/{ => piece}/General.java | 7 ++++++- src/main/java/domain/{ => piece}/Guard.java | 7 ++++++- src/main/java/domain/{ => piece}/Horse.java | 7 ++++++- src/main/java/domain/{ => piece}/Piece.java | 8 +++++++- src/main/java/domain/{ => piece}/PieceFactory.java | 8 +++++++- src/main/java/domain/{ => piece}/Soldier.java | 7 ++++++- src/main/java/domain/{ => player}/Name.java | 2 +- src/main/java/domain/{ => player}/Player.java | 6 +++++- src/main/java/domain/{ => player}/Players.java | 5 ++++- src/main/java/domain/{ => state}/ActiveTurn.java | 2 +- src/main/java/domain/{ => state}/InactiveTurn.java | 2 +- src/main/java/domain/{ => state}/TurnState.java | 2 +- .../domain/{ => strategy}/ContinuousStrategy.java | 5 +++-- src/main/java/domain/{ => strategy}/Direction.java | 2 +- src/main/java/domain/strategy/MovementStrategy.java | 8 ++++++++ .../java/domain/{ => strategy}/OneStepStrategy.java | 5 +++-- src/main/java/domain/{ => strategy}/Path.java | 3 ++- .../java/domain/{ => strategy}/SequenceStrategy.java | 5 +++-- src/main/java/view/OutputView.java | 2 +- src/test/java/domain/GameTest.java | 8 ++++++++ src/test/java/domain/{ => board}/BoardTest.java | 11 ++++++++++- .../java/domain/{ => board}/FormationCommandTest.java | 2 +- src/test/java/domain/{ => board}/FormationTest.java | 7 ++++++- src/test/java/domain/{ => piece}/CannonTest.java | 6 +++++- src/test/java/domain/{ => piece}/ChariotTest.java | 6 +++++- src/test/java/domain/{ => piece}/ElephantTest.java | 6 +++++- src/test/java/domain/{ => piece}/GeneralTest.java | 6 +++++- src/test/java/domain/{ => piece}/GuardTest.java | 6 +++++- src/test/java/domain/{ => piece}/HorseTest.java | 6 +++++- src/test/java/domain/{ => piece}/PieceTest.java | 3 ++- src/test/java/domain/{ => piece}/SoldierTest.java | 5 ++++- src/test/java/domain/{ => player}/NameTest.java | 2 +- src/test/java/domain/{ => player}/PlayerTest.java | 6 +++++- src/test/java/domain/{ => player}/PlayersTest.java | 2 +- 47 files changed, 188 insertions(+), 51 deletions(-) delete mode 100644 src/main/java/domain/MovementStrategy.java rename src/main/java/domain/{ => board}/Board.java (94%) rename src/main/java/domain/{ => board}/BoardFactory.java (93%) rename src/main/java/domain/{ => board}/BoardReader.java (60%) rename src/main/java/domain/{ => board}/Formation.java (95%) rename src/main/java/domain/{ => board}/FormationCommand.java (96%) rename src/main/java/domain/{ => piece}/Cannon.java (93%) rename src/main/java/domain/{ => piece}/Chariot.java (88%) rename src/main/java/domain/{ => piece}/Elephant.java (71%) rename src/main/java/domain/{ => piece}/General.java (74%) rename src/main/java/domain/{ => piece}/Guard.java (71%) rename src/main/java/domain/{ => piece}/Horse.java (71%) rename src/main/java/domain/{ => piece}/Piece.java (89%) rename src/main/java/domain/{ => piece}/PieceFactory.java (90%) rename src/main/java/domain/{ => piece}/Soldier.java (85%) rename src/main/java/domain/{ => player}/Name.java (95%) rename src/main/java/domain/{ => player}/Player.java (88%) rename src/main/java/domain/{ => player}/Players.java (92%) rename src/main/java/domain/{ => state}/ActiveTurn.java (90%) rename src/main/java/domain/{ => state}/InactiveTurn.java (90%) rename src/main/java/domain/{ => state}/TurnState.java (78%) rename src/main/java/domain/{ => strategy}/ContinuousStrategy.java (89%) rename src/main/java/domain/{ => strategy}/Direction.java (97%) create mode 100644 src/main/java/domain/strategy/MovementStrategy.java rename src/main/java/domain/{ => strategy}/OneStepStrategy.java (83%) rename src/main/java/domain/{ => strategy}/Path.java (93%) rename src/main/java/domain/{ => strategy}/SequenceStrategy.java (90%) rename src/test/java/domain/{ => board}/BoardTest.java (95%) rename src/test/java/domain/{ => board}/FormationCommandTest.java (97%) rename src/test/java/domain/{ => board}/FormationTest.java (88%) rename src/test/java/domain/{ => piece}/CannonTest.java (96%) rename src/test/java/domain/{ => piece}/ChariotTest.java (95%) rename src/test/java/domain/{ => piece}/ElephantTest.java (95%) rename src/test/java/domain/{ => piece}/GeneralTest.java (88%) rename src/test/java/domain/{ => piece}/GuardTest.java (86%) rename src/test/java/domain/{ => piece}/HorseTest.java (94%) rename src/test/java/domain/{ => piece}/PieceTest.java (95%) rename src/test/java/domain/{ => piece}/SoldierTest.java (91%) rename src/test/java/domain/{ => player}/NameTest.java (98%) rename src/test/java/domain/{ => player}/PlayerTest.java (93%) rename src/test/java/domain/{ => player}/PlayersTest.java (94%) diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index 1d3ee309f0..acf13fb92f 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -1,5 +1,8 @@ package domain; +import domain.board.Board; +import domain.piece.Piece; +import domain.player.Players; import java.util.Map; public class Game { diff --git a/src/main/java/domain/GameManager.java b/src/main/java/domain/GameManager.java index 4b3dcadebb..e6c5ae2746 100644 --- a/src/main/java/domain/GameManager.java +++ b/src/main/java/domain/GameManager.java @@ -1,5 +1,11 @@ package domain; +import domain.board.Board; +import domain.board.BoardFactory; +import domain.board.Formation; +import domain.board.FormationCommand; +import domain.player.Name; +import domain.player.Players; import java.util.List; import java.util.function.Supplier; import view.InputView; diff --git a/src/main/java/domain/InputParser.java b/src/main/java/domain/InputParser.java index eb1ce70e2e..9041556483 100644 --- a/src/main/java/domain/InputParser.java +++ b/src/main/java/domain/InputParser.java @@ -1,6 +1,7 @@ package domain; +import domain.player.Name; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; diff --git a/src/main/java/domain/MovementStrategy.java b/src/main/java/domain/MovementStrategy.java deleted file mode 100644 index d64d210ad1..0000000000 --- a/src/main/java/domain/MovementStrategy.java +++ /dev/null @@ -1,7 +0,0 @@ -package domain; - -import java.util.List; - -public interface MovementStrategy { - List generatePaths(Position current, BoardReader board); -} diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index 774bed4f53..86a067df55 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -1,5 +1,6 @@ package domain; +import domain.strategy.Direction; import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/board/Board.java similarity index 94% rename from src/main/java/domain/Board.java rename to src/main/java/domain/board/Board.java index 8072299ca6..33972ad4e2 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/board/Board.java @@ -1,5 +1,8 @@ -package domain; +package domain.board; +import domain.MovablePositions; +import domain.piece.Piece; +import domain.Position; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/domain/BoardFactory.java b/src/main/java/domain/board/BoardFactory.java similarity index 93% rename from src/main/java/domain/BoardFactory.java rename to src/main/java/domain/board/BoardFactory.java index 91aa4174b8..d64dbea8f8 100644 --- a/src/main/java/domain/BoardFactory.java +++ b/src/main/java/domain/board/BoardFactory.java @@ -1,5 +1,9 @@ -package domain; +package domain.board; +import domain.piece.Piece; +import domain.piece.PieceFactory; +import domain.Position; +import domain.Side; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/domain/BoardReader.java b/src/main/java/domain/board/BoardReader.java similarity index 60% rename from src/main/java/domain/BoardReader.java rename to src/main/java/domain/board/BoardReader.java index b05bf99cc9..cdbb81feb9 100644 --- a/src/main/java/domain/BoardReader.java +++ b/src/main/java/domain/board/BoardReader.java @@ -1,7 +1,9 @@ -package domain; +package domain.board; + +import domain.piece.Piece; +import domain.Position; public interface BoardReader { - boolean isWithinRange(Position position); boolean isEmpty(Position position); Piece getPiece(Position position); } diff --git a/src/main/java/domain/Formation.java b/src/main/java/domain/board/Formation.java similarity index 95% rename from src/main/java/domain/Formation.java rename to src/main/java/domain/board/Formation.java index ebfdab3c1a..0a9155682d 100644 --- a/src/main/java/domain/Formation.java +++ b/src/main/java/domain/board/Formation.java @@ -1,5 +1,9 @@ -package domain; +package domain.board; +import domain.piece.Piece; +import domain.piece.PieceFactory; +import domain.Position; +import domain.Side; import java.util.Arrays; import java.util.List; import java.util.Map; diff --git a/src/main/java/domain/FormationCommand.java b/src/main/java/domain/board/FormationCommand.java similarity index 96% rename from src/main/java/domain/FormationCommand.java rename to src/main/java/domain/board/FormationCommand.java index 6a8520eaf2..471b53a43d 100644 --- a/src/main/java/domain/FormationCommand.java +++ b/src/main/java/domain/board/FormationCommand.java @@ -1,4 +1,4 @@ -package domain; +package domain.board; import java.util.Arrays; diff --git a/src/main/java/domain/Cannon.java b/src/main/java/domain/piece/Cannon.java similarity index 93% rename from src/main/java/domain/Cannon.java rename to src/main/java/domain/piece/Cannon.java index a68573244a..7f8b46d7aa 100644 --- a/src/main/java/domain/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -1,5 +1,10 @@ -package domain; +package domain.piece; +import domain.board.BoardReader; +import domain.strategy.MovementStrategy; +import domain.strategy.Path; +import domain.Position; +import domain.Side; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/Chariot.java b/src/main/java/domain/piece/Chariot.java similarity index 88% rename from src/main/java/domain/Chariot.java rename to src/main/java/domain/piece/Chariot.java index b791e570d2..e2c72b1f92 100644 --- a/src/main/java/domain/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -1,5 +1,10 @@ -package domain; +package domain.piece; +import domain.board.BoardReader; +import domain.strategy.MovementStrategy; +import domain.strategy.Path; +import domain.Position; +import domain.Side; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/Elephant.java b/src/main/java/domain/piece/Elephant.java similarity index 71% rename from src/main/java/domain/Elephant.java rename to src/main/java/domain/piece/Elephant.java index 54df243de4..2dd534bee2 100644 --- a/src/main/java/domain/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -1,5 +1,10 @@ -package domain; +package domain.piece; +import domain.board.BoardReader; +import domain.strategy.MovementStrategy; +import domain.strategy.Path; +import domain.Position; +import domain.Side; import java.util.List; public class Elephant extends Piece { diff --git a/src/main/java/domain/General.java b/src/main/java/domain/piece/General.java similarity index 74% rename from src/main/java/domain/General.java rename to src/main/java/domain/piece/General.java index 5b05b40b87..737d843c39 100644 --- a/src/main/java/domain/General.java +++ b/src/main/java/domain/piece/General.java @@ -1,5 +1,10 @@ -package domain; +package domain.piece; +import domain.board.BoardReader; +import domain.strategy.MovementStrategy; +import domain.strategy.Path; +import domain.Position; +import domain.Side; import java.util.List; public class General extends Piece { diff --git a/src/main/java/domain/Guard.java b/src/main/java/domain/piece/Guard.java similarity index 71% rename from src/main/java/domain/Guard.java rename to src/main/java/domain/piece/Guard.java index e6b6902db3..cb7abbe43c 100644 --- a/src/main/java/domain/Guard.java +++ b/src/main/java/domain/piece/Guard.java @@ -1,5 +1,10 @@ -package domain; +package domain.piece; +import domain.board.BoardReader; +import domain.strategy.MovementStrategy; +import domain.strategy.Path; +import domain.Position; +import domain.Side; import java.util.List; public class Guard extends Piece { diff --git a/src/main/java/domain/Horse.java b/src/main/java/domain/piece/Horse.java similarity index 71% rename from src/main/java/domain/Horse.java rename to src/main/java/domain/piece/Horse.java index a704da8dc0..fdc6498f36 100644 --- a/src/main/java/domain/Horse.java +++ b/src/main/java/domain/piece/Horse.java @@ -1,5 +1,10 @@ -package domain; +package domain.piece; +import domain.board.BoardReader; +import domain.strategy.MovementStrategy; +import domain.strategy.Path; +import domain.Position; +import domain.Side; import java.util.List; public class Horse extends Piece { diff --git a/src/main/java/domain/Piece.java b/src/main/java/domain/piece/Piece.java similarity index 89% rename from src/main/java/domain/Piece.java rename to src/main/java/domain/piece/Piece.java index cd19b6e68f..106359a893 100644 --- a/src/main/java/domain/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -1,5 +1,11 @@ -package domain; +package domain.piece; +import domain.MovablePositions; +import domain.strategy.Path; +import domain.Position; +import domain.Side; +import domain.board.BoardReader; +import domain.strategy.MovementStrategy; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/PieceFactory.java b/src/main/java/domain/piece/PieceFactory.java similarity index 90% rename from src/main/java/domain/PieceFactory.java rename to src/main/java/domain/piece/PieceFactory.java index adc049421b..aec73a52f5 100644 --- a/src/main/java/domain/PieceFactory.java +++ b/src/main/java/domain/piece/PieceFactory.java @@ -1,5 +1,11 @@ -package domain; +package domain.piece; +import domain.strategy.Direction; +import domain.Side; +import domain.strategy.ContinuousStrategy; +import domain.strategy.MovementStrategy; +import domain.strategy.OneStepStrategy; +import domain.strategy.SequenceStrategy; import java.util.Map; public class PieceFactory { diff --git a/src/main/java/domain/Soldier.java b/src/main/java/domain/piece/Soldier.java similarity index 85% rename from src/main/java/domain/Soldier.java rename to src/main/java/domain/piece/Soldier.java index 41415de0c4..f118e290e0 100644 --- a/src/main/java/domain/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -1,5 +1,10 @@ -package domain; +package domain.piece; +import domain.board.BoardReader; +import domain.strategy.MovementStrategy; +import domain.strategy.Path; +import domain.Position; +import domain.Side; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/Name.java b/src/main/java/domain/player/Name.java similarity index 95% rename from src/main/java/domain/Name.java rename to src/main/java/domain/player/Name.java index 19115a0f01..fb043114a1 100644 --- a/src/main/java/domain/Name.java +++ b/src/main/java/domain/player/Name.java @@ -1,4 +1,4 @@ -package domain; +package domain.player; public record Name(String name) { diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/player/Player.java similarity index 88% rename from src/main/java/domain/Player.java rename to src/main/java/domain/player/Player.java index 78fa8dabbc..63a8cf0783 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/player/Player.java @@ -1,4 +1,8 @@ -package domain; +package domain.player; + +import domain.piece.Piece; +import domain.Side; +import domain.state.TurnState; public class Player { private final Name name; diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/player/Players.java similarity index 92% rename from src/main/java/domain/Players.java rename to src/main/java/domain/player/Players.java index 1e94f2b961..3c91ecc139 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/player/Players.java @@ -1,5 +1,8 @@ -package domain; +package domain.player; +import domain.state.ActiveTurn; +import domain.state.InactiveTurn; +import domain.Side; import java.util.List; public class Players { diff --git a/src/main/java/domain/ActiveTurn.java b/src/main/java/domain/state/ActiveTurn.java similarity index 90% rename from src/main/java/domain/ActiveTurn.java rename to src/main/java/domain/state/ActiveTurn.java index 4892cb4d64..cc7b256287 100644 --- a/src/main/java/domain/ActiveTurn.java +++ b/src/main/java/domain/state/ActiveTurn.java @@ -1,4 +1,4 @@ -package domain; +package domain.state; public class ActiveTurn implements TurnState { diff --git a/src/main/java/domain/InactiveTurn.java b/src/main/java/domain/state/InactiveTurn.java similarity index 90% rename from src/main/java/domain/InactiveTurn.java rename to src/main/java/domain/state/InactiveTurn.java index b4dd7215c1..b836d5515a 100644 --- a/src/main/java/domain/InactiveTurn.java +++ b/src/main/java/domain/state/InactiveTurn.java @@ -1,4 +1,4 @@ -package domain; +package domain.state; public class InactiveTurn implements TurnState { diff --git a/src/main/java/domain/TurnState.java b/src/main/java/domain/state/TurnState.java similarity index 78% rename from src/main/java/domain/TurnState.java rename to src/main/java/domain/state/TurnState.java index a2e49397a6..36d5fdc1f3 100644 --- a/src/main/java/domain/TurnState.java +++ b/src/main/java/domain/state/TurnState.java @@ -1,4 +1,4 @@ -package domain; +package domain.state; public interface TurnState { boolean isCurrent(); diff --git a/src/main/java/domain/ContinuousStrategy.java b/src/main/java/domain/strategy/ContinuousStrategy.java similarity index 89% rename from src/main/java/domain/ContinuousStrategy.java rename to src/main/java/domain/strategy/ContinuousStrategy.java index 4a9835d12a..6023de1e57 100644 --- a/src/main/java/domain/ContinuousStrategy.java +++ b/src/main/java/domain/strategy/ContinuousStrategy.java @@ -1,5 +1,6 @@ -package domain; +package domain.strategy; +import domain.Position; import java.util.ArrayList; import java.util.List; @@ -11,7 +12,7 @@ public ContinuousStrategy(List directions) { } @Override - public List generatePaths(Position current, BoardReader board) { + public List generatePaths(Position current) { List paths = new ArrayList<>(); for (Direction direction : directions) { Path path = createPath(current, direction); diff --git a/src/main/java/domain/Direction.java b/src/main/java/domain/strategy/Direction.java similarity index 97% rename from src/main/java/domain/Direction.java rename to src/main/java/domain/strategy/Direction.java index e85573f4c4..edbad83fe7 100644 --- a/src/main/java/domain/Direction.java +++ b/src/main/java/domain/strategy/Direction.java @@ -1,4 +1,4 @@ -package domain; +package domain.strategy; import java.util.List; diff --git a/src/main/java/domain/strategy/MovementStrategy.java b/src/main/java/domain/strategy/MovementStrategy.java new file mode 100644 index 0000000000..10f7867a99 --- /dev/null +++ b/src/main/java/domain/strategy/MovementStrategy.java @@ -0,0 +1,8 @@ +package domain.strategy; + +import domain.Position; +import java.util.List; + +public interface MovementStrategy { + List generatePaths(Position current); +} diff --git a/src/main/java/domain/OneStepStrategy.java b/src/main/java/domain/strategy/OneStepStrategy.java similarity index 83% rename from src/main/java/domain/OneStepStrategy.java rename to src/main/java/domain/strategy/OneStepStrategy.java index 0fe2a4460b..0a8f8faf48 100644 --- a/src/main/java/domain/OneStepStrategy.java +++ b/src/main/java/domain/strategy/OneStepStrategy.java @@ -1,5 +1,6 @@ -package domain; +package domain.strategy; +import domain.Position; import java.util.ArrayList; import java.util.List; @@ -11,7 +12,7 @@ public OneStepStrategy(List directions) { } @Override - public List generatePaths(Position current, BoardReader board) { + public List generatePaths(Position current) { List paths = new ArrayList<>(); for (Direction direction : directions) { if (current.canMove(direction)) { diff --git a/src/main/java/domain/Path.java b/src/main/java/domain/strategy/Path.java similarity index 93% rename from src/main/java/domain/Path.java rename to src/main/java/domain/strategy/Path.java index f4aa858279..399729b29c 100644 --- a/src/main/java/domain/Path.java +++ b/src/main/java/domain/strategy/Path.java @@ -1,5 +1,6 @@ -package domain; +package domain.strategy; +import domain.Position; import java.util.List; public class Path { diff --git a/src/main/java/domain/SequenceStrategy.java b/src/main/java/domain/strategy/SequenceStrategy.java similarity index 90% rename from src/main/java/domain/SequenceStrategy.java rename to src/main/java/domain/strategy/SequenceStrategy.java index db0f0c3147..6816089b9b 100644 --- a/src/main/java/domain/SequenceStrategy.java +++ b/src/main/java/domain/strategy/SequenceStrategy.java @@ -1,5 +1,6 @@ -package domain; +package domain.strategy; +import domain.Position; import java.util.ArrayList; import java.util.List; @@ -11,7 +12,7 @@ public SequenceStrategy(List> sequences) { } @Override - public List generatePaths(Position current, BoardReader board) { + public List generatePaths(Position current) { List paths = new ArrayList<>(); for (List sequence : sequences) { Path path = createPath(current, sequence); diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 5b393cbce9..73b78fa691 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,6 +1,6 @@ package view; -import domain.Piece; +import domain.piece.Piece; import domain.Position; import domain.Side; import java.util.List; diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index 4883eaa8be..50d19137d3 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -3,6 +3,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import domain.board.Board; +import domain.board.BoardFactory; +import domain.board.Formation; +import domain.board.FormationCommand; +import domain.piece.Piece; +import domain.piece.PieceFactory; +import domain.player.Name; +import domain.player.Players; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/BoardTest.java b/src/test/java/domain/board/BoardTest.java similarity index 95% rename from src/test/java/domain/BoardTest.java rename to src/test/java/domain/board/BoardTest.java index 993f284499..c4d6dd9263 100644 --- a/src/test/java/domain/BoardTest.java +++ b/src/test/java/domain/board/BoardTest.java @@ -1,8 +1,17 @@ -package domain; +package domain.board; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import domain.MovablePositions; +import domain.Position; +import domain.Side; +import domain.piece.Chariot; +import domain.piece.Elephant; +import domain.piece.General; +import domain.piece.Horse; +import domain.piece.Piece; +import domain.piece.PieceFactory; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/domain/FormationCommandTest.java b/src/test/java/domain/board/FormationCommandTest.java similarity index 97% rename from src/test/java/domain/FormationCommandTest.java rename to src/test/java/domain/board/FormationCommandTest.java index b1c52e1149..737cf9b4e4 100644 --- a/src/test/java/domain/FormationCommandTest.java +++ b/src/test/java/domain/board/FormationCommandTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.board; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/domain/FormationTest.java b/src/test/java/domain/board/FormationTest.java similarity index 88% rename from src/test/java/domain/FormationTest.java rename to src/test/java/domain/board/FormationTest.java index fd1e4028dd..2d69c91be0 100644 --- a/src/test/java/domain/FormationTest.java +++ b/src/test/java/domain/board/FormationTest.java @@ -1,7 +1,12 @@ -package domain; +package domain.board; import static org.assertj.core.api.Assertions.assertThat; +import domain.Position; +import domain.Side; +import domain.piece.Elephant; +import domain.piece.Horse; +import domain.piece.Piece; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/CannonTest.java b/src/test/java/domain/piece/CannonTest.java similarity index 96% rename from src/test/java/domain/CannonTest.java rename to src/test/java/domain/piece/CannonTest.java index 2e0699c3a1..8acdd21c4e 100644 --- a/src/test/java/domain/CannonTest.java +++ b/src/test/java/domain/piece/CannonTest.java @@ -1,7 +1,11 @@ -package domain; +package domain.piece; import static org.assertj.core.api.Assertions.assertThat; +import domain.MovablePositions; +import domain.Position; +import domain.Side; +import domain.board.Board; import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/ChariotTest.java b/src/test/java/domain/piece/ChariotTest.java similarity index 95% rename from src/test/java/domain/ChariotTest.java rename to src/test/java/domain/piece/ChariotTest.java index 2425b90cdc..a485c66cce 100644 --- a/src/test/java/domain/ChariotTest.java +++ b/src/test/java/domain/piece/ChariotTest.java @@ -1,7 +1,11 @@ -package domain; +package domain.piece; import static org.assertj.core.api.Assertions.assertThat; +import domain.MovablePositions; +import domain.Position; +import domain.Side; +import domain.board.Board; import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/ElephantTest.java b/src/test/java/domain/piece/ElephantTest.java similarity index 95% rename from src/test/java/domain/ElephantTest.java rename to src/test/java/domain/piece/ElephantTest.java index e973358283..98a91afdee 100644 --- a/src/test/java/domain/ElephantTest.java +++ b/src/test/java/domain/piece/ElephantTest.java @@ -1,7 +1,11 @@ -package domain; +package domain.piece; import static org.assertj.core.api.Assertions.assertThat; +import domain.MovablePositions; +import domain.Position; +import domain.Side; +import domain.board.Board; import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/GeneralTest.java b/src/test/java/domain/piece/GeneralTest.java similarity index 88% rename from src/test/java/domain/GeneralTest.java rename to src/test/java/domain/piece/GeneralTest.java index 8314f9ce5b..0e57f42a0c 100644 --- a/src/test/java/domain/GeneralTest.java +++ b/src/test/java/domain/piece/GeneralTest.java @@ -1,7 +1,11 @@ -package domain; +package domain.piece; import static org.assertj.core.api.Assertions.assertThat; +import domain.MovablePositions; +import domain.Position; +import domain.Side; +import domain.board.Board; import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/GuardTest.java b/src/test/java/domain/piece/GuardTest.java similarity index 86% rename from src/test/java/domain/GuardTest.java rename to src/test/java/domain/piece/GuardTest.java index e9e80bad8b..4fb0ce785b 100644 --- a/src/test/java/domain/GuardTest.java +++ b/src/test/java/domain/piece/GuardTest.java @@ -1,7 +1,11 @@ -package domain; +package domain.piece; import static org.assertj.core.api.Assertions.assertThat; +import domain.MovablePositions; +import domain.Position; +import domain.Side; +import domain.board.Board; import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/HorseTest.java b/src/test/java/domain/piece/HorseTest.java similarity index 94% rename from src/test/java/domain/HorseTest.java rename to src/test/java/domain/piece/HorseTest.java index e37288aeba..a4cb899478 100644 --- a/src/test/java/domain/HorseTest.java +++ b/src/test/java/domain/piece/HorseTest.java @@ -1,7 +1,11 @@ -package domain; +package domain.piece; import static org.assertj.core.api.Assertions.assertThat; +import domain.MovablePositions; +import domain.Position; +import domain.Side; +import domain.board.Board; import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/PieceTest.java b/src/test/java/domain/piece/PieceTest.java similarity index 95% rename from src/test/java/domain/PieceTest.java rename to src/test/java/domain/piece/PieceTest.java index 77b0c74897..7b7751dab8 100644 --- a/src/test/java/domain/PieceTest.java +++ b/src/test/java/domain/piece/PieceTest.java @@ -1,8 +1,9 @@ -package domain; +package domain.piece; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; +import domain.Side; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/src/test/java/domain/SoldierTest.java b/src/test/java/domain/piece/SoldierTest.java similarity index 91% rename from src/test/java/domain/SoldierTest.java rename to src/test/java/domain/piece/SoldierTest.java index 580593a31c..b33ebcb64d 100644 --- a/src/test/java/domain/SoldierTest.java +++ b/src/test/java/domain/piece/SoldierTest.java @@ -1,7 +1,10 @@ -package domain; +package domain.piece; import static org.assertj.core.api.Assertions.assertThat; +import domain.Position; +import domain.Side; +import domain.board.Board; import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/NameTest.java b/src/test/java/domain/player/NameTest.java similarity index 98% rename from src/test/java/domain/NameTest.java rename to src/test/java/domain/player/NameTest.java index d967f3bfa0..c0abff4945 100644 --- a/src/test/java/domain/NameTest.java +++ b/src/test/java/domain/player/NameTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.player; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/player/PlayerTest.java similarity index 93% rename from src/test/java/domain/PlayerTest.java rename to src/test/java/domain/player/PlayerTest.java index d53fc63c59..5f8f254f39 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/player/PlayerTest.java @@ -1,8 +1,12 @@ -package domain; +package domain.player; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import domain.Side; +import domain.piece.Piece; +import domain.piece.PieceFactory; +import domain.state.ActiveTurn; import org.junit.jupiter.api.Test; class PlayerTest { diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/player/PlayersTest.java similarity index 94% rename from src/test/java/domain/PlayersTest.java rename to src/test/java/domain/player/PlayersTest.java index 4838176480..7303fac791 100644 --- a/src/test/java/domain/PlayersTest.java +++ b/src/test/java/domain/player/PlayersTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.player; import static org.assertj.core.api.Assertions.assertThatThrownBy; From 23862a71717f13bd86aedb92da7c22c0cb3ac9a8 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 15:36:51 +0900 Subject: [PATCH 48/92] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EB=B0=8F=20=EC=B6=95=EC=95=BD=EB=90=9C=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=20=EB=AA=85=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 --- ...ovablePositions.java => Destinations.java} | 15 ++++++++++-- src/main/java/domain/Game.java | 15 ++++++------ src/main/java/domain/GameManager.java | 6 ++--- src/main/java/domain/board/Board.java | 21 +++++++--------- .../domain/strategy/ContinuousStrategy.java | 8 +++---- src/main/java/domain/strategy/Path.java | 8 +++---- .../domain/strategy/SequenceStrategy.java | 8 +++---- src/test/java/domain/GameTest.java | 10 ++++---- src/test/java/domain/board/BoardTest.java | 24 +++++++++---------- src/test/java/domain/piece/CannonTest.java | 10 ++++---- src/test/java/domain/piece/ChariotTest.java | 8 +++---- src/test/java/domain/piece/ElephantTest.java | 6 ++--- src/test/java/domain/piece/GeneralTest.java | 4 ++-- src/test/java/domain/piece/GuardTest.java | 4 ++-- src/test/java/domain/piece/HorseTest.java | 6 ++--- 15 files changed, 80 insertions(+), 73 deletions(-) rename src/main/java/domain/{MovablePositions.java => Destinations.java} (52%) diff --git a/src/main/java/domain/MovablePositions.java b/src/main/java/domain/Destinations.java similarity index 52% rename from src/main/java/domain/MovablePositions.java rename to src/main/java/domain/Destinations.java index d1f5e52f1b..4b96f371fa 100644 --- a/src/main/java/domain/MovablePositions.java +++ b/src/main/java/domain/Destinations.java @@ -2,13 +2,20 @@ import java.util.List; -public class MovablePositions { +public class Destinations { private final List positions; - public MovablePositions(List positions) { + public Destinations(List positions) { + validate(positions); this.positions = List.copyOf(positions); } + private void validate(List positions) { + if (positions.isEmpty()) { + throw new IllegalArgumentException("이동 가능한 목적지가 없습니다."); + } + } + public List getPositions() { return positions; } @@ -18,4 +25,8 @@ public void validateDestinations(Position target) { throw new IllegalArgumentException("선택할 수 없는 기물입니다."); } } + + public boolean isEmpty() { + return positions.isEmpty(); + } } diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index acf13fb92f..b0d44bc8f6 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -18,24 +18,25 @@ public Side getCurrentSide() { return players.getCurrentSide(); } - public MovablePositions selectSource(Position position) { + public Destinations selectSource(Position position) { Piece piece = board.getPiece(position); players.getCurrentPlayer().validateAlly(piece); return findMovablePositions(position); } - public void move(Position from, Position to) { - selectSource(from).validateDestinations(to); - movePiece(from, to); + public void move(Position source, Position target) { + Destinations destinations = selectSource(source); + destinations.validateDestinations(target); + movePiece(source, target); players.switchPlayer(); } - private MovablePositions findMovablePositions(Position position) { + private Destinations findMovablePositions(Position position) { return board.findMovablePositions(position); } - private void movePiece(Position from, Position to) { - this.board = board.movePiece(from, to); + private void movePiece(Position source, Position target) { + this.board = board.movePiece(source, target); } public Map getBoard() { diff --git a/src/main/java/domain/GameManager.java b/src/main/java/domain/GameManager.java index e6c5ae2746..0d23669ede 100644 --- a/src/main/java/domain/GameManager.java +++ b/src/main/java/domain/GameManager.java @@ -48,10 +48,10 @@ private Formation getFormation(Side side) { } private void playTurn(Game game) { - Position from = selectPiecePosition(game); + Position source = selectPiecePosition(game); retry(() -> { - Position to = InputParser.parsePosition(inputView.readTargetPosition()); - game.move(from, to); + Position target = InputParser.parsePosition(inputView.readTargetPosition()); + game.move(source, target); }); outputView.printBoard(game.getBoard()); } diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java index 33972ad4e2..1bae94d13f 100644 --- a/src/main/java/domain/board/Board.java +++ b/src/main/java/domain/board/Board.java @@ -1,6 +1,6 @@ package domain.board; -import domain.MovablePositions; +import domain.Destinations; import domain.piece.Piece; import domain.Position; import java.util.HashMap; @@ -13,7 +13,7 @@ public Board(Map board) { this.board = Map.copyOf(board); } - public MovablePositions findMovablePositions(Position position) { + public Destinations findMovablePositions(Position position) { validatePieceExists(position); Piece piece = board.get(position); return piece.findMovablePositions(position, this); @@ -25,13 +25,10 @@ private void validatePieceExists(Position position) { } } - public Board movePiece(Position from, Position to) { + public Board movePiece(Position source, Position target) { Map nextBoardMap = new HashMap<>(this.board); - Piece movingPiece = nextBoardMap.remove(from); - if (movingPiece == null) { - throw new IllegalArgumentException("출발지에 기물이 없습니다."); - } - nextBoardMap.put(to, movingPiece); + Piece movingPiece = nextBoardMap.remove(source); + nextBoardMap.put(target, movingPiece); return new Board(nextBoardMap); } @@ -41,11 +38,6 @@ public boolean isGameOver() { .count() < 2; } - @Override - public boolean isWithinRange(Position position) { - return Position.isWithinRange(position.getX(), position.getY()); - } - @Override public boolean isEmpty(Position position) { return !board.containsKey(position); @@ -53,6 +45,9 @@ public boolean isEmpty(Position position) { @Override public Piece getPiece(Position position) { + if (isEmpty(position)) { + throw new IllegalArgumentException("선택한 좌표에 기물이 존재하지 않습니다."); + } return board.get(position); } diff --git a/src/main/java/domain/strategy/ContinuousStrategy.java b/src/main/java/domain/strategy/ContinuousStrategy.java index 6023de1e57..982cf3ed67 100644 --- a/src/main/java/domain/strategy/ContinuousStrategy.java +++ b/src/main/java/domain/strategy/ContinuousStrategy.java @@ -25,11 +25,11 @@ public List generatePaths(Position current) { private Path createPath(Position current, Direction direction) { List positions = new ArrayList<>(); - Position pos = current; + Position position = current; - while (pos.canMove(direction)) { - pos = pos.move(direction); - positions.add(pos); + while (position.canMove(direction)) { + position = position.move(direction); + positions.add(position); } return new Path(positions); } diff --git a/src/main/java/domain/strategy/Path.java b/src/main/java/domain/strategy/Path.java index 399729b29c..50022309e8 100644 --- a/src/main/java/domain/strategy/Path.java +++ b/src/main/java/domain/strategy/Path.java @@ -10,10 +10,6 @@ public Path(List positions) { this.positions = List.copyOf(positions); } - public boolean isEmpty() { - return positions.isEmpty(); - } - public Position getDestination() { if (isEmpty()) { throw new IllegalStateException("경로가 존재하지 않습니다."); @@ -28,6 +24,10 @@ public List getObstacles() { return positions.subList(0, positions.size() - 1); } + public boolean isEmpty() { + return positions.isEmpty(); + } + public List getPositions() { return positions; } diff --git a/src/main/java/domain/strategy/SequenceStrategy.java b/src/main/java/domain/strategy/SequenceStrategy.java index 6816089b9b..756a18a47e 100644 --- a/src/main/java/domain/strategy/SequenceStrategy.java +++ b/src/main/java/domain/strategy/SequenceStrategy.java @@ -25,14 +25,14 @@ public List generatePaths(Position current) { private Path createPath(Position current, List sequence) { List positions = new ArrayList<>(); - Position pos = current; + Position position = current; for (Direction direction : sequence) { - if (!pos.canMove(direction)) { + if (!position.canMove(direction)) { return new Path(List.of()); } - pos = pos.move(direction); - positions.add(pos); + position = position.move(direction); + positions.add(position); } return new Path(positions); } diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index 50d19137d3..929e400b0c 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -29,11 +29,11 @@ void setUp() { @Test void 기물_이동이_완료되면_턴이_상대방_진영으로_변경된다() { // Given: 초나라 졸(0,3)을 (0,4)로 전진 - Position from = Position.of(0, 3); - Position to = Position.of(0, 4); + Position source = Position.of(0, 3); + Position target = Position.of(0, 4); // When - game.move(from, to); + game.move(source, target); // Then assertThat(game.getCurrentSide()).isEqualTo(Side.HAN); @@ -59,11 +59,11 @@ void setUp() { @Test void 선택한_기물이_이동할_수_없는_위치를_목적지로_입력하면_예외가_발생한다() { // Given: 초나라 졸(0,3) 선택 (졸은 대각선 이동 불가) - Position from = Position.of(0, 3); + Position source = Position.of(0, 3); Position invalidTo = Position.of(1, 4); // When & Then: validateDestinations(to) 호출 시 예외 발생 - assertThatThrownBy(() -> game.move(from, invalidTo)) + assertThatThrownBy(() -> game.move(source, invalidTo)) .isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/domain/board/BoardTest.java b/src/test/java/domain/board/BoardTest.java index c4d6dd9263..2cc5bf52f7 100644 --- a/src/test/java/domain/board/BoardTest.java +++ b/src/test/java/domain/board/BoardTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import domain.MovablePositions; +import domain.Destinations; import domain.Position; import domain.Side; import domain.piece.Chariot; @@ -73,22 +73,22 @@ class BoardTest { @DisplayName("목적지에 적군 기물이 있으면 보드에서 해당 기물을 제거하고 이동한다") void captureEnemy() { // Given: (0, 3)에 초나라 졸, (0, 4)에 한나라 졸 배치 - Position from = Position.of(0, 3); - Position to = Position.of(0, 4); + Position source = Position.of(0, 3); + Position target = Position.of(0, 4); Piece choSoldier = PieceFactory.createSoldier(Side.CHO); Piece hanSoldier = PieceFactory.createSoldier(Side.HAN); Board board = new Board(Map.of( - from, choSoldier, - to, hanSoldier + source, choSoldier, + target, hanSoldier )); // When: (0, 3)의 초나라 졸이 (0, 4)의 한나라 졸을 잡음 - Board movedBoard = board.movePiece(from, to); + Board movedBoard = board.movePiece(source, target); // Then: 출발지는 비어있고, 도착지에는 초나라 졸이 위치함 - assertThat(movedBoard.isEmpty(from)).isTrue(); - assertThat(movedBoard.getPiece(to)).isSameAs(choSoldier); + assertThat(movedBoard.isEmpty(source)).isTrue(); + assertThat(movedBoard.getPiece(target)).isSameAs(choSoldier); } @Test @@ -97,14 +97,14 @@ void moveEmptySource() { // Given: 비어있는 보드 Board board = new Board(Map.of()); Position emptyPos = Position.of(0, 0); - Position to = Position.of(0, 1); + Position target = Position.of(0, 1); // When & Then: 빈 좌표를 선택하여 이동을 시도하거나 이동 가능한 위치를 찾을 때 예외 발생 assertThatThrownBy(() -> board.findMovablePositions(emptyPos)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("기물이 존재하지 않는 위치입니다."); - assertThatThrownBy(() -> board.movePiece(emptyPos, to)) + assertThatThrownBy(() -> board.movePiece(emptyPos, target)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("출발지에 기물이 없습니다."); } @@ -121,9 +121,9 @@ void validateAllyAtDestination() { )); // When: 차(Chariot)의 이동 가능 위치 탐색 - MovablePositions movablePositions = board.findMovablePositions(chariotPos); + Destinations destinations = board.findMovablePositions(chariotPos); // Then: 아군이 있는 (0, 1)은 목적지 목록에 포함되지 않음 - assertThat(movablePositions.getPositions()).doesNotContain(allyPos); + assertThat(destinations.getPositions()).doesNotContain(allyPos); } } diff --git a/src/test/java/domain/piece/CannonTest.java b/src/test/java/domain/piece/CannonTest.java index 8acdd21c4e..bc1ba14bf7 100644 --- a/src/test/java/domain/piece/CannonTest.java +++ b/src/test/java/domain/piece/CannonTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import domain.MovablePositions; +import domain.Destinations; import domain.Position; import domain.Side; import domain.board.Board; @@ -25,7 +25,7 @@ void jumpOverBridge() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 다리(1, 3) 이전인 (1, 2)는 못 가고, 다리 너머인 (1, 4)부터 끝까지 이동 가능 assertThat(movable.getPositions()).doesNotContain(Position.of(1, 2)); @@ -45,7 +45,7 @@ void cannotJumpOverAnotherCannon() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 다리가 포(Cannon)인 경우 뛰어넘을 수 없으므로 이동 가능한 좌표가 없어야 함 assertThat(movable.getPositions()).isEmpty(); @@ -66,7 +66,7 @@ void captureEnemy() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 적군(1, 5)까지는 갈 수 있지만, 그 너머(1, 6)는 갈 수 없음 assertThat(movable.getPositions()).contains(Position.of(1, 4), Position.of(1, 5)); @@ -88,7 +88,7 @@ void cannotCaptureEnemyCannon() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 적군 포(1, 5) 직전인 (1, 4)까지만 갈 수 있고 (1, 5)는 포함되지 않음 assertThat(movable.getPositions()).contains(Position.of(1, 4)); diff --git a/src/test/java/domain/piece/ChariotTest.java b/src/test/java/domain/piece/ChariotTest.java index a485c66cce..fb56554c74 100644 --- a/src/test/java/domain/piece/ChariotTest.java +++ b/src/test/java/domain/piece/ChariotTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import domain.MovablePositions; +import domain.Destinations; import domain.Position; import domain.Side; import domain.board.Board; @@ -21,7 +21,7 @@ void move() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 같은 행(0, 1~9)과 같은 열(1~8, 0)의 모든 위치가 포함되어야 함 (총 9+8=17개) assertThat(movable.getPositions()).hasSize(17); @@ -41,7 +41,7 @@ void allyObstacle() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: (0, 1), (0, 2)는 가능하지만 (0, 3)과 그 너머(0, 4)는 불가능해야 함 assertThat(movable.getPositions()).contains(Position.of(0, 1), Position.of(0, 2)); @@ -61,7 +61,7 @@ void enemyCapture() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 적군이 있는 (5, 0)까지는 이동 가능하지만, 그 너머(6, 0)는 불가능해야 함 assertThat(movable.getPositions()).contains(Position.of(1, 0), Position.of(4, 0), Position.of(5, 0)); diff --git a/src/test/java/domain/piece/ElephantTest.java b/src/test/java/domain/piece/ElephantTest.java index 98a91afdee..50f7aae4fb 100644 --- a/src/test/java/domain/piece/ElephantTest.java +++ b/src/test/java/domain/piece/ElephantTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import domain.MovablePositions; +import domain.Destinations; import domain.Position; import domain.Side; import domain.board.Board; @@ -21,7 +21,7 @@ void move() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 8방향의 최종 목적지 확인 assertThat(movable.getPositions()).containsExactlyInAnyOrder( @@ -48,7 +48,7 @@ void bridge() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then // 1. 북쪽 직진 멱이 막혔으므로 (6, 7)과 (2, 7)은 없어야 함 diff --git a/src/test/java/domain/piece/GeneralTest.java b/src/test/java/domain/piece/GeneralTest.java index 0e57f42a0c..64162aaa2f 100644 --- a/src/test/java/domain/piece/GeneralTest.java +++ b/src/test/java/domain/piece/GeneralTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import domain.MovablePositions; +import domain.Destinations; import domain.Position; import domain.Side; import domain.board.Board; @@ -22,7 +22,7 @@ void move() { ); Board board = new Board(pieces); - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); assertThat(movable.getPositions()).containsExactlyInAnyOrder( Position.of(4, 0), Position.of(3, 1), Position.of(5, 1) diff --git a/src/test/java/domain/piece/GuardTest.java b/src/test/java/domain/piece/GuardTest.java index 4fb0ce785b..8baf2f8a88 100644 --- a/src/test/java/domain/piece/GuardTest.java +++ b/src/test/java/domain/piece/GuardTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import domain.MovablePositions; +import domain.Destinations; import domain.Position; import domain.Side; import domain.board.Board; @@ -21,7 +21,7 @@ void move() { ); Board board = new Board(pieces); - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); assertThat(movable.getPositions()).containsExactlyInAnyOrder( Position.of(4, 1), Position.of(2, 1), Position.of(3,0) diff --git a/src/test/java/domain/piece/HorseTest.java b/src/test/java/domain/piece/HorseTest.java index a4cb899478..0ffdbbab21 100644 --- a/src/test/java/domain/piece/HorseTest.java +++ b/src/test/java/domain/piece/HorseTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import domain.MovablePositions; +import domain.Destinations; import domain.Position; import domain.Side; import domain.board.Board; @@ -21,7 +21,7 @@ void move() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 8방향의 L자형 목적지 확인 assertThat(movable.getPositions()).containsExactlyInAnyOrder( @@ -45,7 +45,7 @@ void bridge() { Board board = new Board(pieces); // When - MovablePositions movable = board.findMovablePositions(current); + Destinations movable = board.findMovablePositions(current); // Then: 북쪽 멱이 막혔으므로 북쪽 대각선 목적지인 (3, 6)과 (5, 6)은 제외되어야 함 assertThat(movable.getPositions()).doesNotContain( From f660c3f6146ac87b15ff9af235101949c93e2d0a Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 15:37:44 +0900 Subject: [PATCH 49/92] =?UTF-8?q?refactor(OutputView):=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=20=ED=8F=AC=EB=A7=B7=EC=9D=84=20=EC=A0=84=EA=B0=81=20?= =?UTF-8?q?=EC=9C=A0=EB=8B=88=EC=BD=94=EB=93=9C=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/OutputView.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 73b78fa691..0897f24c2b 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -11,13 +11,21 @@ public class OutputView { private static final String RED = "\u001B[31m"; private static final String BLUE = "\u001B[34m"; private static final String RESET = "\u001B[0m"; + private static final String EMPTY = "\uFF0B"; + private static final String SPACE = "\u3000"; + private static final List NUMBERS = List.of( + "\uFF10", "\uFF11", "\uFF12", "\uFF13", "\uFF14", + "\uFF15", "\uFF16", "\uFF17", "\uFF18", "\uFF19" + ); public void printBoard(Map board) { System.out.println(); - System.out.println(" 0 1 2 3 4 5 6 7 8"); + System.out.print(SPACE.repeat(3)); + NUMBERS.stream().filter(number -> !number.equals("\uFF19")).forEach(number -> System.out.print(number + SPACE)); + System.out.println(); for (int y = 9; y >= 0; y--) { - System.out.print(" " + y + " "); + System.out.print(SPACE + NUMBERS.get(y) + SPACE); for (int x = 0; x <= 8; x++) { Position position = Position.of(x, y); if (board.containsKey(position)) { @@ -25,7 +33,7 @@ public void printBoard(Map board) { System.out.print(RESET); continue; } - System.out.printf("%-3s", "+"); + System.out.printf(EMPTY + SPACE); } System.out.println(); } @@ -33,7 +41,7 @@ public void printBoard(Map board) { private void printPiece(Piece piece) { String color = getColor(piece.getSide()); - System.out.printf(color + "%-3s", piece); + System.out.printf(color + piece + SPACE); } private String getColor(Side side) { From c90daf6005249a71dbd78f638bca43aa30482717 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 15:38:04 +0900 Subject: [PATCH 50/92] =?UTF-8?q?refactor(Side):=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=95=88=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=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 --- src/main/java/domain/Side.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java index aaf5e1a7a8..3ac2633bfb 100644 --- a/src/main/java/domain/Side.java +++ b/src/main/java/domain/Side.java @@ -76,10 +76,6 @@ public boolean isCho() { return this.equals(Side.CHO); } - public boolean isHan() { - return this.equals(Side.HAN); - } - public String getName() { return name; } From 20598397576012cf396e160ed036df6d0bbfa8c8 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 15:38:47 +0900 Subject: [PATCH 51/92] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EB=B0=8F=20=EC=B6=95=EC=95=BD=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=AA=85=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/main/java/domain/piece/Piece.java | 14 +++++++------- src/main/java/domain/piece/Soldier.java | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 106359a893..c49f0b1d6f 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -1,6 +1,6 @@ package domain.piece; -import domain.MovablePositions; +import domain.Destinations; import domain.strategy.Path; import domain.Position; import domain.Side; @@ -18,10 +18,10 @@ protected Piece(Side side, MovementStrategy movementStrategy) { this.movementStrategy = movementStrategy; } - public MovablePositions findMovablePositions(Position current, BoardReader board) { - List paths = movementStrategy.generatePaths(current, board); + public Destinations findMovablePositions(Position current, BoardReader board) { + List paths = movementStrategy.generatePaths(current); List validDestinations = filterValidPositions(current, paths, board); - return new MovablePositions(validDestinations); + return new Destinations(validDestinations); } protected abstract List filterValidPositions(Position current, List paths, BoardReader board); @@ -45,12 +45,12 @@ private boolean isObstaclesClear(Path path, BoardReader board) { return true; } - protected boolean isValidDestination(Position dest, BoardReader board) { - return board.isEmpty(dest) || !board.getPiece(dest).isAlly(side); + protected boolean isValidDestination(Position destination, BoardReader board) { + return board.isEmpty(destination) || !board.getPiece(destination).isAlly(side); } public boolean isAlly(Side other) { - return this.side == other; + return this.side.isAlly(other); } public Side getSide() { diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java index f118e290e0..909923fe0f 100644 --- a/src/main/java/domain/piece/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -17,22 +17,22 @@ public Soldier(Side side, MovementStrategy movementStrategy) { protected List filterValidPositions(Position current, List paths, BoardReader board) { List valid = new ArrayList<>(); for (Path path : paths) { - Position dest = path.getDestination(); - if (isBackward(current, dest)) { + Position destination = path.getDestination(); + if (isBackward(current, destination)) { continue; } - if (isValidDestination(dest, board)) { - valid.add(dest); + if (isValidDestination(destination, board)) { + valid.add(destination); } } return valid; } - private boolean isBackward(Position current, Position dest) { + private boolean isBackward(Position current, Position destination) { if (getSide().isCho()) { - return dest.getY() < current.getY(); + return destination.getY() < current.getY(); } - return dest.getY() > current.getY(); + return destination.getY() > current.getY(); } @Override From d4dfde29f969057ed4d3bb1130b0a2c224ea4479 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 15:39:08 +0900 Subject: [PATCH 52/92] =?UTF-8?q?refactor:=20Depth=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/domain/piece/Cannon.java | 46 +++++++++++++++---------- src/main/java/domain/piece/Chariot.java | 31 +++++++++-------- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index 7f8b46d7aa..4064f7395d 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -34,12 +34,18 @@ private void addJumpPathPositions(List valid, Path path, BoardReader b } private int findBridgeIndex(List positions, BoardReader board) { - for (int i = 0; i < positions.size(); i++) { - if (!board.isEmpty(positions.get(i))) { - return i; - } + int index = 0; + while (index < positions.size() && board.isEmpty(positions.get(index))) { + index++; } - return -1; + return findValidIndex(index, positions.size()); + } + + private int findValidIndex(int index, int size) { + if (index == size) { + return -1; + } + return index; } private boolean isInvalidBridge(int index, List positions, BoardReader board) { @@ -51,27 +57,29 @@ private boolean isInvalidBridge(int index, List positions, BoardReader } private void collectValidDestinations(List valid, List positions, int startIndex, BoardReader board) { - for (int i = startIndex; i < positions.size(); i++) { - if (processPositionAfterJump(valid, positions.get(i), board)) { - break; - } - } + int obstacleIndex = findObstacleIndex(positions, startIndex, board); + valid.addAll(positions.subList(startIndex, obstacleIndex)); + addCatchableIfPossible(valid, positions, obstacleIndex, board); } - private boolean processPositionAfterJump(List valid, Position pos, BoardReader board) { - if (board.isEmpty(pos)) { - valid.add(pos); - return false; + private int findObstacleIndex(List positions, int startIndex, BoardReader board) { + int index = startIndex; + while (index < positions.size() && board.isEmpty(positions.get(index))) { + index++; } + return index; + } - addIfCatchable(valid, pos, board); - return true; + private void addCatchableIfPossible(List valid, List positions, int index, BoardReader board) { + if (index < positions.size()) { + addIfCatchable(valid, positions.get(index), board); + } } - private void addIfCatchable(List valid, Position pos, BoardReader board) { - Piece target = board.getPiece(pos); + private void addIfCatchable(List valid, Position position, BoardReader board) { + Piece target = board.getPiece(position); if (!target.isCannon() && !target.isAlly(getSide())) { - valid.add(pos); + valid.add(position); } } diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java index e2c72b1f92..f95c4247d8 100644 --- a/src/main/java/domain/piece/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -23,27 +23,30 @@ protected List filterValidPositions(Position current, List paths } private void collectPathPositions(List valid, Path path, BoardReader board) { - for (Position pos : path.getPositions()) { - if (processPosition(valid, pos, board)) { - break; - } - } + List positions = path.getPositions(); + int obstacleIndex = findObstacleIndex(positions, board); + valid.addAll(positions.subList(0, obstacleIndex)); + addCaptureIfPossible(valid, positions, obstacleIndex, board); } - private boolean processPosition(List valid, Position pos, BoardReader board) { - if (board.isEmpty(pos)) { - valid.add(pos); - return false; + private int findObstacleIndex(List positions, BoardReader board) { + int index = 0; + while (index < positions.size() && board.isEmpty(positions.get(index))) { + index++; } + return index; + } - addIfEnemy(valid, pos, board); - return true; + private void addCaptureIfPossible(List valid, List positions, int index, BoardReader board) { + if (index < positions.size()) { + addIfEnemy(valid, positions.get(index), board); + } } - private void addIfEnemy(List valid, Position pos, BoardReader board) { - Piece target = board.getPiece(pos); + private void addIfEnemy(List valid, Position position, BoardReader board) { + Piece target = board.getPiece(position); if (!target.isAlly(getSide())) { - valid.add(pos); + valid.add(position); } } From 08c2567733561e3eea85b84d583de814fb124116 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 15:50:54 +0900 Subject: [PATCH 53/92] =?UTF-8?q?refactor:=20import=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++------- src/main/java/domain/board/Board.java | 2 +- src/main/java/domain/board/BoardFactory.java | 4 ++-- src/main/java/domain/board/BoardReader.java | 2 +- src/main/java/domain/board/Formation.java | 4 ++-- src/main/java/domain/piece/Cannon.java | 4 ++-- src/main/java/domain/piece/Chariot.java | 4 ++-- src/main/java/domain/piece/Elephant.java | 4 ++-- src/main/java/domain/piece/General.java | 4 ++-- src/main/java/domain/piece/Guard.java | 4 ++-- src/main/java/domain/piece/Horse.java | 4 ++-- src/main/java/domain/piece/Piece.java | 2 +- src/main/java/domain/piece/PieceFactory.java | 2 +- src/main/java/domain/player/Player.java | 2 +- src/main/java/domain/player/Players.java | 2 +- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 6b2b999977..0a884c72c8 100644 --- a/README.md +++ b/README.md @@ -170,18 +170,18 @@ ## 프로그래밍 요구 사항 -- [ ] 자바 코드 컨벤션을 지키면서 프로그래밍한다. - - [ ] 기본적으로[Java Style Guide](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java)을 원칙으로 한다. +- [x] 자바 코드 컨벤션을 지키면서 프로그래밍한다. + - [x] 기본적으로[Java Style Guide](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java)을 원칙으로 한다. - [ ] indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다. - [ ] 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - [ ] 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. -- [ ] 3항 연산자를 쓰지 않는다. -- [ ] else 예약어를 쓰지 않는다. - - [ ] else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. - - [ ] 힌트: if문에서 값을 반환하는 방식으로 구현하면 else 예약어를 사용하지 않아도 된다. +- [x] 3항 연산자를 쓰지 않는다. +- [x] else 예약어를 쓰지 않는다. + - [x] else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. + - [x] 힌트: if문에서 값을 반환하는 방식으로 구현하면 else 예약어를 사용하지 않아도 된다. - [ ] 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외 - [ ] 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다. - - [ ] UI 로직을 view.InputView, ResultView와 같은 클래스를 추가해 분리한다. + - [x] UI 로직을 view.InputView, ResultView와 같은 클래스를 추가해 분리한다. - [ ] 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. - [ ] 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라. - [ ] 배열 대신 컬렉션을 사용한다. diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java index 1bae94d13f..6e76e90982 100644 --- a/src/main/java/domain/board/Board.java +++ b/src/main/java/domain/board/Board.java @@ -1,8 +1,8 @@ package domain.board; import domain.Destinations; -import domain.piece.Piece; import domain.Position; +import domain.piece.Piece; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/domain/board/BoardFactory.java b/src/main/java/domain/board/BoardFactory.java index d64dbea8f8..934664c316 100644 --- a/src/main/java/domain/board/BoardFactory.java +++ b/src/main/java/domain/board/BoardFactory.java @@ -1,9 +1,9 @@ package domain.board; -import domain.piece.Piece; -import domain.piece.PieceFactory; import domain.Position; import domain.Side; +import domain.piece.Piece; +import domain.piece.PieceFactory; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/domain/board/BoardReader.java b/src/main/java/domain/board/BoardReader.java index cdbb81feb9..9cd1b992c0 100644 --- a/src/main/java/domain/board/BoardReader.java +++ b/src/main/java/domain/board/BoardReader.java @@ -1,7 +1,7 @@ package domain.board; -import domain.piece.Piece; import domain.Position; +import domain.piece.Piece; public interface BoardReader { boolean isEmpty(Position position); diff --git a/src/main/java/domain/board/Formation.java b/src/main/java/domain/board/Formation.java index 0a9155682d..fe3dac9897 100644 --- a/src/main/java/domain/board/Formation.java +++ b/src/main/java/domain/board/Formation.java @@ -1,9 +1,9 @@ package domain.board; -import domain.piece.Piece; -import domain.piece.PieceFactory; import domain.Position; import domain.Side; +import domain.piece.Piece; +import domain.piece.PieceFactory; import java.util.Arrays; import java.util.List; import java.util.Map; diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index 4064f7395d..5d1d0fc092 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -1,10 +1,10 @@ package domain.piece; +import domain.Position; +import domain.Side; import domain.board.BoardReader; import domain.strategy.MovementStrategy; import domain.strategy.Path; -import domain.Position; -import domain.Side; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java index f95c4247d8..c04ca215e4 100644 --- a/src/main/java/domain/piece/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -1,10 +1,10 @@ package domain.piece; +import domain.Position; +import domain.Side; import domain.board.BoardReader; import domain.strategy.MovementStrategy; import domain.strategy.Path; -import domain.Position; -import domain.Side; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java index 2dd534bee2..ee0e0d5fbd 100644 --- a/src/main/java/domain/piece/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -1,10 +1,10 @@ package domain.piece; +import domain.Position; +import domain.Side; import domain.board.BoardReader; import domain.strategy.MovementStrategy; import domain.strategy.Path; -import domain.Position; -import domain.Side; import java.util.List; public class Elephant extends Piece { diff --git a/src/main/java/domain/piece/General.java b/src/main/java/domain/piece/General.java index 737d843c39..fcdc8208f7 100644 --- a/src/main/java/domain/piece/General.java +++ b/src/main/java/domain/piece/General.java @@ -1,10 +1,10 @@ package domain.piece; +import domain.Position; +import domain.Side; import domain.board.BoardReader; import domain.strategy.MovementStrategy; import domain.strategy.Path; -import domain.Position; -import domain.Side; import java.util.List; public class General extends Piece { diff --git a/src/main/java/domain/piece/Guard.java b/src/main/java/domain/piece/Guard.java index cb7abbe43c..1b3422d6bf 100644 --- a/src/main/java/domain/piece/Guard.java +++ b/src/main/java/domain/piece/Guard.java @@ -1,10 +1,10 @@ package domain.piece; +import domain.Position; +import domain.Side; import domain.board.BoardReader; import domain.strategy.MovementStrategy; import domain.strategy.Path; -import domain.Position; -import domain.Side; import java.util.List; public class Guard extends Piece { diff --git a/src/main/java/domain/piece/Horse.java b/src/main/java/domain/piece/Horse.java index fdc6498f36..63469b7abe 100644 --- a/src/main/java/domain/piece/Horse.java +++ b/src/main/java/domain/piece/Horse.java @@ -1,10 +1,10 @@ package domain.piece; +import domain.Position; +import domain.Side; import domain.board.BoardReader; import domain.strategy.MovementStrategy; import domain.strategy.Path; -import domain.Position; -import domain.Side; import java.util.List; public class Horse extends Piece { diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index c49f0b1d6f..25697e9a56 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -1,11 +1,11 @@ package domain.piece; import domain.Destinations; -import domain.strategy.Path; import domain.Position; import domain.Side; import domain.board.BoardReader; import domain.strategy.MovementStrategy; +import domain.strategy.Path; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/piece/PieceFactory.java b/src/main/java/domain/piece/PieceFactory.java index aec73a52f5..c4330c3a87 100644 --- a/src/main/java/domain/piece/PieceFactory.java +++ b/src/main/java/domain/piece/PieceFactory.java @@ -1,8 +1,8 @@ package domain.piece; -import domain.strategy.Direction; import domain.Side; import domain.strategy.ContinuousStrategy; +import domain.strategy.Direction; import domain.strategy.MovementStrategy; import domain.strategy.OneStepStrategy; import domain.strategy.SequenceStrategy; diff --git a/src/main/java/domain/player/Player.java b/src/main/java/domain/player/Player.java index 63a8cf0783..6f0ec9b044 100644 --- a/src/main/java/domain/player/Player.java +++ b/src/main/java/domain/player/Player.java @@ -1,7 +1,7 @@ package domain.player; -import domain.piece.Piece; import domain.Side; +import domain.piece.Piece; import domain.state.TurnState; public class Player { diff --git a/src/main/java/domain/player/Players.java b/src/main/java/domain/player/Players.java index 3c91ecc139..6157065fba 100644 --- a/src/main/java/domain/player/Players.java +++ b/src/main/java/domain/player/Players.java @@ -1,8 +1,8 @@ package domain.player; +import domain.Side; import domain.state.ActiveTurn; import domain.state.InactiveTurn; -import domain.Side; import java.util.List; public class Players { From 47bb454ba3fd61375c21da0f525be7d909f07e4c Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 15:51:46 +0900 Subject: [PATCH 54/92] =?UTF-8?q?refactor(OutputView):=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/OutputView.java | 42 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 0897f24c2b..11676cdfad 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,10 +1,11 @@ package view; -import domain.piece.Piece; import domain.Position; import domain.Side; +import domain.piece.Piece; import java.util.List; import java.util.Map; +import java.util.stream.IntStream; public class OutputView { @@ -20,28 +21,29 @@ public class OutputView { public void printBoard(Map board) { System.out.println(); - System.out.print(SPACE.repeat(3)); - NUMBERS.stream().filter(number -> !number.equals("\uFF19")).forEach(number -> System.out.print(number + SPACE)); - System.out.println(); - + System.out.println(buildHeader()); for (int y = 9; y >= 0; y--) { - System.out.print(SPACE + NUMBERS.get(y) + SPACE); - for (int x = 0; x <= 8; x++) { - Position position = Position.of(x, y); - if (board.containsKey(position)) { - printPiece(board.get(position)); - System.out.print(RESET); - continue; - } - System.out.printf(EMPTY + SPACE); - } - System.out.println(); + System.out.println(buildRow(board, y)); } } - private void printPiece(Piece piece) { - String color = getColor(piece.getSide()); - System.out.printf(color + piece + SPACE); + private String buildHeader() { + return SPACE.repeat(3) + String.join(SPACE, IntStream.range(0, 9) + .mapToObj(NUMBERS::get) + .toList()) + SPACE; + } + + private String buildRow(Map board, int y) { + return SPACE + NUMBERS.get(y) + SPACE + IntStream.rangeClosed(0, 8) + .mapToObj(x -> formatCell(board.get(Position.of(x, y)))) + .reduce("", String::concat); + } + + private String formatCell(Piece piece) { + if (piece == null) { + return EMPTY + SPACE; + } + return getColor(piece.getSide()) + piece + SPACE + RESET; } private String getColor(Side side) { @@ -56,7 +58,7 @@ public void printError(String message) { } public void printDestinations(List destinations) { - System.out.println(destinations); + System.out.println(String.join(", ", destinations.stream().map(Position::toString).toList())); } public void printWinner(String winner) { From 3b8a8f91d26bde850f6a2ab17da6a2a1fdd81819 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 15:52:11 +0900 Subject: [PATCH 55/92] =?UTF-8?q?refactor(Soldier):=20depth=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 --- src/main/java/domain/piece/Soldier.java | 28 ++++++++++--------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java index 909923fe0f..07468cab9b 100644 --- a/src/main/java/domain/piece/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -1,11 +1,10 @@ package domain.piece; +import domain.Position; +import domain.Side; import domain.board.BoardReader; import domain.strategy.MovementStrategy; import domain.strategy.Path; -import domain.Position; -import domain.Side; -import java.util.ArrayList; import java.util.List; public class Soldier extends Piece { @@ -15,24 +14,19 @@ public Soldier(Side side, MovementStrategy movementStrategy) { @Override protected List filterValidPositions(Position current, List paths, BoardReader board) { - List valid = new ArrayList<>(); - for (Path path : paths) { - Position destination = path.getDestination(); - if (isBackward(current, destination)) { - continue; - } - if (isValidDestination(destination, board)) { - valid.add(destination); - } - } - return valid; + return paths.stream() + .map(Path::getDestination) + .filter(destination -> isForwardOrSideways(current, destination)) + .filter(destination -> isValidDestination(destination, board)) + .toList(); } - private boolean isBackward(Position current, Position destination) { + private boolean isForwardOrSideways(Position current, Position destination) { + int deltaY = destination.getY() - current.getY(); if (getSide().isCho()) { - return destination.getY() < current.getY(); + return deltaY >= 0; } - return destination.getY() > current.getY(); + return deltaY <= 0; } @Override From 6d7b921c88fe4d506c84660af75410a67c994b80 Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 17:09:33 +0900 Subject: [PATCH 56/92] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=AA=85=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/main/java/domain/Game.java | 6 ++--- src/main/java/domain/board/Board.java | 21 ++++++++--------- src/main/java/domain/piece/Piece.java | 24 ++++++++++++-------- src/test/java/domain/board/BoardTest.java | 10 ++++---- src/test/java/domain/piece/CannonTest.java | 14 ++++++------ src/test/java/domain/piece/ChariotTest.java | 6 ++--- src/test/java/domain/piece/ElephantTest.java | 4 ++-- src/test/java/domain/piece/GeneralTest.java | 2 +- src/test/java/domain/piece/GuardTest.java | 2 +- src/test/java/domain/piece/HorseTest.java | 4 ++-- src/test/java/domain/piece/SoldierTest.java | 4 ++-- src/test/java/domain/player/PlayerTest.java | 2 +- 12 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index b0d44bc8f6..a9348f2ab7 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -21,7 +21,7 @@ public Side getCurrentSide() { public Destinations selectSource(Position position) { Piece piece = board.getPiece(position); players.getCurrentPlayer().validateAlly(piece); - return findMovablePositions(position); + return findDestinations(position); } public void move(Position source, Position target) { @@ -31,8 +31,8 @@ public void move(Position source, Position target) { players.switchPlayer(); } - private Destinations findMovablePositions(Position position) { - return board.findMovablePositions(position); + private Destinations findDestinations(Position position) { + return board.findDestinations(position); } private void movePiece(Position source, Position target) { diff --git a/src/main/java/domain/board/Board.java b/src/main/java/domain/board/Board.java index 6e76e90982..7da466b301 100644 --- a/src/main/java/domain/board/Board.java +++ b/src/main/java/domain/board/Board.java @@ -13,25 +13,26 @@ public Board(Map board) { this.board = Map.copyOf(board); } - public Destinations findMovablePositions(Position position) { + public Destinations findDestinations(Position position) { validatePieceExists(position); Piece piece = board.get(position); - return piece.findMovablePositions(position, this); - } - - private void validatePieceExists(Position position) { - if (!board.containsKey(position)) { - throw new IllegalArgumentException("기물이 존재하지 않는 위치입니다."); - } + return piece.findDestinations(position, this); } public Board movePiece(Position source, Position target) { + validatePieceExists(source); Map nextBoardMap = new HashMap<>(this.board); Piece movingPiece = nextBoardMap.remove(source); nextBoardMap.put(target, movingPiece); return new Board(nextBoardMap); } + private void validatePieceExists(Position position) { + if (isEmpty(position)) { + throw new IllegalArgumentException("기물이 존재하지 않는 위치입니다."); + } + } + public boolean isGameOver() { return board.values().stream() .filter(Piece::isGeneral) @@ -45,9 +46,7 @@ public boolean isEmpty(Position position) { @Override public Piece getPiece(Position position) { - if (isEmpty(position)) { - throw new IllegalArgumentException("선택한 좌표에 기물이 존재하지 않습니다."); - } + validatePieceExists(position); return board.get(position); } diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 25697e9a56..51f518f313 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -18,7 +18,7 @@ protected Piece(Side side, MovementStrategy movementStrategy) { this.movementStrategy = movementStrategy; } - public Destinations findMovablePositions(Position current, BoardReader board) { + public Destinations findDestinations(Position current, BoardReader board) { List paths = movementStrategy.generatePaths(current); List validDestinations = filterValidPositions(current, paths, board); return new Destinations(validDestinations); @@ -29,20 +29,24 @@ public Destinations findMovablePositions(Position current, BoardReader board) { protected List filterStandardPaths(List paths, BoardReader board) { List valid = new ArrayList<>(); for (Path path : paths) { - if (isObstaclesClear(path, board) && isValidDestination(path.getDestination(), board)) { - valid.add(path.getDestination()); - } + addIfValidDestination(valid, path, board); } return valid; } - private boolean isObstaclesClear(Path path, BoardReader board) { - for (Position obstacle : path.getObstacles()) { - if (!board.isEmpty(obstacle)) { - return false; - } + private void addIfValidDestination(List valid, Path path, BoardReader board) { + if (isMovablePath(path, board)) { + valid.add(path.getDestination()); } - return true; + } + + private boolean isMovablePath(Path path, BoardReader board) { + return isObstaclesClear(path, board) && isValidDestination(path.getDestination(), board); + } + + private boolean isObstaclesClear(Path path, BoardReader board) { + return path.getObstacles().stream() + .allMatch(board::isEmpty); } protected boolean isValidDestination(Position destination, BoardReader board) { diff --git a/src/test/java/domain/board/BoardTest.java b/src/test/java/domain/board/BoardTest.java index 2cc5bf52f7..3541d13772 100644 --- a/src/test/java/domain/board/BoardTest.java +++ b/src/test/java/domain/board/BoardTest.java @@ -100,13 +100,11 @@ void moveEmptySource() { Position target = Position.of(0, 1); // When & Then: 빈 좌표를 선택하여 이동을 시도하거나 이동 가능한 위치를 찾을 때 예외 발생 - assertThatThrownBy(() -> board.findMovablePositions(emptyPos)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("기물이 존재하지 않는 위치입니다."); + assertThatThrownBy(() -> board.findDestinations(emptyPos)) + .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> board.movePiece(emptyPos, target)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("출발지에 기물이 없습니다."); + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -121,7 +119,7 @@ void validateAllyAtDestination() { )); // When: 차(Chariot)의 이동 가능 위치 탐색 - Destinations destinations = board.findMovablePositions(chariotPos); + Destinations destinations = board.findDestinations(chariotPos); // Then: 아군이 있는 (0, 1)은 목적지 목록에 포함되지 않음 assertThat(destinations.getPositions()).doesNotContain(allyPos); diff --git a/src/test/java/domain/piece/CannonTest.java b/src/test/java/domain/piece/CannonTest.java index bc1ba14bf7..51a048eef5 100644 --- a/src/test/java/domain/piece/CannonTest.java +++ b/src/test/java/domain/piece/CannonTest.java @@ -1,6 +1,7 @@ package domain.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import domain.Destinations; import domain.Position; @@ -25,7 +26,7 @@ void jumpOverBridge() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: 다리(1, 3) 이전인 (1, 2)는 못 가고, 다리 너머인 (1, 4)부터 끝까지 이동 가능 assertThat(movable.getPositions()).doesNotContain(Position.of(1, 2)); @@ -44,11 +45,10 @@ void cannotJumpOverAnotherCannon() { ); Board board = new Board(pieces); - // When - Destinations movable = board.findMovablePositions(current); + // When & Then 이동할 목적지가 없어서 예외가 발생 + assertThatThrownBy(() -> board.findDestinations(current)) + .isInstanceOf(IllegalArgumentException.class); - // Then: 다리가 포(Cannon)인 경우 뛰어넘을 수 없으므로 이동 가능한 좌표가 없어야 함 - assertThat(movable.getPositions()).isEmpty(); } @Test @@ -66,7 +66,7 @@ void captureEnemy() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: 적군(1, 5)까지는 갈 수 있지만, 그 너머(1, 6)는 갈 수 없음 assertThat(movable.getPositions()).contains(Position.of(1, 4), Position.of(1, 5)); @@ -88,7 +88,7 @@ void cannotCaptureEnemyCannon() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: 적군 포(1, 5) 직전인 (1, 4)까지만 갈 수 있고 (1, 5)는 포함되지 않음 assertThat(movable.getPositions()).contains(Position.of(1, 4)); diff --git a/src/test/java/domain/piece/ChariotTest.java b/src/test/java/domain/piece/ChariotTest.java index fb56554c74..8ffa222965 100644 --- a/src/test/java/domain/piece/ChariotTest.java +++ b/src/test/java/domain/piece/ChariotTest.java @@ -21,7 +21,7 @@ void move() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: 같은 행(0, 1~9)과 같은 열(1~8, 0)의 모든 위치가 포함되어야 함 (총 9+8=17개) assertThat(movable.getPositions()).hasSize(17); @@ -41,7 +41,7 @@ void allyObstacle() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: (0, 1), (0, 2)는 가능하지만 (0, 3)과 그 너머(0, 4)는 불가능해야 함 assertThat(movable.getPositions()).contains(Position.of(0, 1), Position.of(0, 2)); @@ -61,7 +61,7 @@ void enemyCapture() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: 적군이 있는 (5, 0)까지는 이동 가능하지만, 그 너머(6, 0)는 불가능해야 함 assertThat(movable.getPositions()).contains(Position.of(1, 0), Position.of(4, 0), Position.of(5, 0)); diff --git a/src/test/java/domain/piece/ElephantTest.java b/src/test/java/domain/piece/ElephantTest.java index 50f7aae4fb..528e83f58e 100644 --- a/src/test/java/domain/piece/ElephantTest.java +++ b/src/test/java/domain/piece/ElephantTest.java @@ -21,7 +21,7 @@ void move() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: 8방향의 최종 목적지 확인 assertThat(movable.getPositions()).containsExactlyInAnyOrder( @@ -48,7 +48,7 @@ void bridge() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then // 1. 북쪽 직진 멱이 막혔으므로 (6, 7)과 (2, 7)은 없어야 함 diff --git a/src/test/java/domain/piece/GeneralTest.java b/src/test/java/domain/piece/GeneralTest.java index 64162aaa2f..8d9fcc82ff 100644 --- a/src/test/java/domain/piece/GeneralTest.java +++ b/src/test/java/domain/piece/GeneralTest.java @@ -22,7 +22,7 @@ void move() { ); Board board = new Board(pieces); - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); assertThat(movable.getPositions()).containsExactlyInAnyOrder( Position.of(4, 0), Position.of(3, 1), Position.of(5, 1) diff --git a/src/test/java/domain/piece/GuardTest.java b/src/test/java/domain/piece/GuardTest.java index 8baf2f8a88..c64bafb7a7 100644 --- a/src/test/java/domain/piece/GuardTest.java +++ b/src/test/java/domain/piece/GuardTest.java @@ -21,7 +21,7 @@ void move() { ); Board board = new Board(pieces); - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); assertThat(movable.getPositions()).containsExactlyInAnyOrder( Position.of(4, 1), Position.of(2, 1), Position.of(3,0) diff --git a/src/test/java/domain/piece/HorseTest.java b/src/test/java/domain/piece/HorseTest.java index 0ffdbbab21..bd13cc6889 100644 --- a/src/test/java/domain/piece/HorseTest.java +++ b/src/test/java/domain/piece/HorseTest.java @@ -21,7 +21,7 @@ void move() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: 8방향의 L자형 목적지 확인 assertThat(movable.getPositions()).containsExactlyInAnyOrder( @@ -45,7 +45,7 @@ void bridge() { Board board = new Board(pieces); // When - Destinations movable = board.findMovablePositions(current); + Destinations movable = board.findDestinations(current); // Then: 북쪽 멱이 막혔으므로 북쪽 대각선 목적지인 (3, 6)과 (5, 6)은 제외되어야 함 assertThat(movable.getPositions()).doesNotContain( diff --git a/src/test/java/domain/piece/SoldierTest.java b/src/test/java/domain/piece/SoldierTest.java index b33ebcb64d..e44412358d 100644 --- a/src/test/java/domain/piece/SoldierTest.java +++ b/src/test/java/domain/piece/SoldierTest.java @@ -20,10 +20,10 @@ void moveBySide() { hanPos, PieceFactory.createSoldier(Side.HAN) )); - assertThat(board.findMovablePositions(choPos).getPositions()).containsExactlyInAnyOrder( + assertThat(board.findDestinations(choPos).getPositions()).containsExactlyInAnyOrder( Position.of(4, 4), Position.of(3, 3), Position.of(5, 3) ); - assertThat(board.findMovablePositions(hanPos).getPositions()).containsExactlyInAnyOrder( + assertThat(board.findDestinations(hanPos).getPositions()).containsExactlyInAnyOrder( Position.of(4, 5), Position.of(3, 6), Position.of(5, 6) ); } diff --git a/src/test/java/domain/player/PlayerTest.java b/src/test/java/domain/player/PlayerTest.java index 5f8f254f39..4dcac94a08 100644 --- a/src/test/java/domain/player/PlayerTest.java +++ b/src/test/java/domain/player/PlayerTest.java @@ -38,7 +38,7 @@ class PlayerTest { // When & Then: 초나라 플레이어가 한나라 기물을 validateAlly 할 때 예외 발생 assertThatThrownBy(() -> choPlayer.validateAlly(hanPiece)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("자신의 기물만"); + .hasMessageContaining("상대방의 기물은 움직일 수 없습니다."); } @Test From 5f3e627f3ad55ac7bdde3ec84ba9b5556878900b Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 17:10:05 +0900 Subject: [PATCH 57/92] =?UTF-8?q?refactor:=20depth=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/strategy/ContinuousStrategy.java | 13 ++++---- .../java/domain/strategy/OneStepStrategy.java | 13 ++++---- .../domain/strategy/SequenceStrategy.java | 30 ++++++++++--------- src/main/java/view/OutputView.java | 4 +-- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/main/java/domain/strategy/ContinuousStrategy.java b/src/main/java/domain/strategy/ContinuousStrategy.java index 982cf3ed67..1d373c41ff 100644 --- a/src/main/java/domain/strategy/ContinuousStrategy.java +++ b/src/main/java/domain/strategy/ContinuousStrategy.java @@ -3,6 +3,7 @@ import domain.Position; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class ContinuousStrategy implements MovementStrategy { private final List directions; @@ -13,14 +14,10 @@ public ContinuousStrategy(List directions) { @Override public List generatePaths(Position current) { - List paths = new ArrayList<>(); - for (Direction direction : directions) { - Path path = createPath(current, direction); - if (!path.isEmpty()) { - paths.add(path); - } - } - return paths; + return directions.stream() + .map(direction -> createPath(current, direction)) + .filter(path -> !path.isEmpty()) + .toList(); } private Path createPath(Position current, Direction direction) { diff --git a/src/main/java/domain/strategy/OneStepStrategy.java b/src/main/java/domain/strategy/OneStepStrategy.java index 0a8f8faf48..4b397613d2 100644 --- a/src/main/java/domain/strategy/OneStepStrategy.java +++ b/src/main/java/domain/strategy/OneStepStrategy.java @@ -1,8 +1,8 @@ package domain.strategy; import domain.Position; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class OneStepStrategy implements MovementStrategy { private final List directions; @@ -13,12 +13,9 @@ public OneStepStrategy(List directions) { @Override public List generatePaths(Position current) { - List paths = new ArrayList<>(); - for (Direction direction : directions) { - if (current.canMove(direction)) { - paths.add(new Path(List.of(current.move(direction)))); - } - } - return paths; + return directions.stream() + .filter(current::canMove) + .map(direction -> new Path(List.of(current.move(direction)))) + .toList(); } } diff --git a/src/main/java/domain/strategy/SequenceStrategy.java b/src/main/java/domain/strategy/SequenceStrategy.java index 756a18a47e..eb0bf1ba84 100644 --- a/src/main/java/domain/strategy/SequenceStrategy.java +++ b/src/main/java/domain/strategy/SequenceStrategy.java @@ -3,6 +3,7 @@ import domain.Position; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class SequenceStrategy implements MovementStrategy { private final List> sequences; @@ -13,27 +14,28 @@ public SequenceStrategy(List> sequences) { @Override public List generatePaths(Position current) { - List paths = new ArrayList<>(); - for (List sequence : sequences) { - Path path = createPath(current, sequence); - if (!path.isEmpty()) { - paths.add(path); - } - } - return paths; + return sequences.stream() + .map(sequence -> createPath(current, sequence)) + .filter(path -> !path.isEmpty()) + .toList(); } private Path createPath(Position current, List sequence) { List positions = new ArrayList<>(); Position position = current; - - for (Direction direction : sequence) { - if (!position.canMove(direction)) { - return new Path(List.of()); - } - position = position.move(direction); + int index = 0; + while (index < sequence.size() && position.canMove(sequence.get(index))) { + position = position.move(sequence.get(index)); positions.add(position); + index++; + } + if (isInvalidPath(sequence, index)) { + return new Path(List.of()); } return new Path(positions); } + + private boolean isInvalidPath(List sequence, int index) { + return index < sequence.size(); + } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 11676cdfad..09d55a68e4 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -21,10 +21,10 @@ public class OutputView { public void printBoard(Map board) { System.out.println(); - System.out.println(buildHeader()); for (int y = 9; y >= 0; y--) { System.out.println(buildRow(board, y)); } + System.out.println(buildHeader()); } private String buildHeader() { @@ -62,6 +62,6 @@ public void printDestinations(List destinations) { } public void printWinner(String winner) { - System.out.printf("%s가 승리했습니다.%n", winner); + System.out.printf("%s(이/가) 승리했습니다.%n", winner); } } From 70eadf024dc2387798a80929af67a9289f5dbf2a Mon Sep 17 00:00:00 2001 From: Minjae Kang Date: Tue, 31 Mar 2026 17:10:13 +0900 Subject: [PATCH 58/92] =?UTF-8?q?docs:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0a884c72c8..6ac9911dea 100644 --- a/README.md +++ b/README.md @@ -172,9 +172,9 @@ - [x] 자바 코드 컨벤션을 지키면서 프로그래밍한다. - [x] 기본적으로[Java Style Guide](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java)을 원칙으로 한다. -- [ ] indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다. - - [ ] 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - [ ] 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +- [x] indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다. + - [x] 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - [x] 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. - [x] 3항 연산자를 쓰지 않는다. - [x] else 예약어를 쓰지 않는다. - [x] else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. @@ -182,18 +182,18 @@ - [ ] 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외 - [ ] 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다. - [x] UI 로직을 view.InputView, ResultView와 같은 클래스를 추가해 분리한다. -- [ ] 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. - - [ ] 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라. -- [ ] 배열 대신 컬렉션을 사용한다. +- [x] 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. + - [x] 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라. +- [x] 배열 대신 컬렉션을 사용한다. - [ ] 모든 원시 값과 문자열을 포장한다. -- [ ] 줄여 쓰지 않는다(축약 금지). -- [ ] 일급 컬렉션을 쓴다. -- [ ] 모든 엔티티를 작게 유지한다. -- [ ] 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. +- [x] 줄여 쓰지 않는다(축약 금지). +- [x] 일급 컬렉션을 쓴다. +- [x] 모든 엔티티를 작게 유지한다. +- [x] 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. ### 추가된 요구 사항 -- [ ] 도메인의 의존성을 최소한으로 구현한다. +- [x] 도메인의 의존성을 최소한으로 구현한다. - [ ] 한 줄에 점을 하나만 찍는다. - [ ] 게터/세터/프로퍼티를 쓰지 않는다. - [ ] 모든 객체지향 생활 체조 원칙을 잘 지키며 구현한다. From 4c65a219553925e852d6b578bba7f4bc7c076e1e Mon Sep 17 00:00:00 2001 From: khcho96 Date: Tue, 31 Mar 2026 17:27:46 +0900 Subject: [PATCH 59/92] =?UTF-8?q?docs(README):=20=EB=A6=AC=EB=93=9C?= =?UTF-8?q?=EB=AF=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/README.md b/README.md index 6ac9911dea..051fece684 100644 --- a/README.md +++ b/README.md @@ -296,48 +296,3 @@ > (기준) 확장성 판단 기준: 새로운 기물 추가나 규칙 변경 시 기존 코드 수정 범위가 작아야 한다. > > (금지) 이번 미션에서 새 기물 추가를 위해 기존 분기문을 계속 늘리는 방식으로 확장하지 않는다 - - -### 👨‍💻 고민과 결정 (나는 왜 이렇게 구현할까?) - -**1. 기물과 보드의 협력 설계: 내부 자료구조 노출 방지와 책임 할당** - -- **고민:** 기물이 이동 가능 위치를 찾으려면 보드의 상태(빈칸, 아군/적군 위치)를 알아야 한다. 기존처럼 `Map`를 기물에게 직접 넘기면 보드의 내부 구현이 노출되고 캡슐화가 깨진다. 반대로, 기물이 보드 상태를 아예 모른 채 '이동 경로(Route)' 데이터만 반환하고 외부 심판(Validator)이 이를 판정하게 만드는 것이 맞을까? - -- **결정:** 보드 내부 구조를 추상화한 `BoardReader` 인터페이스를 기물에게 주입하는 방식을 선택했다. - -- **근거:** 첫째, 기물이 `Map`이라는 보드의 세부 구현에 강하게 의존하는 구조를 탈피하고 싶었다. 둘째, 외부 심판(Validator) 방식을 도입하기에는 마(馬), 상(象)처럼 기물마다 확인해야 할 경유지(멱)의 개수가 다르고 포(包)처럼 예외적인 규칙도 많다. 이 모든 경우의 수를 외부 심판 하나에 위임하면 심판 객체의 책임이 너무 무거워지고 비대해진다. `BoardReader`를 도입하면 보드의 실제 자료구조는 완벽히 숨기면서도, 각 기물이 자신의 고유한 이동 규칙을 객체지향적으로 응집도 있게 캡슐화할 수 있다 - -**2. 상태 변경과 불변성: `move` 시 가변 Map 갱신 vs 새로운 Board 반환** - -- **고민:** 기물이 이동할 때 기존 `Board` 내부의 Map을 `put/remove`로 갱신하는 것이 직관적이다. 하지만 상태가 가변적이면 사이드 이펙트가 우려된다. - -- **결정:** `Map.copyOf()`를 활용하여 기존 상태와 연결고리가 끊어진 불변 맵을 만들고, 이를 가진 새로운 Board 객체를 반환하도록 구현한다. - -- **근거:** 가변 상태를 사용할 경우 부수효과(Side-effect)로 인해 예상치 못한 곳에서 데이터가 변경되는 버그가 발생하거나, 동시성 문제까지 신경 써야 하는 복잡성이 생긴다. 상태가 변할 때마다 기존 상태를 수정하는 대신 새로운 불변 상태를 생성하여 반환하도록 설계하면, 동작의 예측 가능성이 높아지고 이런 부작용에 대한 우려를 원천적으로 차단할 수 있다. - -**3. 기물 객체의 라이프사이클: 매번 `new` 생성 vs 기물 인스턴스 캐싱 재사용** - -- **고민:** 게임 초기화 시 32개의 기물을 배치할 때, `new Horse(Side.CHO)` 형태로 매번 인스턴스를 생성하는 것이 자원 낭비는 아닐까? - -- **결정:** `PieceFactory`를 도입하여 기물 인스턴스를 캐싱하고 재사용한다. - -- **근거:** 팀 규칙에 따라 기물은 자신의 '위치(Position)'를 상태로 갖지 않는 완벽한 불변 객체다. 즉, 보드판 위에 있는 2개의 '초나라 마'는 메모리 상에서 완전히 동일한 상태를 갖는다. 이를 싱글톤처럼 1개만 생성하여 공유함으로써 메모리를 절약한다. - - -**4. 다채로운 이동 규칙의 중복 제거: 하드코딩 vs 전략(Strategy) 패턴** - -- **고민:** 기물 내부에 상, 하, 좌, 우 방향에 따라 `if`문과 `while`문이 산재해 있어 코드 중복이 매우 심하다. 이 반복적이고 고정적인 로직들을 어떻게 관리해야 재사용성과 응집도를 높일 수 있을까? - -- **결정:** 상, 하, 좌, 우 등의 좌표 증감값을 `Direction` Enum으로 도출하고, 고정적인 공통 탐색 알고리즘을 `OneStepStrategy`, `SlideStrategy`, `JumpSlideStrategy`, `SequenceStrategy`로 추상화하여 기물에 주입한다. - -- **근거:** 상하좌우 방향마다 고정되고 반복적으로 발생하는 탐색 로직(`if`, `while`)을 다형성으로 추상화하면 코드의 재사용성이 크게 향상된다. 기물은 '자신이 갈 수 있는 방향'이라는 순수 도메인 지식만 가지고, 실제 보드를 탐색하는 복잡한 절차는 전략 객체가 담당하게 역할을 분리함으로써, 규칙 변경이나 확장 시 해당 전략만 수정하면 되는 높은 응집도를 확보할 수 있다. - - -**5. 다형성의 활용: 상태 패턴(State) vs 전략 패턴(Strategy)의 분리 적용** - -- **고민:** `if/else` 분기문을 제거하고 다형성을 활용해야 하는 상황이 많다. 기물마다 이동하는 규칙이 다르고, 플레이어의 턴(현재 턴, 대기 턴) 상태도 다르다. 이들을 구조가 비슷하다는 이유로 모두 동일한 디자인 패턴으로 묶어서 처리하는 것이 맞을까? - -- **결정:** 런타임에 빈번하게 교체되는 플레이어의 턴 관리에는 상태 패턴(State Pattern)을 적용하고, 객체 생성 시 한 번 정해지면 변하지 않는 기물의 이동 규칙에는 전략 패턴(Strategy Pattern)을 적용하여 용도를 분리했다. - -- **근거:** 두 패턴은 클래스 다이어그램 구조가 거의 동일하지만 설계의 '의도(Intent)'가 다르다. 턴 상태(`ActiveTurn`, `InactiveTurn`)는 게임 진행에 따라 매 턴마다 동적으로 상태가 교체되며 스스로 책임을 넘기는 행위가 중요하므로 상태 패턴이 적합하다. 반면, 기물의 이동 규칙(`OneStepStrategy`, `SlideStrategy`)은 기물 생성 시 알고리즘이 한 번 주입되면 게임이 끝날 때까지 변경될 일이 없으므로 전략 패턴이 부합한다. '상태 변경의 빈도'와 '의도'를 기준으로 패턴을 다르게 적용하여 객체지향적 설계의 목적을 코드에 명확히 드러냈다. From f8f2ed34ff400fb500ea36844bb2291e397b94f4 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:30:27 +0900 Subject: [PATCH 60/92] =?UTF-8?q?refactor(Soldier):=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A3=BC=EC=9E=85=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/piece/Soldier.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java index 07468cab9b..e0fc4bc958 100644 --- a/src/main/java/domain/piece/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -14,19 +14,7 @@ public Soldier(Side side, MovementStrategy movementStrategy) { @Override protected List filterValidPositions(Position current, List paths, BoardReader board) { - return paths.stream() - .map(Path::getDestination) - .filter(destination -> isForwardOrSideways(current, destination)) - .filter(destination -> isValidDestination(destination, board)) - .toList(); - } - - private boolean isForwardOrSideways(Position current, Position destination) { - int deltaY = destination.getY() - current.getY(); - if (getSide().isCho()) { - return deltaY >= 0; - } - return deltaY <= 0; + return filterStandardPaths(paths, board); } @Override From 4b5706d83169c28ced2fcbf39513fe9621b326c5 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:30:36 +0900 Subject: [PATCH 61/92] =?UTF-8?q?refactor(Side):=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A3=BC=EC=9E=85=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/Side.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java index 3ac2633bfb..009b277950 100644 --- a/src/main/java/domain/Side.java +++ b/src/main/java/domain/Side.java @@ -1,5 +1,6 @@ package domain; +import domain.strategy.Direction; import java.util.List; public enum Side { @@ -28,6 +29,11 @@ public int soldierY() { public List formationX() { return List.of(1, 2, 6, 7); } + + @Override + public Direction soldierForward() { + return Direction.N; + } }, HAN("한") { @Override @@ -54,6 +60,11 @@ public int soldierY() { public List formationX() { return List.of(7, 6, 2, 1); } + + @Override + public Direction soldierForward() { + return Direction.S; + } }; private final String name; @@ -67,15 +78,12 @@ public List formationX() { public abstract int cannonY(); public abstract int soldierY(); public abstract List formationX(); + public abstract Direction soldierForward(); public boolean isAlly(Side other) { return this.equals(other); } - public boolean isCho() { - return this.equals(Side.CHO); - } - public String getName() { return name; } From 33a65937c262f9936fbaed491f2e12c30755a089 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:30:54 +0900 Subject: [PATCH 62/92] =?UTF-8?q?refactor(PieceFactory):=20=EC=A0=84?= =?UTF-8?q?=EB=9E=B5=ED=8C=A8=ED=84=B4=20=EC=A3=BC=EC=9E=85=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=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/domain/piece/PieceFactory.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/domain/piece/PieceFactory.java b/src/main/java/domain/piece/PieceFactory.java index c4330c3a87..09aabe8093 100644 --- a/src/main/java/domain/piece/PieceFactory.java +++ b/src/main/java/domain/piece/PieceFactory.java @@ -23,8 +23,8 @@ Side.CHO, new Guard(Side.CHO, LINEAR_ONE_STEP), Side.HAN, new Guard(Side.HAN, LINEAR_ONE_STEP) ); private static final Map SOLDIERS = Map.of( - Side.CHO, new Soldier(Side.CHO, LINEAR_ONE_STEP), - Side.HAN, new Soldier(Side.HAN, LINEAR_ONE_STEP) + Side.CHO, new Soldier(Side.CHO, soldierStrategy(Side.CHO)), + Side.HAN, new Soldier(Side.HAN, soldierStrategy(Side.HAN)) ); private static final Map HORSES = Map.of( Side.CHO, new Horse(Side.CHO, HORSE_STRATEGY), @@ -52,4 +52,8 @@ private PieceFactory() {} public static Chariot createChariot(Side side) { return CHARIOTS.get(side); } public static Cannon createCannon(Side side) { return CANNONS.get(side); } public static Soldier createSoldier(Side side) { return SOLDIERS.get(side); } + + private static MovementStrategy soldierStrategy(Side side) { + return new OneStepStrategy(Direction.soldier(side)); + } } From 4ca23b6da6cca12fd6891afeb37ae6cfdb16cf63 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:31:11 +0900 Subject: [PATCH 63/92] =?UTF-8?q?refactor(Direction):=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A3=BC=EC=9E=85=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/strategy/Direction.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/domain/strategy/Direction.java b/src/main/java/domain/strategy/Direction.java index edbad83fe7..aed0fc10e7 100644 --- a/src/main/java/domain/strategy/Direction.java +++ b/src/main/java/domain/strategy/Direction.java @@ -1,5 +1,6 @@ package domain.strategy; +import domain.Side; import java.util.List; public enum Direction { @@ -42,6 +43,10 @@ public static List> elephantSequences() { ); } + public static List soldier(Side side) { + return List.of(E, W, side.soldierForward()); + } + public int getDx() { return dx; } From b447a32b872d7820573aebd5607a116d449a6253 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:31:38 +0900 Subject: [PATCH 64/92] =?UTF-8?q?refactor(Formation):=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=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/domain/board/Formation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/domain/board/Formation.java b/src/main/java/domain/board/Formation.java index fe3dac9897..28452af56b 100644 --- a/src/main/java/domain/board/Formation.java +++ b/src/main/java/domain/board/Formation.java @@ -75,9 +75,9 @@ public static Formation from(FormationCommand formationCommand) { public abstract void placeElephant(Map pieces, Side side); private static void place(Map pieces, Side side, List orders) { - List a = side.formationX(); + List formationX = side.formationX(); for (int i = 0; i < orders.size(); i++) { - pieces.put(Position.of(a.get(i), side.baseY()), orders.get(i)); + pieces.put(Position.of(formationX.get(i), side.baseY()), orders.get(i)); } } } From 38345fab9826cd6fe5bb00435a1f1f7324b4eefc Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:31:49 +0900 Subject: [PATCH 65/92] =?UTF-8?q?refactor(InputParser):=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/InputParser.java | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/domain/InputParser.java b/src/main/java/domain/InputParser.java index 9041556483..17066b4b74 100644 --- a/src/main/java/domain/InputParser.java +++ b/src/main/java/domain/InputParser.java @@ -10,20 +10,13 @@ public class InputParser { public static final Pattern POSITION_PATTERN = Pattern.compile(" *\\( *\\d+ *, *\\d+ *\\) *"); public static Name parseName(String input) { - if (input == null || input.isBlank()) { - throw new IllegalArgumentException("빈 값은 입력할 수 없습니다."); - } + validateNullOrBlank(input); return new Name(input.strip()); } public static Position parsePosition(String input) { - if (input == null || input.isBlank()) { - throw new IllegalArgumentException("빈 값은 입력할 수 없습니다."); - } - - if (!POSITION_PATTERN.matcher(input).matches()) { - throw new IllegalArgumentException("질못된 입력 형식입니다."); - } + validateNullOrBlank(input); + validateInputFormat(input); return getPosition(input); } @@ -36,4 +29,16 @@ private static Position getPosition(String input) { return Position.of(coordinate.get(0), coordinate.get(1)); } + + private static void validateNullOrBlank(String input) { + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("빈 값은 입력할 수 없습니다."); + } + } + + private static void validateInputFormat(String input) { + if (!POSITION_PATTERN.matcher(input).matches()) { + throw new IllegalArgumentException("질못된 입력 형식입니다."); + } + } } From a77b6f712dfb0a58d6ad4d96063296375c6b63cd Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:32:41 +0900 Subject: [PATCH 66/92] =?UTF-8?q?refactor(Player):=20equals=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9C=BC=EB=A1=9C=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/domain/player/Player.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/domain/player/Player.java b/src/main/java/domain/player/Player.java index 6f0ec9b044..07b7e43176 100644 --- a/src/main/java/domain/player/Player.java +++ b/src/main/java/domain/player/Player.java @@ -16,7 +16,7 @@ public Player(Name name, Side side, TurnState turnState) { } public void validateAlly(Piece piece) { - if (piece.getSide() != side) { + if (!piece.getSide().equals(side)) { throw new IllegalArgumentException("상대방의 기물은 움직일 수 없습니다."); } } From a818c7633e293d33a5b434b291b116940d4f6f97 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:38:57 +0900 Subject: [PATCH 67/92] =?UTF-8?q?refactor(InputParser):=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=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/domain/GameManager.java | 1 + src/main/java/{domain => parser}/InputParser.java | 3 ++- src/test/java/domain/InputParserTest.java | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) rename src/main/java/{domain => parser}/InputParser.java (97%) diff --git a/src/main/java/domain/GameManager.java b/src/main/java/domain/GameManager.java index 0d23669ede..faed13df07 100644 --- a/src/main/java/domain/GameManager.java +++ b/src/main/java/domain/GameManager.java @@ -8,6 +8,7 @@ import domain.player.Players; import java.util.List; import java.util.function.Supplier; +import parser.InputParser; import view.InputView; import view.OutputView; diff --git a/src/main/java/domain/InputParser.java b/src/main/java/parser/InputParser.java similarity index 97% rename from src/main/java/domain/InputParser.java rename to src/main/java/parser/InputParser.java index 17066b4b74..a53b52625e 100644 --- a/src/main/java/domain/InputParser.java +++ b/src/main/java/parser/InputParser.java @@ -1,6 +1,7 @@ -package domain; +package parser; +import domain.Position; import domain.player.Name; import java.util.Arrays; import java.util.List; diff --git a/src/test/java/domain/InputParserTest.java b/src/test/java/domain/InputParserTest.java index 0bd87d0741..ba9ed9ce1b 100644 --- a/src/test/java/domain/InputParserTest.java +++ b/src/test/java/domain/InputParserTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; +import parser.InputParser; class InputParserTest { From c0f941b7ef62f9391af5272acfb32de3b8cb4e46 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:47:22 +0900 Subject: [PATCH 68/92] =?UTF-8?q?test(InputParser):=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=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/{domain => parser}/InputParserTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename src/test/java/{domain => parser}/InputParserTest.java (95%) diff --git a/src/test/java/domain/InputParserTest.java b/src/test/java/parser/InputParserTest.java similarity index 95% rename from src/test/java/domain/InputParserTest.java rename to src/test/java/parser/InputParserTest.java index ba9ed9ce1b..5aa74537f8 100644 --- a/src/test/java/domain/InputParserTest.java +++ b/src/test/java/parser/InputParserTest.java @@ -1,4 +1,4 @@ -package domain; +package parser; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -6,7 +6,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; -import parser.InputParser; class InputParserTest { From eeba6f60707fb727ba2317749333501ed2338bf0 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:47:37 +0900 Subject: [PATCH 69/92] =?UTF-8?q?refactor(GameManager):=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=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/Application.java | 2 +- src/main/java/{domain => application}/GameManager.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) rename src/main/java/{domain => application}/GameManager.java (96%) diff --git a/src/main/java/Application.java b/src/main/java/Application.java index 1350f30072..3d47303d23 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,4 +1,4 @@ -import domain.GameManager; +import application.GameManager; import java.util.Scanner; import view.InputView; import view.OutputView; diff --git a/src/main/java/domain/GameManager.java b/src/main/java/application/GameManager.java similarity index 96% rename from src/main/java/domain/GameManager.java rename to src/main/java/application/GameManager.java index faed13df07..3114c32547 100644 --- a/src/main/java/domain/GameManager.java +++ b/src/main/java/application/GameManager.java @@ -1,5 +1,8 @@ -package domain; +package application; +import domain.Game; +import domain.Position; +import domain.Side; import domain.board.Board; import domain.board.BoardFactory; import domain.board.Formation; From 0a52da4e4ec5222e439913958022cd2eb4619363 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 13:52:56 +0900 Subject: [PATCH 70/92] =?UTF-8?q?refactor(InputParser):=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=A0=9C=EC=96=B4=EC=9E=90=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/parser/InputParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/parser/InputParser.java b/src/main/java/parser/InputParser.java index a53b52625e..50fc01c61d 100644 --- a/src/main/java/parser/InputParser.java +++ b/src/main/java/parser/InputParser.java @@ -8,7 +8,7 @@ import java.util.regex.Pattern; public class InputParser { - public static final Pattern POSITION_PATTERN = Pattern.compile(" *\\( *\\d+ *, *\\d+ *\\) *"); + private static final Pattern POSITION_PATTERN = Pattern.compile(" *\\( *\\d+ *, *\\d+ *\\) *"); public static Name parseName(String input) { validateNullOrBlank(input); From d4717d72cd5a708f5e8215c0d7d6060e5848e4d3 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 15:27:16 +0900 Subject: [PATCH 71/92] =?UTF-8?q?refactor(Game):=20=EC=9A=B0=EC=8A=B9?= =?UTF-8?q?=EC=9E=90=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EC=A7=81=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 --- src/main/java/domain/Game.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index a9348f2ab7..aeaf53f038 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -20,7 +20,7 @@ public Side getCurrentSide() { public Destinations selectSource(Position position) { Piece piece = board.getPiece(position); - players.getCurrentPlayer().validateAlly(piece); + players.getActiveTurnPlayer().validateAlly(piece); return findDestinations(position); } @@ -48,7 +48,6 @@ public boolean isOver() { } public String getWinner() { - players.switchPlayer(); - return players.getCurrentPlayer().getName(); + return players.getInActiveTurnPlayer().getName(); } } From 821b8c9c2f94d41e89599e05edc29333a75bf82f Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 15:27:24 +0900 Subject: [PATCH 72/92] =?UTF-8?q?refactor(Players):=20=EC=9A=B0=EC=8A=B9?= =?UTF-8?q?=EC=9E=90=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EC=A7=81=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 --- src/main/java/domain/player/Players.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/domain/player/Players.java b/src/main/java/domain/player/Players.java index 6157065fba..18b01ba59b 100644 --- a/src/main/java/domain/player/Players.java +++ b/src/main/java/domain/player/Players.java @@ -26,13 +26,20 @@ private static void validateDuplicateName(Name choName, Name hanName) { } } - public Player getCurrentPlayer() { + public Player getActiveTurnPlayer() { return players.stream() .filter(Player::isCurrentTurn) .findFirst() .orElseThrow(() -> new IllegalArgumentException("현재 턴인 플레이어가 없습니다.")); } + public Player getInActiveTurnPlayer() { + return players.stream() + .filter(player -> !player.isCurrentTurn()) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("현재 턴인 플레이어가 없습니다.")); + } + public Side getCurrentSide() { return players.stream() .filter(Player::isCurrentTurn) From 131e31b11829c9e24fed6fd34c9fa20cc8b4e59c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 15:30:22 +0900 Subject: [PATCH 73/92] =?UTF-8?q?refactor(Players):=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4=EC=9E=90=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/domain/player/Players.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/domain/player/Players.java b/src/main/java/domain/player/Players.java index 18b01ba59b..a08a51fb25 100644 --- a/src/main/java/domain/player/Players.java +++ b/src/main/java/domain/player/Players.java @@ -6,10 +6,10 @@ import java.util.List; public class Players { - List players; + private final List players; private Players(Player cho, Player han) { - this.players = List.of(cho, han); + this.players = java.util.List.of(cho, han); } public static Players createInitial(Name choName, Name hanName) { From f980be37d23fc40e3d30e04321299a9f07b84484 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 15:52:14 +0900 Subject: [PATCH 74/92] =?UTF-8?q?refactor(Destinations):=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=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/domain/Destinations.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/domain/Destinations.java b/src/main/java/domain/Destinations.java index 4b96f371fa..41566baa40 100644 --- a/src/main/java/domain/Destinations.java +++ b/src/main/java/domain/Destinations.java @@ -22,11 +22,7 @@ public List getPositions() { public void validateDestinations(Position target) { if (!positions.contains(target)) { - throw new IllegalArgumentException("선택할 수 없는 기물입니다."); + throw new IllegalArgumentException("이동할 수 없는 위치입니다."); } } - - public boolean isEmpty() { - return positions.isEmpty(); - } } From c68872d3bc186911cb3488e4bea02bc8dad0e422 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 16:34:16 +0900 Subject: [PATCH 75/92] =?UTF-8?q?refactor(InputParser):=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=ED=98=95=EC=8B=9D=20=EB=B0=8F=20=EC=83=81=EC=88=98?= =?UTF-8?q?=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/main/java/parser/InputParser.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/parser/InputParser.java b/src/main/java/parser/InputParser.java index 50fc01c61d..e54ca93757 100644 --- a/src/main/java/parser/InputParser.java +++ b/src/main/java/parser/InputParser.java @@ -8,7 +8,7 @@ import java.util.regex.Pattern; public class InputParser { - private static final Pattern POSITION_PATTERN = Pattern.compile(" *\\( *\\d+ *, *\\d+ *\\) *"); + private static final Pattern COORDINATE_CSV_PATTERN = Pattern.compile(" *\\d+ *, *\\d+ *"); public static Name parseName(String input) { validateNullOrBlank(input); @@ -23,7 +23,7 @@ public static Position parsePosition(String input) { } private static Position getPosition(String input) { - List coordinate = Arrays.stream(input.strip().substring(1, input.length() - 1).split(",")) + List coordinate = Arrays.stream(input.strip().split(",")) .map(String::strip) .map(Integer::parseInt) .toList(); @@ -38,7 +38,7 @@ private static void validateNullOrBlank(String input) { } private static void validateInputFormat(String input) { - if (!POSITION_PATTERN.matcher(input).matches()) { + if (!COORDINATE_CSV_PATTERN.matcher(input).matches()) { throw new IllegalArgumentException("질못된 입력 형식입니다."); } } From 5f0bcc4d305e5b54a1b6478af883ed1b730b79dc Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 16:34:39 +0900 Subject: [PATCH 76/92] =?UTF-8?q?refactor(InputView):=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=EC=8B=9C=20=EC=9E=85=EB=A0=A5=20=EC=98=88?= =?UTF-8?q?=EC=8B=9C=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/InputView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index b65ed43ef9..07a51bdb7f 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -28,12 +28,12 @@ public String readFormation(Side side) { } public String readSourcePosition(Side side) { - System.out.println(side.getName() + "나라 플레이어 차례입니다. 이동 시킬 기물의 위치를 입력하세요."); + System.out.println(side.getName() + "나라 플레이어 차례입니다. 이동 시킬 기물의 위치를 입력하세요. 예) 1,3"); return scanner.nextLine(); } public String readTargetPosition() { - System.out.println("선택한 기물이 이동할 수 있는 위치입니다. 이동할 위치를 입력하세요."); + System.out.println("선택한 기물이 이동할 수 있는 위치입니다. 이동할 위치를 입력하세요. 예) 1,3"); return scanner.nextLine(); } } From d02195e94493ebaa428d4f3d585e3601a4dc0f63 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 16:54:58 +0900 Subject: [PATCH 77/92] =?UTF-8?q?refactor(Command):=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=B0=8F=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=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/application/GameManager.java | 4 +-- src/main/java/domain/board/Formation.java | 19 ++++++------- .../Command.java} | 8 +++--- src/test/java/domain/GameTest.java | 4 +-- .../domain/board/FormationCommandTest.java | 26 ------------------ src/test/java/domain/board/FormationTest.java | 9 ++++--- src/test/java/parser/CommandTest.java | 27 +++++++++++++++++++ 7 files changed, 50 insertions(+), 47 deletions(-) rename src/main/java/{domain/board/FormationCommand.java => parser/Command.java} (74%) delete mode 100644 src/test/java/domain/board/FormationCommandTest.java create mode 100644 src/test/java/parser/CommandTest.java diff --git a/src/main/java/application/GameManager.java b/src/main/java/application/GameManager.java index 3114c32547..d23ef9dc66 100644 --- a/src/main/java/application/GameManager.java +++ b/src/main/java/application/GameManager.java @@ -6,7 +6,7 @@ import domain.board.Board; import domain.board.BoardFactory; import domain.board.Formation; -import domain.board.FormationCommand; +import parser.Command; import domain.player.Name; import domain.player.Players; import java.util.List; @@ -48,7 +48,7 @@ private Name getPlayerName(Side side) { } private Formation getFormation(Side side) { - return retry(() -> Formation.from(FormationCommand.from(inputView.readFormation(side)))); + return retry(() -> Formation.from(Command.from(inputView.readFormation(side)))); } private void playTurn(Game game) { diff --git a/src/main/java/domain/board/Formation.java b/src/main/java/domain/board/Formation.java index 28452af56b..c4db1a73c7 100644 --- a/src/main/java/domain/board/Formation.java +++ b/src/main/java/domain/board/Formation.java @@ -7,9 +7,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import parser.Command; public enum Formation { - LEFT_ELEPHANT(FormationCommand.FIRST) { + LEFT_ELEPHANT(Command.FIRST) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( @@ -21,7 +22,7 @@ public void placeElephant(Map pieces, Side side) { place(pieces, side, orders); } }, - RIGHT_ELEPHANT(FormationCommand.SECOND) { + RIGHT_ELEPHANT(Command.SECOND) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( @@ -33,7 +34,7 @@ public void placeElephant(Map pieces, Side side) { place(pieces, side, orders); } }, - OUTER_ELEPHANT(FormationCommand.THIRD) { + OUTER_ELEPHANT(Command.THIRD) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( @@ -45,7 +46,7 @@ public void placeElephant(Map pieces, Side side) { place(pieces, side, orders); } }, - INNER_ELEPHANT(FormationCommand.FOURTH) { + INNER_ELEPHANT(Command.FOURTH) { @Override public void placeElephant(Map pieces, Side side) { List orders = List.of( @@ -59,15 +60,15 @@ public void placeElephant(Map pieces, Side side) { }, ; - private final FormationCommand formationCommand; + private final Command command; - Formation(FormationCommand formationCommand) { - this.formationCommand = formationCommand; + Formation(Command command) { + this.command = command; } - public static Formation from(FormationCommand formationCommand) { + public static Formation from(Command command) { return Arrays.stream(values()) - .filter(formation -> formation.formationCommand.equals(formationCommand)) + .filter(formation -> formation.command.equals(command)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("올바른 배치가 아닙니다.")); } diff --git a/src/main/java/domain/board/FormationCommand.java b/src/main/java/parser/Command.java similarity index 74% rename from src/main/java/domain/board/FormationCommand.java rename to src/main/java/parser/Command.java index 471b53a43d..1181f11fbc 100644 --- a/src/main/java/domain/board/FormationCommand.java +++ b/src/main/java/parser/Command.java @@ -1,8 +1,8 @@ -package domain.board; +package parser; import java.util.Arrays; -public enum FormationCommand { +public enum Command { FIRST("1"), SECOND("2"), THIRD("3"), @@ -11,11 +11,11 @@ public enum FormationCommand { private final String input; - FormationCommand(String input) { + Command(String input) { this.input = input; } - public static FormationCommand from(String input) { + public static Command from(String input) { return Arrays.stream(values()) .filter(command -> command.input.equals(input.strip())) .findFirst() diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index 929e400b0c..f66c071773 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -6,7 +6,7 @@ import domain.board.Board; import domain.board.BoardFactory; import domain.board.Formation; -import domain.board.FormationCommand; +import parser.Command; import domain.piece.Piece; import domain.piece.PieceFactory; import domain.player.Name; @@ -22,7 +22,7 @@ class GameTest { @BeforeEach void setUp() { players = Players.createInitial(new Name("cho"), new Name("han")); - Board board = BoardFactory.create(Formation.from(FormationCommand.FIRST), Formation.from(FormationCommand.FIRST)); + Board board = BoardFactory.create(Formation.from(Command.FIRST), Formation.from(Command.FIRST)); game = new Game(board, players); } diff --git a/src/test/java/domain/board/FormationCommandTest.java b/src/test/java/domain/board/FormationCommandTest.java deleted file mode 100644 index 737cf9b4e4..0000000000 --- a/src/test/java/domain/board/FormationCommandTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package domain.board; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.api.Test; - -class FormationCommandTest { - - @Test - void 포메이션_입력이_1_2_3_4_일때_정상_동작한다() { - assertThat(FormationCommand.from("1")).isEqualTo(FormationCommand.FIRST); - assertThat(FormationCommand.from(" 2 ")).isEqualTo(FormationCommand.SECOND); - assertThat(FormationCommand.from("3")).isEqualTo(FormationCommand.THIRD); - assertThat(FormationCommand.from("4")).isEqualTo(FormationCommand.FOURTH); - } - - @Test - void 포메이션_입력이_1_2_3_4_중_하나가_아니면_예외가_발생한다() { - assertThatThrownBy(() -> FormationCommand.from("0")) - .isInstanceOf(IllegalArgumentException.class); - - assertThatThrownBy(() -> FormationCommand.from("5")) - .isInstanceOf(IllegalArgumentException.class); - } -} diff --git a/src/test/java/domain/board/FormationTest.java b/src/test/java/domain/board/FormationTest.java index 2d69c91be0..e2a97855b4 100644 --- a/src/test/java/domain/board/FormationTest.java +++ b/src/test/java/domain/board/FormationTest.java @@ -10,15 +10,16 @@ import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; +import parser.Command; class FormationTest { @Test void 선택값으로_포메이션을_찾는다() { - assertThat(Formation.from(FormationCommand.FIRST)).isEqualTo(Formation.LEFT_ELEPHANT); - assertThat(Formation.from(FormationCommand.SECOND)).isEqualTo(Formation.RIGHT_ELEPHANT); - assertThat(Formation.from(FormationCommand.THIRD)).isEqualTo(Formation.OUTER_ELEPHANT); - assertThat(Formation.from(FormationCommand.FOURTH)).isEqualTo(Formation.INNER_ELEPHANT); + assertThat(Formation.from(Command.FIRST)).isEqualTo(Formation.LEFT_ELEPHANT); + assertThat(Formation.from(Command.SECOND)).isEqualTo(Formation.RIGHT_ELEPHANT); + assertThat(Formation.from(Command.THIRD)).isEqualTo(Formation.OUTER_ELEPHANT); + assertThat(Formation.from(Command.FOURTH)).isEqualTo(Formation.INNER_ELEPHANT); } @Test diff --git a/src/test/java/parser/CommandTest.java b/src/test/java/parser/CommandTest.java new file mode 100644 index 0000000000..7965ffa0e5 --- /dev/null +++ b/src/test/java/parser/CommandTest.java @@ -0,0 +1,27 @@ +package parser; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class CommandTest { + + @Test + void 포메이션_입력이_1_2_3_4_일때_정상_동작한다() { + Assertions.assertThat(Command.from("1")).isEqualTo(Command.FIRST); + assertThat(Command.from(" 2 ")).isEqualTo(Command.SECOND); + assertThat(Command.from("3")).isEqualTo(Command.THIRD); + assertThat(Command.from("4")).isEqualTo(Command.FOURTH); + } + + @Test + void 포메이션_입력이_1_2_3_4_중_하나가_아니면_예외가_발생한다() { + assertThatThrownBy(() -> Command.from("0")) + .isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy(() -> Command.from("5")) + .isInstanceOf(IllegalArgumentException.class); + } +} From 2c63615d80c200a1409c09a4362183179768543a Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 17:17:54 +0900 Subject: [PATCH 78/92] =?UTF-8?q?feat(FormationPiece):=20=EC=83=81?= =?UTF-8?q?=EB=A7=88=20=ED=8C=90=EB=8B=A8=20=EB=A1=9C=EC=A7=81=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=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/domain/board/FormationPiece.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/main/java/domain/board/FormationPiece.java diff --git a/src/main/java/domain/board/FormationPiece.java b/src/main/java/domain/board/FormationPiece.java new file mode 100644 index 0000000000..34ee4a5a18 --- /dev/null +++ b/src/main/java/domain/board/FormationPiece.java @@ -0,0 +1,6 @@ +package domain.board; + +public enum FormationPiece { + HORSE, + ELEPHANT +} From 54358404af21bc83e3fbb3bfb4c3089030a9dffa Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 17:23:43 +0900 Subject: [PATCH 79/92] =?UTF-8?q?refactor(Formation):=20=EC=83=81=EB=A7=88?= =?UTF-8?q?=20=EA=B8=B0=EB=AC=BC=20=EB=B0=B0=EC=B9=98=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20BoardFactory=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/board/Formation.java | 82 ++++++----------------- 1 file changed, 21 insertions(+), 61 deletions(-) diff --git a/src/main/java/domain/board/Formation.java b/src/main/java/domain/board/Formation.java index c4db1a73c7..1c196c8851 100644 --- a/src/main/java/domain/board/Formation.java +++ b/src/main/java/domain/board/Formation.java @@ -1,69 +1,34 @@ package domain.board; -import domain.Position; -import domain.Side; -import domain.piece.Piece; -import domain.piece.PieceFactory; import java.util.Arrays; import java.util.List; -import java.util.Map; import parser.Command; public enum Formation { - LEFT_ELEPHANT(Command.FIRST) { - @Override - public void placeElephant(Map pieces, Side side) { - List orders = List.of( - PieceFactory.createElephant(side), - PieceFactory.createHorse(side), - PieceFactory.createElephant(side), - PieceFactory.createHorse(side) - ); - place(pieces, side, orders); - } - }, - RIGHT_ELEPHANT(Command.SECOND) { - @Override - public void placeElephant(Map pieces, Side side) { - List orders = List.of( - PieceFactory.createHorse(side), - PieceFactory.createElephant(side), - PieceFactory.createHorse(side), - PieceFactory.createElephant(side) - ); - place(pieces, side, orders); - } - }, - OUTER_ELEPHANT(Command.THIRD) { - @Override - public void placeElephant(Map pieces, Side side) { - List orders = List.of( - PieceFactory.createElephant(side), - PieceFactory.createHorse(side), - PieceFactory.createHorse(side), - PieceFactory.createElephant(side) - ); - place(pieces, side, orders); - } - }, - INNER_ELEPHANT(Command.FOURTH) { - @Override - public void placeElephant(Map pieces, Side side) { - List orders = List.of( - PieceFactory.createHorse(side), - PieceFactory.createElephant(side), - PieceFactory.createElephant(side), - PieceFactory.createHorse(side) - ); - place(pieces, side, orders); - } - }, + LEFT_ELEPHANT( + Command.FIRST, + List.of(FormationPiece.ELEPHANT, FormationPiece.HORSE, FormationPiece.ELEPHANT, FormationPiece.HORSE) + ), + RIGHT_ELEPHANT( + Command.SECOND, + List.of(FormationPiece.HORSE, FormationPiece.ELEPHANT, FormationPiece.HORSE, FormationPiece.ELEPHANT) + ), + OUTER_ELEPHANT( + Command.THIRD, + List.of(FormationPiece.ELEPHANT, FormationPiece.HORSE, FormationPiece.HORSE, FormationPiece.ELEPHANT) + ), + INNER_ELEPHANT( + Command.FOURTH, + List.of(FormationPiece.HORSE, FormationPiece.ELEPHANT, FormationPiece.ELEPHANT, FormationPiece.HORSE) + ), ; private final Command command; + private final List orders; - Formation(Command command) { + Formation(Command command, List orders) { this.command = command; + this.orders = List.copyOf(orders); } public static Formation from(Command command) { @@ -73,12 +38,7 @@ public static Formation from(Command command) { .orElseThrow(() -> new IllegalArgumentException("올바른 배치가 아닙니다.")); } - public abstract void placeElephant(Map pieces, Side side); - - private static void place(Map pieces, Side side, List orders) { - List formationX = side.formationX(); - for (int i = 0; i < orders.size(); i++) { - pieces.put(Position.of(formationX.get(i), side.baseY()), orders.get(i)); - } + public List getOrders() { + return orders; } } From a8fd9c44bbc8bdfb2b7740722109f19073f5231b Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 17:24:04 +0900 Subject: [PATCH 80/92] =?UTF-8?q?refactor(BoardFactory):=20=EC=83=81?= =?UTF-8?q?=EB=A7=88=20=EA=B8=B0=EB=AC=BC=20=EB=B0=B0=EC=B9=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20BoardFactory=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/board/BoardFactory.java | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/domain/board/BoardFactory.java b/src/main/java/domain/board/BoardFactory.java index 934664c316..d025f90a88 100644 --- a/src/main/java/domain/board/BoardFactory.java +++ b/src/main/java/domain/board/BoardFactory.java @@ -5,6 +5,7 @@ import domain.piece.Piece; import domain.piece.PieceFactory; import java.util.HashMap; +import java.util.List; import java.util.Map; public class BoardFactory { @@ -15,9 +16,9 @@ private BoardFactory() { public static Board create(Formation choFormation, Formation hanFormation) { Map pieces = new HashMap<>(); placeFixedPieces(pieces, Side.CHO); - choFormation.placeElephant(pieces, Side.CHO); + placeFormationPieces(pieces, Side.CHO, choFormation); placeFixedPieces(pieces, Side.HAN); - hanFormation.placeElephant(pieces, Side.HAN); + placeFormationPieces(pieces, Side.HAN, hanFormation); return new Board(pieces); } @@ -53,4 +54,23 @@ private static void placeSoldiers(Map pieces, Side side) { pieces.put(Position.of(x, side.soldierY()), PieceFactory.createSoldier(side)); } } + + private static void placeFormationPieces(Map pieces, Side side, Formation formation) { + List formationX = side.formationX(); + List orders = formation.getOrders(); + for (int i = 0; i < orders.size(); i++) { + Position position = Position.of(formationX.get(i), side.baseY()); + pieces.put(position, createFormationPiece(orders.get(i), side)); + } + } + + private static Piece createFormationPiece(FormationPiece piece, Side side) { + if (piece == FormationPiece.HORSE) { + return PieceFactory.createHorse(side); + } + if (piece == FormationPiece.ELEPHANT) { + return PieceFactory.createElephant(side); + } + throw new IllegalArgumentException("지원하지 않는 포메이션 기물입니다."); + } } From 2a6d8845b95374b007981b31bb533a2d2cd51c90 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 17:26:04 +0900 Subject: [PATCH 81/92] =?UTF-8?q?test(FormationTest):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/domain/board/FormationTest.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/test/java/domain/board/FormationTest.java b/src/test/java/domain/board/FormationTest.java index e2a97855b4..f0cf0cd4a2 100644 --- a/src/test/java/domain/board/FormationTest.java +++ b/src/test/java/domain/board/FormationTest.java @@ -2,13 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import domain.Position; -import domain.Side; -import domain.piece.Elephant; -import domain.piece.Horse; -import domain.piece.Piece; -import java.util.HashMap; -import java.util.Map; import org.junit.jupiter.api.Test; import parser.Command; @@ -21,16 +14,4 @@ class FormationTest { assertThat(Formation.from(Command.THIRD)).isEqualTo(Formation.OUTER_ELEPHANT); assertThat(Formation.from(Command.FOURTH)).isEqualTo(Formation.INNER_ELEPHANT); } - - @Test - void LEFT_ELEPHANT은_상마상마로_배치한다() { - Map pieces = new HashMap<>(); - - Formation.LEFT_ELEPHANT.placeElephant(pieces, Side.CHO); - - assertThat(pieces.get(Position.of(1, 0))).isInstanceOf(Elephant.class); - assertThat(pieces.get(Position.of(2, 0))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(6, 0))).isInstanceOf(Elephant.class); - assertThat(pieces.get(Position.of(7, 0))).isInstanceOf(Horse.class); - } } From 25b8dff4ca7881e77d0b74d3245967e2f2218b44 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 17:26:16 +0900 Subject: [PATCH 82/92] =?UTF-8?q?test(InputParserTest):=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/parser/InputParserTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/parser/InputParserTest.java b/src/test/java/parser/InputParserTest.java index 5aa74537f8..b2198f8c86 100644 --- a/src/test/java/parser/InputParserTest.java +++ b/src/test/java/parser/InputParserTest.java @@ -10,7 +10,7 @@ class InputParserTest { @ParameterizedTest - @ValueSource(strings = {"(0,3)", "(3, 9)"}) + @ValueSource(strings = {"0,3", "3, 9", " 8 ,0 "}) void 올바른_좌표_형식이_입력되는_경우_정상_동작한다(String input) { assertThatCode(() -> InputParser.parsePosition(input)) .doesNotThrowAnyException(); @@ -18,7 +18,7 @@ class InputParserTest { @ParameterizedTest @NullAndEmptySource - @ValueSource(strings = {" ", "0,3", "a,b", "(a,b)", "()", "(,)"}) + @ValueSource(strings = {" ", "a,b", "(0,3)", "(a,b)", "()", "(,)", "1", "1,2,3"}) void 위치_입력_포맷이_올바른_형태가_아니면_예외가_발생한다(String input) { assertThatThrownBy(() -> InputParser.parsePosition(input)) .isInstanceOf(IllegalArgumentException.class); From 388ceeec6d1abd03b3449be29732e1db6ef13688 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 17:30:55 +0900 Subject: [PATCH 83/92] =?UTF-8?q?feat(PieceType):=20=EB=AA=A8=EB=93=A0=20?= =?UTF-8?q?=EA=B8=B0=EB=AC=BC=20=ED=83=80=EC=9E=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/board/BoardFactory.java | 8 +++---- src/main/java/domain/board/Formation.java | 14 ++++++------ .../java/domain/board/FormationPiece.java | 6 ----- src/main/java/domain/board/PieceType.java | 22 +++++++++++++++++++ 4 files changed, 33 insertions(+), 17 deletions(-) delete mode 100644 src/main/java/domain/board/FormationPiece.java create mode 100644 src/main/java/domain/board/PieceType.java diff --git a/src/main/java/domain/board/BoardFactory.java b/src/main/java/domain/board/BoardFactory.java index d025f90a88..e433b84389 100644 --- a/src/main/java/domain/board/BoardFactory.java +++ b/src/main/java/domain/board/BoardFactory.java @@ -57,18 +57,18 @@ private static void placeSoldiers(Map pieces, Side side) { private static void placeFormationPieces(Map pieces, Side side, Formation formation) { List formationX = side.formationX(); - List orders = formation.getOrders(); + List orders = formation.getOrders(); for (int i = 0; i < orders.size(); i++) { Position position = Position.of(formationX.get(i), side.baseY()); pieces.put(position, createFormationPiece(orders.get(i), side)); } } - private static Piece createFormationPiece(FormationPiece piece, Side side) { - if (piece == FormationPiece.HORSE) { + private static Piece createFormationPiece(PieceType piece, Side side) { + if (piece == PieceType.HORSE) { return PieceFactory.createHorse(side); } - if (piece == FormationPiece.ELEPHANT) { + if (piece == PieceType.ELEPHANT) { return PieceFactory.createElephant(side); } throw new IllegalArgumentException("지원하지 않는 포메이션 기물입니다."); diff --git a/src/main/java/domain/board/Formation.java b/src/main/java/domain/board/Formation.java index 1c196c8851..fd30a99656 100644 --- a/src/main/java/domain/board/Formation.java +++ b/src/main/java/domain/board/Formation.java @@ -7,26 +7,26 @@ public enum Formation { LEFT_ELEPHANT( Command.FIRST, - List.of(FormationPiece.ELEPHANT, FormationPiece.HORSE, FormationPiece.ELEPHANT, FormationPiece.HORSE) + List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE) ), RIGHT_ELEPHANT( Command.SECOND, - List.of(FormationPiece.HORSE, FormationPiece.ELEPHANT, FormationPiece.HORSE, FormationPiece.ELEPHANT) + List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT) ), OUTER_ELEPHANT( Command.THIRD, - List.of(FormationPiece.ELEPHANT, FormationPiece.HORSE, FormationPiece.HORSE, FormationPiece.ELEPHANT) + List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.HORSE, PieceType.ELEPHANT) ), INNER_ELEPHANT( Command.FOURTH, - List.of(FormationPiece.HORSE, FormationPiece.ELEPHANT, FormationPiece.ELEPHANT, FormationPiece.HORSE) + List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.ELEPHANT, PieceType.HORSE) ), ; private final Command command; - private final List orders; + private final List orders; - Formation(Command command, List orders) { + Formation(Command command, List orders) { this.command = command; this.orders = List.copyOf(orders); } @@ -38,7 +38,7 @@ public static Formation from(Command command) { .orElseThrow(() -> new IllegalArgumentException("올바른 배치가 아닙니다.")); } - public List getOrders() { + public List getOrders() { return orders; } } diff --git a/src/main/java/domain/board/FormationPiece.java b/src/main/java/domain/board/FormationPiece.java deleted file mode 100644 index 34ee4a5a18..0000000000 --- a/src/main/java/domain/board/FormationPiece.java +++ /dev/null @@ -1,6 +0,0 @@ -package domain.board; - -public enum FormationPiece { - HORSE, - ELEPHANT -} diff --git a/src/main/java/domain/board/PieceType.java b/src/main/java/domain/board/PieceType.java new file mode 100644 index 0000000000..ff7ed63763 --- /dev/null +++ b/src/main/java/domain/board/PieceType.java @@ -0,0 +1,22 @@ +package domain.board; + +public enum PieceType { + GENERAL("궁"), + CHARIOT("차"), + CANNON("포"), + HORSE("마"), + ELEPHANT("상"), + GUARD("사"), + SOLDIER("졸"), + ; + + private final String name; + + PieceType(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} From 7ef523b0785f13a802d102dd479d7e3e114c2fb8 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 17:46:15 +0900 Subject: [PATCH 84/92] =?UTF-8?q?feat(Piece):=20=EA=B0=81=20=EA=B8=B0?= =?UTF-8?q?=EB=AC=BC=EC=9D=B4=20PieceType=EC=9D=84=20=EA=B0=80=EC=A7=80?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/board/BoardFactory.java | 1 + src/main/java/domain/board/Formation.java | 1 + src/main/java/domain/piece/Cannon.java | 6 ++++-- src/main/java/domain/piece/Chariot.java | 6 ++++-- src/main/java/domain/piece/Elephant.java | 6 ++++-- src/main/java/domain/piece/General.java | 6 ++++-- src/main/java/domain/piece/Guard.java | 6 ++++-- src/main/java/domain/piece/Horse.java | 6 ++++-- src/main/java/domain/piece/Piece.java | 2 ++ src/main/java/domain/{board => piece}/PieceType.java | 2 +- src/main/java/domain/piece/Soldier.java | 6 ++++-- src/main/java/view/OutputView.java | 2 +- 12 files changed, 34 insertions(+), 16 deletions(-) rename src/main/java/domain/{board => piece}/PieceType.java (93%) diff --git a/src/main/java/domain/board/BoardFactory.java b/src/main/java/domain/board/BoardFactory.java index e433b84389..29f7173dd1 100644 --- a/src/main/java/domain/board/BoardFactory.java +++ b/src/main/java/domain/board/BoardFactory.java @@ -4,6 +4,7 @@ import domain.Side; import domain.piece.Piece; import domain.piece.PieceFactory; +import domain.piece.PieceType; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/domain/board/Formation.java b/src/main/java/domain/board/Formation.java index fd30a99656..11d940a95f 100644 --- a/src/main/java/domain/board/Formation.java +++ b/src/main/java/domain/board/Formation.java @@ -1,5 +1,6 @@ package domain.board; +import domain.piece.PieceType; import java.util.Arrays; import java.util.List; import parser.Command; diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index 5d1d0fc092..35f8852625 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -9,6 +9,8 @@ import java.util.List; public class Cannon extends Piece { + private final PieceType pieceType = PieceType.CANNON; + public Cannon(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); } @@ -89,7 +91,7 @@ public boolean isCannon() { } @Override - public String toString() { - return "포"; + public String getName() { + return pieceType.getName(); } } diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java index c04ca215e4..2eaa5473af 100644 --- a/src/main/java/domain/piece/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -9,6 +9,8 @@ import java.util.List; public class Chariot extends Piece { + private final PieceType pieceType = PieceType.CHARIOT; + public Chariot(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); } @@ -51,7 +53,7 @@ private void addIfEnemy(List valid, Position position, BoardReader boa } @Override - public String toString() { - return "차"; + public String getName() { + return pieceType.getName(); } } diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java index ee0e0d5fbd..6375d40f43 100644 --- a/src/main/java/domain/piece/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -8,6 +8,8 @@ import java.util.List; public class Elephant extends Piece { + private final PieceType pieceType = PieceType.ELEPHANT; + public Elephant(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); } @@ -18,7 +20,7 @@ protected List filterValidPositions(Position current, List paths } @Override - public String toString() { - return "상"; + public String getName() { + return pieceType.getName(); } } diff --git a/src/main/java/domain/piece/General.java b/src/main/java/domain/piece/General.java index fcdc8208f7..014a69f09e 100644 --- a/src/main/java/domain/piece/General.java +++ b/src/main/java/domain/piece/General.java @@ -8,6 +8,8 @@ import java.util.List; public class General extends Piece { + private final PieceType pieceType = PieceType.GENERAL; + public General(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); } @@ -23,7 +25,7 @@ public boolean isGeneral() { } @Override - public String toString() { - return "궁"; + public String getName() { + return pieceType.getName(); } } diff --git a/src/main/java/domain/piece/Guard.java b/src/main/java/domain/piece/Guard.java index 1b3422d6bf..f8d6c13b92 100644 --- a/src/main/java/domain/piece/Guard.java +++ b/src/main/java/domain/piece/Guard.java @@ -8,6 +8,8 @@ import java.util.List; public class Guard extends Piece { + private final PieceType pieceType = PieceType.GUARD; + public Guard(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); } @@ -18,7 +20,7 @@ protected List filterValidPositions(Position current, List paths } @Override - public String toString() { - return "사"; + public String getName() { + return pieceType.getName(); } } diff --git a/src/main/java/domain/piece/Horse.java b/src/main/java/domain/piece/Horse.java index 63469b7abe..9df4dcb332 100644 --- a/src/main/java/domain/piece/Horse.java +++ b/src/main/java/domain/piece/Horse.java @@ -8,6 +8,8 @@ import java.util.List; public class Horse extends Piece { + private final PieceType pieceType = PieceType.HORSE; + public Horse(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); } @@ -18,7 +20,7 @@ protected List filterValidPositions(Position current, List paths } @Override - public String toString() { - return "마"; + public String getName() { + return pieceType.getName(); } } diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 51f518f313..409b137aa6 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -68,4 +68,6 @@ public boolean isGeneral() { public boolean isCannon() { return false; } + + public abstract String getName(); } diff --git a/src/main/java/domain/board/PieceType.java b/src/main/java/domain/piece/PieceType.java similarity index 93% rename from src/main/java/domain/board/PieceType.java rename to src/main/java/domain/piece/PieceType.java index ff7ed63763..3c08f23884 100644 --- a/src/main/java/domain/board/PieceType.java +++ b/src/main/java/domain/piece/PieceType.java @@ -1,4 +1,4 @@ -package domain.board; +package domain.piece; public enum PieceType { GENERAL("궁"), diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java index e0fc4bc958..2fd6d32402 100644 --- a/src/main/java/domain/piece/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -8,6 +8,8 @@ import java.util.List; public class Soldier extends Piece { + private final PieceType pieceType = PieceType.SOLDIER; + public Soldier(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); } @@ -18,7 +20,7 @@ protected List filterValidPositions(Position current, List paths } @Override - public String toString() { - return "졸"; + public String getName() { + return pieceType.getName(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 09d55a68e4..9ca0973c11 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -43,7 +43,7 @@ private String formatCell(Piece piece) { if (piece == null) { return EMPTY + SPACE; } - return getColor(piece.getSide()) + piece + SPACE + RESET; + return getColor(piece.getSide()) + piece.getName() + SPACE + RESET; } private String getColor(Side side) { From 37b4bffb33b9a8eb850d1e6552c423a02e226c1c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 19:13:29 +0900 Subject: [PATCH 85/92] =?UTF-8?q?refactor(Path):=20=EB=B9=88=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EA=B0=80=EC=A7=80=EB=8A=94=20Pat?= =?UTF-8?q?h=EA=B0=80=20=EC=83=9D=EC=84=B1=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=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/domain/strategy/Path.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/domain/strategy/Path.java b/src/main/java/domain/strategy/Path.java index 50022309e8..59f3ed6769 100644 --- a/src/main/java/domain/strategy/Path.java +++ b/src/main/java/domain/strategy/Path.java @@ -7,27 +7,20 @@ public class Path { private final List positions; public Path(List positions) { + if (positions == null || positions.isEmpty()) { + throw new IllegalArgumentException("불가능한 경로입니다."); + } this.positions = List.copyOf(positions); } public Position getDestination() { - if (isEmpty()) { - throw new IllegalStateException("경로가 존재하지 않습니다."); - } return positions.getLast(); } public List getObstacles() { - if (isEmpty()) { - return List.of(); - } return positions.subList(0, positions.size() - 1); } - public boolean isEmpty() { - return positions.isEmpty(); - } - public List getPositions() { return positions; } From 78d07b7914589621062e9c3539643b4b7786c4b0 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 19:14:58 +0900 Subject: [PATCH 86/92] =?UTF-8?q?refactor(ContinuousStrategy):=EB=B6=88?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=EA=B2=BD=EB=A1=9C=EC=9D=B4?= =?UTF-8?q?=EB=A9=B4=20Optional.empty=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=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/domain/strategy/ContinuousStrategy.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/domain/strategy/ContinuousStrategy.java b/src/main/java/domain/strategy/ContinuousStrategy.java index 1d373c41ff..79172c3667 100644 --- a/src/main/java/domain/strategy/ContinuousStrategy.java +++ b/src/main/java/domain/strategy/ContinuousStrategy.java @@ -3,7 +3,7 @@ import domain.Position; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; +import java.util.Optional; public class ContinuousStrategy implements MovementStrategy { private final List directions; @@ -16,11 +16,11 @@ public ContinuousStrategy(List directions) { public List generatePaths(Position current) { return directions.stream() .map(direction -> createPath(current, direction)) - .filter(path -> !path.isEmpty()) + .flatMap(Optional::stream) .toList(); } - private Path createPath(Position current, Direction direction) { + private Optional createPath(Position current, Direction direction) { List positions = new ArrayList<>(); Position position = current; @@ -28,6 +28,9 @@ private Path createPath(Position current, Direction direction) { position = position.move(direction); positions.add(position); } - return new Path(positions); + if (positions.isEmpty()) { + return Optional.empty(); + } + return Optional.of(new Path(positions)); } } From 4cbce1a4efc61b84de1874abd505ef265fb181b9 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 19:15:03 +0900 Subject: [PATCH 87/92] =?UTF-8?q?refactor(SequenceStrategy):=EB=B6=88?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=EA=B2=BD=EB=A1=9C=EC=9D=B4?= =?UTF-8?q?=EB=A9=B4=20Optional.empty=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=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/domain/strategy/SequenceStrategy.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/domain/strategy/SequenceStrategy.java b/src/main/java/domain/strategy/SequenceStrategy.java index eb0bf1ba84..7da194a4db 100644 --- a/src/main/java/domain/strategy/SequenceStrategy.java +++ b/src/main/java/domain/strategy/SequenceStrategy.java @@ -3,7 +3,7 @@ import domain.Position; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; +import java.util.Optional; public class SequenceStrategy implements MovementStrategy { private final List> sequences; @@ -16,11 +16,11 @@ public SequenceStrategy(List> sequences) { public List generatePaths(Position current) { return sequences.stream() .map(sequence -> createPath(current, sequence)) - .filter(path -> !path.isEmpty()) + .flatMap(Optional::stream) .toList(); } - private Path createPath(Position current, List sequence) { + private Optional createPath(Position current, List sequence) { List positions = new ArrayList<>(); Position position = current; int index = 0; @@ -30,9 +30,9 @@ private Path createPath(Position current, List sequence) { index++; } if (isInvalidPath(sequence, index)) { - return new Path(List.of()); + return Optional.empty(); } - return new Path(positions); + return Optional.of(new Path(positions)); } private boolean isInvalidPath(List sequence, int index) { From dbcdd0a0bf9cba85afb8115a0c882608455f541e Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 19:15:42 +0900 Subject: [PATCH 88/92] =?UTF-8?q?test(CommandTest):=20=EA=B3=B5=EB=B0=B1?= =?UTF-8?q?=EC=9D=B4=20=ED=8F=AC=ED=95=A8=EB=90=9C=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=94=B0=EB=A1=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/parser/CommandTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/test/java/parser/CommandTest.java b/src/test/java/parser/CommandTest.java index 7965ffa0e5..746abf6843 100644 --- a/src/test/java/parser/CommandTest.java +++ b/src/test/java/parser/CommandTest.java @@ -11,11 +11,18 @@ class CommandTest { @Test void 포메이션_입력이_1_2_3_4_일때_정상_동작한다() { Assertions.assertThat(Command.from("1")).isEqualTo(Command.FIRST); - assertThat(Command.from(" 2 ")).isEqualTo(Command.SECOND); + assertThat(Command.from("2")).isEqualTo(Command.SECOND); assertThat(Command.from("3")).isEqualTo(Command.THIRD); assertThat(Command.from("4")).isEqualTo(Command.FOURTH); } + @Test + void 포메이션_입력에_공백이_포함되어도_정상_동작한다() { + Assertions.assertThat(Command.from("1 ")).isEqualTo(Command.FIRST); + assertThat(Command.from(" 2")).isEqualTo(Command.SECOND); + assertThat(Command.from(" 3 ")).isEqualTo(Command.THIRD); + } + @Test void 포메이션_입력이_1_2_3_4_중_하나가_아니면_예외가_발생한다() { assertThatThrownBy(() -> Command.from("0")) From 827f2a6a4b7927ab668bbc9d05e174412b5489a2 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 19:42:54 +0900 Subject: [PATCH 89/92] =?UTF-8?q?refactor(Piece):=20valid=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20mutable=20accumulator=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=20=ED=8C=A8=ED=84=B4=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=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/domain/piece/Piece.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 409b137aa6..a346426952 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -6,7 +6,6 @@ import domain.board.BoardReader; import domain.strategy.MovementStrategy; import domain.strategy.Path; -import java.util.ArrayList; import java.util.List; public abstract class Piece { @@ -27,17 +26,10 @@ public Destinations findDestinations(Position current, BoardReader board) { protected abstract List filterValidPositions(Position current, List paths, BoardReader board); protected List filterStandardPaths(List paths, BoardReader board) { - List valid = new ArrayList<>(); - for (Path path : paths) { - addIfValidDestination(valid, path, board); - } - return valid; - } - - private void addIfValidDestination(List valid, Path path, BoardReader board) { - if (isMovablePath(path, board)) { - valid.add(path.getDestination()); - } + return paths.stream() + .filter(path -> isMovablePath(path, board)) + .map(Path::getDestination) + .toList(); } private boolean isMovablePath(Path path, BoardReader board) { From 37c2f77aa59a4a336417723a1af1700f3ae894c5 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 19:43:03 +0900 Subject: [PATCH 90/92] =?UTF-8?q?refactor(Chariot):=20valid=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20mutable=20accumulator=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=20=ED=8C=A8=ED=84=B4=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=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/domain/piece/Chariot.java | 29 ++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java index 2eaa5473af..111a74cfb5 100644 --- a/src/main/java/domain/piece/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -7,6 +7,7 @@ import domain.strategy.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class Chariot extends Piece { private final PieceType pieceType = PieceType.CHARIOT; @@ -17,18 +18,17 @@ public Chariot(Side side, MovementStrategy movementStrategy) { @Override protected List filterValidPositions(Position current, List paths, BoardReader board) { - List valid = new ArrayList<>(); - for (Path path : paths) { - collectPathPositions(valid, path, board); - } - return valid; + return paths.stream() + .flatMap(path -> collectPathPositions(path, board).stream()) + .toList(); } - private void collectPathPositions(List valid, Path path, BoardReader board) { + private List collectPathPositions(Path path, BoardReader board) { List positions = path.getPositions(); int obstacleIndex = findObstacleIndex(positions, board); - valid.addAll(positions.subList(0, obstacleIndex)); - addCaptureIfPossible(valid, positions, obstacleIndex, board); + List valid = new ArrayList<>(positions.subList(0, obstacleIndex)); + findCapturePosition(positions, obstacleIndex, board).ifPresent(valid::add); + return valid; } private int findObstacleIndex(List positions, BoardReader board) { @@ -39,17 +39,16 @@ private int findObstacleIndex(List positions, BoardReader board) { return index; } - private void addCaptureIfPossible(List valid, List positions, int index, BoardReader board) { - if (index < positions.size()) { - addIfEnemy(valid, positions.get(index), board); + private Optional findCapturePosition(List positions, int index, BoardReader board) { + if (index >= positions.size()) { + return Optional.empty(); } - } - - private void addIfEnemy(List valid, Position position, BoardReader board) { + Position position = positions.get(index); Piece target = board.getPiece(position); if (!target.isAlly(getSide())) { - valid.add(position); + return Optional.of(position); } + return Optional.empty(); } @Override From 94913253726f2876fff2782c428fd6da7c143d18 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 2 Apr 2026 19:43:09 +0900 Subject: [PATCH 91/92] =?UTF-8?q?refactor(Cannon):=20valid=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20mutable=20accumulator=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=20=ED=8C=A8=ED=84=B4=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=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/domain/piece/Cannon.java | 35 +++++++++++++------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index 35f8852625..3413e2c759 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -7,6 +7,7 @@ import domain.strategy.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class Cannon extends Piece { private final PieceType pieceType = PieceType.CANNON; @@ -17,22 +18,20 @@ public Cannon(Side side, MovementStrategy movementStrategy) { @Override protected List filterValidPositions(Position current, List paths, BoardReader board) { - List valid = new ArrayList<>(); - for (Path path : paths) { - addJumpPathPositions(valid, path, board); - } - return valid; + return paths.stream() + .flatMap(path -> collectJumpPathPositions(path, board).stream()) + .toList(); } - private void addJumpPathPositions(List valid, Path path, BoardReader board) { + private List collectJumpPathPositions(Path path, BoardReader board) { List positions = path.getPositions(); int bridgeIndex = findBridgeIndex(positions, board); if (isInvalidBridge(bridgeIndex, positions, board)) { - return; + return List.of(); } - collectValidDestinations(valid, positions, bridgeIndex + 1, board); + return collectValidDestinations(positions, bridgeIndex + 1, board); } private int findBridgeIndex(List positions, BoardReader board) { @@ -58,10 +57,11 @@ private boolean isInvalidBridge(int index, List positions, BoardReader return bridge.isCannon(); } - private void collectValidDestinations(List valid, List positions, int startIndex, BoardReader board) { + private List collectValidDestinations(List positions, int startIndex, BoardReader board) { int obstacleIndex = findObstacleIndex(positions, startIndex, board); - valid.addAll(positions.subList(startIndex, obstacleIndex)); - addCatchableIfPossible(valid, positions, obstacleIndex, board); + List valid = new ArrayList<>(positions.subList(startIndex, obstacleIndex)); + findCatchablePosition(positions, obstacleIndex, board).ifPresent(valid::add); + return valid; } private int findObstacleIndex(List positions, int startIndex, BoardReader board) { @@ -72,17 +72,16 @@ private int findObstacleIndex(List positions, int startIndex, BoardRea return index; } - private void addCatchableIfPossible(List valid, List positions, int index, BoardReader board) { - if (index < positions.size()) { - addIfCatchable(valid, positions.get(index), board); + private Optional findCatchablePosition(List positions, int index, BoardReader board) { + if (index >= positions.size()) { + return Optional.empty(); } - } - - private void addIfCatchable(List valid, Position position, BoardReader board) { + Position position = positions.get(index); Piece target = board.getPiece(position); if (!target.isCannon() && !target.isAlly(getSide())) { - valid.add(position); + return Optional.of(position); } + return Optional.empty(); } @Override From 512a74e2e0fcf8717d73c11c347198099dc9dbda Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sat, 4 Apr 2026 19:44:34 +0900 Subject: [PATCH 92/92] =?UTF-8?q?refactor(Side,Formation):=20Side=20?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=20=EC=A0=95=EB=B3=B4=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20FormationCommand=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/application/GameManager.java | 3 +- src/main/java/domain/Side.java | 56 ------------------- src/main/java/domain/board/BoardFactory.java | 27 +++++---- src/main/java/domain/board/BoardLayout.java | 12 ++++ .../java/domain/board/BoardLayoutMapper.java | 20 +++++++ src/main/java/domain/board/Formation.java | 17 +----- src/main/java/parser/Command.java | 24 -------- src/main/java/parser/FormationCommand.java | 31 ++++++++++ src/main/java/parser/InputParser.java | 6 ++ src/test/java/domain/GameTest.java | 3 +- src/test/java/domain/board/FormationTest.java | 17 ++++-- src/test/java/parser/CommandTest.java | 34 ----------- .../java/parser/FormationCommandTest.java | 33 +++++++++++ src/test/java/parser/InputParserTest.java | 19 +++++++ 14 files changed, 152 insertions(+), 150 deletions(-) create mode 100644 src/main/java/domain/board/BoardLayout.java create mode 100644 src/main/java/domain/board/BoardLayoutMapper.java delete mode 100644 src/main/java/parser/Command.java create mode 100644 src/main/java/parser/FormationCommand.java delete mode 100644 src/test/java/parser/CommandTest.java create mode 100644 src/test/java/parser/FormationCommandTest.java diff --git a/src/main/java/application/GameManager.java b/src/main/java/application/GameManager.java index d23ef9dc66..283ef7eb9e 100644 --- a/src/main/java/application/GameManager.java +++ b/src/main/java/application/GameManager.java @@ -6,7 +6,6 @@ import domain.board.Board; import domain.board.BoardFactory; import domain.board.Formation; -import parser.Command; import domain.player.Name; import domain.player.Players; import java.util.List; @@ -48,7 +47,7 @@ private Name getPlayerName(Side side) { } private Formation getFormation(Side side) { - return retry(() -> Formation.from(Command.from(inputView.readFormation(side)))); + return retry(() -> InputParser.parseFormation(inputView.readFormation(side))); } private void playTurn(Game game) { diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java index 009b277950..60b09cfafd 100644 --- a/src/main/java/domain/Side.java +++ b/src/main/java/domain/Side.java @@ -1,66 +1,15 @@ package domain; import domain.strategy.Direction; -import java.util.List; public enum Side { CHO("초") { - @Override - public int baseY() { - return 0; - } - - @Override - public int generalY() { - return 1; - } - - @Override - public int cannonY() { - return 2; - } - - @Override - public int soldierY() { - return 3; - } - - @Override - public List formationX() { - return List.of(1, 2, 6, 7); - } - @Override public Direction soldierForward() { return Direction.N; } }, HAN("한") { - @Override - public int baseY() { - return 9; - } - - @Override - public int generalY() { - return 8; - } - - @Override - public int cannonY() { - return 7; - } - - @Override - public int soldierY() { - return 6; - } - - @Override - public List formationX() { - return List.of(7, 6, 2, 1); - } - @Override public Direction soldierForward() { return Direction.S; @@ -73,11 +22,6 @@ public Direction soldierForward() { this.name = name; } - public abstract int baseY(); - public abstract int generalY(); - public abstract int cannonY(); - public abstract int soldierY(); - public abstract List formationX(); public abstract Direction soldierForward(); public boolean isAlly(Side other) { diff --git a/src/main/java/domain/board/BoardFactory.java b/src/main/java/domain/board/BoardFactory.java index 29f7173dd1..f3dc4dea41 100644 --- a/src/main/java/domain/board/BoardFactory.java +++ b/src/main/java/domain/board/BoardFactory.java @@ -10,6 +10,7 @@ import java.util.Map; public class BoardFactory { + private static final BoardLayoutMapper BOARD_LAYOUT_MAPPER = new BoardLayoutMapper(); private BoardFactory() { } @@ -32,35 +33,41 @@ private static void placeFixedPieces(Map pieces, Side side) { } private static void placeGeneral(Map pieces, Side side) { - pieces.put(Position.of(4, side.generalY()), PieceFactory.createGeneral(side)); + BoardLayout boardLayout = BOARD_LAYOUT_MAPPER.get(side); + pieces.put(Position.of(4, boardLayout.generalY()), PieceFactory.createGeneral(side)); } private static void placeChariots(Map pieces, Side side) { - pieces.put(Position.of(0, side.baseY()), PieceFactory.createChariot(side)); - pieces.put(Position.of(8, side.baseY()), PieceFactory.createChariot(side)); + BoardLayout boardLayout = BOARD_LAYOUT_MAPPER.get(side); + pieces.put(Position.of(0, boardLayout.baseY()), PieceFactory.createChariot(side)); + pieces.put(Position.of(8, boardLayout.baseY()), PieceFactory.createChariot(side)); } private static void placeCannons(Map pieces, Side side) { - pieces.put(Position.of(1, side.cannonY()), PieceFactory.createCannon(side)); - pieces.put(Position.of(7, side.cannonY()), PieceFactory.createCannon(side)); + BoardLayout boardLayout = BOARD_LAYOUT_MAPPER.get(side); + pieces.put(Position.of(1, boardLayout.cannonY()), PieceFactory.createCannon(side)); + pieces.put(Position.of(7, boardLayout.cannonY()), PieceFactory.createCannon(side)); } private static void placeGuards(Map pieces, Side side) { - pieces.put(Position.of(3, side.baseY()), PieceFactory.createGuard(side)); - pieces.put(Position.of(5, side.baseY()), PieceFactory.createGuard(side)); + BoardLayout boardLayout = BOARD_LAYOUT_MAPPER.get(side); + pieces.put(Position.of(3, boardLayout.baseY()), PieceFactory.createGuard(side)); + pieces.put(Position.of(5, boardLayout.baseY()), PieceFactory.createGuard(side)); } private static void placeSoldiers(Map pieces, Side side) { + BoardLayout boardLayout = BOARD_LAYOUT_MAPPER.get(side); for (int x = 0; x <= 8; x += 2) { - pieces.put(Position.of(x, side.soldierY()), PieceFactory.createSoldier(side)); + pieces.put(Position.of(x, boardLayout.soldierY()), PieceFactory.createSoldier(side)); } } private static void placeFormationPieces(Map pieces, Side side, Formation formation) { - List formationX = side.formationX(); + BoardLayout boardLayout = BOARD_LAYOUT_MAPPER.get(side); + List formationX = boardLayout.formationX(); List orders = formation.getOrders(); for (int i = 0; i < orders.size(); i++) { - Position position = Position.of(formationX.get(i), side.baseY()); + Position position = Position.of(formationX.get(i), boardLayout.baseY()); pieces.put(position, createFormationPiece(orders.get(i), side)); } } diff --git a/src/main/java/domain/board/BoardLayout.java b/src/main/java/domain/board/BoardLayout.java new file mode 100644 index 0000000000..3329d42db0 --- /dev/null +++ b/src/main/java/domain/board/BoardLayout.java @@ -0,0 +1,12 @@ +package domain.board; + +import java.util.List; + +public record BoardLayout( + int baseY, + int generalY, + int cannonY, + int soldierY, + List formationX +) { +} diff --git a/src/main/java/domain/board/BoardLayoutMapper.java b/src/main/java/domain/board/BoardLayoutMapper.java new file mode 100644 index 0000000000..ef6c6e5d29 --- /dev/null +++ b/src/main/java/domain/board/BoardLayoutMapper.java @@ -0,0 +1,20 @@ +package domain.board; + +import domain.Side; +import java.util.List; +import java.util.Map; + +public class BoardLayoutMapper { + private static final Map LAYOUTS = Map.of( + Side.CHO, new BoardLayout(0, 1, 2, 3, List.of(1, 2, 6, 7)), + Side.HAN, new BoardLayout(9, 8, 7, 6, List.of(7, 6, 2, 1)) + ); + + public BoardLayout get(Side side) { + BoardLayout boardLayout = LAYOUTS.get(side); + if (boardLayout == null) { + throw new IllegalArgumentException("지원하지 않는 진영입니다."); + } + return boardLayout; + } +} diff --git a/src/main/java/domain/board/Formation.java b/src/main/java/domain/board/Formation.java index 11d940a95f..c6f98f371e 100644 --- a/src/main/java/domain/board/Formation.java +++ b/src/main/java/domain/board/Formation.java @@ -1,44 +1,29 @@ package domain.board; import domain.piece.PieceType; -import java.util.Arrays; import java.util.List; -import parser.Command; public enum Formation { LEFT_ELEPHANT( - Command.FIRST, List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE) ), RIGHT_ELEPHANT( - Command.SECOND, List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT) ), OUTER_ELEPHANT( - Command.THIRD, List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.HORSE, PieceType.ELEPHANT) ), INNER_ELEPHANT( - Command.FOURTH, List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.ELEPHANT, PieceType.HORSE) ), ; - private final Command command; private final List orders; - Formation(Command command, List orders) { - this.command = command; + Formation(List orders) { this.orders = List.copyOf(orders); } - public static Formation from(Command command) { - return Arrays.stream(values()) - .filter(formation -> formation.command.equals(command)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("올바른 배치가 아닙니다.")); - } - public List getOrders() { return orders; } diff --git a/src/main/java/parser/Command.java b/src/main/java/parser/Command.java deleted file mode 100644 index 1181f11fbc..0000000000 --- a/src/main/java/parser/Command.java +++ /dev/null @@ -1,24 +0,0 @@ -package parser; - -import java.util.Arrays; - -public enum Command { - FIRST("1"), - SECOND("2"), - THIRD("3"), - FOURTH("4"), - ; - - private final String input; - - Command(String input) { - this.input = input; - } - - public static Command from(String input) { - return Arrays.stream(values()) - .filter(command -> command.input.equals(input.strip())) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("올바른 입력이 아닙니다.")); - } -} diff --git a/src/main/java/parser/FormationCommand.java b/src/main/java/parser/FormationCommand.java new file mode 100644 index 0000000000..cba6d7a1ee --- /dev/null +++ b/src/main/java/parser/FormationCommand.java @@ -0,0 +1,31 @@ +package parser; + +import domain.board.Formation; +import java.util.Arrays; + +public enum FormationCommand { + FIRST("1", Formation.LEFT_ELEPHANT), + SECOND("2", Formation.RIGHT_ELEPHANT), + THIRD("3", Formation.OUTER_ELEPHANT), + FOURTH("4", Formation.INNER_ELEPHANT), + ; + + private final String input; + private final Formation formation; + + FormationCommand(String input, Formation formation) { + this.input = input; + this.formation = formation; + } + + public static FormationCommand from(String input) { + return Arrays.stream(values()) + .filter(command -> command.input.equals(input.strip())) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("올바른 배치가 아닙니다.")); + } + + public Formation toFormation() { + return formation; + } +} diff --git a/src/main/java/parser/InputParser.java b/src/main/java/parser/InputParser.java index e54ca93757..6f94922c05 100644 --- a/src/main/java/parser/InputParser.java +++ b/src/main/java/parser/InputParser.java @@ -2,6 +2,7 @@ import domain.Position; +import domain.board.Formation; import domain.player.Name; import java.util.Arrays; import java.util.List; @@ -22,6 +23,11 @@ public static Position parsePosition(String input) { return getPosition(input); } + public static Formation parseFormation(String input) { + validateNullOrBlank(input); + return FormationCommand.from(input).toFormation(); + } + private static Position getPosition(String input) { List coordinate = Arrays.stream(input.strip().split(",")) .map(String::strip) diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index f66c071773..5428eaf944 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -6,7 +6,6 @@ import domain.board.Board; import domain.board.BoardFactory; import domain.board.Formation; -import parser.Command; import domain.piece.Piece; import domain.piece.PieceFactory; import domain.player.Name; @@ -22,7 +21,7 @@ class GameTest { @BeforeEach void setUp() { players = Players.createInitial(new Name("cho"), new Name("han")); - Board board = BoardFactory.create(Formation.from(Command.FIRST), Formation.from(Command.FIRST)); + Board board = BoardFactory.create(Formation.LEFT_ELEPHANT, Formation.LEFT_ELEPHANT); game = new Game(board, players); } diff --git a/src/test/java/domain/board/FormationTest.java b/src/test/java/domain/board/FormationTest.java index f0cf0cd4a2..a29797be72 100644 --- a/src/test/java/domain/board/FormationTest.java +++ b/src/test/java/domain/board/FormationTest.java @@ -2,16 +2,21 @@ import static org.assertj.core.api.Assertions.assertThat; +import domain.piece.PieceType; +import java.util.List; import org.junit.jupiter.api.Test; -import parser.Command; class FormationTest { @Test - void 선택값으로_포메이션을_찾는다() { - assertThat(Formation.from(Command.FIRST)).isEqualTo(Formation.LEFT_ELEPHANT); - assertThat(Formation.from(Command.SECOND)).isEqualTo(Formation.RIGHT_ELEPHANT); - assertThat(Formation.from(Command.THIRD)).isEqualTo(Formation.OUTER_ELEPHANT); - assertThat(Formation.from(Command.FOURTH)).isEqualTo(Formation.INNER_ELEPHANT); + void 포메이션은_기물_배치_순서를_가진다() { + assertThat(Formation.LEFT_ELEPHANT.getOrders()) + .isEqualTo(List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE)); + assertThat(Formation.RIGHT_ELEPHANT.getOrders()) + .isEqualTo(List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT)); + assertThat(Formation.OUTER_ELEPHANT.getOrders()) + .isEqualTo(List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.HORSE, PieceType.ELEPHANT)); + assertThat(Formation.INNER_ELEPHANT.getOrders()) + .isEqualTo(List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.ELEPHANT, PieceType.HORSE)); } } diff --git a/src/test/java/parser/CommandTest.java b/src/test/java/parser/CommandTest.java deleted file mode 100644 index 746abf6843..0000000000 --- a/src/test/java/parser/CommandTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package parser; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -class CommandTest { - - @Test - void 포메이션_입력이_1_2_3_4_일때_정상_동작한다() { - Assertions.assertThat(Command.from("1")).isEqualTo(Command.FIRST); - assertThat(Command.from("2")).isEqualTo(Command.SECOND); - assertThat(Command.from("3")).isEqualTo(Command.THIRD); - assertThat(Command.from("4")).isEqualTo(Command.FOURTH); - } - - @Test - void 포메이션_입력에_공백이_포함되어도_정상_동작한다() { - Assertions.assertThat(Command.from("1 ")).isEqualTo(Command.FIRST); - assertThat(Command.from(" 2")).isEqualTo(Command.SECOND); - assertThat(Command.from(" 3 ")).isEqualTo(Command.THIRD); - } - - @Test - void 포메이션_입력이_1_2_3_4_중_하나가_아니면_예외가_발생한다() { - assertThatThrownBy(() -> Command.from("0")) - .isInstanceOf(IllegalArgumentException.class); - - assertThatThrownBy(() -> Command.from("5")) - .isInstanceOf(IllegalArgumentException.class); - } -} diff --git a/src/test/java/parser/FormationCommandTest.java b/src/test/java/parser/FormationCommandTest.java new file mode 100644 index 0000000000..64b1d48210 --- /dev/null +++ b/src/test/java/parser/FormationCommandTest.java @@ -0,0 +1,33 @@ +package parser; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import domain.board.Formation; +import org.junit.jupiter.api.Test; + +class FormationCommandTest { + + @Test + void 포메이션_입력이_1_2_3_4_일때_정상_동작한다() { + assertThat(FormationCommand.from("1").toFormation()).isEqualTo(Formation.LEFT_ELEPHANT); + assertThat(FormationCommand.from("2").toFormation()).isEqualTo(Formation.RIGHT_ELEPHANT); + assertThat(FormationCommand.from("3").toFormation()).isEqualTo(Formation.OUTER_ELEPHANT); + assertThat(FormationCommand.from("4").toFormation()).isEqualTo(Formation.INNER_ELEPHANT); + } + + @Test + void 포메이션_입력에_공백이_포함되어도_정상_동작한다() { + assertThat(FormationCommand.from("1 ").toFormation()).isEqualTo(Formation.LEFT_ELEPHANT); + assertThat(FormationCommand.from(" 2").toFormation()).isEqualTo(Formation.RIGHT_ELEPHANT); + assertThat(FormationCommand.from(" 3 ").toFormation()).isEqualTo(Formation.OUTER_ELEPHANT); + } + + @Test + void 포메이션_입력이_1_2_3_4_중_하나가_아니면_예외가_발생한다() { + assertThatThrownBy(() -> FormationCommand.from("0")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> FormationCommand.from("5")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/parser/InputParserTest.java b/src/test/java/parser/InputParserTest.java index b2198f8c86..e30f4879db 100644 --- a/src/test/java/parser/InputParserTest.java +++ b/src/test/java/parser/InputParserTest.java @@ -1,11 +1,14 @@ package parser; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import domain.board.Formation; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.Test; class InputParserTest { @@ -23,4 +26,20 @@ class InputParserTest { assertThatThrownBy(() -> InputParser.parsePosition(input)) .isInstanceOf(IllegalArgumentException.class); } + + @Test + void 포메이션_입력을_도메인_포메이션으로_파싱한다() { + assertThat(InputParser.parseFormation("1")).isEqualTo(Formation.LEFT_ELEPHANT); + assertThat(InputParser.parseFormation("2")).isEqualTo(Formation.RIGHT_ELEPHANT); + assertThat(InputParser.parseFormation("3")).isEqualTo(Formation.OUTER_ELEPHANT); + assertThat(InputParser.parseFormation("4")).isEqualTo(Formation.INNER_ELEPHANT); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" ", "0", "5", "a"}) + void 잘못된_포메이션_입력이면_예외가_발생한다(String input) { + assertThatThrownBy(() -> InputParser.parseFormation(input)) + .isInstanceOf(IllegalArgumentException.class); + } }