From 1b6e5a4401a030e520521ac5bebadb3d2bd3a8a5 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Wed, 25 Mar 2026 17:29:32 +0900 Subject: [PATCH 01/34] =?UTF-8?q?feat:=20=EA=B0=81=20=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EC=A7=84=ED=98=95=20=EC=9E=85=EB=A0=A5=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 | 34 ++++++++++++++++ src/main/java/.gitkeep | 0 src/main/java/Main.java | 9 +++++ .../java/controller/JanggiController.java | 40 +++++++++++++++++++ src/main/java/domain/Formation.java | 28 +++++++++++++ src/main/java/domain/Side.java | 6 +++ src/main/java/view/InputView.java | 29 ++++++++++++++ 7 files changed, 146 insertions(+) delete mode 100644 src/main/java/.gitkeep create mode 100644 src/main/java/Main.java create mode 100644 src/main/java/controller/JanggiController.java create mode 100644 src/main/java/domain/Formation.java create mode 100644 src/main/java/domain/Side.java create mode 100644 src/main/java/view/InputView.java diff --git a/README.md b/README.md index 9775dda0ae..62817e31ca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,37 @@ # java-janggi 장기 미션 저장소 + +# 기능 + +## 1-1 + +### ✅ 각 플레이어 진형 입력 기능 + +### ☑️ 장기판 초기화 기능 + + +## 1-2 + +### ☑️ 이동할 목적지 좌표 입력 기능 + +### ☑️ 이동할 기물 위치 좌표 입력 기능 + +### ☑️ 장기 진행 상태 출력 기능 + +- 장기판을 생성한다. +- 한, 초 모든 기물을 생성한다. +- 기물들을 초기 위치로 세팅한다. + +// enum을 기반으로 포진을 결정하는데 1,2,3,4 -> +// 장기판을 생성할 때 작업 : 9x10의 Position을 모두 만든다. 쫄 뺴치, 포배치, 상배치 + +// 장기판 생성 메서드 + 1. 좌표세팅 메서드 + 2. 7개의 기물 배치 메서드 / (1개의 기물 배치 메서드, 1개의 상/마 배치 메서드) + +보드에 넣 아니면 안넣 새로 setter그런 클래스를 새로 만들것이냐 + +Board, Piece(인터페이스/추상클래스), Piece 구체 클레스, 장기판Maker + +## ⚠️ 기능 예외 사항 \ No newline at end of file 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/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000000..dda2dc085e --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,9 @@ +import controller.JanggiController; +import view.InputView; + +public class Main { + public static void main(String[] args) { + JanggiController janggiController = new JanggiController(new InputView()); + janggiController.run(); + } +} diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java new file mode 100644 index 0000000000..8fe0acb914 --- /dev/null +++ b/src/main/java/controller/JanggiController.java @@ -0,0 +1,40 @@ +package controller; + +import domain.Formation; +import view.InputView; + +public class JanggiController { + + private final InputView inputView; + + public JanggiController(InputView inputView) { + this.inputView = inputView; + } + + public void run() { + Formation Choformation = readChoFormation(); + Formation Hanformation = readHanFormation(); + } + + private Formation readChoFormation() { + while(true) { + try { + String choFormation = inputView.readChoFormation(); + return Formation.from(choFormation); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + private Formation readHanFormation() { + while(true) { + try { + String hanFormation = inputView.readHanFormation(); + return Formation.from(hanFormation); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } +} diff --git a/src/main/java/domain/Formation.java b/src/main/java/domain/Formation.java new file mode 100644 index 0000000000..5128331d97 --- /dev/null +++ b/src/main/java/domain/Formation.java @@ -0,0 +1,28 @@ +package domain; + +public enum Formation { + + 상마마상("1"), + 마상상마("2"), + 상마상마("3"), + 마상마상("4"); + + private final String option; + + Formation(String option) { + this.option = option; + } + + public static Formation from(String input) { + for(Formation formation : Formation.values()) { + if(formation.option.equals(input)) { + return formation; + } + } + throw new IllegalArgumentException("[ERROR] 번호는 1~4 사이의 숫자여야 합니다."); + } + + public String toDisplayString() { + return option + ". " + name(); + } +} diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java new file mode 100644 index 0000000000..c5741b1c41 --- /dev/null +++ b/src/main/java/domain/Side.java @@ -0,0 +1,6 @@ +package domain; + +public enum Side { + CHO, + HAN +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 0000000000..da4caec273 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,29 @@ +package view; + +import domain.Formation; +import java.util.Scanner; + +public class InputView { + private final String READ_FORMATION_MESSAGE = "[%s] 포진을 선택해주세요."; + + private final Scanner scanner = new Scanner(System.in); + + public String readChoFormation() { + System.out.printf(READ_FORMATION_MESSAGE, "초"); + System.out.println(); + return readFormation(); + } + + public String readHanFormation() { + System.out.printf(READ_FORMATION_MESSAGE, "한"); + System.out.println(); + return readFormation(); + } + + private String readFormation() { + for(Formation formation : Formation.values()) { + System.out.println(formation.toDisplayString()); + } + return scanner.nextLine(); + } +} From 7e578e44bbd95b11d4d30a990340dd39061c47a9 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Thu, 26 Mar 2026 13:50:11 +0900 Subject: [PATCH 02/34] =?UTF-8?q?test:=20Board=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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/test/java/BoardTest.java | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/test/java/BoardTest.java diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java new file mode 100644 index 0000000000..f661c65ec6 --- /dev/null +++ b/src/test/java/BoardTest.java @@ -0,0 +1,47 @@ +import domain.*; +import domain.piece.Elephant; +import domain.piece.Horse; +import domain.piece.Piece; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BoardTest { + + @DisplayName("초나라 포진을 상마마상으로 배치한다.") + @Test + void 초나라_포진을_상마마상으로_배치한다() { + Board board = new Board(Formation.from("1"), Formation.from("2")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(2, 10))).isInstanceOf(Elephant.class); + assertThat(pieces.get(Position.of(3, 10))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(7, 10))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(8, 10))).isInstanceOf(Elephant.class); + } + + @DisplayName("한나라 포진을 마상마상으로 배치한다.") + @Test + void 한나라_포진을_마상마상으로_배치한다() { + Board board = new Board(Formation.from("1"), Formation.from("4")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(2, 1))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(3, 1))).isInstanceOf(Elephant.class); + assertThat(pieces.get(Position.of(7, 1))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(8, 1))).isInstanceOf(Elephant.class); + } + + @DisplayName("각 궁이 올바른 진영에 배치된다.") + @Test + void 각_궁이_올바른_진영에_배치된다() { + Board board = new Board(Formation.from("1"), Formation.from("2")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(2, 5)).isSameSide(Side.HAN)).isTrue(); + assertThat(pieces.get(Position.of(9, 5)).isSameSide(Side.CHO)).isTrue(); + } +} From 903920fd7bc8fc2af2e2ef0f78fb8ea496054b22 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Thu, 26 Mar 2026 13:50:28 +0900 Subject: [PATCH 03/34] =?UTF-8?q?test:=20Position=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/PositionTest.java | 25 +++++++++++++++++++++++++ 1 file changed, 25 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..8373df2d72 --- /dev/null +++ b/src/test/java/PositionTest.java @@ -0,0 +1,25 @@ +import domain.Position; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + + +public class PositionTest { + + @Nested + class FromTest { + + @ParameterizedTest + @CsvSource({ + "0, 10", + "1, 11" + }) + void 목적지_좌표가_가로_1_9_세로_1_10을_벗어나면_예외가_발생한다(int x, int y) { + // when & then + Assertions.assertThatThrownBy(() -> Position.of(x, y)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("y 좌표는 1~10, x 좌표는 1~9 사이여야 합니다."); + } + } +} From 9be4c8772d4c4a98c4ca00087442ed02370514e9 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Thu, 26 Mar 2026 13:53:07 +0900 Subject: [PATCH 04/34] =?UTF-8?q?feat:=20Board=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 +-- src/main/java/Main.java | 3 +- .../java/controller/JanggiController.java | 3 +- src/main/java/domain/Board.java | 104 ++++++++++++++++++ src/main/java/domain/Position.java | 38 +++++++ src/main/java/domain/Side.java | 3 +- src/main/java/domain/piece/Cannon.java | 9 ++ src/main/java/domain/piece/Chariot.java | 9 ++ src/main/java/domain/piece/Elephant.java | 9 ++ src/main/java/domain/piece/Empty.java | 10 ++ src/main/java/domain/piece/Guard.java | 9 ++ src/main/java/domain/piece/Horse.java | 9 ++ src/main/java/domain/piece/King.java | 9 ++ src/main/java/domain/piece/Piece.java | 16 +++ src/main/java/domain/piece/Soldier.java | 9 ++ src/test/java/.gitkeep | 0 16 files changed, 238 insertions(+), 19 deletions(-) create mode 100644 src/main/java/domain/Board.java create mode 100644 src/main/java/domain/Position.java create mode 100644 src/main/java/domain/piece/Cannon.java create mode 100644 src/main/java/domain/piece/Chariot.java create mode 100644 src/main/java/domain/piece/Elephant.java create mode 100644 src/main/java/domain/piece/Empty.java create mode 100644 src/main/java/domain/piece/Guard.java create mode 100644 src/main/java/domain/piece/Horse.java create mode 100644 src/main/java/domain/piece/King.java create mode 100644 src/main/java/domain/piece/Piece.java create mode 100644 src/main/java/domain/piece/Soldier.java delete mode 100644 src/test/java/.gitkeep diff --git a/README.md b/README.md index 62817e31ca..c78d333a36 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ### ✅ 각 플레이어 진형 입력 기능 -### ☑️ 장기판 초기화 기능 +### ✅ 장기판 초기화 기능 ## 1-2 @@ -19,19 +19,4 @@ ### ☑️ 장기 진행 상태 출력 기능 -- 장기판을 생성한다. -- 한, 초 모든 기물을 생성한다. -- 기물들을 초기 위치로 세팅한다. - -// enum을 기반으로 포진을 결정하는데 1,2,3,4 -> -// 장기판을 생성할 때 작업 : 9x10의 Position을 모두 만든다. 쫄 뺴치, 포배치, 상배치 - -// 장기판 생성 메서드 - 1. 좌표세팅 메서드 - 2. 7개의 기물 배치 메서드 / (1개의 기물 배치 메서드, 1개의 상/마 배치 메서드) - -보드에 넣 아니면 안넣 새로 setter그런 클래스를 새로 만들것이냐 - -Board, Piece(인터페이스/추상클래스), Piece 구체 클레스, 장기판Maker - ## ⚠️ 기능 예외 사항 \ No newline at end of file diff --git a/src/main/java/Main.java b/src/main/java/Main.java index dda2dc085e..119de2f13d 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -1,9 +1,10 @@ import controller.JanggiController; import view.InputView; +import view.OutputView; public class Main { public static void main(String[] args) { - JanggiController janggiController = new JanggiController(new InputView()); + JanggiController janggiController = new JanggiController(new InputView(), new OutputView()); janggiController.run(); } } diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 8fe0acb914..fd1efa2941 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -2,12 +2,13 @@ import domain.Formation; import view.InputView; +import view.OutputView; public class JanggiController { private final InputView inputView; - public JanggiController(InputView inputView) { + public JanggiController(InputView inputView, OutputView outputView) { this.inputView = inputView; } diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java new file mode 100644 index 0000000000..7218d37cd1 --- /dev/null +++ b/src/main/java/domain/Board.java @@ -0,0 +1,104 @@ +package domain; + +import domain.piece.*; + +import java.util.HashMap; +import java.util.Map; + +public class Board { + + private final Map board = new HashMap<>(); + + public Board(Formation choFormation, Formation hanFormation) { + createBoard(choFormation, hanFormation); + } + + private void createBoard(Formation choFormation, Formation hanFormation) { + for(int x = 1; x<= 9; x++) { + for(int y = 1; y <= 10; y++) { + createPiece(Position.of(x, y), new Empty()); + } + } + + createPiece(Position.of(1, 1), new Chariot(Side.HAN)); + createPiece(Position.of(1, 9), new Chariot(Side.HAN)); + createPiece(Position.of(1, 10), new Chariot(Side.CHO)); + createPiece(Position.of(9, 10), new Chariot(Side.CHO)); + + createPiece(Position.of(3, 2), new Cannon(Side.HAN)); + createPiece(Position.of(3, 8), new Cannon(Side.HAN)); + createPiece(Position.of(8, 2), new Cannon(Side.CHO)); + createPiece(Position.of(8, 8), new Cannon(Side.CHO)); + + createPiece(Position.of(1, 4), new Guard(Side.HAN)); + createPiece(Position.of(1, 6), new Guard(Side.HAN)); + createPiece(Position.of(4, 10), new Guard(Side.CHO)); + createPiece(Position.of(6, 10), new Guard(Side.CHO)); + + createPiece(Position.of(2, 5), new King(Side.HAN)); + createPiece(Position.of(9, 5), new King(Side.CHO)); + + createPiece(Position.of(4, 1), new Soldier(Side.HAN)); + createPiece(Position.of(4, 3), new Soldier(Side.HAN)); + createPiece(Position.of(4, 5), new Soldier(Side.HAN)); + createPiece(Position.of(4, 7), new Soldier(Side.HAN)); + createPiece(Position.of(4, 9), new Soldier(Side.HAN)); + createPiece(Position.of(7, 1), new Soldier(Side.CHO)); + createPiece(Position.of(7, 3), new Soldier(Side.CHO)); + createPiece(Position.of(7, 5), new Soldier(Side.CHO)); + createPiece(Position.of(7, 7), new Soldier(Side.CHO)); + createPiece(Position.of(7, 9), new Soldier(Side.CHO)); + + if(choFormation == Formation.상마마상) { + createPiece(Position.of(2, 10), new Elephant(Side.CHO)); // 마 + createPiece(Position.of(3, 10), new Horse(Side.CHO)); // 상 + createPiece(Position.of(7, 10), new Horse(Side.CHO)); // 상 + createPiece(Position.of(8, 10), new Elephant(Side.CHO)); // 마 + } else if(choFormation == Formation.마상상마) { + createPiece(Position.of(2, 10), new Horse(Side.CHO)); + createPiece(Position.of(3, 10), new Elephant(Side.CHO)); + createPiece(Position.of(7, 10), new Elephant(Side.CHO)); + createPiece(Position.of(8, 10), new Horse(Side.CHO)); + } else if(choFormation == Formation.상마상마) { + createPiece(Position.of(2, 10), new Elephant(Side.CHO)); + createPiece(Position.of(3, 10), new Horse(Side.CHO)); + createPiece(Position.of(7, 10), new Elephant(Side.CHO)); + createPiece(Position.of(8, 10), new Horse(Side.CHO)); + } else if(choFormation == Formation.마상마상) { + createPiece(Position.of(2, 10), new Horse(Side.CHO)); + createPiece(Position.of(3, 10), new Elephant(Side.CHO)); + createPiece(Position.of(7, 10), new Horse(Side.CHO)); + createPiece(Position.of(8, 10), new Elephant(Side.CHO)); + } + + if(hanFormation == Formation.상마마상) { + createPiece(Position.of(2, 1), new Elephant(Side.HAN)); + createPiece(Position.of(3, 1), new Horse(Side.HAN)); // 마 + createPiece(Position.of(7, 1), new Horse(Side.HAN)); // 마 + createPiece(Position.of(8, 1), new Elephant(Side.HAN)); // 상 + } else if(hanFormation == Formation.마상상마) { + createPiece(Position.of(2, 1), new Horse(Side.HAN)); // 상 + createPiece(Position.of(3, 1), new Elephant(Side.HAN)); // 마 + createPiece(Position.of(7, 1), new Elephant(Side.HAN)); // 마 + createPiece(Position.of(8, 1), new Horse(Side.HAN)); // 상 + } else if(hanFormation == Formation.상마상마) { + createPiece(Position.of(2, 1), new Elephant(Side.HAN)); // 상 + createPiece(Position.of(3, 1), new Horse(Side.HAN)); // 마 + createPiece(Position.of(7, 1), new Elephant(Side.HAN)); // 마 + createPiece(Position.of(8, 1), new Horse(Side.HAN)); // 상 + } else if(hanFormation == Formation.마상마상) { + createPiece(Position.of(2, 1), new Horse(Side.HAN)); // 상 + createPiece(Position.of(3, 1), new Elephant(Side.HAN)); // 마 + createPiece(Position.of(7, 1), new Horse(Side.HAN)); // 마 + createPiece(Position.of(8, 1), new Elephant(Side.HAN)); // 상 + } + } + + private void createPiece(Position position, Piece piece) { + board.put(position, piece); + } + + public Map getBoard() { + return board; + } +} diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java new file mode 100644 index 0000000000..e4323d5280 --- /dev/null +++ b/src/main/java/domain/Position.java @@ -0,0 +1,38 @@ +package domain; + +import java.util.Objects; + +public class Position { + + private final int x; + private final int y; + + private Position(int x, int y) { + this.x = x; + this.y = y; + } + + public static Position of(int x, int y) { + validateOutOfRange(x, y); + return new Position(x, y); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Position position = (Position) o; + return x == position.x && y == position.y; + } + + @Override + public int hashCode() { + return Objects.hashCode(x) * 31 + Objects.hashCode(y); + } + + private static void validateOutOfRange(int x, int y) { + if(!((1 <= x && x <= 9) && (1 <= y && y <= 10))) { + throw new IllegalArgumentException("y 좌표는 1~10, x 좌표는 1~9 사이여야 합니다."); + } + } +} \ No newline at end of file diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java index c5741b1c41..5e03cff22a 100644 --- a/src/main/java/domain/Side.java +++ b/src/main/java/domain/Side.java @@ -2,5 +2,6 @@ public enum Side { CHO, - HAN + HAN, + NONE } diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java new file mode 100644 index 0000000000..81856735b5 --- /dev/null +++ b/src/main/java/domain/piece/Cannon.java @@ -0,0 +1,9 @@ +package domain.piece; + +import domain.Side; + +public class Cannon extends Piece { + public Cannon(Side side) { + super(side); + } +} diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java new file mode 100644 index 0000000000..beaa3a5b2d --- /dev/null +++ b/src/main/java/domain/piece/Chariot.java @@ -0,0 +1,9 @@ +package domain.piece; + +import domain.Side; + +public class Chariot extends Piece { + public Chariot(Side side) { + super(side); + } +} diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java new file mode 100644 index 0000000000..c578249278 --- /dev/null +++ b/src/main/java/domain/piece/Elephant.java @@ -0,0 +1,9 @@ +package domain.piece; + +import domain.Side; + +public class Elephant extends Piece { + public Elephant(Side side) { + super(side); + } +} diff --git a/src/main/java/domain/piece/Empty.java b/src/main/java/domain/piece/Empty.java new file mode 100644 index 0000000000..ec62fa6579 --- /dev/null +++ b/src/main/java/domain/piece/Empty.java @@ -0,0 +1,10 @@ +package domain.piece; + +import domain.Side; + +public class Empty extends Piece{ + + public Empty() { + super(Side.NONE); + } +} diff --git a/src/main/java/domain/piece/Guard.java b/src/main/java/domain/piece/Guard.java new file mode 100644 index 0000000000..83ba88aa72 --- /dev/null +++ b/src/main/java/domain/piece/Guard.java @@ -0,0 +1,9 @@ +package domain.piece; + +import domain.Side; + +public class Guard extends Piece { + public Guard(Side side) { + super(side); + } +} diff --git a/src/main/java/domain/piece/Horse.java b/src/main/java/domain/piece/Horse.java new file mode 100644 index 0000000000..f51ce741b2 --- /dev/null +++ b/src/main/java/domain/piece/Horse.java @@ -0,0 +1,9 @@ +package domain.piece; + +import domain.Side; + +public class Horse extends Piece { + public Horse(Side side) { + super(side); + } +} diff --git a/src/main/java/domain/piece/King.java b/src/main/java/domain/piece/King.java new file mode 100644 index 0000000000..dec8858a2b --- /dev/null +++ b/src/main/java/domain/piece/King.java @@ -0,0 +1,9 @@ +package domain.piece; + +import domain.Side; + +public class King extends Piece { + public King(Side side) { + super(side); + } +} diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java new file mode 100644 index 0000000000..05dd02fbd1 --- /dev/null +++ b/src/main/java/domain/piece/Piece.java @@ -0,0 +1,16 @@ +package domain.piece; + +import domain.Side; + +public abstract class Piece { + + private final Side side; + + protected Piece(Side side) { + this.side = side; + } + + public boolean isSameSide(Side side) { + return this.side == side; + } +} diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java new file mode 100644 index 0000000000..ba942baa46 --- /dev/null +++ b/src/main/java/domain/piece/Soldier.java @@ -0,0 +1,9 @@ +package domain.piece; + +import domain.Side; + +public class Soldier extends Piece { + public Soldier(Side side) { + super(side); + } +} diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 From d96f89991ea38a46f03c98c4cde12e18845f8fac Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Thu, 26 Mar 2026 15:57:51 +0900 Subject: [PATCH 05/34] =?UTF-8?q?feat:=20=EC=9E=A5=EA=B8=B0=ED=8C=90=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=B6=9C=EB=A0=A5=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 --- README.md | 2 + .../java/controller/JanggiController.java | 15 ++- src/main/java/domain/Board.java | 103 +++++++++--------- src/main/java/domain/piece/Cannon.java | 5 + src/main/java/domain/piece/Chariot.java | 5 + src/main/java/domain/piece/Elephant.java | 5 + src/main/java/domain/piece/Empty.java | 6 +- src/main/java/domain/piece/Guard.java | 5 + src/main/java/domain/piece/Horse.java | 5 + src/main/java/domain/piece/King.java | 5 + src/main/java/domain/piece/Piece.java | 6 + src/main/java/domain/piece/Soldier.java | 8 ++ src/main/java/view/OutputView.java | 51 +++++++++ src/test/java/BoardTest.java | 4 +- 14 files changed, 169 insertions(+), 56 deletions(-) create mode 100644 src/main/java/view/OutputView.java diff --git a/README.md b/README.md index c78d333a36..3cc476a535 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ ### ✅ 장기판 초기화 기능 +### ✅ 장기판 상태 출력 기능 + ## 1-2 diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index fd1efa2941..83b97f3e1d 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,5 +1,6 @@ package controller; +import domain.Board; import domain.Formation; import view.InputView; import view.OutputView; @@ -7,16 +8,26 @@ public class JanggiController { private final InputView inputView; + private final OutputView outputView; public JanggiController(InputView inputView, OutputView outputView) { this.inputView = inputView; + this.outputView = outputView; } public void run() { - Formation Choformation = readChoFormation(); - Formation Hanformation = readHanFormation(); + Board board = initBoard(); } + private Board initBoard() { + Formation choformation = readChoFormation(); + Formation hanformation = readHanFormation(); + Board board = new Board(choformation, hanformation); + outputView.printBoardStatus(board.getBoard()); + return board; + } + + private Formation readChoFormation() { while(true) { try { diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 7218d37cd1..b3110be907 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -14,83 +14,84 @@ public Board(Formation choFormation, Formation hanFormation) { } private void createBoard(Formation choFormation, Formation hanFormation) { - for(int x = 1; x<= 9; x++) { - for(int y = 1; y <= 10; y++) { + for (int x = 1; x <= 9; x++) { + for (int y = 1; y <= 10; y++) { createPiece(Position.of(x, y), new Empty()); } } + // 한나라 (HAN) - 위쪽 (y=1~4) createPiece(Position.of(1, 1), new Chariot(Side.HAN)); - createPiece(Position.of(1, 9), new Chariot(Side.HAN)); + createPiece(Position.of(9, 1), new Chariot(Side.HAN)); + createPiece(Position.of(4, 1), new Guard(Side.HAN)); + createPiece(Position.of(6, 1), new Guard(Side.HAN)); + createPiece(Position.of(5, 2), new King(Side.HAN)); + createPiece(Position.of(2, 3), new Cannon(Side.HAN)); + createPiece(Position.of(8, 3), new Cannon(Side.HAN)); + createPiece(Position.of(1, 4), new Soldier(Side.HAN)); + createPiece(Position.of(3, 4), new Soldier(Side.HAN)); + createPiece(Position.of(5, 4), new Soldier(Side.HAN)); + createPiece(Position.of(7, 4), new Soldier(Side.HAN)); + createPiece(Position.of(9, 4), new Soldier(Side.HAN)); + + // 초나라 (CHO) - 아래쪽 (y=7~10) createPiece(Position.of(1, 10), new Chariot(Side.CHO)); createPiece(Position.of(9, 10), new Chariot(Side.CHO)); - - createPiece(Position.of(3, 2), new Cannon(Side.HAN)); - createPiece(Position.of(3, 8), new Cannon(Side.HAN)); - createPiece(Position.of(8, 2), new Cannon(Side.CHO)); - createPiece(Position.of(8, 8), new Cannon(Side.CHO)); - - createPiece(Position.of(1, 4), new Guard(Side.HAN)); - createPiece(Position.of(1, 6), new Guard(Side.HAN)); createPiece(Position.of(4, 10), new Guard(Side.CHO)); createPiece(Position.of(6, 10), new Guard(Side.CHO)); - - createPiece(Position.of(2, 5), new King(Side.HAN)); - createPiece(Position.of(9, 5), new King(Side.CHO)); - - createPiece(Position.of(4, 1), new Soldier(Side.HAN)); - createPiece(Position.of(4, 3), new Soldier(Side.HAN)); - createPiece(Position.of(4, 5), new Soldier(Side.HAN)); - createPiece(Position.of(4, 7), new Soldier(Side.HAN)); - createPiece(Position.of(4, 9), new Soldier(Side.HAN)); - createPiece(Position.of(7, 1), new Soldier(Side.CHO)); - createPiece(Position.of(7, 3), new Soldier(Side.CHO)); - createPiece(Position.of(7, 5), new Soldier(Side.CHO)); + createPiece(Position.of(5, 9), new King(Side.CHO)); + createPiece(Position.of(2, 8), new Cannon(Side.CHO)); + createPiece(Position.of(8, 8), new Cannon(Side.CHO)); + createPiece(Position.of(1, 7), new Soldier(Side.CHO)); + createPiece(Position.of(3, 7), new Soldier(Side.CHO)); + createPiece(Position.of(5, 7), new Soldier(Side.CHO)); createPiece(Position.of(7, 7), new Soldier(Side.CHO)); - createPiece(Position.of(7, 9), new Soldier(Side.CHO)); + createPiece(Position.of(9, 7), new Soldier(Side.CHO)); - if(choFormation == Formation.상마마상) { - createPiece(Position.of(2, 10), new Elephant(Side.CHO)); // 마 - createPiece(Position.of(3, 10), new Horse(Side.CHO)); // 상 - createPiece(Position.of(7, 10), new Horse(Side.CHO)); // 상 - createPiece(Position.of(8, 10), new Elephant(Side.CHO)); // 마 - } else if(choFormation == Formation.마상상마) { + // 초나라 마/상 배치 + if (choFormation == Formation.상마마상) { + createPiece(Position.of(2, 10), new Elephant(Side.CHO)); + createPiece(Position.of(3, 10), new Horse(Side.CHO)); + createPiece(Position.of(7, 10), new Horse(Side.CHO)); + createPiece(Position.of(8, 10), new Elephant(Side.CHO)); + } else if (choFormation == Formation.마상상마) { createPiece(Position.of(2, 10), new Horse(Side.CHO)); createPiece(Position.of(3, 10), new Elephant(Side.CHO)); createPiece(Position.of(7, 10), new Elephant(Side.CHO)); createPiece(Position.of(8, 10), new Horse(Side.CHO)); - } else if(choFormation == Formation.상마상마) { + } else if (choFormation == Formation.상마상마) { createPiece(Position.of(2, 10), new Elephant(Side.CHO)); createPiece(Position.of(3, 10), new Horse(Side.CHO)); createPiece(Position.of(7, 10), new Elephant(Side.CHO)); createPiece(Position.of(8, 10), new Horse(Side.CHO)); - } else if(choFormation == Formation.마상마상) { + } else if (choFormation == Formation.마상마상) { createPiece(Position.of(2, 10), new Horse(Side.CHO)); createPiece(Position.of(3, 10), new Elephant(Side.CHO)); createPiece(Position.of(7, 10), new Horse(Side.CHO)); createPiece(Position.of(8, 10), new Elephant(Side.CHO)); } - if(hanFormation == Formation.상마마상) { + // 한나라 마/상 배치 + if (hanFormation == Formation.상마마상) { + createPiece(Position.of(2, 1), new Elephant(Side.HAN)); + createPiece(Position.of(3, 1), new Horse(Side.HAN)); + createPiece(Position.of(7, 1), new Horse(Side.HAN)); + createPiece(Position.of(8, 1), new Elephant(Side.HAN)); + } else if (hanFormation == Formation.마상상마) { + createPiece(Position.of(2, 1), new Horse(Side.HAN)); + createPiece(Position.of(3, 1), new Elephant(Side.HAN)); + createPiece(Position.of(7, 1), new Elephant(Side.HAN)); + createPiece(Position.of(8, 1), new Horse(Side.HAN)); + } else if (hanFormation == Formation.상마상마) { createPiece(Position.of(2, 1), new Elephant(Side.HAN)); - createPiece(Position.of(3, 1), new Horse(Side.HAN)); // 마 - createPiece(Position.of(7, 1), new Horse(Side.HAN)); // 마 - createPiece(Position.of(8, 1), new Elephant(Side.HAN)); // 상 - } else if(hanFormation == Formation.마상상마) { - createPiece(Position.of(2, 1), new Horse(Side.HAN)); // 상 - createPiece(Position.of(3, 1), new Elephant(Side.HAN)); // 마 - createPiece(Position.of(7, 1), new Elephant(Side.HAN)); // 마 - createPiece(Position.of(8, 1), new Horse(Side.HAN)); // 상 - } else if(hanFormation == Formation.상마상마) { - createPiece(Position.of(2, 1), new Elephant(Side.HAN)); // 상 - createPiece(Position.of(3, 1), new Horse(Side.HAN)); // 마 - createPiece(Position.of(7, 1), new Elephant(Side.HAN)); // 마 - createPiece(Position.of(8, 1), new Horse(Side.HAN)); // 상 - } else if(hanFormation == Formation.마상마상) { - createPiece(Position.of(2, 1), new Horse(Side.HAN)); // 상 - createPiece(Position.of(3, 1), new Elephant(Side.HAN)); // 마 - createPiece(Position.of(7, 1), new Horse(Side.HAN)); // 마 - createPiece(Position.of(8, 1), new Elephant(Side.HAN)); // 상 + createPiece(Position.of(3, 1), new Horse(Side.HAN)); + createPiece(Position.of(7, 1), new Elephant(Side.HAN)); + createPiece(Position.of(8, 1), new Horse(Side.HAN)); + } else if (hanFormation == Formation.마상마상) { + createPiece(Position.of(2, 1), new Horse(Side.HAN)); + createPiece(Position.of(3, 1), new Elephant(Side.HAN)); + createPiece(Position.of(7, 1), new Horse(Side.HAN)); + createPiece(Position.of(8, 1), new Elephant(Side.HAN)); } } diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index 81856735b5..de3870bcdb 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -6,4 +6,9 @@ public class Cannon extends Piece { public Cannon(Side side) { super(side); } + + @Override + public String getName() { + return "포"; + } } diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java index beaa3a5b2d..fc6d2bb689 100644 --- a/src/main/java/domain/piece/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -6,4 +6,9 @@ public class Chariot extends Piece { public Chariot(Side side) { super(side); } + + @Override + public String getName() { + return "차"; + } } diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java index c578249278..405f247701 100644 --- a/src/main/java/domain/piece/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -6,4 +6,9 @@ public class Elephant extends Piece { public Elephant(Side side) { super(side); } + + @Override + public String getName() { + return "상"; + } } diff --git a/src/main/java/domain/piece/Empty.java b/src/main/java/domain/piece/Empty.java index ec62fa6579..54a5a4804a 100644 --- a/src/main/java/domain/piece/Empty.java +++ b/src/main/java/domain/piece/Empty.java @@ -3,8 +3,12 @@ import domain.Side; public class Empty extends Piece{ - public Empty() { super(Side.NONE); } + + @Override + public String getName() { + return "."; + } } diff --git a/src/main/java/domain/piece/Guard.java b/src/main/java/domain/piece/Guard.java index 83ba88aa72..dcb6e107eb 100644 --- a/src/main/java/domain/piece/Guard.java +++ b/src/main/java/domain/piece/Guard.java @@ -6,4 +6,9 @@ public class Guard extends Piece { public Guard(Side side) { super(side); } + + @Override + public String getName() { + return "사"; + } } diff --git a/src/main/java/domain/piece/Horse.java b/src/main/java/domain/piece/Horse.java index f51ce741b2..3a9eaa260d 100644 --- a/src/main/java/domain/piece/Horse.java +++ b/src/main/java/domain/piece/Horse.java @@ -6,4 +6,9 @@ public class Horse extends Piece { public Horse(Side side) { super(side); } + + @Override + public String getName() { + return "마"; + } } diff --git a/src/main/java/domain/piece/King.java b/src/main/java/domain/piece/King.java index dec8858a2b..0eb3f3e250 100644 --- a/src/main/java/domain/piece/King.java +++ b/src/main/java/domain/piece/King.java @@ -6,4 +6,9 @@ public class King extends Piece { public King(Side side) { super(side); } + + @Override + public String getName() { + return "왕"; + } } diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 05dd02fbd1..634dfa39fe 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -13,4 +13,10 @@ protected Piece(Side side) { public boolean isSameSide(Side side) { return this.side == side; } + + public Side getSide() { + return side; + } + + public abstract String getName(); } diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java index ba942baa46..1101c84970 100644 --- a/src/main/java/domain/piece/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -6,4 +6,12 @@ public class Soldier extends Piece { public Soldier(Side side) { super(side); } + + @Override + public String getName() { + if (isSameSide(Side.CHO)) { + return "졸"; + } + return "병"; + } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 0000000000..de79907f71 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,51 @@ +package view; + +import domain.Position; +import domain.Side; +import domain.piece.Piece; + +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 printBoardStatus(Map board) { + System.out.println(); + System.out.println(" 1 2 3 4 5 6 7 8 9"); + + for (int y = 1; y <= 10; y++) { + printRowNumber(y); + for (int x = 1; x <= 9; x++) { + Piece piece = board.get(Position.of(x, y)); + printPiece(piece); + } + System.out.println(RESET); + } + } + + private void printRowNumber(int y) { + if (y < 10) { + System.out.print(" " + y + " "); + } else { + System.out.print(y + " "); + } + } + + private void printPiece(Piece piece) { + String color = getColor(piece.getSide()); + System.out.print(color + piece.getName() + " "); + } + + private String getColor(Side side) { + if (side == Side.HAN) { + return RED; + } + if (side == Side.CHO) { + return BLUE; + } + return RESET; + } +} diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java index f661c65ec6..b28e703532 100644 --- a/src/test/java/BoardTest.java +++ b/src/test/java/BoardTest.java @@ -41,7 +41,7 @@ public class BoardTest { Board board = new Board(Formation.from("1"), Formation.from("2")); Map pieces = board.getBoard(); - assertThat(pieces.get(Position.of(2, 5)).isSameSide(Side.HAN)).isTrue(); - assertThat(pieces.get(Position.of(9, 5)).isSameSide(Side.CHO)).isTrue(); + assertThat(pieces.get(Position.of(5, 2)).isSameSide(Side.HAN)).isTrue(); + assertThat(pieces.get(Position.of(5, 9)).isSameSide(Side.CHO)).isTrue(); } } From a9010a4c936edf852c640ba2e50813b918defa2c Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Thu, 26 Mar 2026 20:15:10 +0900 Subject: [PATCH 06/34] =?UTF-8?q?refactor:=20Board=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=20=EB=A1=9C=EC=A7=81=20=EC=A4=91=EB=B3=B5=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/Board.java | 117 ++++++++-------------- src/main/java/domain/Formation.java | 20 +++- src/main/java/domain/piece/PieceType.java | 56 +++++++++++ 3 files changed, 111 insertions(+), 82 deletions(-) create mode 100644 src/main/java/domain/piece/PieceType.java diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index b3110be907..47e2c80d4b 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -3,6 +3,7 @@ import domain.piece.*; import java.util.HashMap; +import java.util.List; import java.util.Map; public class Board { @@ -13,93 +14,55 @@ public Board(Formation choFormation, Formation hanFormation) { createBoard(choFormation, hanFormation); } + public Map getBoard() { + return board; + } + private void createBoard(Formation choFormation, Formation hanFormation) { for (int x = 1; x <= 9; x++) { for (int y = 1; y <= 10; y++) { - createPiece(Position.of(x, y), new Empty()); + placePiece(Position.of(x, y), PieceType.EMPTY.create(Side.NONE)); } } + placePieces(choFormation, Side.CHO); + placePieces(hanFormation, Side.HAN); + } - // 한나라 (HAN) - 위쪽 (y=1~4) - createPiece(Position.of(1, 1), new Chariot(Side.HAN)); - createPiece(Position.of(9, 1), new Chariot(Side.HAN)); - createPiece(Position.of(4, 1), new Guard(Side.HAN)); - createPiece(Position.of(6, 1), new Guard(Side.HAN)); - createPiece(Position.of(5, 2), new King(Side.HAN)); - createPiece(Position.of(2, 3), new Cannon(Side.HAN)); - createPiece(Position.of(8, 3), new Cannon(Side.HAN)); - createPiece(Position.of(1, 4), new Soldier(Side.HAN)); - createPiece(Position.of(3, 4), new Soldier(Side.HAN)); - createPiece(Position.of(5, 4), new Soldier(Side.HAN)); - createPiece(Position.of(7, 4), new Soldier(Side.HAN)); - createPiece(Position.of(9, 4), new Soldier(Side.HAN)); - - // 초나라 (CHO) - 아래쪽 (y=7~10) - createPiece(Position.of(1, 10), new Chariot(Side.CHO)); - createPiece(Position.of(9, 10), new Chariot(Side.CHO)); - createPiece(Position.of(4, 10), new Guard(Side.CHO)); - createPiece(Position.of(6, 10), new Guard(Side.CHO)); - createPiece(Position.of(5, 9), new King(Side.CHO)); - createPiece(Position.of(2, 8), new Cannon(Side.CHO)); - createPiece(Position.of(8, 8), new Cannon(Side.CHO)); - createPiece(Position.of(1, 7), new Soldier(Side.CHO)); - createPiece(Position.of(3, 7), new Soldier(Side.CHO)); - createPiece(Position.of(5, 7), new Soldier(Side.CHO)); - createPiece(Position.of(7, 7), new Soldier(Side.CHO)); - createPiece(Position.of(9, 7), new Soldier(Side.CHO)); - - // 초나라 마/상 배치 - if (choFormation == Formation.상마마상) { - createPiece(Position.of(2, 10), new Elephant(Side.CHO)); - createPiece(Position.of(3, 10), new Horse(Side.CHO)); - createPiece(Position.of(7, 10), new Horse(Side.CHO)); - createPiece(Position.of(8, 10), new Elephant(Side.CHO)); - } else if (choFormation == Formation.마상상마) { - createPiece(Position.of(2, 10), new Horse(Side.CHO)); - createPiece(Position.of(3, 10), new Elephant(Side.CHO)); - createPiece(Position.of(7, 10), new Elephant(Side.CHO)); - createPiece(Position.of(8, 10), new Horse(Side.CHO)); - } else if (choFormation == Formation.상마상마) { - createPiece(Position.of(2, 10), new Elephant(Side.CHO)); - createPiece(Position.of(3, 10), new Horse(Side.CHO)); - createPiece(Position.of(7, 10), new Elephant(Side.CHO)); - createPiece(Position.of(8, 10), new Horse(Side.CHO)); - } else if (choFormation == Formation.마상마상) { - createPiece(Position.of(2, 10), new Horse(Side.CHO)); - createPiece(Position.of(3, 10), new Elephant(Side.CHO)); - createPiece(Position.of(7, 10), new Horse(Side.CHO)); - createPiece(Position.of(8, 10), new Elephant(Side.CHO)); - } + private void placePieces(Formation hanFormation, Side side) { - // 한나라 마/상 배치 - if (hanFormation == Formation.상마마상) { - createPiece(Position.of(2, 1), new Elephant(Side.HAN)); - createPiece(Position.of(3, 1), new Horse(Side.HAN)); - createPiece(Position.of(7, 1), new Horse(Side.HAN)); - createPiece(Position.of(8, 1), new Elephant(Side.HAN)); - } else if (hanFormation == Formation.마상상마) { - createPiece(Position.of(2, 1), new Horse(Side.HAN)); - createPiece(Position.of(3, 1), new Elephant(Side.HAN)); - createPiece(Position.of(7, 1), new Elephant(Side.HAN)); - createPiece(Position.of(8, 1), new Horse(Side.HAN)); - } else if (hanFormation == Formation.상마상마) { - createPiece(Position.of(2, 1), new Elephant(Side.HAN)); - createPiece(Position.of(3, 1), new Horse(Side.HAN)); - createPiece(Position.of(7, 1), new Elephant(Side.HAN)); - createPiece(Position.of(8, 1), new Horse(Side.HAN)); - } else if (hanFormation == Formation.마상마상) { - createPiece(Position.of(2, 1), new Horse(Side.HAN)); - createPiece(Position.of(3, 1), new Elephant(Side.HAN)); - createPiece(Position.of(7, 1), new Horse(Side.HAN)); - createPiece(Position.of(8, 1), new Elephant(Side.HAN)); - } + List rows = getRowForSide(side); + + placePiece(Position.of(1, rows.get(0)), PieceType.CHARIOT.create(side)); + placePiece(Position.of(9, rows.get(0)), PieceType.CHARIOT.create(side)); + + placePiece(Position.of(4, rows.get(0)), PieceType.GUARD.create(side)); + placePiece(Position.of(6, rows.get(0)), PieceType.GUARD.create(side)); + + placePiece(Position.of(5, rows.get(1)), PieceType.KING.create(side)); + + placePiece(Position.of(2, rows.get(2)), PieceType.CANNON.create(side)); + placePiece(Position.of(8, rows.get(2)), PieceType.CANNON.create(side)); + + placePiece(Position.of(1, rows.get(3)), PieceType.SOLDIER.create(side)); + placePiece(Position.of(3, rows.get(3)), PieceType.SOLDIER.create(side)); + placePiece(Position.of(5, rows.get(3)), PieceType.SOLDIER.create(side)); + placePiece(Position.of(7, rows.get(3)), PieceType.SOLDIER.create(side)); + placePiece(Position.of(9, rows.get(3)), PieceType.SOLDIER.create(side)); + + placePiece(Position.of(2, rows.get(0)), hanFormation.getPieceTypes().get(0).create(side)); + placePiece(Position.of(3, rows.get(0)), hanFormation.getPieceTypes().get(1).create(side)); + placePiece(Position.of(7, rows.get(0)), hanFormation.getPieceTypes().get(2).create(side)); + placePiece(Position.of(8, rows.get(0)), hanFormation.getPieceTypes().get(3).create(side)); } - private void createPiece(Position position, Piece piece) { - board.put(position, piece); + private List getRowForSide(Side side) { + if(side == Side.HAN) { + return List.of(1,2,3,4); + } + return List.of(10,9,8,7); } - public Map getBoard() { - return board; + private void placePiece(Position position, Piece piece) { + board.put(position, piece); } } diff --git a/src/main/java/domain/Formation.java b/src/main/java/domain/Formation.java index 5128331d97..9982ac30f2 100644 --- a/src/main/java/domain/Formation.java +++ b/src/main/java/domain/Formation.java @@ -1,16 +1,22 @@ package domain; +import domain.piece.PieceType; + +import java.util.List; + public enum Formation { - 상마마상("1"), - 마상상마("2"), - 상마상마("3"), - 마상마상("4"); + 상마마상("1", List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.HORSE, PieceType.ELEPHANT)), + 마상상마("2", List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.ELEPHANT, PieceType.HORSE)), + 상마상마("3", List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE)), + 마상마상("4", List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT)); private final String option; + private final List pieceTypes; - Formation(String option) { + Formation(String option, List pieceTypes) { this.option = option; + this.pieceTypes = pieceTypes; } public static Formation from(String input) { @@ -22,6 +28,10 @@ public static Formation from(String input) { throw new IllegalArgumentException("[ERROR] 번호는 1~4 사이의 숫자여야 합니다."); } + public List getPieceTypes() { + return pieceTypes; + } + public String toDisplayString() { return option + ". " + name(); } diff --git a/src/main/java/domain/piece/PieceType.java b/src/main/java/domain/piece/PieceType.java new file mode 100644 index 0000000000..30656741d8 --- /dev/null +++ b/src/main/java/domain/piece/PieceType.java @@ -0,0 +1,56 @@ +package domain.piece; + +import domain.Side; + +public enum PieceType { + ELEPHANT{ + @Override + public Piece create(Side side) { + return new Elephant(side); + } + }, + CANNON{ + @Override + public Piece create(Side side) { + return new Cannon(side); + } + }, + CHARIOT{ + @Override + public Piece create(Side side) { + return new Chariot(side); + } + }, + GUARD{ + @Override + public Piece create(Side side) { + return new Guard(side); + } + }, + HORSE{ + @Override + public Piece create(Side side) { + return new Horse(side); + } + }, + KING{ + @Override + public Piece create(Side side) { + return new King(side); + } + }, + EMPTY{ + @Override + public Piece create(Side side) { + return new Empty(); + } + }, + SOLDIER{ + @Override + public Piece create(Side side) { + return new Soldier(side); + } + }; + + public abstract Piece create (Side side); +} From 08e53e33efaf6b4953a7ef15fd33b590646cb6ee Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Thu, 26 Mar 2026 20:35:35 +0900 Subject: [PATCH 07/34] =?UTF-8?q?feat:=20=EC=9B=80=EC=A7=81=EC=9D=BC=20?= =?UTF-8?q?=EA=B8=B0=EB=AC=BC=20=EC=A2=8C=ED=91=9C=20=EC=9E=85=EB=A0=A5=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 | 4 ++-- .../java/controller/JanggiController.java | 19 ++++++++++++++++++- src/main/java/view/InputView.java | 11 +++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3cc476a535..3ab223b65d 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ ## 1-2 -### ☑️ 이동할 목적지 좌표 입력 기능 +### ✅ 이동할 기물 위치 좌표 입력 기능 -### ☑️ 이동할 기물 위치 좌표 입력 기능 +### ☑️ 이동할 목적지 좌표 입력 기능 ### ☑️ 장기 진행 상태 출력 기능 diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 83b97f3e1d..174344935e 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -2,6 +2,7 @@ import domain.Board; import domain.Formation; +import domain.Position; import view.InputView; import view.OutputView; @@ -17,6 +18,23 @@ public JanggiController(InputView inputView, OutputView outputView) { public void run() { Board board = initBoard(); + processMove(board); + } + + private void processMove(Board board) { + Position sourcePosition = readSourcePosition(); + } + + private Position readSourcePosition() { + while (true) { + try { + int x = inputView.readSourceXPosition(); + int y = inputView.readSourceYPosition(); + return Position.of(x,y); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } } private Board initBoard() { @@ -27,7 +45,6 @@ private Board initBoard() { return board; } - private Formation readChoFormation() { while(true) { try { diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index da4caec273..0eb6688fdc 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -20,6 +20,17 @@ public String readHanFormation() { return readFormation(); } + public int readSourceXPosition() { + System.out.println(); + System.out.println("움직일 기물의 x 좌표를 입력해주세요. (x 범위 1 ~ 9)"); + return scanner.nextInt(); + } + + public int readSourceYPosition() { + System.out.println("움직일 기물의 y 좌표를 입력해주세요. (y 범위 1 ~ 10)"); + return scanner.nextInt(); + } + private String readFormation() { for(Formation formation : Formation.values()) { System.out.println(formation.toDisplayString()); From 7b91ab867f605cc490b194a49503a8dfac7c3540 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Thu, 26 Mar 2026 20:39:12 +0900 Subject: [PATCH 08/34] =?UTF-8?q?feat:=20=EB=AA=A9=EC=A0=81=EC=A7=80=20?= =?UTF-8?q?=EC=A2=8C=ED=91=9C=20=EC=9E=85=EB=A0=A5=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 --- README.md | 4 +++- src/main/java/controller/JanggiController.java | 13 +++++++++++++ src/main/java/view/InputView.java | 11 +++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ab223b65d..8f828e2ec5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,9 @@ ### ✅ 이동할 기물 위치 좌표 입력 기능 -### ☑️ 이동할 목적지 좌표 입력 기능 +### ✅ 이동할 목적지 좌표 입력 기능 + +### ☑️ 기물 이동 기능 ### ☑️ 장기 진행 상태 출력 기능 diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 174344935e..977a4d558a 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -23,6 +23,19 @@ public void run() { private void processMove(Board board) { Position sourcePosition = readSourcePosition(); + Position targetPosition = readTargetPosition(); + } + + private Position readTargetPosition() { + while (true) { + try { + int x = inputView.readTargetXPosition(); + int y = inputView.readTargetYPosition(); + return Position.of(x,y); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } } private Position readSourcePosition() { diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 0eb6688fdc..93f23f3914 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -31,6 +31,17 @@ public int readSourceYPosition() { return scanner.nextInt(); } + public int readTargetXPosition() { + System.out.println(); + System.out.println("목적지 x 좌표를 입력해주세요. (x 범위 1 ~ 9)"); + return scanner.nextInt(); + } + + public int readTargetYPosition() { + System.out.println("목적지 y 좌표를 입력해주세요. (y 범위 1 ~ 10)"); + return scanner.nextInt(); + } + private String readFormation() { for(Formation formation : Formation.values()) { System.out.println(formation.toDisplayString()); From 6ca130e07bb93d5052acf2a61ceac6b0a549d433 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Thu, 26 Mar 2026 21:54:40 +0900 Subject: [PATCH 09/34] =?UTF-8?q?feat:=20=ED=84=B4=EC=A0=9C=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=EB=B0=8F=20=EC=9E=A5=EA=B8=B0=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=EC=83=81=ED=83=9C=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++-- src/main/java/controller/JanggiController.java | 16 ++++++++++++++-- src/main/java/view/OutputView.java | 9 +++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8f828e2ec5..9faabe171c 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,10 @@ ### ✅ 이동할 목적지 좌표 입력 기능 -### ☑️ 기물 이동 기능 +### ✅ 턴제 시스템 기능 + +### ✅ 장기 진행 상태 출력 기능 -### ☑️ 장기 진행 상태 출력 기능 +### ☑️ 기물 이동 기능 ## ⚠️ 기능 예외 사항 \ No newline at end of file diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 977a4d558a..70c63dca5e 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -3,6 +3,7 @@ import domain.Board; import domain.Formation; import domain.Position; +import domain.Side; import view.InputView; import view.OutputView; @@ -22,8 +23,19 @@ public void run() { } private void processMove(Board board) { - Position sourcePosition = readSourcePosition(); - Position targetPosition = readTargetPosition(); + Side turn = Side.CHO; + while (true) { + outputView.printCurrentTurn(turn); + Position sourcePosition = readSourcePosition(); + Position targetPosition = readTargetPosition(); + try { + board.movePiece(sourcePosition, targetPosition); + turn = turn.opposite(); + outputView.printBoardStatus(board.getBoard()); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } } private Position readTargetPosition() { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index de79907f71..054af696bd 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -26,6 +26,15 @@ public void printBoardStatus(Map board) { } } + public void printCurrentTurn(Side turn) { + System.out.println(); + if(turn == Side.CHO) { + System.out.println("초의 차례입니다."); + return; + } + System.out.println("한의 차례입니다."); + } + private void printRowNumber(int y) { if (y < 10) { System.out.print(" " + y + " "); From 1f698a8ce1c6e9650839d5338cef6f9ebf3ef593 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Fri, 27 Mar 2026 22:10:42 +0900 Subject: [PATCH 10/34] =?UTF-8?q?test:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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/test/java/BoardTest.java | 440 +++++++++++++++++-- src/test/java/domain/piece/CannonTest.java | 89 ++++ src/test/java/domain/piece/ChariotTest.java | 39 ++ src/test/java/domain/piece/ElephantTest.java | 39 ++ src/test/java/domain/piece/GuardTest.java | 40 ++ src/test/java/domain/piece/HorseTest.java | 39 ++ src/test/java/domain/piece/KingTest.java | 41 ++ src/test/java/domain/piece/PieceTest.java | 44 ++ src/test/java/domain/piece/SoldierTest.java | 39 ++ 9 files changed, 781 insertions(+), 29 deletions(-) create mode 100644 src/test/java/domain/piece/CannonTest.java create mode 100644 src/test/java/domain/piece/ChariotTest.java create mode 100644 src/test/java/domain/piece/ElephantTest.java create mode 100644 src/test/java/domain/piece/GuardTest.java create mode 100644 src/test/java/domain/piece/HorseTest.java create mode 100644 src/test/java/domain/piece/KingTest.java create mode 100644 src/test/java/domain/piece/PieceTest.java create mode 100644 src/test/java/domain/piece/SoldierTest.java diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java index b28e703532..c44be46333 100644 --- a/src/test/java/BoardTest.java +++ b/src/test/java/BoardTest.java @@ -1,9 +1,11 @@ import domain.*; -import domain.piece.Elephant; -import domain.piece.Horse; -import domain.piece.Piece; +import domain.piece.*; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import java.util.Map; @@ -11,37 +13,417 @@ public class BoardTest { - @DisplayName("초나라 포진을 상마마상으로 배치한다.") - @Test - void 초나라_포진을_상마마상으로_배치한다() { - Board board = new Board(Formation.from("1"), Formation.from("2")); - Map pieces = board.getBoard(); - - assertThat(pieces.get(Position.of(2, 10))).isInstanceOf(Elephant.class); - assertThat(pieces.get(Position.of(3, 10))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(7, 10))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(8, 10))).isInstanceOf(Elephant.class); + @Nested + @DisplayName("장기판 초기화") + class initBoard { + + @DisplayName("초나라 포진을 상마마상으로 배치한다.") + @Test + void 초나라_포진을_상마마상으로_배치한다() { + Board board = new Board(Formation.from("1"), Formation.from("2")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(2, 10))).isInstanceOf(Elephant.class); + assertThat(pieces.get(Position.of(3, 10))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(7, 10))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(8, 10))).isInstanceOf(Elephant.class); + } + + @DisplayName("한나라 포진을 마상마상으로 배치한다.") + @Test + void 한나라_포진을_마상마상으로_배치한다() { + Board board = new Board(Formation.from("1"), Formation.from("4")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(2, 1))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(3, 1))).isInstanceOf(Elephant.class); + assertThat(pieces.get(Position.of(7, 1))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(8, 1))).isInstanceOf(Elephant.class); + } + + @DisplayName("각 궁이 올바른 진영에 배치된다.") + @Test + void 각_궁이_올바른_진영에_배치된다() { + Board board = new Board(Formation.from("1"), Formation.from("2")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(5, 2)).isSameSide(Side.HAN)).isTrue(); + assertThat(pieces.get(Position.of(5, 9)).isSameSide(Side.CHO)).isTrue(); + } } - @DisplayName("한나라 포진을 마상마상으로 배치한다.") - @Test - void 한나라_포진을_마상마상으로_배치한다() { - Board board = new Board(Formation.from("1"), Formation.from("4")); - Map pieces = board.getBoard(); - - assertThat(pieces.get(Position.of(2, 1))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(3, 1))).isInstanceOf(Elephant.class); - assertThat(pieces.get(Position.of(7, 1))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(8, 1))).isInstanceOf(Elephant.class); + @ParameterizedTest + @CsvSource({ + "CHO, 1, 4", + "HAN, 1, 7" + }) + @DisplayName("자신의 기물이 아닌 경우 이동할 수 없다.") + void 자신의_기물이_아닌_경우_이동할_수_없다(Side side, int sourceX, int sourceY) { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position sourcePosition = Position.of(sourceX, sourceY); + Position mockPosition = Position.of(1, 1); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(side, sourcePosition, mockPosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("선택한 기물은 아군 기물이 아닙니다."); + } - @DisplayName("각 궁이 올바른 진영에 배치된다.") + @DisplayName("출발지에 기물이 존재하지 않는 경우 이동할 수 없다.") @Test - void 각_궁이_올바른_진영에_배치된다() { - Board board = new Board(Formation.from("1"), Formation.from("2")); - Map pieces = board.getBoard(); + void 기물이_존재하지_않는_경우_이동할_수_없다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position sourcePosition = Position.of(5, 5); + Position mockPosition = Position.of(1, 1); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(Side.CHO, sourcePosition, mockPosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("해당 위치에 기물이 존재하지 않습니다."); + } + + @Nested + @DisplayName("기물 이동") + class MovePiece { + + @Nested + @DisplayName("차") + class ChariotMove { + + @Test + @DisplayName("차가 세로로 이동한다.") + void 차가_세로로_이동한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(1, 3); + + // when + board.movePiece(Side.HAN, source, target); + + // then + assertThat(board.getBoard().get(target)).isInstanceOf(Chariot.class); + assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("차가 가로로 이동한다.") + void 차가_가로로_이동한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(1, 2); + board.movePiece(Side.HAN, source, target); + + // when + board.movePiece(Side.HAN, target, Position.of(3, 2)); + + // then + assertThat(board.getBoard().get(Position.of(3, 2))).isInstanceOf(Chariot.class); + } + + @Test + @DisplayName("차가 대각선으로 이동하면 예외가 발생한다.") + void 차가_대각선으로_이동하면_예외가_발생한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(3, 3); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("상") + class ElephantMove { + + @Test + @DisplayName("상이 대각선 방향으로 이동한다.") + void 상이_대각선_방향으로_이동한다() { + // given - 상마상마 포진 사용 + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(2, 1); + Position target = Position.of(4, 4); + + // when + board.movePiece(Side.HAN, source, target); + + // then + assertThat(board.getBoard().get(target)).isInstanceOf(Elephant.class); + assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("상이 이동 불가능한 위치로 이동하면 예외가 발생한다.") + void 상이_이동_불가능한_위치로_이동하면_예외가_발생한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(2, 1); + Position target = Position.of(3, 2); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("마") + class HorseMove { + + @Test + @DisplayName("마가 날 일자로 이동한다.") + void 마가_날_일자로_이동한다() { + // given - 마상마상 포진 사용 (2,1에 마가 있음) + Board board = new Board(Formation.from("1"), Formation.from("4")); + Position source = Position.of(2, 1); + Position target = Position.of(3, 3); + + // when + board.movePiece(Side.HAN, source, target); + + // then + assertThat(board.getBoard().get(target)).isInstanceOf(Horse.class); + assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("마가 이동 불가능한 위치로 이동하면 예외가 발생한다.") + void 마가_이동_불가능한_위치로_이동하면_예외가_발생한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("4")); + Position source = Position.of(2, 1); + Position target = Position.of(4, 4); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("포") + class CannonMove { + + @Test + @DisplayName("포가 기물을 하나 뛰어넘어 이동한다.") + void 포가_기물을_하나_뛰어넘어_이동한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + // 사(4,1)를 (4,3)으로 이동시켜 포가 뛰어넘을 기물 배치 + board.movePiece(Side.HAN, Position.of(4, 1), Position.of(4, 2)); + board.movePiece(Side.HAN, Position.of(4, 2), Position.of(4, 3)); + + // when - (2,3) 포가 (4,3) 사를 뛰어넘어 (5,3)으로 이동 + board.movePiece(Side.HAN, Position.of(2, 3), Position.of(5, 3)); + + // then + assertThat(board.getBoard().get(Position.of(5, 3))).isInstanceOf(Cannon.class); + } + + @Test + @DisplayName("포가 뛰어넘을 기물이 없으면 예외가 발생한다.") + void 포가_뛰어넘을_기물이_없으면_예외가_발생한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(2, 3); + Position target = Position.of(5, 3); + + // when & then - 중간에 기물이 없음 + Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("포가 포를 뛰어넘으면 예외가 발생한다.") + void 포가_포를_뛰어넘으면_예외가_발생한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + // (8,3) 포를 (5,3)으로 이동하기 위해 먼저 중간에 기물 배치 + board.movePiece(Side.HAN, Position.of(6, 1), Position.of(6, 2)); + board.movePiece(Side.HAN, Position.of(6, 2), Position.of(6, 3)); + // (8,3) 포가 (6,3) 사를 뛰어넘어 (5,3)으로 이동 + board.movePiece(Side.HAN, Position.of(8, 3), Position.of(5, 3)); + + // 이제 (2,3) 포와 (5,3) 포 사이에 (5,3) 포를 뛰어넘는 상황 만들기 + // (2,3) 포가 (5,3) 포를 뛰어넘어 (7,3)으로 이동 시도 + Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, Position.of(2, 3), Position.of(7, 3))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("포를 넘어갈 수 없습니다."); + } + } + + @Nested + @DisplayName("사") + class GuardMove { + + @Test + @DisplayName("사가 한 칸 이동한다.") + void 사가_한_칸_이동한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(4, 1); + Position target = Position.of(5, 1); + + // when + board.movePiece(Side.HAN, source, target); + + // then + assertThat(board.getBoard().get(target)).isInstanceOf(Guard.class); + assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("사가 두 칸 이상 이동하면 예외가 발생한다.") + void 사가_두_칸_이상_이동하면_예외가_발생한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(4, 1); + Position target = Position.of(4, 3); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("궁") + class KingMove { + + @Test + @DisplayName("궁이 한 칸 이동한다.") + void 궁이_한_칸_이동한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(5, 2); + Position target = Position.of(5, 1); + + // when + board.movePiece(Side.HAN, source, target); + + // then + assertThat(board.getBoard().get(target)).isInstanceOf(King.class); + assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("궁이 두 칸 이상 이동하면 예외가 발생한다.") + void 궁이_두_칸_이상_이동하면_예외가_발생한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(5, 2); + Position target = Position.of(5, 4); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("졸/병") + class SoldierMove { + + @Test + @DisplayName("졸이 앞으로 한 칸 이동한다.") + void 졸이_앞으로_한_칸_이동한다() { + // given - CHO 졸은 위로 이동 + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 7); + Position target = Position.of(1, 6); + + // when + board.movePiece(Side.CHO, source, target); + + // then + assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); + assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("병이 앞으로 한 칸 이동한다.") + void 병이_앞으로_한_칸_이동한다() { + // given - HAN 병은 아래로 이동 + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 4); + Position target = Position.of(1, 5); + + // when + board.movePiece(Side.HAN, source, target); + + // then + assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); + assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("졸이 옆으로 한 칸 이동한다.") + void 졸이_옆으로_한_칸_이동한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 7); + Position target = Position.of(2, 7); + + // when + board.movePiece(Side.CHO, source, target); + + // then + assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); + } + + @Test + @DisplayName("졸이 뒤로 이동하면 예외가 발생한다.") + void 졸이_뒤로_이동하면_예외가_발생한다() { + // given - CHO 졸은 아래로 이동 불가 + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 7); + Position target = Position.of(1, 8); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(Side.CHO, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("적 기물 잡기") + class CapturePiece { + + @Test + @DisplayName("적 기물을 잡을 수 있다.") + void 적_기물을_잡을_수_있다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + // 차를 이동시켜 적 졸을 잡기 + board.movePiece(Side.HAN, Position.of(1, 1), Position.of(1, 5)); + // (1,5)에서 (1,7) CHO 졸 잡기 + board.movePiece(Side.HAN, Position.of(1, 5), Position.of(1, 7)); + + // then + assertThat(board.getBoard().get(Position.of(1, 7))).isInstanceOf(Chariot.class); + assertThat(board.getBoard().get(Position.of(1, 7)).isSameSide(Side.HAN)).isTrue(); + } + + @Test + @DisplayName("아군 기물을 잡으면 예외가 발생한다.") + void 아군_기물을_잡으면_예외가_발생한다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(1, 4); // HAN 졸 위치 - assertThat(pieces.get(Position.of(5, 2)).isSameSide(Side.HAN)).isTrue(); - assertThat(pieces.get(Position.of(5, 9)).isSameSide(Side.CHO)).isTrue(); + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("아군 기물은 잡을 수 없습니다."); + } + } } } diff --git a/src/test/java/domain/piece/CannonTest.java b/src/test/java/domain/piece/CannonTest.java new file mode 100644 index 0000000000..a33fd7fe24 --- /dev/null +++ b/src/test/java/domain/piece/CannonTest.java @@ -0,0 +1,89 @@ +package domain.piece; + +import domain.Position; +import domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 1. List로 경로를 받아 기물이 이동 가능한지 판단한다. [기물 테스트에서 진행] + */ + +class CannonTest { + + @DisplayName("포는 이동한 경로를 반환한다.") + @Test + void 포는_이동한_경로를_반환한다() { + // given + Position sourcePosition = Position.of(1,1); + Position targetPosition = Position.of(1,5); + Piece piece = new Cannon(Side.HAN); + + // when + List positions = piece.findRoute(sourcePosition, targetPosition); + + // then + List expected = List.of(Position.of(1, 2), Position.of(1, 3), Position.of(1, 4)); + for(int i = 0; i < 3; i++) { + Assertions.assertThat(positions.get(i)).isEqualTo(expected.get(i)); + } + } + + @DisplayName("포는 목적지까지 이동할 수 없으면 예외를 발생한다.") + @Test + void 포는_목적지까지_이동할_수_없으면_예외를_발생한다() { + // given + Position sourcePosition = Position.of(1,1); + Position targetPosition = Position.of(2,5); + Piece piece = new Cannon(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("이동할 수 없는 목적지입니다."); + } + + @DisplayName("포 이동 경로에 포를 제외한 기물이 하나 존재해야 한다.") + @Test + void 포_이동_경로에_포를_제외한_기물이_하나_존재해야_한다() { + // given + List pieces = List.of(new Empty(), new Horse(Side.CHO), new Empty()); + // when + Piece cannon = new Cannon(Side.CHO); + boolean actual = cannon.checkRoute(pieces); + // then + boolean expected = true; + Assertions.assertThat(actual).isEqualTo(expected); + } + + @DisplayName("포 이동 경로에 포가 존재하면 예외를 발생한다.") + @Test + void 포_이동_경로에_포가_존재하면_예외를_발생한다() { + // given + List pieces = List.of(new Empty(), new Cannon(Side.CHO), new Empty()); + Piece cannon = new Cannon(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> cannon.checkRoute(pieces)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("포를 넘어갈 수 없습니다."); + } + + @DisplayName("포 이동 경로에 기물이 두 개 이상 존재하면 예외를 발생한다.") + @Test + void 포_이동_경로에_기물이_두_개_이상_존재하면_예외를_발생한다() { + // given + List pieces = List.of(new Empty(), new Horse(Side.CHO), new Elephant(Side.CHO)); + Piece cannon = new Cannon(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> cannon.checkRoute(pieces)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("이동할 수 없는 목적지입니다."); + } +} diff --git a/src/test/java/domain/piece/ChariotTest.java b/src/test/java/domain/piece/ChariotTest.java new file mode 100644 index 0000000000..fd18df3e0b --- /dev/null +++ b/src/test/java/domain/piece/ChariotTest.java @@ -0,0 +1,39 @@ +package domain.piece; + +import domain.Position; +import domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.*; + +class ChariotTest { + + @ParameterizedTest + @CsvSource({ + "2, 3, 2, 7, HAN, true", + "8, 3, 2, 3, HAN, true", + "2, 3, 4, 6, HAN, false", + "8, 3, 5, 9, HAN, false" + }) + @DisplayName("기물이 이동할 수 있는 위치인지 검증한다.") + void 기물이_이동할_수_있는_위치인지_검증한다( + int sourceX, int sourceY, int targetX, int targetY, Side side, boolean expected + ) { + // given + Position sourcePosition = Position.of(sourceX, sourceY); + Position targetPosition = Position.of(targetX, targetY); + Piece piece = new Chariot(side); + + // when & then + if (expected) { + Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) + .doesNotThrowAnyException(); + } else { + Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class); + } + } +} diff --git a/src/test/java/domain/piece/ElephantTest.java b/src/test/java/domain/piece/ElephantTest.java new file mode 100644 index 0000000000..5ed628b7be --- /dev/null +++ b/src/test/java/domain/piece/ElephantTest.java @@ -0,0 +1,39 @@ +package domain.piece; + +import domain.Position; +import domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.*; + +class ElephantTest { + + @ParameterizedTest + @CsvSource({ + "3, 1, 5, 4, HAN, true", + "3, 1, 1, 4, HAN, true", + "3, 1, 3, 2, HAN, false", + "3, 1, 4, 1, HAN, false" + }) + @DisplayName("기물이 이동할 수 있는 위치인지 검증한다.") + void 기물이_이동할_수_있는_위치인지_검증한다( + int sourceX, int sourceY, int targetX, int targetY, Side side, boolean pass + ) { + // given + Position sourcePosition = Position.of(sourceX, sourceY); + Position targetPosition = Position.of(targetX, targetY); + Piece piece = new Elephant(side); + + // when & then + if (pass) { + Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) + .doesNotThrowAnyException(); + } else { + Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class); + } + } +} \ No newline at end of file diff --git a/src/test/java/domain/piece/GuardTest.java b/src/test/java/domain/piece/GuardTest.java new file mode 100644 index 0000000000..f1914a6af9 --- /dev/null +++ b/src/test/java/domain/piece/GuardTest.java @@ -0,0 +1,40 @@ +package domain.piece; + +import domain.Position; +import domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.*; + +class GuardTest { + + @ParameterizedTest + @CsvSource({ + "4, 1, 4, 2, HAN, true", + "6, 1, 5, 1, HAN, true", + "6, 2, 6, 1, HAN, true", + "4, 1, 4, 3, HAN, false", + "6, 1, 4, 1, HAN, false" + }) + @DisplayName("기물이 이동할 수 있는 위치인지 검증한다.") + void 기물이_이동할_수_있는_위치인지_검증한다( + int sourceX, int sourceY, int targetX, int targetY, Side side, boolean expected + ) { + // given + Position sourcePosition = Position.of(sourceX, sourceY); + Position targetPosition = Position.of(targetX, targetY); + Piece piece = new Guard(side); + + // when & then + if (expected) { + Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) + .doesNotThrowAnyException(); + } else { + Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class); + } + } +} diff --git a/src/test/java/domain/piece/HorseTest.java b/src/test/java/domain/piece/HorseTest.java new file mode 100644 index 0000000000..92ddb393b2 --- /dev/null +++ b/src/test/java/domain/piece/HorseTest.java @@ -0,0 +1,39 @@ +package domain.piece; + +import domain.Position; +import domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.*; + +class HorseTest { + + @ParameterizedTest + @CsvSource({ + "3, 2, 1, 1, HAN, true", + "3, 2, 5, 1, HAN, true", + "3, 2, 1, 5, HAN, false", + "3, 2, 3, 5, HAN, false", + }) + @DisplayName("마가 이동할 수 있는 위치인지 검증한다.") + void 기물이_이동할_수_있는_위치인지_검증한다( + int sourceX, int sourceY, int targetX, int targetY, Side side, boolean pass + ) { + // given + Position sourcePosition = Position.of(sourceX, sourceY); + Position targetPosition = Position.of(targetX, targetY); + Piece piece = new Horse(side); + + // when & then + if (pass) { + Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) + .doesNotThrowAnyException(); + } else { + Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class); + } + } +} diff --git a/src/test/java/domain/piece/KingTest.java b/src/test/java/domain/piece/KingTest.java new file mode 100644 index 0000000000..4ddda56c05 --- /dev/null +++ b/src/test/java/domain/piece/KingTest.java @@ -0,0 +1,41 @@ +package domain.piece; + +import domain.Position; +import domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.*; + +class KingTest { + + @ParameterizedTest + @CsvSource({ + "5, 2, 5, 3, HAN, true", + "5, 2, 5, 1, HAN, true", + "5, 2, 4, 2, HAN, true", + "5, 2, 6, 2, HAN, true", + + "5, 2, 5, 4, HAN, false" + }) + @DisplayName("궁이 이동할 수 있는 위치인지 검증한다.") + void 기물이_이동할_수_있는_위치인지_검증한다( + int sourceX, int sourceY, int targetX, int targetY, Side side, boolean pass + ) { + // given + Position sourcePosition = Position.of(sourceX, sourceY); + Position targetPosition = Position.of(targetX, targetY); + Piece piece = new Horse(side); + + // when & then + if (pass) { + Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) + .doesNotThrowAnyException(); + } else { + Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class); + } + } +} diff --git a/src/test/java/domain/piece/PieceTest.java b/src/test/java/domain/piece/PieceTest.java new file mode 100644 index 0000000000..45f49adf4f --- /dev/null +++ b/src/test/java/domain/piece/PieceTest.java @@ -0,0 +1,44 @@ +package domain.piece; + +import domain.Position; +import domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class PieceTest { + + static class TestPiece extends Piece{ + + public TestPiece() { + super(Side.CHO); + } + + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + return List.of(); + } + + @Override + public String getName() { + return ""; + } + } + + + @DisplayName("기물 이동 경로는 모두 비어있어야한다.") + @Test + void 기물_이동_경로는_모두_비어있어야한다() { + // given + List pieces = List.of(new Empty(), new Empty()); + // when + Piece piece = new TestPiece(); + boolean actual = piece.checkRoute(pieces); + + // then + boolean expected = true; + Assertions.assertThat(actual).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/domain/piece/SoldierTest.java b/src/test/java/domain/piece/SoldierTest.java new file mode 100644 index 0000000000..2bd37d1b96 --- /dev/null +++ b/src/test/java/domain/piece/SoldierTest.java @@ -0,0 +1,39 @@ +package domain.piece; + +import domain.Position; +import domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.*; + +class SoldierTest { + + @ParameterizedTest + @CsvSource({ + "1, 7, 1, 6, CHO, true", + "1, 7, 1, 5, CHO, false", + "1, 4, 1, 5, HAN, true", + "1, 4, 1, 3, HAN, false" + }) + @DisplayName("초/병이 이동할 수 있는 위치인지 검증한다.") + void 기물이_이동할_수_있는_위치인지_검증한다( + int sourceX, int sourceY, int targetX, int targetY, Side side, boolean pass + ) { + // given + Position sourcePosition = Position.of(sourceX, sourceY); + Position targetPosition = Position.of(targetX, targetY); + Piece piece = new Soldier(side); + + // when & then + if (pass) { + Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) + .doesNotThrowAnyException(); + } else { + Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class); + } + } +} From 0b0a46c6afa2f5eb37c09ecbe27fd44c3ff2c5ad Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Fri, 27 Mar 2026 22:17:50 +0900 Subject: [PATCH 11/34] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=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 --- README.md | 2 +- .../java/controller/JanggiController.java | 2 +- src/main/java/domain/Board.java | 31 +++++++++ src/main/java/domain/Direction.java | 28 +++++++++ src/main/java/domain/Position.java | 4 ++ src/main/java/domain/Side.java | 9 ++- src/main/java/domain/piece/Cannon.java | 63 +++++++++++++++++++ src/main/java/domain/piece/Chariot.java | 33 ++++++++++ src/main/java/domain/piece/Elephant.java | 34 ++++++++++ src/main/java/domain/piece/Empty.java | 9 +++ src/main/java/domain/piece/Guard.java | 29 +++++++++ src/main/java/domain/piece/Horse.java | 34 ++++++++++ src/main/java/domain/piece/King.java | 29 +++++++++ src/main/java/domain/piece/Piece.java | 22 ++++++- src/main/java/domain/piece/Soldier.java | 36 +++++++++++ 15 files changed, 361 insertions(+), 4 deletions(-) create mode 100644 src/main/java/domain/Direction.java diff --git a/README.md b/README.md index 9faabe171c..a438ef6cd7 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,6 @@ ### ✅ 장기 진행 상태 출력 기능 -### ☑️ 기물 이동 기능 +### ✅ 기물 이동 기능 ## ⚠️ 기능 예외 사항 \ No newline at end of file diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 70c63dca5e..7cddd35579 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -29,7 +29,7 @@ private void processMove(Board board) { Position sourcePosition = readSourcePosition(); Position targetPosition = readTargetPosition(); try { - board.movePiece(sourcePosition, targetPosition); + board.movePiece(turn, sourcePosition, targetPosition); turn = turn.opposite(); outputView.printBoardStatus(board.getBoard()); } catch (IllegalArgumentException e) { diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 47e2c80d4b..fcc7f2e97a 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -2,6 +2,7 @@ import domain.piece.*; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,6 +15,36 @@ public Board(Formation choFormation, Formation hanFormation) { createBoard(choFormation, hanFormation); } + public void movePiece(Side side, Position sourcePosition, Position targetPosition) { + // 보드 출발지에서 기물을 꺼낸다. + Piece piece = board.get(sourcePosition); + // Empty인지 아닌지 확인한다. + if(piece instanceof Empty) { + throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); + } + // 팀인지 확인한다. + if(!piece.getSide().equals(side)) { + throw new IllegalArgumentException("선택한 기물은 아군 기물이 아닙니다."); + } + + // 기물에게 경로를 묻는다. + List route = piece.findRoute(sourcePosition, targetPosition); + List pieces = new ArrayList<>(); + for(Position position : route) { + if(!position.equals(targetPosition)) { + pieces.add(board.get(position)); + } + } + + // 경로에 있는 기물을 확인하여 이동 가능한 경로인지 확인한다 + piece.checkRoute(pieces); + + piece.checkTarget(board.get(targetPosition)); + // 목적지로 보내고 출발지를 Empty로 채운다. + board.put(targetPosition, board.get(sourcePosition)); + board.put(sourcePosition, new Empty()); + } + 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..a2f968ef4c --- /dev/null +++ b/src/main/java/domain/Direction.java @@ -0,0 +1,28 @@ +package domain; + +public enum Direction { + UP(0,-1), + DOWN(0, 1), + LEFT(-1, 0), + RIGHT(1, 0), + UP_LEFT(-1, -1), + UP_RIGHT(1, -1), + DOWN_LEFT(-1, 1), + DOWN_RIGHT(1, 1); + + private final int x; + private final int y; + + Direction(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } +} diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index e4323d5280..3cb44f46b4 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -35,4 +35,8 @@ private static void validateOutOfRange(int x, int y) { throw new IllegalArgumentException("y 좌표는 1~10, x 좌표는 1~9 사이여야 합니다."); } } + + public Position createPosition(int dx, int dy) { + return Position.of(x + dx, y + dy); + } } \ No newline at end of file diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java index 5e03cff22a..f954a3af07 100644 --- a/src/main/java/domain/Side.java +++ b/src/main/java/domain/Side.java @@ -3,5 +3,12 @@ public enum Side { CHO, HAN, - NONE + NONE; + + public Side opposite() { + if (this == CHO) { + return HAN; + } + return CHO; + } } diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index de3870bcdb..50ac0e465f 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -1,12 +1,75 @@ package domain.piece; +import domain.Direction; +import domain.Position; import domain.Side; +import java.util.ArrayList; +import java.util.List; + public class Cannon extends Piece { + + private final List> movementStrategy = List.of( + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + ); + public Cannon(Side side) { super(side); } + + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + for (List path : movementStrategy) { + List positions = new ArrayList<>(); + Direction direction = path.getFirst(); + Position position = sourcePosition; + + while (true) { + try { + Position nextPosition = position.createPosition(direction.getX(), direction.getY()); + positions.add(nextPosition); + if (nextPosition.equals(targetPosition)) { + return positions; + } + position = nextPosition; + } catch (IllegalArgumentException e) { + break; + } + } + } + throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + } + + @Override + public boolean checkRoute(List pieces) { + // 목적지까지는 포가아닌 기물이 한 개만 있어야 함. + int cnt = 0; + for(Piece piece : pieces) { + if(piece instanceof Cannon) { + throw new IllegalArgumentException("포를 넘어갈 수 없습니다."); + } + if(!(piece instanceof Empty)) { + cnt++; + } + } + if(cnt != 1) { + throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + } + return true; + } + + @Override + public void checkTarget(Piece piece) { + if(piece.getSide().equals(side)) { + throw new IllegalArgumentException("아군 기물은 잡을 수 없습니다."); + } + + if(piece instanceof Cannon) { + throw new IllegalArgumentException("포는 포끼리 잡을 수 없습니다."); + } + } + @Override public String getName() { return "포"; diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java index fc6d2bb689..21ae418acc 100644 --- a/src/main/java/domain/piece/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -1,12 +1,45 @@ package domain.piece; +import domain.Direction; +import domain.Position; import domain.Side; +import java.util.ArrayList; +import java.util.List; + public class Chariot extends Piece { + + private final List> movementStrategy = List.of( + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + ); + public Chariot(Side side) { super(side); } + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + for (List path : movementStrategy) { + List positions = new ArrayList<>(); + Direction direction = path.getFirst(); + Position position = sourcePosition; + + while (true) { + try { + Position nextPosition = position.createPosition(direction.getX(), direction.getY()); + positions.add(nextPosition); + if (nextPosition.equals(targetPosition)) { + return positions; + } + position = nextPosition; + } catch (IllegalArgumentException e) { + break; + } + } + } + throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + } + @Override public String getName() { return "차"; diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java index 405f247701..bdb66b710b 100644 --- a/src/main/java/domain/piece/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -1,12 +1,46 @@ package domain.piece; +import domain.Direction; +import domain.Position; import domain.Side; +import java.util.ArrayList; +import java.util.List; + public class Elephant extends Piece { + + private final List> movementStrategy = List.of( + List.of(Direction.UP, Direction.UP_LEFT, Direction.UP_LEFT), List.of(Direction.UP, Direction.UP_RIGHT, Direction.UP_RIGHT), + List.of(Direction.RIGHT, Direction.UP_RIGHT, Direction.UP_RIGHT), List.of(Direction.RIGHT, Direction.DOWN_RIGHT, Direction.DOWN_RIGHT), + List.of(Direction.DOWN, Direction.DOWN_LEFT, Direction.DOWN_LEFT), List.of(Direction.DOWN, Direction.DOWN_RIGHT, Direction.DOWN_RIGHT), + List.of(Direction.LEFT, Direction.UP_LEFT, Direction.UP_LEFT), List.of(Direction.LEFT, Direction.DOWN_LEFT, Direction.DOWN_LEFT) + ); + public Elephant(Side side) { super(side); } + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + for (List path : movementStrategy) { + List positions = new ArrayList<>(); + Position current = sourcePosition; + + try { + for (Direction direction : path) { + current = current.createPosition(direction.getX(), direction.getY()); + positions.add(current); + } + if (current.equals(targetPosition)) { + return positions; + } + } catch (IllegalArgumentException e) { + // 경계 벗어남, 다음 경로 시도 + } + } + throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + } + @Override public String getName() { return "상"; diff --git a/src/main/java/domain/piece/Empty.java b/src/main/java/domain/piece/Empty.java index 54a5a4804a..f0b0f77758 100644 --- a/src/main/java/domain/piece/Empty.java +++ b/src/main/java/domain/piece/Empty.java @@ -1,12 +1,21 @@ package domain.piece; +import domain.Position; import domain.Side; +import java.util.List; + public class Empty extends Piece{ + public Empty() { super(Side.NONE); } + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + return List.of(); + } + @Override public String getName() { return "."; diff --git a/src/main/java/domain/piece/Guard.java b/src/main/java/domain/piece/Guard.java index dcb6e107eb..05494582bc 100644 --- a/src/main/java/domain/piece/Guard.java +++ b/src/main/java/domain/piece/Guard.java @@ -1,12 +1,41 @@ package domain.piece; +import domain.Direction; +import domain.Position; import domain.Side; +import java.util.ArrayList; +import java.util.List; + public class Guard extends Piece { + + private final List> movementStrategy = List.of( + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + ); + public Guard(Side side) { super(side); } + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + List positions = new ArrayList<>(); + for(List path : movementStrategy) { + for(Direction direction : path) { + try{ + positions.add(sourcePosition.createPosition(direction.getX(), direction.getY())); + } catch (IllegalArgumentException e) { + break; + } + } + if(positions.contains(targetPosition)) { + return List.copyOf(positions); + } + positions.clear(); + } + throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + } + @Override public String getName() { return "사"; diff --git a/src/main/java/domain/piece/Horse.java b/src/main/java/domain/piece/Horse.java index 3a9eaa260d..865b13320a 100644 --- a/src/main/java/domain/piece/Horse.java +++ b/src/main/java/domain/piece/Horse.java @@ -1,12 +1,46 @@ package domain.piece; +import domain.Direction; +import domain.Position; import domain.Side; +import java.util.ArrayList; +import java.util.List; + public class Horse extends Piece { + + private final List> movementStrategy = List.of( + List.of(Direction.UP, Direction.UP_LEFT), List.of(Direction.UP, Direction.UP_RIGHT), + List.of(Direction.RIGHT, Direction.UP_RIGHT), List.of(Direction.RIGHT, Direction.DOWN_RIGHT), + List.of(Direction.DOWN, Direction.DOWN_LEFT), List.of(Direction.DOWN, Direction.DOWN_RIGHT), + List.of(Direction.LEFT, Direction.UP_LEFT), List.of(Direction.LEFT, Direction.DOWN_LEFT) + ); + public Horse(Side side) { super(side); } + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + List positions = new ArrayList<>(); + for(List path : movementStrategy) { + Position position = sourcePosition; + for(Direction direction : path) { + try{ + position = position.createPosition(direction.getX(), direction.getY()); + positions.add(position); + } catch (IllegalArgumentException e) { + break; + } + } + if(positions.contains(targetPosition)) { + return List.copyOf(positions); + } + positions.clear(); + } + throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + } + @Override public String getName() { return "마"; diff --git a/src/main/java/domain/piece/King.java b/src/main/java/domain/piece/King.java index 0eb3f3e250..7c52be7801 100644 --- a/src/main/java/domain/piece/King.java +++ b/src/main/java/domain/piece/King.java @@ -1,12 +1,41 @@ package domain.piece; +import domain.Direction; +import domain.Position; import domain.Side; +import java.util.ArrayList; +import java.util.List; + public class King extends Piece { + + private final List> movementStrategy = List.of( + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + ); + public King(Side side) { super(side); } + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + List positions = new ArrayList<>(); + for(List path : movementStrategy) { + for(Direction direction : path) { + try{ + positions.add(sourcePosition.createPosition(direction.getX(), direction.getY())); + } catch (IllegalArgumentException e) { + break; + } + } + if(positions.contains(targetPosition)) { + return List.copyOf(positions); + } + positions.clear(); + } + throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + } + @Override public String getName() { return "왕"; diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 634dfa39fe..6a5dd5a9ce 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -1,10 +1,13 @@ package domain.piece; +import domain.Position; import domain.Side; +import java.util.List; + public abstract class Piece { - private final Side side; + protected final Side side; protected Piece(Side side) { this.side = side; @@ -18,5 +21,22 @@ public Side getSide() { return side; } + public void checkTarget(Piece piece) { + if(side.equals(piece.side)) { + throw new IllegalArgumentException("아군 기물은 잡을 수 없습니다."); + } + } + + public abstract List findRoute(Position sourcePosition, Position targetPosition); + + public boolean checkRoute(List pieces) { + for(Piece piece : pieces) { + if(!(piece instanceof Empty)) { + return false; + } + } + return true; + } + public abstract String getName(); } diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java index 1101c84970..8beff9f87f 100644 --- a/src/main/java/domain/piece/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -1,10 +1,46 @@ package domain.piece; +import domain.Direction; +import domain.Position; import domain.Side; +import java.util.ArrayList; +import java.util.List; + public class Soldier extends Piece { + + private final List> movementStrategy; + public Soldier(Side side) { super(side); + if(Side.CHO == side) { + movementStrategy = List.of( + List.of(Direction.UP), List.of(Direction.RIGHT), List.of(Direction.LEFT) + ); + return; + } + movementStrategy = List.of( + List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + ); + } + + @Override + public List findRoute(Position sourcePosition, Position targetPosition) { + List positions = new ArrayList<>(); + for(List path : movementStrategy) { + for(Direction direction : path) { + try{ + positions.add(sourcePosition.createPosition(direction.getX(), direction.getY())); + } catch (IllegalArgumentException e) { + break; + } + } + if(positions.contains(targetPosition)) { + return List.copyOf(positions); + } + positions.clear(); + } + throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); } @Override From 668acedda2e1185b69e3f0e73b67c1dffb9335b4 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 10:07:57 +0900 Subject: [PATCH 12/34] =?UTF-8?q?feat:=20=ED=84=B4=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20Turn=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 11 +-- src/main/java/domain/Board.java | 4 +- src/main/java/domain/Turn.java | 18 ++++ src/main/java/domain/piece/Elephant.java | 3 +- src/main/java/view/OutputView.java | 5 +- src/test/java/BoardTest.java | 91 +++++++++++-------- 6 files changed, 80 insertions(+), 52 deletions(-) create mode 100644 src/main/java/domain/Turn.java diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 7cddd35579..3ab3e01073 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,9 +1,6 @@ package controller; -import domain.Board; -import domain.Formation; -import domain.Position; -import domain.Side; +import domain.*; import view.InputView; import view.OutputView; @@ -23,18 +20,18 @@ public void run() { } private void processMove(Board board) { - Side turn = Side.CHO; + Turn turn = new Turn(Side.CHO); while (true) { outputView.printCurrentTurn(turn); Position sourcePosition = readSourcePosition(); Position targetPosition = readTargetPosition(); try { board.movePiece(turn, sourcePosition, targetPosition); - turn = turn.opposite(); - outputView.printBoardStatus(board.getBoard()); + turn.next(); } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); } + outputView.printBoardStatus(board.getBoard()); } } diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index fcc7f2e97a..479f40e05f 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -15,7 +15,7 @@ public Board(Formation choFormation, Formation hanFormation) { createBoard(choFormation, hanFormation); } - public void movePiece(Side side, Position sourcePosition, Position targetPosition) { + public void movePiece(Turn turn, Position sourcePosition, Position targetPosition) { // 보드 출발지에서 기물을 꺼낸다. Piece piece = board.get(sourcePosition); // Empty인지 아닌지 확인한다. @@ -23,7 +23,7 @@ public void movePiece(Side side, Position sourcePosition, Position targetPositio throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); } // 팀인지 확인한다. - if(!piece.getSide().equals(side)) { + if(!piece.getSide().equals(turn.current())) { throw new IllegalArgumentException("선택한 기물은 아군 기물이 아닙니다."); } diff --git a/src/main/java/domain/Turn.java b/src/main/java/domain/Turn.java new file mode 100644 index 0000000000..b26236a783 --- /dev/null +++ b/src/main/java/domain/Turn.java @@ -0,0 +1,18 @@ +package domain; + +public class Turn { + + private Side current; + + public Turn(Side side) { + this.current = side; + } + + public Side current() { + return current; + } + + public void next() { + current = current.opposite(); + } +} diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java index bdb66b710b..cb976620da 100644 --- a/src/main/java/domain/piece/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -25,7 +25,6 @@ public List findRoute(Position sourcePosition, Position targetPosition for (List path : movementStrategy) { List positions = new ArrayList<>(); Position current = sourcePosition; - try { for (Direction direction : path) { current = current.createPosition(direction.getX(), direction.getY()); @@ -38,7 +37,7 @@ public List findRoute(Position sourcePosition, Position targetPosition // 경계 벗어남, 다음 경로 시도 } } - throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + throw new IllegalArgumentException(INVALID_TARGET_POSITION); } @Override diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 054af696bd..3444cba976 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -2,6 +2,7 @@ import domain.Position; import domain.Side; +import domain.Turn; import domain.piece.Piece; import java.util.Map; @@ -26,9 +27,9 @@ public void printBoardStatus(Map board) { } } - public void printCurrentTurn(Side turn) { + public void printCurrentTurn(Turn turn) { System.out.println(); - if(turn == Side.CHO) { + if(turn.current() == Side.CHO) { System.out.println("초의 차례입니다."); return; } diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java index c44be46333..fa988da93b 100644 --- a/src/test/java/BoardTest.java +++ b/src/test/java/BoardTest.java @@ -4,8 +4,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import java.util.Map; @@ -52,20 +50,34 @@ class initBoard { } } - @ParameterizedTest - @CsvSource({ - "CHO, 1, 4", - "HAN, 1, 7" - }) - @DisplayName("자신의 기물이 아닌 경우 이동할 수 없다.") - void 자신의_기물이_아닌_경우_이동할_수_없다(Side side, int sourceX, int sourceY) { + @DisplayName("초 턴에 한 기물을 선택한 경우 이동할 수 없다.") + @Test + void 초_턴에_한_기물을_선택한_경우_이동할_수_없다() { + // given + Board board = new Board(Formation.from("1"), Formation.from("1")); + Turn turn = new Turn(Side.CHO); + Position sourcePosition = Position.of(1, 4); + Position mockPosition = Position.of(1, 1); + + // when & then + Assertions.assertThatThrownBy(() -> board.movePiece(turn, sourcePosition, mockPosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("선택한 기물은 아군 기물이 아닙니다."); + + } + + + @DisplayName("한 턴에 초 기물을 선택한 경우 이동할 수 없다.") + @Test + void 한_턴에_초_기물을_선택한_경우_이동할_수_없다() { // given Board board = new Board(Formation.from("1"), Formation.from("1")); - Position sourcePosition = Position.of(sourceX, sourceY); + Turn turn = new Turn(Side.HAN); + Position sourcePosition = Position.of(1, 7); Position mockPosition = Position.of(1, 1); // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(side, sourcePosition, mockPosition)) + Assertions.assertThatThrownBy(() -> board.movePiece(turn, sourcePosition, mockPosition)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("선택한 기물은 아군 기물이 아닙니다."); @@ -76,11 +88,12 @@ class initBoard { void 기물이_존재하지_않는_경우_이동할_수_없다() { // given Board board = new Board(Formation.from("1"), Formation.from("1")); + Turn turn = new Turn(Side.CHO); Position sourcePosition = Position.of(5, 5); Position mockPosition = Position.of(1, 1); // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(Side.CHO, sourcePosition, mockPosition)) + Assertions.assertThatThrownBy(() -> board.movePiece(turn, sourcePosition, mockPosition)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("해당 위치에 기물이 존재하지 않습니다."); } @@ -102,7 +115,7 @@ class ChariotMove { Position target = Position.of(1, 3); // when - board.movePiece(Side.HAN, source, target); + board.movePiece(new Turn(Side.HAN), source, target); // then assertThat(board.getBoard().get(target)).isInstanceOf(Chariot.class); @@ -116,10 +129,10 @@ class ChariotMove { Board board = new Board(Formation.from("1"), Formation.from("1")); Position source = Position.of(1, 1); Position target = Position.of(1, 2); - board.movePiece(Side.HAN, source, target); + board.movePiece(new Turn(Side.HAN), source, target); // when - board.movePiece(Side.HAN, target, Position.of(3, 2)); + board.movePiece(new Turn(Side.HAN), target, Position.of(3, 2)); // then assertThat(board.getBoard().get(Position.of(3, 2))).isInstanceOf(Chariot.class); @@ -134,7 +147,7 @@ class ChariotMove { Position target = Position.of(3, 3); // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) .isInstanceOf(IllegalArgumentException.class); } } @@ -152,7 +165,7 @@ class ElephantMove { Position target = Position.of(4, 4); // when - board.movePiece(Side.HAN, source, target); + board.movePiece(new Turn(Side.HAN), source, target); // then assertThat(board.getBoard().get(target)).isInstanceOf(Elephant.class); @@ -168,7 +181,7 @@ class ElephantMove { Position target = Position.of(3, 2); // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) .isInstanceOf(IllegalArgumentException.class); } } @@ -186,7 +199,7 @@ class HorseMove { Position target = Position.of(3, 3); // when - board.movePiece(Side.HAN, source, target); + board.movePiece(new Turn(Side.HAN), source, target); // then assertThat(board.getBoard().get(target)).isInstanceOf(Horse.class); @@ -202,7 +215,7 @@ class HorseMove { Position target = Position.of(4, 4); // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) .isInstanceOf(IllegalArgumentException.class); } } @@ -217,11 +230,11 @@ class CannonMove { // given Board board = new Board(Formation.from("1"), Formation.from("1")); // 사(4,1)를 (4,3)으로 이동시켜 포가 뛰어넘을 기물 배치 - board.movePiece(Side.HAN, Position.of(4, 1), Position.of(4, 2)); - board.movePiece(Side.HAN, Position.of(4, 2), Position.of(4, 3)); + board.movePiece(new Turn(Side.HAN), Position.of(4, 1), Position.of(4, 2)); + board.movePiece(new Turn(Side.HAN), Position.of(4, 2), Position.of(4, 3)); // when - (2,3) 포가 (4,3) 사를 뛰어넘어 (5,3)으로 이동 - board.movePiece(Side.HAN, Position.of(2, 3), Position.of(5, 3)); + board.movePiece(new Turn(Side.HAN), Position.of(2, 3), Position.of(5, 3)); // then assertThat(board.getBoard().get(Position.of(5, 3))).isInstanceOf(Cannon.class); @@ -236,7 +249,7 @@ class CannonMove { Position target = Position.of(5, 3); // when & then - 중간에 기물이 없음 - Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) .isInstanceOf(IllegalArgumentException.class); } @@ -246,14 +259,14 @@ class CannonMove { // given Board board = new Board(Formation.from("1"), Formation.from("1")); // (8,3) 포를 (5,3)으로 이동하기 위해 먼저 중간에 기물 배치 - board.movePiece(Side.HAN, Position.of(6, 1), Position.of(6, 2)); - board.movePiece(Side.HAN, Position.of(6, 2), Position.of(6, 3)); + board.movePiece(new Turn(Side.HAN), Position.of(6, 1), Position.of(6, 2)); + board.movePiece(new Turn(Side.HAN), Position.of(6, 2), Position.of(6, 3)); // (8,3) 포가 (6,3) 사를 뛰어넘어 (5,3)으로 이동 - board.movePiece(Side.HAN, Position.of(8, 3), Position.of(5, 3)); + board.movePiece(new Turn(Side.HAN), Position.of(8, 3), Position.of(5, 3)); // 이제 (2,3) 포와 (5,3) 포 사이에 (5,3) 포를 뛰어넘는 상황 만들기 // (2,3) 포가 (5,3) 포를 뛰어넘어 (7,3)으로 이동 시도 - Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, Position.of(2, 3), Position.of(7, 3))) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), Position.of(2, 3), Position.of(7, 3))) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("포를 넘어갈 수 없습니다."); } @@ -272,7 +285,7 @@ class GuardMove { Position target = Position.of(5, 1); // when - board.movePiece(Side.HAN, source, target); + board.movePiece(new Turn(Side.HAN), source, target); // then assertThat(board.getBoard().get(target)).isInstanceOf(Guard.class); @@ -288,7 +301,7 @@ class GuardMove { Position target = Position.of(4, 3); // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) .isInstanceOf(IllegalArgumentException.class); } } @@ -306,7 +319,7 @@ class KingMove { Position target = Position.of(5, 1); // when - board.movePiece(Side.HAN, source, target); + board.movePiece(new Turn(Side.HAN), source, target); // then assertThat(board.getBoard().get(target)).isInstanceOf(King.class); @@ -322,7 +335,7 @@ class KingMove { Position target = Position.of(5, 4); // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) .isInstanceOf(IllegalArgumentException.class); } } @@ -340,7 +353,7 @@ class SoldierMove { Position target = Position.of(1, 6); // when - board.movePiece(Side.CHO, source, target); + board.movePiece(new Turn(Side.CHO), source, target); // then assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); @@ -356,7 +369,7 @@ class SoldierMove { Position target = Position.of(1, 5); // when - board.movePiece(Side.HAN, source, target); + board.movePiece(new Turn(Side.HAN), source, target); // then assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); @@ -372,7 +385,7 @@ class SoldierMove { Position target = Position.of(2, 7); // when - board.movePiece(Side.CHO, source, target); + board.movePiece(new Turn(Side.CHO), source, target); // then assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); @@ -387,7 +400,7 @@ class SoldierMove { Position target = Position.of(1, 8); // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(Side.CHO, source, target)) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.CHO), source, target)) .isInstanceOf(IllegalArgumentException.class); } } @@ -402,9 +415,9 @@ class CapturePiece { // given Board board = new Board(Formation.from("1"), Formation.from("1")); // 차를 이동시켜 적 졸을 잡기 - board.movePiece(Side.HAN, Position.of(1, 1), Position.of(1, 5)); + board.movePiece(new Turn(Side.HAN), Position.of(1, 1), Position.of(1, 5)); // (1,5)에서 (1,7) CHO 졸 잡기 - board.movePiece(Side.HAN, Position.of(1, 5), Position.of(1, 7)); + board.movePiece(new Turn(Side.HAN), Position.of(1, 5), Position.of(1, 7)); // then assertThat(board.getBoard().get(Position.of(1, 7))).isInstanceOf(Chariot.class); @@ -420,7 +433,7 @@ class CapturePiece { Position target = Position.of(1, 4); // HAN 졸 위치 // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(Side.HAN, source, target)) + Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("아군 기물은 잡을 수 없습니다."); } From 8542552cd6410c72a578c657437f0df2351aba12 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 10:09:07 +0900 Subject: [PATCH 13/34] =?UTF-8?q?fix:=20KingTest=EC=97=90=EC=84=9C=20Horse?= =?UTF-8?q?=EB=A1=9C=20=EC=83=9D=EC=84=B1=ED=95=98=EB=8D=98=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=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/domain/piece/KingTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/domain/piece/KingTest.java b/src/test/java/domain/piece/KingTest.java index 4ddda56c05..cf79e93c21 100644 --- a/src/test/java/domain/piece/KingTest.java +++ b/src/test/java/domain/piece/KingTest.java @@ -7,8 +7,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static org.junit.jupiter.api.Assertions.*; - class KingTest { @ParameterizedTest @@ -27,7 +25,7 @@ class KingTest { // given Position sourcePosition = Position.of(sourceX, sourceY); Position targetPosition = Position.of(targetX, targetY); - Piece piece = new Horse(side); + Piece piece = new King(side); // when & then if (pass) { From 4c158e804de41c6657293075cc94fb254df8d800 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 10:51:37 +0900 Subject: [PATCH 14/34] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=20=EB=A1=9C=EC=A7=81=EC=9D=98=20=ED=95=98?= =?UTF-8?q?=EB=93=9C=EC=BD=94=EB=94=A9=EC=9D=84=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/Board.java | 64 +++++++++++++++++---------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 479f40e05f..8ecbc3bc3d 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -9,6 +9,18 @@ public class Board { + private final int BACK_Y = 0; + private final int KING_Y = 1; + private final int CANNON_Y = 2; + private final int SOLDIER_Y = 3; + + private final List CANNON_X = List.of(2, 8); + private final List CHARIOT_X = List.of(1, 9); + private final List GUARD_X = List.of(4, 6); + private final List KING_X = List.of(5); + private final List SOLIDER_X = List.of(1, 3, 5, 7, 9); + private final List FORMATION_X = List.of(2, 3, 7, 8); + private final Map board = new HashMap<>(); public Board(Formation choFormation, Formation hanFormation) { @@ -16,18 +28,15 @@ public Board(Formation choFormation, Formation hanFormation) { } public void movePiece(Turn turn, Position sourcePosition, Position targetPosition) { - // 보드 출발지에서 기물을 꺼낸다. Piece piece = board.get(sourcePosition); - // Empty인지 아닌지 확인한다. + if(piece instanceof Empty) { throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); } - // 팀인지 확인한다. if(!piece.getSide().equals(turn.current())) { throw new IllegalArgumentException("선택한 기물은 아군 기물이 아닙니다."); } - // 기물에게 경로를 묻는다. List route = piece.findRoute(sourcePosition, targetPosition); List pieces = new ArrayList<>(); for(Position position : route) { @@ -35,12 +44,9 @@ public void movePiece(Turn turn, Position sourcePosition, Position targetPositio pieces.add(board.get(position)); } } - - // 경로에 있는 기물을 확인하여 이동 가능한 경로인지 확인한다 piece.checkRoute(pieces); piece.checkTarget(board.get(targetPosition)); - // 목적지로 보내고 출발지를 Empty로 채운다. board.put(targetPosition, board.get(sourcePosition)); board.put(sourcePosition, new Empty()); } @@ -59,31 +65,14 @@ private void createBoard(Formation choFormation, Formation hanFormation) { placePieces(hanFormation, Side.HAN); } - private void placePieces(Formation hanFormation, Side side) { - + private void placePieces(Formation formation, Side side) { List rows = getRowForSide(side); - - placePiece(Position.of(1, rows.get(0)), PieceType.CHARIOT.create(side)); - placePiece(Position.of(9, rows.get(0)), PieceType.CHARIOT.create(side)); - - placePiece(Position.of(4, rows.get(0)), PieceType.GUARD.create(side)); - placePiece(Position.of(6, rows.get(0)), PieceType.GUARD.create(side)); - - placePiece(Position.of(5, rows.get(1)), PieceType.KING.create(side)); - - placePiece(Position.of(2, rows.get(2)), PieceType.CANNON.create(side)); - placePiece(Position.of(8, rows.get(2)), PieceType.CANNON.create(side)); - - placePiece(Position.of(1, rows.get(3)), PieceType.SOLDIER.create(side)); - placePiece(Position.of(3, rows.get(3)), PieceType.SOLDIER.create(side)); - placePiece(Position.of(5, rows.get(3)), PieceType.SOLDIER.create(side)); - placePiece(Position.of(7, rows.get(3)), PieceType.SOLDIER.create(side)); - placePiece(Position.of(9, rows.get(3)), PieceType.SOLDIER.create(side)); - - placePiece(Position.of(2, rows.get(0)), hanFormation.getPieceTypes().get(0).create(side)); - placePiece(Position.of(3, rows.get(0)), hanFormation.getPieceTypes().get(1).create(side)); - placePiece(Position.of(7, rows.get(0)), hanFormation.getPieceTypes().get(2).create(side)); - placePiece(Position.of(8, rows.get(0)), hanFormation.getPieceTypes().get(3).create(side)); + placePiece(CANNON_X, rows.get(CANNON_Y), PieceType.CANNON, side); + placePiece(CHARIOT_X, rows.get(BACK_Y), PieceType.CHARIOT, side); + placePiece(GUARD_X, rows.get(BACK_Y), PieceType.GUARD, side); + placePiece(KING_X, rows.get(KING_Y), PieceType.KING, side); + placePiece(SOLIDER_X, rows.get(SOLDIER_Y), PieceType.SOLDIER, side); + placeFormationPiece(formation, FORMATION_X, rows.get(BACK_Y), side); } private List getRowForSide(Side side) { @@ -96,4 +85,17 @@ private List getRowForSide(Side side) { private void placePiece(Position position, Piece piece) { board.put(position, piece); } + + private void placePiece(List xPositions, int y, PieceType pieceType, Side side) { + for(int x : xPositions) { + board.put(Position.of(x, y), pieceType.create(side)); + } + } + + private void placeFormationPiece(Formation formation, List formationX, int y, Side side) { + List pieceTypes = formation.getPieceTypes(); + for(int i = 0; i < pieceTypes.size(); i++) { + board.put(Position.of(formationX.get(i), y), pieceTypes.get(i).create(side)); + } + } } From 4895748d20070875efcce246c37105cf82a4edc1 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 11:07:41 +0900 Subject: [PATCH 15/34] =?UTF-8?q?refactor:=20movePiece=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A1=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/domain/Board.java | 44 ++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 8ecbc3bc3d..9d4c6d4a88 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -30,23 +30,13 @@ public Board(Formation choFormation, Formation hanFormation) { public void movePiece(Turn turn, Position sourcePosition, Position targetPosition) { Piece piece = board.get(sourcePosition); - if(piece instanceof Empty) { - throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); - } - if(!piece.getSide().equals(turn.current())) { - throw new IllegalArgumentException("선택한 기물은 아군 기물이 아닙니다."); - } + validatePieceExists(piece); + validateOwnPiece(piece, turn); List route = piece.findRoute(sourcePosition, targetPosition); - List pieces = new ArrayList<>(); - for(Position position : route) { - if(!position.equals(targetPosition)) { - pieces.add(board.get(position)); - } - } - piece.checkRoute(pieces); + findPiecesOnRoute(piece, route, targetPosition); - piece.checkTarget(board.get(targetPosition)); + validateAvailableTarget(piece, targetPosition); board.put(targetPosition, board.get(sourcePosition)); board.put(sourcePosition, new Empty()); } @@ -98,4 +88,30 @@ private void placeFormationPiece(Formation formation, List formationX, board.put(Position.of(formationX.get(i), y), pieceTypes.get(i).create(side)); } } + + private void validatePieceExists(Piece piece) { + if(piece instanceof Empty) { + throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); + } + } + + private void findPiecesOnRoute(Piece piece, List route, Position targetPosition) { + List pieces = new ArrayList<>(); + for(Position position : route) { + if(!position.equals(targetPosition)) { + pieces.add(board.get(position)); + } + } + piece.checkRoute(pieces); + } + + private void validateOwnPiece(Piece piece, Turn turn) { + if(!piece.getSide().equals(turn.current())) { + throw new IllegalArgumentException("선택한 기물은 아군 기물이 아닙니다."); + } + } + + private void validateAvailableTarget(Piece piece, Position targetPosition) { + piece.checkTarget(board.get(targetPosition)); + } } From 50f7d45a1c791946dc3df9092dbbd550a9c44f8e Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 11:38:10 +0900 Subject: [PATCH 16/34] =?UTF-8?q?refactor:=20MovementStrategy=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/piece/Cannon.java | 45 ++++++------------- src/main/java/domain/piece/Chariot.java | 7 +-- src/main/java/domain/piece/Elephant.java | 3 +- src/main/java/domain/piece/Empty.java | 4 +- src/main/java/domain/piece/Guard.java | 8 ++-- src/main/java/domain/piece/Horse.java | 28 +++--------- src/main/java/domain/piece/King.java | 8 ++-- src/main/java/domain/piece/Piece.java | 16 ++++--- src/main/java/domain/piece/PieceType.java | 14 +++--- src/main/java/domain/piece/Soldier.java | 15 ++++--- .../java/domain/strategy/LinearMovement.java | 42 +++++++++++++++++ .../domain/strategy/MovementStrategy.java | 18 ++++++++ .../java/domain/strategy/PathMovement.java | 37 +++++++++++++++ 13 files changed, 163 insertions(+), 82 deletions(-) create mode 100644 src/main/java/domain/strategy/LinearMovement.java create mode 100644 src/main/java/domain/strategy/MovementStrategy.java create mode 100644 src/main/java/domain/strategy/PathMovement.java diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index 50ac0e465f..d1cc5186b6 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -3,70 +3,53 @@ import domain.Direction; import domain.Position; import domain.Side; +import domain.strategy.MovementStrategy; -import java.util.ArrayList; import java.util.List; public class Cannon extends Piece { - private final List> movementStrategy = List.of( - List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) - ); + private final String CANNOT_JUMP_WITH_CANNON = ERROR_PREFIX + "포를 넘어갈 수 없습니다."; + private final String CANNOT_CAPTURE_CANNON_WITH_CANNON = ERROR_PREFIX + "포는 포끼리 잡을 수 없습니다."; - public Cannon(Side side) { - super(side); + private final List> paths = List.of( + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT)); + + public Cannon(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override public List findRoute(Position sourcePosition, Position targetPosition) { - for (List path : movementStrategy) { - List positions = new ArrayList<>(); - Direction direction = path.getFirst(); - Position position = sourcePosition; - - while (true) { - try { - Position nextPosition = position.createPosition(direction.getX(), direction.getY()); - positions.add(nextPosition); - if (nextPosition.equals(targetPosition)) { - return positions; - } - position = nextPosition; - } catch (IllegalArgumentException e) { - break; - } - } - } - throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + return movementStrategy.findRoute(paths, sourcePosition, targetPosition); } @Override - public boolean checkRoute(List pieces) { + public void checkRoute(List pieces) { // 목적지까지는 포가아닌 기물이 한 개만 있어야 함. int cnt = 0; for(Piece piece : pieces) { if(piece instanceof Cannon) { - throw new IllegalArgumentException("포를 넘어갈 수 없습니다."); + throw new IllegalArgumentException(CANNOT_JUMP_WITH_CANNON); } if(!(piece instanceof Empty)) { cnt++; } } if(cnt != 1) { - throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + throw new IllegalArgumentException(INVALID_TARGET_POSITION); } - return true; } @Override public void checkTarget(Piece piece) { if(piece.getSide().equals(side)) { - throw new IllegalArgumentException("아군 기물은 잡을 수 없습니다."); + throw new IllegalArgumentException(CANNOT_CAPTURE_OWN_PIECE); } if(piece instanceof Cannon) { - throw new IllegalArgumentException("포는 포끼리 잡을 수 없습니다."); + throw new IllegalArgumentException(CANNOT_CAPTURE_CANNON_WITH_CANNON); } } diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java index 21ae418acc..50c212d2c6 100644 --- a/src/main/java/domain/piece/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -3,6 +3,7 @@ import domain.Direction; import domain.Position; import domain.Side; +import domain.strategy.MovementStrategy; import java.util.ArrayList; import java.util.List; @@ -13,8 +14,8 @@ public class Chariot extends Piece { List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); - public Chariot(Side side) { - super(side); + public Chariot(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override @@ -37,7 +38,7 @@ public List findRoute(Position sourcePosition, Position targetPosition } } } - throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + throw new IllegalArgumentException(INVALID_TARGET_POSITION); } @Override diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java index cb976620da..717fecd0d4 100644 --- a/src/main/java/domain/piece/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -3,6 +3,7 @@ import domain.Direction; import domain.Position; import domain.Side; +import domain.strategy.PathMovement; import java.util.ArrayList; import java.util.List; @@ -17,7 +18,7 @@ public class Elephant extends Piece { ); public Elephant(Side side) { - super(side); + super(side, new PathMovement()); } @Override diff --git a/src/main/java/domain/piece/Empty.java b/src/main/java/domain/piece/Empty.java index f0b0f77758..2e270a28cb 100644 --- a/src/main/java/domain/piece/Empty.java +++ b/src/main/java/domain/piece/Empty.java @@ -8,12 +8,12 @@ public class Empty extends Piece{ public Empty() { - super(Side.NONE); + super(Side.NONE, null); } @Override public List findRoute(Position sourcePosition, Position targetPosition) { - return List.of(); + throw new IllegalArgumentException(INVALID_TARGET_POSITION); } @Override diff --git a/src/main/java/domain/piece/Guard.java b/src/main/java/domain/piece/Guard.java index 05494582bc..aaebf7bead 100644 --- a/src/main/java/domain/piece/Guard.java +++ b/src/main/java/domain/piece/Guard.java @@ -3,6 +3,8 @@ import domain.Direction; import domain.Position; import domain.Side; +import domain.strategy.MovementStrategy; +import domain.strategy.PathMovement; import java.util.ArrayList; import java.util.List; @@ -13,8 +15,8 @@ public class Guard extends Piece { List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); - public Guard(Side side) { - super(side); + public Guard(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override @@ -33,7 +35,7 @@ public List findRoute(Position sourcePosition, Position targetPosition } positions.clear(); } - throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + throw new IllegalArgumentException(INVALID_TARGET_POSITION); } @Override diff --git a/src/main/java/domain/piece/Horse.java b/src/main/java/domain/piece/Horse.java index 865b13320a..160d221fdb 100644 --- a/src/main/java/domain/piece/Horse.java +++ b/src/main/java/domain/piece/Horse.java @@ -3,42 +3,28 @@ import domain.Direction; import domain.Position; import domain.Side; +import domain.strategy.LinearMovement; +import domain.strategy.MovementStrategy; +import domain.strategy.PathMovement; -import java.util.ArrayList; import java.util.List; public class Horse extends Piece { - private final List> movementStrategy = List.of( + private final List> paths = List.of( List.of(Direction.UP, Direction.UP_LEFT), List.of(Direction.UP, Direction.UP_RIGHT), List.of(Direction.RIGHT, Direction.UP_RIGHT), List.of(Direction.RIGHT, Direction.DOWN_RIGHT), List.of(Direction.DOWN, Direction.DOWN_LEFT), List.of(Direction.DOWN, Direction.DOWN_RIGHT), List.of(Direction.LEFT, Direction.UP_LEFT), List.of(Direction.LEFT, Direction.DOWN_LEFT) ); - public Horse(Side side) { - super(side); + public Horse(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override public List findRoute(Position sourcePosition, Position targetPosition) { - List positions = new ArrayList<>(); - for(List path : movementStrategy) { - Position position = sourcePosition; - for(Direction direction : path) { - try{ - position = position.createPosition(direction.getX(), direction.getY()); - positions.add(position); - } catch (IllegalArgumentException e) { - break; - } - } - if(positions.contains(targetPosition)) { - return List.copyOf(positions); - } - positions.clear(); - } - throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + return movementStrategy.findRoute(paths, sourcePosition, targetPosition); } @Override diff --git a/src/main/java/domain/piece/King.java b/src/main/java/domain/piece/King.java index 7c52be7801..e68ac3d55a 100644 --- a/src/main/java/domain/piece/King.java +++ b/src/main/java/domain/piece/King.java @@ -3,6 +3,8 @@ import domain.Direction; import domain.Position; import domain.Side; +import domain.strategy.MovementStrategy; +import domain.strategy.PathMovement; import java.util.ArrayList; import java.util.List; @@ -13,8 +15,8 @@ public class King extends Piece { List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); - public King(Side side) { - super(side); + public King(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override @@ -33,7 +35,7 @@ public List findRoute(Position sourcePosition, Position targetPosition } positions.clear(); } - throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + throw new IllegalArgumentException(INVALID_TARGET_POSITION); } @Override diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 6a5dd5a9ce..d82ead43ce 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -2,15 +2,22 @@ import domain.Position; import domain.Side; +import domain.strategy.MovementStrategy; import java.util.List; public abstract class Piece { + protected final String ERROR_PREFIX = "[ERROR] "; + protected final String CANNOT_CAPTURE_OWN_PIECE = ERROR_PREFIX + "아군 기물은 잡을 수 없습니다."; + protected final String INVALID_TARGET_POSITION = ERROR_PREFIX + "이동할 수 없는 목적지입니다."; + protected final Side side; + protected final MovementStrategy movementStrategy; - protected Piece(Side side) { + protected Piece(Side side, MovementStrategy movementStrategy) { this.side = side; + this.movementStrategy = movementStrategy; } public boolean isSameSide(Side side) { @@ -23,19 +30,18 @@ public Side getSide() { public void checkTarget(Piece piece) { if(side.equals(piece.side)) { - throw new IllegalArgumentException("아군 기물은 잡을 수 없습니다."); + throw new IllegalArgumentException(CANNOT_CAPTURE_OWN_PIECE); } } public abstract List findRoute(Position sourcePosition, Position targetPosition); - public boolean checkRoute(List pieces) { + public void checkRoute(List pieces) { for(Piece piece : pieces) { if(!(piece instanceof Empty)) { - return false; + throw new IllegalArgumentException(CANNOT_CAPTURE_OWN_PIECE); } } - return true; } public abstract String getName(); diff --git a/src/main/java/domain/piece/PieceType.java b/src/main/java/domain/piece/PieceType.java index 30656741d8..3731e14c0a 100644 --- a/src/main/java/domain/piece/PieceType.java +++ b/src/main/java/domain/piece/PieceType.java @@ -1,6 +1,8 @@ package domain.piece; import domain.Side; +import domain.strategy.LinearMovement; +import domain.strategy.PathMovement; public enum PieceType { ELEPHANT{ @@ -12,31 +14,31 @@ public Piece create(Side side) { CANNON{ @Override public Piece create(Side side) { - return new Cannon(side); + return new Cannon(side, new LinearMovement()); } }, CHARIOT{ @Override public Piece create(Side side) { - return new Chariot(side); + return new Chariot(side, new LinearMovement()); } }, GUARD{ @Override public Piece create(Side side) { - return new Guard(side); + return new Guard(side, new PathMovement()); } }, HORSE{ @Override public Piece create(Side side) { - return new Horse(side); + return new Horse(side, new PathMovement()); } }, KING{ @Override public Piece create(Side side) { - return new King(side); + return new King(side, new PathMovement()); } }, EMPTY{ @@ -48,7 +50,7 @@ public Piece create(Side side) { SOLDIER{ @Override public Piece create(Side side) { - return new Soldier(side); + return new Soldier(side, new PathMovement()); } }; diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java index 8beff9f87f..811850a26f 100644 --- a/src/main/java/domain/piece/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -3,23 +3,24 @@ import domain.Direction; import domain.Position; import domain.Side; +import domain.strategy.MovementStrategy; import java.util.ArrayList; import java.util.List; public class Soldier extends Piece { - private final List> movementStrategy; + private final List> paths; - public Soldier(Side side) { - super(side); + public Soldier(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); if(Side.CHO == side) { - movementStrategy = List.of( + paths = List.of( List.of(Direction.UP), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); return; } - movementStrategy = List.of( + paths = List.of( List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); } @@ -27,7 +28,7 @@ public Soldier(Side side) { @Override public List findRoute(Position sourcePosition, Position targetPosition) { List positions = new ArrayList<>(); - for(List path : movementStrategy) { + for(List path : paths) { for(Direction direction : path) { try{ positions.add(sourcePosition.createPosition(direction.getX(), direction.getY())); @@ -40,7 +41,7 @@ public List findRoute(Position sourcePosition, Position targetPosition } positions.clear(); } - throw new IllegalArgumentException("이동할 수 없는 목적지입니다."); + throw new IllegalArgumentException(INVALID_TARGET_POSITION); } @Override diff --git a/src/main/java/domain/strategy/LinearMovement.java b/src/main/java/domain/strategy/LinearMovement.java new file mode 100644 index 0000000000..44a1635e40 --- /dev/null +++ b/src/main/java/domain/strategy/LinearMovement.java @@ -0,0 +1,42 @@ +package domain.strategy; + +import domain.Direction; +import domain.Position; + +import java.util.ArrayList; +import java.util.List; + +public class LinearMovement extends MovementStrategy { + + @Override + public List findRoute(List> paths, Position sourcePosition, Position targetPosition) { + for(List path: paths) { + List positions = buildRoute(path, sourcePosition, targetPosition); + if(positions.contains(targetPosition)) { + return List.copyOf(positions); + } + } + throw new IllegalArgumentException(INVALID_TARGET_POSITION); + } + + @Override + protected List buildRoute(List path, Position sourcePosition, Position targetPosition) { + List route = new ArrayList<>(); + Direction direction = path.getFirst(); + Position position = sourcePosition; + while (true) { + try { + Position nextPosition = position.createPosition(direction.getX(), direction.getY()); + route.add(nextPosition); + if (nextPosition.equals(targetPosition)) { + return List.copyOf(route); + } + position = nextPosition; + } catch (IllegalArgumentException e) { + route.clear(); + break; + } + } + return List.copyOf(route); + } +} diff --git a/src/main/java/domain/strategy/MovementStrategy.java b/src/main/java/domain/strategy/MovementStrategy.java new file mode 100644 index 0000000000..2ddb11777a --- /dev/null +++ b/src/main/java/domain/strategy/MovementStrategy.java @@ -0,0 +1,18 @@ +package domain.strategy; + +import domain.Direction; +import domain.Position; + +import java.util.List; + +public abstract class MovementStrategy { + + protected final String ERROR_PREFIX = "[ERROR] "; + protected final String INVALID_TARGET_POSITION = ERROR_PREFIX + "이동할 수 없는 목적지입니다."; + + + public abstract List findRoute(List> paths, Position sourcePosition, Position targetPosition); + + protected abstract List buildRoute(List route, Position sourcePosition, Position targetPosition); +} + diff --git a/src/main/java/domain/strategy/PathMovement.java b/src/main/java/domain/strategy/PathMovement.java new file mode 100644 index 0000000000..835fab30f8 --- /dev/null +++ b/src/main/java/domain/strategy/PathMovement.java @@ -0,0 +1,37 @@ +package domain.strategy; + +import domain.Direction; +import domain.Position; + +import java.util.ArrayList; +import java.util.List; + +public class PathMovement extends MovementStrategy{ + + @Override + public List findRoute(List> paths, Position sourcePosition, Position targetPosition) { + for (List path : paths) { + List route = buildRoute(path, sourcePosition, targetPosition); + if(route.contains(targetPosition)){ + return List.copyOf(route); + } + } + throw new IllegalArgumentException(INVALID_TARGET_POSITION); + } + + @Override + protected List buildRoute(List path, Position sourcePosition, Position targetPosition) { + List route = new ArrayList<>(); + Position position = sourcePosition; + for(Direction direction : path) { + try{ + position = position.createPosition(direction.getX(), direction.getY()); + route.add(position); + } catch (IllegalArgumentException e) { + route.clear(); + break; + } + } + return route; + } +} From b79715079cb71cee3af67e2c09ef3c574850f399 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 11:38:20 +0900 Subject: [PATCH 17/34] =?UTF-8?q?test:=20=EA=B8=B0=EB=AC=BC=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/BoardTest.java | 6 +++--- src/test/java/domain/piece/CannonTest.java | 22 ++++++++++----------- src/test/java/domain/piece/ChariotTest.java | 4 +--- src/test/java/domain/piece/GuardTest.java | 2 +- src/test/java/domain/piece/HorseTest.java | 2 +- src/test/java/domain/piece/KingTest.java | 2 +- src/test/java/domain/piece/PieceTest.java | 9 ++++----- src/test/java/domain/piece/SoldierTest.java | 2 +- 8 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java index fa988da93b..7085c61e07 100644 --- a/src/test/java/BoardTest.java +++ b/src/test/java/BoardTest.java @@ -414,10 +414,10 @@ class CapturePiece { void 적_기물을_잡을_수_있다() { // given Board board = new Board(Formation.from("1"), Formation.from("1")); + // 차를 이동시켜 적 졸을 잡기 - board.movePiece(new Turn(Side.HAN), Position.of(1, 1), Position.of(1, 5)); - // (1,5)에서 (1,7) CHO 졸 잡기 - board.movePiece(new Turn(Side.HAN), Position.of(1, 5), Position.of(1, 7)); + board.movePiece(new Turn(Side.HAN), Position.of(1, 4), Position.of(2, 4)); + board.movePiece(new Turn(Side.HAN), Position.of(1, 1), Position.of(1, 7)); // then assertThat(board.getBoard().get(Position.of(1, 7))).isInstanceOf(Chariot.class); diff --git a/src/test/java/domain/piece/CannonTest.java b/src/test/java/domain/piece/CannonTest.java index a33fd7fe24..9f0fabf626 100644 --- a/src/test/java/domain/piece/CannonTest.java +++ b/src/test/java/domain/piece/CannonTest.java @@ -22,7 +22,7 @@ class CannonTest { // given Position sourcePosition = Position.of(1,1); Position targetPosition = Position.of(1,5); - Piece piece = new Cannon(Side.HAN); + Piece piece = PieceType.CANNON.create(Side.HAN); // when List positions = piece.findRoute(sourcePosition, targetPosition); @@ -40,7 +40,7 @@ class CannonTest { // given Position sourcePosition = Position.of(1,1); Position targetPosition = Position.of(2,5); - Piece piece = new Cannon(Side.CHO); + Piece piece = PieceType.CANNON.create(Side.CHO); // when & then Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) @@ -52,21 +52,19 @@ class CannonTest { @Test void 포_이동_경로에_포를_제외한_기물이_하나_존재해야_한다() { // given - List pieces = List.of(new Empty(), new Horse(Side.CHO), new Empty()); + List pieces = List.of(new Empty(), PieceType.HORSE.create(Side.CHO), new Empty()); // when - Piece cannon = new Cannon(Side.CHO); - boolean actual = cannon.checkRoute(pieces); - // then - boolean expected = true; - Assertions.assertThat(actual).isEqualTo(expected); + Piece cannon = PieceType.CANNON.create(Side.CHO); + Assertions.assertThatCode(() -> cannon.checkRoute(pieces)) + .doesNotThrowAnyException(); } @DisplayName("포 이동 경로에 포가 존재하면 예외를 발생한다.") @Test void 포_이동_경로에_포가_존재하면_예외를_발생한다() { // given - List pieces = List.of(new Empty(), new Cannon(Side.CHO), new Empty()); - Piece cannon = new Cannon(Side.CHO); + List pieces = List.of(new Empty(), PieceType.CANNON.create(Side.CHO), new Empty()); + Piece cannon = PieceType.CANNON.create(Side.CHO); // when & then Assertions.assertThatThrownBy(() -> cannon.checkRoute(pieces)) @@ -78,8 +76,8 @@ class CannonTest { @Test void 포_이동_경로에_기물이_두_개_이상_존재하면_예외를_발생한다() { // given - List pieces = List.of(new Empty(), new Horse(Side.CHO), new Elephant(Side.CHO)); - Piece cannon = new Cannon(Side.CHO); + List pieces = List.of(PieceType.EMPTY.create(Side.NONE), PieceType.HORSE.create(Side.CHO), PieceType.ELEPHANT.create(Side.CHO)); + Piece cannon = PieceType.CANNON.create(Side.HAN); // when & then Assertions.assertThatThrownBy(() -> cannon.checkRoute(pieces)) diff --git a/src/test/java/domain/piece/ChariotTest.java b/src/test/java/domain/piece/ChariotTest.java index fd18df3e0b..db55109d46 100644 --- a/src/test/java/domain/piece/ChariotTest.java +++ b/src/test/java/domain/piece/ChariotTest.java @@ -7,8 +7,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static org.junit.jupiter.api.Assertions.*; - class ChariotTest { @ParameterizedTest @@ -25,7 +23,7 @@ class ChariotTest { // given Position sourcePosition = Position.of(sourceX, sourceY); Position targetPosition = Position.of(targetX, targetY); - Piece piece = new Chariot(side); + Piece piece = PieceType.CHARIOT.create(Side.CHO); // when & then if (expected) { diff --git a/src/test/java/domain/piece/GuardTest.java b/src/test/java/domain/piece/GuardTest.java index f1914a6af9..64a6cf1b54 100644 --- a/src/test/java/domain/piece/GuardTest.java +++ b/src/test/java/domain/piece/GuardTest.java @@ -26,7 +26,7 @@ class GuardTest { // given Position sourcePosition = Position.of(sourceX, sourceY); Position targetPosition = Position.of(targetX, targetY); - Piece piece = new Guard(side); + Piece piece = PieceType.GUARD.create(side); // when & then if (expected) { diff --git a/src/test/java/domain/piece/HorseTest.java b/src/test/java/domain/piece/HorseTest.java index 92ddb393b2..fe961bb304 100644 --- a/src/test/java/domain/piece/HorseTest.java +++ b/src/test/java/domain/piece/HorseTest.java @@ -25,7 +25,7 @@ class HorseTest { // given Position sourcePosition = Position.of(sourceX, sourceY); Position targetPosition = Position.of(targetX, targetY); - Piece piece = new Horse(side); + Piece piece = PieceType.HORSE.create(Side.CHO); // when & then if (pass) { diff --git a/src/test/java/domain/piece/KingTest.java b/src/test/java/domain/piece/KingTest.java index cf79e93c21..64c95f6388 100644 --- a/src/test/java/domain/piece/KingTest.java +++ b/src/test/java/domain/piece/KingTest.java @@ -25,7 +25,7 @@ class KingTest { // given Position sourcePosition = Position.of(sourceX, sourceY); Position targetPosition = Position.of(targetX, targetY); - Piece piece = new King(side); + Piece piece = PieceType.KING.create(side); // when & then if (pass) { diff --git a/src/test/java/domain/piece/PieceTest.java b/src/test/java/domain/piece/PieceTest.java index 45f49adf4f..fceac9af60 100644 --- a/src/test/java/domain/piece/PieceTest.java +++ b/src/test/java/domain/piece/PieceTest.java @@ -2,6 +2,7 @@ import domain.Position; import domain.Side; +import domain.strategy.PathMovement; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,7 +14,7 @@ class PieceTest { static class TestPiece extends Piece{ public TestPiece() { - super(Side.CHO); + super(Side.CHO, new PathMovement()); } @Override @@ -35,10 +36,8 @@ public String getName() { List pieces = List.of(new Empty(), new Empty()); // when Piece piece = new TestPiece(); - boolean actual = piece.checkRoute(pieces); - + Assertions.assertThatCode(() -> piece.checkRoute(pieces)) + .doesNotThrowAnyException(); // then - boolean expected = true; - Assertions.assertThat(actual).isEqualTo(expected); } } \ No newline at end of file diff --git a/src/test/java/domain/piece/SoldierTest.java b/src/test/java/domain/piece/SoldierTest.java index 2bd37d1b96..ec99729ceb 100644 --- a/src/test/java/domain/piece/SoldierTest.java +++ b/src/test/java/domain/piece/SoldierTest.java @@ -25,7 +25,7 @@ class SoldierTest { // given Position sourcePosition = Position.of(sourceX, sourceY); Position targetPosition = Position.of(targetX, targetY); - Piece piece = new Soldier(side); + Piece piece = PieceType.SOLDIER.create(side); // when & then if (pass) { From 46319b0fa333e7d61c615851deedb012f8a1242d Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 11:57:41 +0900 Subject: [PATCH 18/34] =?UTF-8?q?test:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20if=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?/=EB=AC=B4=ED=9A=A8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/domain/piece/CannonTest.java | 6 -- src/test/java/domain/piece/ChariotTest.java | 50 +++++++----- src/test/java/domain/piece/ElephantTest.java | 54 ++++++------ src/test/java/domain/piece/GuardTest.java | 54 ++++++------ src/test/java/domain/piece/HorseTest.java | 52 ++++++------ src/test/java/domain/piece/KingTest.java | 51 +++++++----- src/test/java/domain/piece/SoldierTest.java | 86 ++++++++++++++------ 7 files changed, 211 insertions(+), 142 deletions(-) diff --git a/src/test/java/domain/piece/CannonTest.java b/src/test/java/domain/piece/CannonTest.java index 9f0fabf626..0943b0fa13 100644 --- a/src/test/java/domain/piece/CannonTest.java +++ b/src/test/java/domain/piece/CannonTest.java @@ -8,12 +8,6 @@ import java.util.List; -import static org.junit.jupiter.api.Assertions.*; - -/** - * 1. List로 경로를 받아 기물이 이동 가능한지 판단한다. [기물 테스트에서 진행] - */ - class CannonTest { @DisplayName("포는 이동한 경로를 반환한다.") diff --git a/src/test/java/domain/piece/ChariotTest.java b/src/test/java/domain/piece/ChariotTest.java index db55109d46..5874ab9018 100644 --- a/src/test/java/domain/piece/ChariotTest.java +++ b/src/test/java/domain/piece/ChariotTest.java @@ -9,29 +9,37 @@ class ChariotTest { - @ParameterizedTest + private static final Side IRRELEVANT_SIDE = Side.CHO; + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") + @CsvSource({ + "2, 3, 2, 7", + "8, 3, 2, 3" + }) + @DisplayName("차가 이동 가능한 위치로 이동하면 경로를 반환한다") + void 차가_이동_가능한_위치로_이동하면_경로를_반환한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.CHARIOT.create(IRRELEVANT_SIDE); + + Assertions.assertThatCode(() -> piece.findRoute(source, target)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") @CsvSource({ - "2, 3, 2, 7, HAN, true", - "8, 3, 2, 3, HAN, true", - "2, 3, 4, 6, HAN, false", - "8, 3, 5, 9, HAN, false" + "2, 3, 4, 6", + "8, 3, 5, 9" }) - @DisplayName("기물이 이동할 수 있는 위치인지 검증한다.") - void 기물이_이동할_수_있는_위치인지_검증한다( - int sourceX, int sourceY, int targetX, int targetY, Side side, boolean expected - ) { - // given - Position sourcePosition = Position.of(sourceX, sourceY); - Position targetPosition = Position.of(targetX, targetY); - Piece piece = PieceType.CHARIOT.create(Side.CHO); + @DisplayName("차가 이동 불가능한 위치로 이동하면 예외가 발생한다") + void 차가_이동_불가능한_위치로_이동하면_예외가_발생한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.CHARIOT.create(IRRELEVANT_SIDE); - // when & then - if (expected) { - Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) - .doesNotThrowAnyException(); - } else { - Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) - .isInstanceOf(IllegalArgumentException.class); - } + Assertions.assertThatThrownBy(() -> piece.findRoute(source, target)) + .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/domain/piece/ElephantTest.java b/src/test/java/domain/piece/ElephantTest.java index 5ed628b7be..5f7bed4230 100644 --- a/src/test/java/domain/piece/ElephantTest.java +++ b/src/test/java/domain/piece/ElephantTest.java @@ -7,33 +7,41 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static org.junit.jupiter.api.Assertions.*; - class ElephantTest { - @ParameterizedTest + private static final Side IRRELEVANT_SIDE = Side.HAN; + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") + @CsvSource({ + "3, 1, 5, 4", + "3, 1, 1, 4" + }) + @DisplayName("상이 이동 가능한 위치로 이동하면 경로를 반환한다") + void 상이_이동_가능한_위치로_이동하면_경로를_반환한다( + int sourceX, int sourceY, int targetX, int targetY + ) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = new Elephant(IRRELEVANT_SIDE); + + Assertions.assertThatCode(() -> piece.findRoute(source, target)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") @CsvSource({ - "3, 1, 5, 4, HAN, true", - "3, 1, 1, 4, HAN, true", - "3, 1, 3, 2, HAN, false", - "3, 1, 4, 1, HAN, false" + "3, 1, 3, 2", + "3, 1, 4, 1" }) - @DisplayName("기물이 이동할 수 있는 위치인지 검증한다.") - void 기물이_이동할_수_있는_위치인지_검증한다( - int sourceX, int sourceY, int targetX, int targetY, Side side, boolean pass + @DisplayName("상이 이동 불가능한 위치로 이동하면 예외가 발생한다") + void 상이_이동_불가능한_위치로_이동하면_예외가_발생한다( + int sourceX, int sourceY, int targetX, int targetY ) { - // given - Position sourcePosition = Position.of(sourceX, sourceY); - Position targetPosition = Position.of(targetX, targetY); - Piece piece = new Elephant(side); + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = new Elephant(IRRELEVANT_SIDE); - // when & then - if (pass) { - Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) - .doesNotThrowAnyException(); - } else { - Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) - .isInstanceOf(IllegalArgumentException.class); - } + Assertions.assertThatThrownBy(() -> piece.findRoute(source, target)) + .isInstanceOf(IllegalArgumentException.class); } -} \ No newline at end of file +} diff --git a/src/test/java/domain/piece/GuardTest.java b/src/test/java/domain/piece/GuardTest.java index 64a6cf1b54..e0e723593e 100644 --- a/src/test/java/domain/piece/GuardTest.java +++ b/src/test/java/domain/piece/GuardTest.java @@ -7,34 +7,42 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static org.junit.jupiter.api.Assertions.*; - class GuardTest { - @ParameterizedTest + private static final Side IRRELEVANT_SIDE = Side.HAN; + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") + @CsvSource({ + "4, 1, 4, 2", + "6, 1, 5, 1", + "6, 2, 6, 1" + }) + @DisplayName("사가 이동 가능한 위치로 이동하면 경로를 반환한다.") + void 상이_이동_가능한_위치로_이동하면_경로를_반환한다( + int sourceX, int sourceY, int targetX, int targetY + ) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.GUARD.create(IRRELEVANT_SIDE); + + Assertions.assertThatCode(() -> piece.findRoute(source, target)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") @CsvSource({ - "4, 1, 4, 2, HAN, true", - "6, 1, 5, 1, HAN, true", - "6, 2, 6, 1, HAN, true", - "4, 1, 4, 3, HAN, false", - "6, 1, 4, 1, HAN, false" + "4, 1, 4, 3", + "6, 1, 4, 1" }) - @DisplayName("기물이 이동할 수 있는 위치인지 검증한다.") - void 기물이_이동할_수_있는_위치인지_검증한다( - int sourceX, int sourceY, int targetX, int targetY, Side side, boolean expected + @DisplayName("사가 이동 불가능한 위치로 이동하면 예외가 발생한다.") + void 상이_이동_불가능한_위치로_이동하면_예외가_발생한다( + int sourceX, int sourceY, int targetX, int targetY ) { - // given - Position sourcePosition = Position.of(sourceX, sourceY); - Position targetPosition = Position.of(targetX, targetY); - Piece piece = PieceType.GUARD.create(side); + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.GUARD.create(IRRELEVANT_SIDE); - // when & then - if (expected) { - Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) - .doesNotThrowAnyException(); - } else { - Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) - .isInstanceOf(IllegalArgumentException.class); - } + Assertions.assertThatThrownBy(() -> piece.findRoute(source, target)) + .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/domain/piece/HorseTest.java b/src/test/java/domain/piece/HorseTest.java index fe961bb304..c151bdfe38 100644 --- a/src/test/java/domain/piece/HorseTest.java +++ b/src/test/java/domain/piece/HorseTest.java @@ -7,33 +7,39 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static org.junit.jupiter.api.Assertions.*; - class HorseTest { - @ParameterizedTest + private static final Side IRRELEVANT_SIDE = Side.HAN; + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") + @CsvSource({ + "3, 2, 1, 1", + "3, 2, 5, 1" + }) + @DisplayName("마가 이동 가능한 위치로 이동하면 경로를 반환한다") + void 마가_이동_가능한_위치로_이동하면_경로를_반환한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.HORSE.create(IRRELEVANT_SIDE); + + Assertions.assertThatCode(() -> piece.findRoute(source, target)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") @CsvSource({ - "3, 2, 1, 1, HAN, true", - "3, 2, 5, 1, HAN, true", - "3, 2, 1, 5, HAN, false", - "3, 2, 3, 5, HAN, false", + "3, 2, 1, 5", + "3, 2, 3, 5" }) - @DisplayName("마가 이동할 수 있는 위치인지 검증한다.") - void 기물이_이동할_수_있는_위치인지_검증한다( - int sourceX, int sourceY, int targetX, int targetY, Side side, boolean pass - ) { - // given - Position sourcePosition = Position.of(sourceX, sourceY); - Position targetPosition = Position.of(targetX, targetY); - Piece piece = PieceType.HORSE.create(Side.CHO); + @DisplayName("마가 이동 불가능한 위치로 이동하면 예외가 발생한다") + void 마가_이동_불가능한_위치로_이동하면_예외가_발생한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.HORSE.create(IRRELEVANT_SIDE); - // when & then - if (pass) { - Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) - .doesNotThrowAnyException(); - } else { - Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) - .isInstanceOf(IllegalArgumentException.class); - } + Assertions.assertThatThrownBy(() -> piece.findRoute(source, target)) + .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/domain/piece/KingTest.java b/src/test/java/domain/piece/KingTest.java index 64c95f6388..e2c8875f58 100644 --- a/src/test/java/domain/piece/KingTest.java +++ b/src/test/java/domain/piece/KingTest.java @@ -9,31 +9,38 @@ class KingTest { - @ParameterizedTest + private static final Side IRRELEVANT_SIDE = Side.HAN; + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") @CsvSource({ - "5, 2, 5, 3, HAN, true", - "5, 2, 5, 1, HAN, true", - "5, 2, 4, 2, HAN, true", - "5, 2, 6, 2, HAN, true", + "5, 2, 5, 3", + "5, 2, 5, 1", + "5, 2, 4, 2", + "5, 2, 6, 2" + }) + @DisplayName("궁이 이동 가능한 위치로 이동하면 경로를 반환한다") + void 궁이_이동_가능한_위치로_이동하면_경로를_반환한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.KING.create(IRRELEVANT_SIDE); - "5, 2, 5, 4, HAN, false" + Assertions.assertThatCode(() -> piece.findRoute(source, target)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") + @CsvSource({ + "5, 2, 5, 4" }) - @DisplayName("궁이 이동할 수 있는 위치인지 검증한다.") - void 기물이_이동할_수_있는_위치인지_검증한다( - int sourceX, int sourceY, int targetX, int targetY, Side side, boolean pass - ) { - // given - Position sourcePosition = Position.of(sourceX, sourceY); - Position targetPosition = Position.of(targetX, targetY); - Piece piece = PieceType.KING.create(side); + @DisplayName("궁이 이동 불가능한 위치로 이동하면 예외가 발생한다") + void 궁이_이동_불가능한_위치로_이동하면_예외가_발생한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.KING.create(IRRELEVANT_SIDE); - // when & then - if (pass) { - Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) - .doesNotThrowAnyException(); - } else { - Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) - .isInstanceOf(IllegalArgumentException.class); - } + Assertions.assertThatThrownBy(() -> piece.findRoute(source, target)) + .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/domain/piece/SoldierTest.java b/src/test/java/domain/piece/SoldierTest.java index ec99729ceb..07f1ec6a1a 100644 --- a/src/test/java/domain/piece/SoldierTest.java +++ b/src/test/java/domain/piece/SoldierTest.java @@ -7,33 +7,71 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static org.junit.jupiter.api.Assertions.*; - class SoldierTest { - @ParameterizedTest + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") @CsvSource({ - "1, 7, 1, 6, CHO, true", - "1, 7, 1, 5, CHO, false", - "1, 4, 1, 5, HAN, true", - "1, 4, 1, 3, HAN, false" + "5, 7, 5, 6", + "5, 7, 4, 7", + "5, 7, 6, 7" }) - @DisplayName("초/병이 이동할 수 있는 위치인지 검증한다.") - void 기물이_이동할_수_있는_위치인지_검증한다( - int sourceX, int sourceY, int targetX, int targetY, Side side, boolean pass - ) { - // given - Position sourcePosition = Position.of(sourceX, sourceY); - Position targetPosition = Position.of(targetX, targetY); - Piece piece = PieceType.SOLDIER.create(side); - - // when & then - if (pass) { - Assertions.assertThatCode(() -> piece.findRoute(sourcePosition, targetPosition)) - .doesNotThrowAnyException(); - } else { - Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) - .isInstanceOf(IllegalArgumentException.class); - } + @DisplayName("초 병이 이동 가능한 위치로 이동하면 경로를 반환한다") + void 초_병이_이동_가능한_위치로_이동하면_경로를_반환한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.SOLDIER.create(Side.CHO); + + Assertions.assertThatCode(() -> piece.findRoute(source, target)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") + @CsvSource({ + "5, 7, 5, 8", + "5, 7, 5, 5" + }) + @DisplayName("초 병이 이동 불가능한 위치로 이동하면 예외가 발생한다") + void 초_병이_이동_불가능한_위치로_이동하면_예외가_발생한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.SOLDIER.create(Side.CHO); + + Assertions.assertThatThrownBy(() -> piece.findRoute(source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") + @CsvSource({ + "5, 4, 5, 5", + "5, 4, 4, 4", + "5, 4, 6, 4" + }) + @DisplayName("한 졸이 이동 가능한 위치로 이동하면 경로를 반환한다") + void 한_졸이_이동_가능한_위치로_이동하면_경로를_반환한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.SOLDIER.create(Side.HAN); + + Assertions.assertThatCode(() -> piece.findRoute(source, target)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") + @CsvSource({ + "5, 4, 5, 3", + "5, 4, 5, 2" + }) + @DisplayName("한 졸이 이동 불가능한 위치로 이동하면 예외가 발생한다") + void 한_졸이_이동_불가능한_위치로_이동하면_예외가_발생한다( + int sourceX, int sourceY, int targetX, int targetY) { + Position source = Position.of(sourceX, sourceY); + Position target = Position.of(targetX, targetY); + Piece piece = PieceType.SOLDIER.create(Side.HAN); + + Assertions.assertThatThrownBy(() -> piece.findRoute(source, target)) + .isInstanceOf(IllegalArgumentException.class); } } From ccf486f326656f877b76c38416d881efb6618feb Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 12:03:25 +0900 Subject: [PATCH 19/34] =?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=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=BD=EB=A1=9C=20=EA=B3=84=EC=82=B0=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/Position.java | 6 ++++ src/main/java/domain/piece/King.java | 1 - .../java/domain/strategy/LinearMovement.java | 33 +++++-------------- .../domain/strategy/MovementStrategy.java | 11 +++++-- .../java/domain/strategy/PathMovement.java | 29 +++++----------- 5 files changed, 33 insertions(+), 47 deletions(-) diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index 3cb44f46b4..1495ac1e6a 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -36,6 +36,12 @@ private static void validateOutOfRange(int x, int y) { } } + public boolean canMove(int dx, int dy) { + int nx = x + dx; + int ny = y + dy; + return 1 <= nx && nx <= 9 && 1 <= ny && ny <= 10; + } + public Position createPosition(int dx, int dy) { return Position.of(x + dx, y + dy); } diff --git a/src/main/java/domain/piece/King.java b/src/main/java/domain/piece/King.java index e68ac3d55a..d1dcfaa32d 100644 --- a/src/main/java/domain/piece/King.java +++ b/src/main/java/domain/piece/King.java @@ -4,7 +4,6 @@ import domain.Position; import domain.Side; import domain.strategy.MovementStrategy; -import domain.strategy.PathMovement; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/domain/strategy/LinearMovement.java b/src/main/java/domain/strategy/LinearMovement.java index 44a1635e40..ca4e13388a 100644 --- a/src/main/java/domain/strategy/LinearMovement.java +++ b/src/main/java/domain/strategy/LinearMovement.java @@ -8,35 +8,20 @@ public class LinearMovement extends MovementStrategy { - @Override - public List findRoute(List> paths, Position sourcePosition, Position targetPosition) { - for(List path: paths) { - List positions = buildRoute(path, sourcePosition, targetPosition); - if(positions.contains(targetPosition)) { - return List.copyOf(positions); - } - } - throw new IllegalArgumentException(INVALID_TARGET_POSITION); - } - @Override protected List buildRoute(List path, Position sourcePosition, Position targetPosition) { List route = new ArrayList<>(); Direction direction = path.getFirst(); - Position position = sourcePosition; - while (true) { - try { - Position nextPosition = position.createPosition(direction.getX(), direction.getY()); - route.add(nextPosition); - if (nextPosition.equals(targetPosition)) { - return List.copyOf(route); - } - position = nextPosition; - } catch (IllegalArgumentException e) { - route.clear(); - break; + Position current = sourcePosition; + + while (current.canMove(direction.getX(), direction.getY())) { + Position next = current.createPosition(direction.getX(), direction.getY()); + route.add(next); + if (next.equals(targetPosition)) { + return List.copyOf(route); } + current = next; } - return List.copyOf(route); + return List.of(); } } diff --git a/src/main/java/domain/strategy/MovementStrategy.java b/src/main/java/domain/strategy/MovementStrategy.java index 2ddb11777a..62617e7fa4 100644 --- a/src/main/java/domain/strategy/MovementStrategy.java +++ b/src/main/java/domain/strategy/MovementStrategy.java @@ -10,8 +10,15 @@ public abstract class MovementStrategy { protected final String ERROR_PREFIX = "[ERROR] "; protected final String INVALID_TARGET_POSITION = ERROR_PREFIX + "이동할 수 없는 목적지입니다."; - - public abstract List findRoute(List> paths, Position sourcePosition, Position targetPosition); + public List findRoute(List> paths, Position sourcePosition, Position targetPosition) { + for(List path: paths) { + List positions = buildRoute(path, sourcePosition, targetPosition); + if(positions.contains(targetPosition)) { + return positions; + } + } + throw new IllegalArgumentException(INVALID_TARGET_POSITION); + } protected abstract List buildRoute(List route, Position sourcePosition, Position targetPosition); } diff --git a/src/main/java/domain/strategy/PathMovement.java b/src/main/java/domain/strategy/PathMovement.java index 835fab30f8..3e73f9e9a5 100644 --- a/src/main/java/domain/strategy/PathMovement.java +++ b/src/main/java/domain/strategy/PathMovement.java @@ -9,29 +9,18 @@ public class PathMovement extends MovementStrategy{ @Override - public List findRoute(List> paths, Position sourcePosition, Position targetPosition) { - for (List path : paths) { - List route = buildRoute(path, sourcePosition, targetPosition); - if(route.contains(targetPosition)){ - return List.copyOf(route); - } - } - throw new IllegalArgumentException(INVALID_TARGET_POSITION); - } - - @Override - protected List buildRoute(List path, Position sourcePosition, Position targetPosition) { + protected List buildRoute(List path, Position source, Position target) { List route = new ArrayList<>(); - Position position = sourcePosition; - for(Direction direction : path) { - try{ - position = position.createPosition(direction.getX(), direction.getY()); - route.add(position); - } catch (IllegalArgumentException e) { - route.clear(); - break; + Position current = source; + + for (Direction direction : path) { + if (!current.canMove(direction.getX(), direction.getY())) { + return List.of(); } + current = current.createPosition(direction.getX(), direction.getY()); + route.add(current); } + return route; } } From 3fa6172e51ddd01dc2520e474ba068f8480be2fa Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 12:50:15 +0900 Subject: [PATCH 20/34] =?UTF-8?q?docs:=20README.md=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a438ef6cd7..b00418a764 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,70 @@ # 기능 -## 1-1 +## 1-1 요구사항 +- [x] 각 플레이어 진형 입력 기능 +- [x] 장기판 초기화 기능 +- [x] 장기판 상태 출력 기능 -### ✅ 각 플레이어 진형 입력 기능 +## 1-2 요구사항 +- [x] 이동할 기물 위치 좌표 입력 기능 +- [x] 이동할 목적지 좌표 입력 기능 +- [x] 턴제 시스템 기능 +- [x] 장기 진행 상태 출력 기능 +- [x] 기물 이동 기능 -### ✅ 장기판 초기화 기능 +## ⚠️ 기능 예외 사항 -### ✅ 장기판 상태 출력 기능 +### 입력 검증 +- 포진 선택: 1~4 범위 외 입력 시 예외 +- 좌표 범위: x(1~9), y(1~10) 범위 외 입력 시 예외 +### 기물 선택 검증 +- 선택한 위치에 기물이 존재하지 않을 경우 예외 +- 선택한 기물이 아군 기물이 아닐 경우 예외 -## 1-2 +### 이동 규칙 검증 +- 목적지가 기물의 이동 규칙에 맞지 않을 경우 예외 +- 목적지에 아군 기물이 존재할 경우 예외 +- 이동 경로에 기물이 존재할 경우 예외 (포 제외) -### ✅ 이동할 기물 위치 좌표 입력 기능 +### 포 규칙 검증 +- 이동 경로에 포가 존재할 경우 예외 +- 이동 경로에 기물이 정확히 1개가 아닐 경우 예외 +- 포로 포를 잡으려 할 경우 예외 -### ✅ 이동할 목적지 좌표 입력 기능 +--- -### ✅ 턴제 시스템 기능 +## 📁 프로젝트 구조 -### ✅ 장기 진행 상태 출력 기능 - -### ✅ 기물 이동 기능 - -## ⚠️ 기능 예외 사항 \ No newline at end of file +``` +src/main/java +├── Main.java +├── controller +│ └── JanggiController.java +├── domain +│ ├── Board.java +│ ├── Direction.java +│ ├── Formation.java +│ ├── Position.java +│ ├── Side.java +│ ├── Turn.java +│ ├── piece +│ │ ├── Piece.java +│ │ ├── PieceType.java +│ │ ├── King.java +│ │ ├── Guard.java +│ │ ├── Horse.java +│ │ ├── Elephant.java +│ │ ├── Chariot.java +│ │ ├── Cannon.java +│ │ ├── Soldier.java +│ │ └── Empty.java +│ └── strategy +│ ├── MovementStrategy.java +│ ├── PathMovement.java +│ └── LinearMovement.java +└── view + ├── InputView.java + └── OutputView.java +``` \ No newline at end of file From d9dd2faecf555fd0634d26cbaa3f68df5b3a9afe Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 15:06:34 +0900 Subject: [PATCH 21/34] =?UTF-8?q?refactor:=20Position=20=EC=BA=90=EC=8B=B1?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/Position.java | 31 ++++++++++-------------------- src/test/java/PositionTest.java | 2 +- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index 1495ac1e6a..987b56d298 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -1,9 +1,11 @@ package domain; -import java.util.Objects; +import java.util.HashMap; +import java.util.Map; public class Position { + private static final Map CACHE = new HashMap<>(); private final int x; private final int y; @@ -14,26 +16,7 @@ private Position(int x, int y) { public static Position of(int x, int y) { validateOutOfRange(x, y); - return new Position(x, y); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Position position = (Position) o; - return x == position.x && y == position.y; - } - - @Override - public int hashCode() { - return Objects.hashCode(x) * 31 + Objects.hashCode(y); - } - - private static void validateOutOfRange(int x, int y) { - if(!((1 <= x && x <= 9) && (1 <= y && y <= 10))) { - throw new IllegalArgumentException("y 좌표는 1~10, x 좌표는 1~9 사이여야 합니다."); - } + return CACHE.computeIfAbsent(x * 100 + y, k -> new Position(x, y)); } public boolean canMove(int dx, int dy) { @@ -45,4 +28,10 @@ public boolean canMove(int dx, int dy) { public Position createPosition(int dx, int dy) { return Position.of(x + dx, y + dy); } + + private static void validateOutOfRange(int x, int y) { + if(!((1 <= x && x <= 9) && (1 <= y && y <= 10))) { + throw new IllegalArgumentException("x 좌표는 1~9, y 좌표는 1~10, 사이여야 합니다."); + } + } } \ No newline at end of file diff --git a/src/test/java/PositionTest.java b/src/test/java/PositionTest.java index 8373df2d72..4c8b5f66a1 100644 --- a/src/test/java/PositionTest.java +++ b/src/test/java/PositionTest.java @@ -19,7 +19,7 @@ class FromTest { // when & then Assertions.assertThatThrownBy(() -> Position.of(x, y)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("y 좌표는 1~10, x 좌표는 1~9 사이여야 합니다."); + .hasMessageContaining("x 좌표는 1~9, y 좌표는 1~10, 사이여야 합니다."); } } } From 6b196a3152f0578f337925871a40fa490cc09981 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 15:07:26 +0900 Subject: [PATCH 22/34] =?UTF-8?q?test:=20Position=20canMove=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/PositionTest.java | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/test/java/PositionTest.java b/src/test/java/PositionTest.java index 4c8b5f66a1..5777519c08 100644 --- a/src/test/java/PositionTest.java +++ b/src/test/java/PositionTest.java @@ -22,4 +22,39 @@ class FromTest { .hasMessageContaining("x 좌표는 1~9, y 좌표는 1~10, 사이여야 합니다."); } } + + @Nested + class CamMoveTest { + + @ParameterizedTest + @CsvSource({ + "1, 1, 0, 1", + "5, 5, 1, 0", + "9, 10, -1, -1" + }) + void 이동_가능한_좌표이면_true를_반환한다(int x, int y, int dx, int dy) { + // given + Position position = Position.of(x, y); + // when + boolean result = position.canMove(dx, dy); + // then + Assertions.assertThat(result).isTrue(); + } + + @ParameterizedTest + @CsvSource({ + "1, 1, -1, 0", + "1, 1, 0, -1", + "9, 10, 1, 0", + "9, 10, 0, 1" + }) + void 이동_불가능한_좌표이면_false를_반환한다(int x, int y, int dx, int dy) { + // given + Position position = Position.of(x, y); + // when + boolean result = position.canMove(dx, dy); + // then + Assertions.assertThat(result).isFalse(); + } + } } From cfe69c0fd89ae7b45f63c2a274b99bd57b1e08d3 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Sun, 29 Mar 2026 15:24:02 +0900 Subject: [PATCH 23/34] =?UTF-8?q?refactor:=20ERROR=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=83=81=EC=88=98=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/JanggiController.java | 10 +++++----- src/main/java/domain/Board.java | 7 +++++-- src/main/java/domain/Formation.java | 5 +++-- src/main/java/domain/Position.java | 4 +++- src/main/java/domain/piece/Cannon.java | 4 ++-- src/main/java/domain/piece/Piece.java | 5 ++--- src/main/java/domain/strategy/MovementStrategy.java | 3 +-- src/main/java/view/OutputView.java | 5 +++++ 8 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 3ab3e01073..2f81d6c3ef 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -29,7 +29,7 @@ private void processMove(Board board) { board.movePiece(turn, sourcePosition, targetPosition); turn.next(); } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); + outputView.printErrorMessage(e.getMessage()); } outputView.printBoardStatus(board.getBoard()); } @@ -42,7 +42,7 @@ private Position readTargetPosition() { int y = inputView.readTargetYPosition(); return Position.of(x,y); } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); + outputView.printErrorMessage(e.getMessage()); } } } @@ -54,7 +54,7 @@ private Position readSourcePosition() { int y = inputView.readSourceYPosition(); return Position.of(x,y); } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); + outputView.printErrorMessage(e.getMessage()); } } } @@ -73,7 +73,7 @@ private Formation readChoFormation() { String choFormation = inputView.readChoFormation(); return Formation.from(choFormation); } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); + outputView.printErrorMessage(e.getMessage()); } } } @@ -84,7 +84,7 @@ private Formation readHanFormation() { String hanFormation = inputView.readHanFormation(); return Formation.from(hanFormation); } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); + outputView.printErrorMessage(e.getMessage()); } } } diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 9d4c6d4a88..af7329079c 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -9,6 +9,9 @@ public class Board { + private static final String PIECE_NOT_FOUND = "해당 위치에 기물이 존재하지 않습니다."; + private static final String NOT_OWN_PIECE = "선택한 기물은 아군 기물이 아닙니다."; + private final int BACK_Y = 0; private final int KING_Y = 1; private final int CANNON_Y = 2; @@ -91,7 +94,7 @@ private void placeFormationPiece(Formation formation, List formationX, private void validatePieceExists(Piece piece) { if(piece instanceof Empty) { - throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); + throw new IllegalArgumentException(PIECE_NOT_FOUND); } } @@ -107,7 +110,7 @@ private void findPiecesOnRoute(Piece piece, List route, Position targe private void validateOwnPiece(Piece piece, Turn turn) { if(!piece.getSide().equals(turn.current())) { - throw new IllegalArgumentException("선택한 기물은 아군 기물이 아닙니다."); + throw new IllegalArgumentException(NOT_OWN_PIECE); } } diff --git a/src/main/java/domain/Formation.java b/src/main/java/domain/Formation.java index 9982ac30f2..a46bdbf916 100644 --- a/src/main/java/domain/Formation.java +++ b/src/main/java/domain/Formation.java @@ -5,12 +5,13 @@ import java.util.List; public enum Formation { - 상마마상("1", List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.HORSE, PieceType.ELEPHANT)), 마상상마("2", List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.ELEPHANT, PieceType.HORSE)), 상마상마("3", List.of(PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE)), 마상마상("4", List.of(PieceType.HORSE, PieceType.ELEPHANT, PieceType.HORSE, PieceType.ELEPHANT)); + private static final String INVALID_OPTION_RANGE = "번호는 1~4 사이의 숫자여야 합니다."; + private final String option; private final List pieceTypes; @@ -25,7 +26,7 @@ public static Formation from(String input) { return formation; } } - throw new IllegalArgumentException("[ERROR] 번호는 1~4 사이의 숫자여야 합니다."); + throw new IllegalArgumentException(INVALID_OPTION_RANGE); } public List getPieceTypes() { diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index 987b56d298..fed1d2eda6 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -5,6 +5,8 @@ public class Position { + private static final String INVALID_POSITION_RANGE = "x 좌표는 1~9, y 좌표는 1~10, 사이여야 합니다."; + private static final Map CACHE = new HashMap<>(); private final int x; private final int y; @@ -31,7 +33,7 @@ public Position createPosition(int dx, int dy) { private static void validateOutOfRange(int x, int y) { if(!((1 <= x && x <= 9) && (1 <= y && y <= 10))) { - throw new IllegalArgumentException("x 좌표는 1~9, y 좌표는 1~10, 사이여야 합니다."); + throw new IllegalArgumentException(INVALID_POSITION_RANGE); } } } \ No newline at end of file diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index d1cc5186b6..92fecfa04c 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -9,8 +9,8 @@ public class Cannon extends Piece { - private final String CANNOT_JUMP_WITH_CANNON = ERROR_PREFIX + "포를 넘어갈 수 없습니다."; - private final String CANNOT_CAPTURE_CANNON_WITH_CANNON = ERROR_PREFIX + "포는 포끼리 잡을 수 없습니다."; + private static final String CANNOT_JUMP_WITH_CANNON = "포를 넘어갈 수 없습니다."; + private static final String CANNOT_CAPTURE_CANNON_WITH_CANNON = "포는 포끼리 잡을 수 없습니다."; private final List> paths = List.of( List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT)); diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index d82ead43ce..119dbdcbca 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -8,9 +8,8 @@ public abstract class Piece { - protected final String ERROR_PREFIX = "[ERROR] "; - protected final String CANNOT_CAPTURE_OWN_PIECE = ERROR_PREFIX + "아군 기물은 잡을 수 없습니다."; - protected final String INVALID_TARGET_POSITION = ERROR_PREFIX + "이동할 수 없는 목적지입니다."; + protected static final String CANNOT_CAPTURE_OWN_PIECE = "아군 기물은 잡을 수 없습니다."; + protected static final String INVALID_TARGET_POSITION = "이동할 수 없는 목적지입니다."; protected final Side side; protected final MovementStrategy movementStrategy; diff --git a/src/main/java/domain/strategy/MovementStrategy.java b/src/main/java/domain/strategy/MovementStrategy.java index 62617e7fa4..0b4ca4645d 100644 --- a/src/main/java/domain/strategy/MovementStrategy.java +++ b/src/main/java/domain/strategy/MovementStrategy.java @@ -7,8 +7,7 @@ public abstract class MovementStrategy { - protected final String ERROR_PREFIX = "[ERROR] "; - protected final String INVALID_TARGET_POSITION = ERROR_PREFIX + "이동할 수 없는 목적지입니다."; + protected final String INVALID_TARGET_POSITION = "이동할 수 없는 목적지입니다."; public List findRoute(List> paths, Position sourcePosition, Position targetPosition) { for(List path: paths) { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 3444cba976..0c56d14a71 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -12,6 +12,7 @@ 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 ERROR_PREFIX = "[ERROR] "; public void printBoardStatus(Map board) { System.out.println(); @@ -58,4 +59,8 @@ private String getColor(Side side) { } return RESET; } + + public void printErrorMessage(String errorMessage) { + System.out.println(ERROR_PREFIX + errorMessage); + } } From 88cfd05e406b31dd4328e922efcbcbf5d32600f9 Mon Sep 17 00:00:00 2001 From: yeo-li Date: Sun, 29 Mar 2026 16:46:55 +0900 Subject: [PATCH 24/34] =?UTF-8?q?refactor:=20findRoute=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20movementStrategy.findRoute()=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/piece/Cannon.java | 20 +++++++------- src/main/java/domain/piece/Chariot.java | 26 +++---------------- src/main/java/domain/piece/Elephant.java | 33 +++++++----------------- src/main/java/domain/piece/Guard.java | 23 +++-------------- src/main/java/domain/piece/King.java | 22 +++------------- src/main/java/domain/piece/Soldier.java | 24 +++-------------- 6 files changed, 32 insertions(+), 116 deletions(-) diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index 92fecfa04c..e3bf2cadeb 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -4,7 +4,6 @@ import domain.Position; import domain.Side; import domain.strategy.MovementStrategy; - import java.util.List; public class Cannon extends Piece { @@ -13,7 +12,7 @@ public class Cannon extends Piece { private static final String CANNOT_CAPTURE_CANNON_WITH_CANNON = "포는 포끼리 잡을 수 없습니다."; private final List> paths = List.of( - List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT)); + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT)); public Cannon(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); @@ -27,28 +26,27 @@ public List findRoute(Position sourcePosition, Position targetPosition @Override public void checkRoute(List pieces) { - // 목적지까지는 포가아닌 기물이 한 개만 있어야 함. - int cnt = 0; - for(Piece piece : pieces) { - if(piece instanceof Cannon) { + int count = 0; + for (Piece piece : pieces) { + if (piece instanceof Cannon) { throw new IllegalArgumentException(CANNOT_JUMP_WITH_CANNON); } - if(!(piece instanceof Empty)) { - cnt++; + if (!(piece instanceof Empty)) { + count++; } } - if(cnt != 1) { + if (count != 1) { throw new IllegalArgumentException(INVALID_TARGET_POSITION); } } @Override public void checkTarget(Piece piece) { - if(piece.getSide().equals(side)) { + if (piece.getSide().equals(side)) { throw new IllegalArgumentException(CANNOT_CAPTURE_OWN_PIECE); } - if(piece instanceof Cannon) { + if (piece instanceof Cannon) { throw new IllegalArgumentException(CANNOT_CAPTURE_CANNON_WITH_CANNON); } } diff --git a/src/main/java/domain/piece/Chariot.java b/src/main/java/domain/piece/Chariot.java index 50c212d2c6..9deb4bb936 100644 --- a/src/main/java/domain/piece/Chariot.java +++ b/src/main/java/domain/piece/Chariot.java @@ -4,14 +4,12 @@ import domain.Position; import domain.Side; import domain.strategy.MovementStrategy; - -import java.util.ArrayList; import java.util.List; public class Chariot extends Piece { - private final List> movementStrategy = List.of( - List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + private final List> paths = List.of( + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); public Chariot(Side side, MovementStrategy movementStrategy) { @@ -20,25 +18,7 @@ public Chariot(Side side, MovementStrategy movementStrategy) { @Override public List findRoute(Position sourcePosition, Position targetPosition) { - for (List path : movementStrategy) { - List positions = new ArrayList<>(); - Direction direction = path.getFirst(); - Position position = sourcePosition; - - while (true) { - try { - Position nextPosition = position.createPosition(direction.getX(), direction.getY()); - positions.add(nextPosition); - if (nextPosition.equals(targetPosition)) { - return positions; - } - position = nextPosition; - } catch (IllegalArgumentException e) { - break; - } - } - } - throw new IllegalArgumentException(INVALID_TARGET_POSITION); + return movementStrategy.findRoute(paths, sourcePosition, targetPosition); } @Override diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java index 717fecd0d4..a51db3faae 100644 --- a/src/main/java/domain/piece/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -4,17 +4,19 @@ import domain.Position; import domain.Side; import domain.strategy.PathMovement; - -import java.util.ArrayList; import java.util.List; public class Elephant extends Piece { - private final List> movementStrategy = List.of( - List.of(Direction.UP, Direction.UP_LEFT, Direction.UP_LEFT), List.of(Direction.UP, Direction.UP_RIGHT, Direction.UP_RIGHT), - List.of(Direction.RIGHT, Direction.UP_RIGHT, Direction.UP_RIGHT), List.of(Direction.RIGHT, Direction.DOWN_RIGHT, Direction.DOWN_RIGHT), - List.of(Direction.DOWN, Direction.DOWN_LEFT, Direction.DOWN_LEFT), List.of(Direction.DOWN, Direction.DOWN_RIGHT, Direction.DOWN_RIGHT), - List.of(Direction.LEFT, Direction.UP_LEFT, Direction.UP_LEFT), List.of(Direction.LEFT, Direction.DOWN_LEFT, Direction.DOWN_LEFT) + private final List> paths = List.of( + List.of(Direction.UP, Direction.UP_LEFT, Direction.UP_LEFT), + List.of(Direction.UP, Direction.UP_RIGHT, Direction.UP_RIGHT), + List.of(Direction.RIGHT, Direction.UP_RIGHT, Direction.UP_RIGHT), + List.of(Direction.RIGHT, Direction.DOWN_RIGHT, Direction.DOWN_RIGHT), + List.of(Direction.DOWN, Direction.DOWN_LEFT, Direction.DOWN_LEFT), + List.of(Direction.DOWN, Direction.DOWN_RIGHT, Direction.DOWN_RIGHT), + List.of(Direction.LEFT, Direction.UP_LEFT, Direction.UP_LEFT), + List.of(Direction.LEFT, Direction.DOWN_LEFT, Direction.DOWN_LEFT) ); public Elephant(Side side) { @@ -23,22 +25,7 @@ public Elephant(Side side) { @Override public List findRoute(Position sourcePosition, Position targetPosition) { - for (List path : movementStrategy) { - List positions = new ArrayList<>(); - Position current = sourcePosition; - try { - for (Direction direction : path) { - current = current.createPosition(direction.getX(), direction.getY()); - positions.add(current); - } - if (current.equals(targetPosition)) { - return positions; - } - } catch (IllegalArgumentException e) { - // 경계 벗어남, 다음 경로 시도 - } - } - throw new IllegalArgumentException(INVALID_TARGET_POSITION); + return movementStrategy.findRoute(paths, sourcePosition, targetPosition); } @Override diff --git a/src/main/java/domain/piece/Guard.java b/src/main/java/domain/piece/Guard.java index aaebf7bead..b9dfbcd31e 100644 --- a/src/main/java/domain/piece/Guard.java +++ b/src/main/java/domain/piece/Guard.java @@ -4,15 +4,12 @@ import domain.Position; import domain.Side; import domain.strategy.MovementStrategy; -import domain.strategy.PathMovement; - -import java.util.ArrayList; import java.util.List; public class Guard extends Piece { - private final List> movementStrategy = List.of( - List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + private final List> paths = List.of( + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); public Guard(Side side, MovementStrategy movementStrategy) { @@ -21,21 +18,7 @@ public Guard(Side side, MovementStrategy movementStrategy) { @Override public List findRoute(Position sourcePosition, Position targetPosition) { - List positions = new ArrayList<>(); - for(List path : movementStrategy) { - for(Direction direction : path) { - try{ - positions.add(sourcePosition.createPosition(direction.getX(), direction.getY())); - } catch (IllegalArgumentException e) { - break; - } - } - if(positions.contains(targetPosition)) { - return List.copyOf(positions); - } - positions.clear(); - } - throw new IllegalArgumentException(INVALID_TARGET_POSITION); + return movementStrategy.findRoute(paths, sourcePosition, targetPosition); } @Override diff --git a/src/main/java/domain/piece/King.java b/src/main/java/domain/piece/King.java index d1dcfaa32d..ccc7e25e69 100644 --- a/src/main/java/domain/piece/King.java +++ b/src/main/java/domain/piece/King.java @@ -4,14 +4,12 @@ import domain.Position; import domain.Side; import domain.strategy.MovementStrategy; - -import java.util.ArrayList; import java.util.List; public class King extends Piece { - private final List> movementStrategy = List.of( - List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + private final List> paths = List.of( + List.of(Direction.UP), List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); public King(Side side, MovementStrategy movementStrategy) { @@ -20,21 +18,7 @@ public King(Side side, MovementStrategy movementStrategy) { @Override public List findRoute(Position sourcePosition, Position targetPosition) { - List positions = new ArrayList<>(); - for(List path : movementStrategy) { - for(Direction direction : path) { - try{ - positions.add(sourcePosition.createPosition(direction.getX(), direction.getY())); - } catch (IllegalArgumentException e) { - break; - } - } - if(positions.contains(targetPosition)) { - return List.copyOf(positions); - } - positions.clear(); - } - throw new IllegalArgumentException(INVALID_TARGET_POSITION); + return movementStrategy.findRoute(paths, sourcePosition, targetPosition); } @Override diff --git a/src/main/java/domain/piece/Soldier.java b/src/main/java/domain/piece/Soldier.java index 811850a26f..f7bb7d59b9 100644 --- a/src/main/java/domain/piece/Soldier.java +++ b/src/main/java/domain/piece/Soldier.java @@ -4,8 +4,6 @@ import domain.Position; import domain.Side; import domain.strategy.MovementStrategy; - -import java.util.ArrayList; import java.util.List; public class Soldier extends Piece { @@ -14,34 +12,20 @@ public class Soldier extends Piece { public Soldier(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); - if(Side.CHO == side) { + if (Side.CHO == side) { paths = List.of( - List.of(Direction.UP), List.of(Direction.RIGHT), List.of(Direction.LEFT) + List.of(Direction.UP), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); return; } paths = List.of( - List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) + List.of(Direction.DOWN), List.of(Direction.RIGHT), List.of(Direction.LEFT) ); } @Override public List findRoute(Position sourcePosition, Position targetPosition) { - List positions = new ArrayList<>(); - for(List path : paths) { - for(Direction direction : path) { - try{ - positions.add(sourcePosition.createPosition(direction.getX(), direction.getY())); - } catch (IllegalArgumentException e) { - break; - } - } - if(positions.contains(targetPosition)) { - return List.copyOf(positions); - } - positions.clear(); - } - throw new IllegalArgumentException(INVALID_TARGET_POSITION); + return movementStrategy.findRoute(paths, sourcePosition, targetPosition); } @Override From 909b7c5af92a04348a6cbb9dc7df2aae1a89d493 Mon Sep 17 00:00:00 2001 From: yeo-li Date: Wed, 1 Apr 2026 09:04:15 +0900 Subject: [PATCH 25/34] =?UTF-8?q?fix:=20findRoute()=20targetPosition?= =?UTF-8?q?=EC=9D=B4=20List=EC=9D=98=20=EB=A7=88=EC=A7=80?= =?UTF-8?q?=EB=A7=89=EC=9D=B8=EC=A7=80=20=ED=99=95=EC=9D=B8=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/strategy/MovementStrategy.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/domain/strategy/MovementStrategy.java b/src/main/java/domain/strategy/MovementStrategy.java index 0b4ca4645d..03fd70db4a 100644 --- a/src/main/java/domain/strategy/MovementStrategy.java +++ b/src/main/java/domain/strategy/MovementStrategy.java @@ -2,7 +2,6 @@ import domain.Direction; import domain.Position; - import java.util.List; public abstract class MovementStrategy { @@ -10,15 +9,15 @@ public abstract class MovementStrategy { protected final String INVALID_TARGET_POSITION = "이동할 수 없는 목적지입니다."; public List findRoute(List> paths, Position sourcePosition, Position targetPosition) { - for(List path: paths) { + for (List path : paths) { List positions = buildRoute(path, sourcePosition, targetPosition); - if(positions.contains(targetPosition)) { + if (!positions.isEmpty() && positions.getLast().equals(targetPosition)) { return positions; } } throw new IllegalArgumentException(INVALID_TARGET_POSITION); } - protected abstract List buildRoute(List route, Position sourcePosition, Position targetPosition); + protected abstract List buildRoute(List route, Position sourcePosition, + Position targetPosition); } - From cb5d39d863dfcd8e1bb6c8ec25255ae746c10d5c Mon Sep 17 00:00:00 2001 From: yeo-li Date: Wed, 1 Apr 2026 09:51:24 +0900 Subject: [PATCH 26/34] =?UTF-8?q?feat(input):=20=EC=A2=8C=ED=91=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=ED=8C=8C=EC=8B=B1=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 23 +++++++++++-------- src/main/java/util/Parser.java | 12 ++++++++++ src/main/java/view/InputView.java | 19 +++++++-------- src/test/java/util/ParserTest.java | 18 +++++++++++++++ 4 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 src/main/java/util/Parser.java create mode 100644 src/test/java/util/ParserTest.java diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 2f81d6c3ef..a634cc8fc5 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,6 +1,11 @@ package controller; -import domain.*; +import domain.Board; +import domain.Formation; +import domain.Position; +import domain.Side; +import domain.Turn; +import util.Parser; import view.InputView; import view.OutputView; @@ -38,9 +43,9 @@ private void processMove(Board board) { private Position readTargetPosition() { while (true) { try { - int x = inputView.readTargetXPosition(); - int y = inputView.readTargetYPosition(); - return Position.of(x,y); + int x = Parser.parseInput(inputView.readTargetXPosition()); + int y = Parser.parseInput(inputView.readTargetYPosition()); + return Position.of(x, y); } catch (IllegalArgumentException e) { outputView.printErrorMessage(e.getMessage()); } @@ -50,9 +55,9 @@ private Position readTargetPosition() { private Position readSourcePosition() { while (true) { try { - int x = inputView.readSourceXPosition(); - int y = inputView.readSourceYPosition(); - return Position.of(x,y); + int x = Parser.parseInput(inputView.readSourceXPosition()); + int y = Parser.parseInput(inputView.readSourceYPosition()); + return Position.of(x, y); } catch (IllegalArgumentException e) { outputView.printErrorMessage(e.getMessage()); } @@ -68,7 +73,7 @@ private Board initBoard() { } private Formation readChoFormation() { - while(true) { + while (true) { try { String choFormation = inputView.readChoFormation(); return Formation.from(choFormation); @@ -79,7 +84,7 @@ private Formation readChoFormation() { } private Formation readHanFormation() { - while(true) { + while (true) { try { String hanFormation = inputView.readHanFormation(); return Formation.from(hanFormation); diff --git a/src/main/java/util/Parser.java b/src/main/java/util/Parser.java new file mode 100644 index 0000000000..b5a06e7141 --- /dev/null +++ b/src/main/java/util/Parser.java @@ -0,0 +1,12 @@ +package util; + +public class Parser { + + public static int parseInput(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("유효한 입력값이 아닙니다."); + } + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 93f23f3914..c064e5b0f1 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -4,6 +4,7 @@ import java.util.Scanner; public class InputView { + private final String READ_FORMATION_MESSAGE = "[%s] 포진을 선택해주세요."; private final Scanner scanner = new Scanner(System.in); @@ -20,30 +21,30 @@ public String readHanFormation() { return readFormation(); } - public int readSourceXPosition() { + public String readSourceXPosition() { System.out.println(); System.out.println("움직일 기물의 x 좌표를 입력해주세요. (x 범위 1 ~ 9)"); - return scanner.nextInt(); + return scanner.nextLine(); } - public int readSourceYPosition() { + public String readSourceYPosition() { System.out.println("움직일 기물의 y 좌표를 입력해주세요. (y 범위 1 ~ 10)"); - return scanner.nextInt(); + return scanner.nextLine(); } - public int readTargetXPosition() { + public String readTargetXPosition() { System.out.println(); System.out.println("목적지 x 좌표를 입력해주세요. (x 범위 1 ~ 9)"); - return scanner.nextInt(); + return scanner.nextLine(); } - public int readTargetYPosition() { + public String readTargetYPosition() { System.out.println("목적지 y 좌표를 입력해주세요. (y 범위 1 ~ 10)"); - return scanner.nextInt(); + return scanner.nextLine(); } private String readFormation() { - for(Formation formation : Formation.values()) { + for (Formation formation : Formation.values()) { System.out.println(formation.toDisplayString()); } return scanner.nextLine(); diff --git a/src/test/java/util/ParserTest.java b/src/test/java/util/ParserTest.java new file mode 100644 index 0000000000..dfc1b15b0d --- /dev/null +++ b/src/test/java/util/ParserTest.java @@ -0,0 +1,18 @@ +package util; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class ParserTest { + + @ParameterizedTest + @ValueSource(strings = {"a", "10000000000000000000000", "ㅁ"}) + void Integer로_변환되지_않는_값은_예외를_발생한다(String input) { + + // when & then + Assertions.assertThatThrownBy(() -> Parser.parseInput(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("유효한 입력값이 아닙니다."); + } +} \ No newline at end of file From 669439787be542a5a4b899780bd6dc9d116adfff Mon Sep 17 00:00:00 2001 From: yeo-li Date: Wed, 1 Apr 2026 17:13:49 +0900 Subject: [PATCH 27/34] =?UTF-8?q?fix:=20Piece.checkRoute()=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=A9=94=EC=84=B8=EC=A7=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/piece/Piece.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 119dbdcbca..78a4bc6d5e 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -3,7 +3,6 @@ import domain.Position; import domain.Side; import domain.strategy.MovementStrategy; - import java.util.List; public abstract class Piece { @@ -28,7 +27,7 @@ public Side getSide() { } public void checkTarget(Piece piece) { - if(side.equals(piece.side)) { + if (side.equals(piece.side)) { throw new IllegalArgumentException(CANNOT_CAPTURE_OWN_PIECE); } } @@ -36,9 +35,9 @@ public void checkTarget(Piece piece) { public abstract List findRoute(Position sourcePosition, Position targetPosition); public void checkRoute(List pieces) { - for(Piece piece : pieces) { - if(!(piece instanceof Empty)) { - throw new IllegalArgumentException(CANNOT_CAPTURE_OWN_PIECE); + for (Piece piece : pieces) { + if (!(piece instanceof Empty)) { + throw new IllegalArgumentException(INVALID_TARGET_POSITION); } } } From e4bb0454c478a42ac6babe3963700b80ab27b13d Mon Sep 17 00:00:00 2001 From: yeo-li Date: Thu, 2 Apr 2026 16:41:59 +0900 Subject: [PATCH 28/34] =?UTF-8?q?refactor:=20Board=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EB=B9=84=EC=A7=80=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20Game=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 26 +- src/main/java/domain/Board.java | 75 ++-- src/main/java/domain/Game.java | 40 ++ src/main/java/domain/piece/Piece.java | 14 + src/main/java/view/OutputView.java | 6 +- src/test/java/BoardTest.java | 414 ++---------------- src/test/java/GameTest.java | 403 +++++++++++++++++ 7 files changed, 526 insertions(+), 452 deletions(-) create mode 100644 src/main/java/domain/Game.java create mode 100644 src/test/java/GameTest.java diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index a634cc8fc5..91ed4aeecf 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,10 +1,9 @@ package controller; -import domain.Board; import domain.Formation; +import domain.Game; import domain.Position; import domain.Side; -import domain.Turn; import util.Parser; import view.InputView; import view.OutputView; @@ -20,23 +19,22 @@ public JanggiController(InputView inputView, OutputView outputView) { } public void run() { - Board board = initBoard(); - processMove(board); + Game game = initGame(); + processMove(game); } - private void processMove(Board board) { - Turn turn = new Turn(Side.CHO); + private void processMove(Game game) { while (true) { - outputView.printCurrentTurn(turn); + Side currentTurn = game.getCurrentTurn(); + outputView.printCurrentTurn(currentTurn); Position sourcePosition = readSourcePosition(); Position targetPosition = readTargetPosition(); try { - board.movePiece(turn, sourcePosition, targetPosition); - turn.next(); + game.move(sourcePosition, targetPosition); } catch (IllegalArgumentException e) { outputView.printErrorMessage(e.getMessage()); } - outputView.printBoardStatus(board.getBoard()); + outputView.printBoardStatus(game.getBoard()); } } @@ -64,12 +62,12 @@ private Position readSourcePosition() { } } - private Board initBoard() { + private Game initGame() { Formation choformation = readChoFormation(); Formation hanformation = readHanFormation(); - Board board = new Board(choformation, hanformation); - outputView.printBoardStatus(board.getBoard()); - return board; + Game game = new Game(choformation, hanformation); + outputView.printBoardStatus(game.getBoard()); + return game; } private Formation readChoFormation() { diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index af7329079c..63bf54ec4b 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -1,7 +1,8 @@ package domain; -import domain.piece.*; - +import domain.piece.Empty; +import domain.piece.Piece; +import domain.piece.PieceType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -9,20 +10,17 @@ public class Board { - private static final String PIECE_NOT_FOUND = "해당 위치에 기물이 존재하지 않습니다."; - private static final String NOT_OWN_PIECE = "선택한 기물은 아군 기물이 아닙니다."; - - private final int BACK_Y = 0; - private final int KING_Y = 1; - private final int CANNON_Y = 2; - private final int SOLDIER_Y = 3; + private static final int BACK_Y = 0; + private static final int KING_Y = 1; + private static final int CANNON_Y = 2; + private static final int SOLDIER_Y = 3; - private final List CANNON_X = List.of(2, 8); - private final List CHARIOT_X = List.of(1, 9); - private final List GUARD_X = List.of(4, 6); - private final List KING_X = List.of(5); - private final List SOLIDER_X = List.of(1, 3, 5, 7, 9); - private final List FORMATION_X = List.of(2, 3, 7, 8); + private static final List CANNON_X = List.of(2, 8); + private static final List CHARIOT_X = List.of(1, 9); + private static final List GUARD_X = List.of(4, 6); + private static final List KING_X = List.of(5); + private static final List SOLIDER_X = List.of(1, 3, 5, 7, 9); + private static final List FORMATION_X = List.of(2, 3, 7, 8); private final Map board = new HashMap<>(); @@ -30,22 +28,13 @@ public Board(Formation choFormation, Formation hanFormation) { createBoard(choFormation, hanFormation); } - public void movePiece(Turn turn, Position sourcePosition, Position targetPosition) { - Piece piece = board.get(sourcePosition); - - validatePieceExists(piece); - validateOwnPiece(piece, turn); - - List route = piece.findRoute(sourcePosition, targetPosition); - findPiecesOnRoute(piece, route, targetPosition); - - validateAvailableTarget(piece, targetPosition); + public void movePiece(Position sourcePosition, Position targetPosition) { board.put(targetPosition, board.get(sourcePosition)); board.put(sourcePosition, new Empty()); } public Map getBoard() { - return board; + return Map.copyOf(board); } private void createBoard(Formation choFormation, Formation hanFormation) { @@ -69,10 +58,10 @@ private void placePieces(Formation formation, Side side) { } private List getRowForSide(Side side) { - if(side == Side.HAN) { - return List.of(1,2,3,4); + if (side == Side.HAN) { + return List.of(1, 2, 3, 4); } - return List.of(10,9,8,7); + return List.of(10, 9, 8, 7); } private void placePiece(Position position, Piece piece) { @@ -80,41 +69,29 @@ private void placePiece(Position position, Piece piece) { } private void placePiece(List xPositions, int y, PieceType pieceType, Side side) { - for(int x : xPositions) { + for (int x : xPositions) { board.put(Position.of(x, y), pieceType.create(side)); } } private void placeFormationPiece(Formation formation, List formationX, int y, Side side) { List pieceTypes = formation.getPieceTypes(); - for(int i = 0; i < pieceTypes.size(); i++) { + for (int i = 0; i < pieceTypes.size(); i++) { board.put(Position.of(formationX.get(i), y), pieceTypes.get(i).create(side)); } } - private void validatePieceExists(Piece piece) { - if(piece instanceof Empty) { - throw new IllegalArgumentException(PIECE_NOT_FOUND); - } - } - - private void findPiecesOnRoute(Piece piece, List route, Position targetPosition) { + public List findPiecesOnRoute(List route, Position targetPosition) { List pieces = new ArrayList<>(); - for(Position position : route) { - if(!position.equals(targetPosition)) { + for (Position position : route) { + if (!position.equals(targetPosition)) { pieces.add(board.get(position)); } } - piece.checkRoute(pieces); - } - - private void validateOwnPiece(Piece piece, Turn turn) { - if(!piece.getSide().equals(turn.current())) { - throw new IllegalArgumentException(NOT_OWN_PIECE); - } + return pieces; } - private void validateAvailableTarget(Piece piece, Position targetPosition) { - piece.checkTarget(board.get(targetPosition)); + 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 new file mode 100644 index 0000000000..f53376dc46 --- /dev/null +++ b/src/main/java/domain/Game.java @@ -0,0 +1,40 @@ +package domain; + +import domain.piece.Piece; +import java.util.List; +import java.util.Map; + +public class Game { + + private final Board board; + private final Turn turn; + + public Game(Formation choFormation, Formation hanFormation) { + this.board = new Board(choFormation, hanFormation); + this.turn = new Turn(Side.CHO); + } + + public void move(Position sourcePosition, Position targetPosition) { + validateMovement(sourcePosition, targetPosition); + board.movePiece(sourcePosition, targetPosition); + turn.next(); + } + + + private void validateMovement(Position sourcePosition, Position targetPosition) { + Piece sourcePiece = board.getPiece(sourcePosition); + Piece targetPiece = board.getPiece(targetPosition); + sourcePiece.validateMovement(turn.current(), targetPiece); + List route = sourcePiece.findRoute(sourcePosition, targetPosition); + List pieces = board.findPiecesOnRoute(route, targetPosition); + sourcePiece.checkRoute(pieces); + } + + public Side getCurrentTurn() { + return turn.current(); + } + + public Map getBoard() { + return board.getBoard(); + } +} diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 78a4bc6d5e..2c1d8c0222 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -7,6 +7,9 @@ public abstract class Piece { + private static final String PIECE_NOT_FOUND = "해당 위치에 기물이 존재하지 않습니다."; + private static final String NOT_OWN_PIECE = "선택한 기물은 아군 기물이 아닙니다."; + protected static final String CANNOT_CAPTURE_OWN_PIECE = "아군 기물은 잡을 수 없습니다."; protected static final String INVALID_TARGET_POSITION = "이동할 수 없는 목적지입니다."; @@ -42,5 +45,16 @@ public void checkRoute(List pieces) { } } + public void validateMovement(Side currentTurn, Piece targetPiece) { + if (this instanceof Empty) { + throw new IllegalArgumentException(PIECE_NOT_FOUND); + } + if (!side.equals(currentTurn)) { + throw new IllegalArgumentException(NOT_OWN_PIECE); + } + + checkTarget(targetPiece); + } + public abstract String getName(); } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 0c56d14a71..1e4cbc7d8a 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -2,9 +2,7 @@ import domain.Position; import domain.Side; -import domain.Turn; import domain.piece.Piece; - import java.util.Map; public class OutputView { @@ -28,9 +26,9 @@ public void printBoardStatus(Map board) { } } - public void printCurrentTurn(Turn turn) { + public void printCurrentTurn(Side currentSide) { System.out.println(); - if(turn.current() == Side.CHO) { + if (currentSide == Side.CHO) { System.out.println("초의 차례입니다."); return; } diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java index 7085c61e07..b2dba3ecdf 100644 --- a/src/test/java/BoardTest.java +++ b/src/test/java/BoardTest.java @@ -1,19 +1,24 @@ -import domain.*; -import domain.piece.*; -import org.assertj.core.api.Assertions; +import static org.assertj.core.api.Assertions.assertThat; + +import domain.Board; +import domain.Formation; +import domain.Position; +import domain.Side; +import domain.piece.Chariot; +import domain.piece.Elephant; +import domain.piece.Empty; +import domain.piece.Horse; +import domain.piece.Piece; +import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - public class BoardTest { @Nested @DisplayName("장기판 초기화") - class initBoard { + class InitBoard { @DisplayName("초나라 포진을 상마마상으로 배치한다.") @Test @@ -50,393 +55,32 @@ class initBoard { } } - @DisplayName("초 턴에 한 기물을 선택한 경우 이동할 수 없다.") - @Test - void 초_턴에_한_기물을_선택한_경우_이동할_수_없다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Turn turn = new Turn(Side.CHO); - Position sourcePosition = Position.of(1, 4); - Position mockPosition = Position.of(1, 1); - - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(turn, sourcePosition, mockPosition)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("선택한 기물은 아군 기물이 아닙니다."); - - } - - - @DisplayName("한 턴에 초 기물을 선택한 경우 이동할 수 없다.") - @Test - void 한_턴에_초_기물을_선택한_경우_이동할_수_없다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Turn turn = new Turn(Side.HAN); - Position sourcePosition = Position.of(1, 7); - Position mockPosition = Position.of(1, 1); - - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(turn, sourcePosition, mockPosition)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("선택한 기물은 아군 기물이 아닙니다."); - - } - - @DisplayName("출발지에 기물이 존재하지 않는 경우 이동할 수 없다.") - @Test - void 기물이_존재하지_않는_경우_이동할_수_없다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Turn turn = new Turn(Side.CHO); - Position sourcePosition = Position.of(5, 5); - Position mockPosition = Position.of(1, 1); - - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(turn, sourcePosition, mockPosition)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("해당 위치에 기물이 존재하지 않습니다."); - } - @Nested @DisplayName("기물 이동") class MovePiece { - @Nested - @DisplayName("차") - class ChariotMove { - - @Test - @DisplayName("차가 세로로 이동한다.") - void 차가_세로로_이동한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(1, 1); - Position target = Position.of(1, 3); - - // when - board.movePiece(new Turn(Side.HAN), source, target); - - // then - assertThat(board.getBoard().get(target)).isInstanceOf(Chariot.class); - assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); - } - - @Test - @DisplayName("차가 가로로 이동한다.") - void 차가_가로로_이동한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(1, 1); - Position target = Position.of(1, 2); - board.movePiece(new Turn(Side.HAN), source, target); - - // when - board.movePiece(new Turn(Side.HAN), target, Position.of(3, 2)); - - // then - assertThat(board.getBoard().get(Position.of(3, 2))).isInstanceOf(Chariot.class); - } - - @Test - @DisplayName("차가 대각선으로 이동하면 예외가 발생한다.") - void 차가_대각선으로_이동하면_예외가_발생한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(1, 1); - Position target = Position.of(3, 3); - - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) - .isInstanceOf(IllegalArgumentException.class); - } - } - - @Nested - @DisplayName("상") - class ElephantMove { - - @Test - @DisplayName("상이 대각선 방향으로 이동한다.") - void 상이_대각선_방향으로_이동한다() { - // given - 상마상마 포진 사용 - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(2, 1); - Position target = Position.of(4, 4); - - // when - board.movePiece(new Turn(Side.HAN), source, target); - - // then - assertThat(board.getBoard().get(target)).isInstanceOf(Elephant.class); - assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); - } - - @Test - @DisplayName("상이 이동 불가능한 위치로 이동하면 예외가 발생한다.") - void 상이_이동_불가능한_위치로_이동하면_예외가_발생한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(2, 1); - Position target = Position.of(3, 2); - - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) - .isInstanceOf(IllegalArgumentException.class); - } - } - - @Nested - @DisplayName("마") - class HorseMove { - - @Test - @DisplayName("마가 날 일자로 이동한다.") - void 마가_날_일자로_이동한다() { - // given - 마상마상 포진 사용 (2,1에 마가 있음) - Board board = new Board(Formation.from("1"), Formation.from("4")); - Position source = Position.of(2, 1); - Position target = Position.of(3, 3); - - // when - board.movePiece(new Turn(Side.HAN), source, target); - - // then - assertThat(board.getBoard().get(target)).isInstanceOf(Horse.class); - assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); - } - - @Test - @DisplayName("마가 이동 불가능한 위치로 이동하면 예외가 발생한다.") - void 마가_이동_불가능한_위치로_이동하면_예외가_발생한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("4")); - Position source = Position.of(2, 1); - Position target = Position.of(4, 4); - - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) - .isInstanceOf(IllegalArgumentException.class); - } - } - - @Nested - @DisplayName("포") - class CannonMove { - - @Test - @DisplayName("포가 기물을 하나 뛰어넘어 이동한다.") - void 포가_기물을_하나_뛰어넘어_이동한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - // 사(4,1)를 (4,3)으로 이동시켜 포가 뛰어넘을 기물 배치 - board.movePiece(new Turn(Side.HAN), Position.of(4, 1), Position.of(4, 2)); - board.movePiece(new Turn(Side.HAN), Position.of(4, 2), Position.of(4, 3)); - - // when - (2,3) 포가 (4,3) 사를 뛰어넘어 (5,3)으로 이동 - board.movePiece(new Turn(Side.HAN), Position.of(2, 3), Position.of(5, 3)); - - // then - assertThat(board.getBoard().get(Position.of(5, 3))).isInstanceOf(Cannon.class); - } - - @Test - @DisplayName("포가 뛰어넘을 기물이 없으면 예외가 발생한다.") - void 포가_뛰어넘을_기물이_없으면_예외가_발생한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(2, 3); - Position target = Position.of(5, 3); - - // when & then - 중간에 기물이 없음 - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("포가 포를 뛰어넘으면 예외가 발생한다.") - void 포가_포를_뛰어넘으면_예외가_발생한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - // (8,3) 포를 (5,3)으로 이동하기 위해 먼저 중간에 기물 배치 - board.movePiece(new Turn(Side.HAN), Position.of(6, 1), Position.of(6, 2)); - board.movePiece(new Turn(Side.HAN), Position.of(6, 2), Position.of(6, 3)); - // (8,3) 포가 (6,3) 사를 뛰어넘어 (5,3)으로 이동 - board.movePiece(new Turn(Side.HAN), Position.of(8, 3), Position.of(5, 3)); - - // 이제 (2,3) 포와 (5,3) 포 사이에 (5,3) 포를 뛰어넘는 상황 만들기 - // (2,3) 포가 (5,3) 포를 뛰어넘어 (7,3)으로 이동 시도 - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), Position.of(2, 3), Position.of(7, 3))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("포를 넘어갈 수 없습니다."); - } - } - - @Nested - @DisplayName("사") - class GuardMove { - - @Test - @DisplayName("사가 한 칸 이동한다.") - void 사가_한_칸_이동한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(4, 1); - Position target = Position.of(5, 1); - - // when - board.movePiece(new Turn(Side.HAN), source, target); - - // then - assertThat(board.getBoard().get(target)).isInstanceOf(Guard.class); - assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); - } - - @Test - @DisplayName("사가 두 칸 이상 이동하면 예외가 발생한다.") - void 사가_두_칸_이상_이동하면_예외가_발생한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(4, 1); - Position target = Position.of(4, 3); - - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) - .isInstanceOf(IllegalArgumentException.class); - } - } - - @Nested - @DisplayName("궁") - class KingMove { - - @Test - @DisplayName("궁이 한 칸 이동한다.") - void 궁이_한_칸_이동한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(5, 2); - Position target = Position.of(5, 1); - - // when - board.movePiece(new Turn(Side.HAN), source, target); - - // then - assertThat(board.getBoard().get(target)).isInstanceOf(King.class); - assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); - } - - @Test - @DisplayName("궁이 두 칸 이상 이동하면 예외가 발생한다.") - void 궁이_두_칸_이상_이동하면_예외가_발생한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(5, 2); - Position target = Position.of(5, 4); - - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) - .isInstanceOf(IllegalArgumentException.class); - } - } - - @Nested - @DisplayName("졸/병") - class SoldierMove { - - @Test - @DisplayName("졸이 앞으로 한 칸 이동한다.") - void 졸이_앞으로_한_칸_이동한다() { - // given - CHO 졸은 위로 이동 - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(1, 7); - Position target = Position.of(1, 6); - - // when - board.movePiece(new Turn(Side.CHO), source, target); - - // then - assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); - assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); - } - - @Test - @DisplayName("병이 앞으로 한 칸 이동한다.") - void 병이_앞으로_한_칸_이동한다() { - // given - HAN 병은 아래로 이동 - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(1, 4); - Position target = Position.of(1, 5); - - // when - board.movePiece(new Turn(Side.HAN), source, target); - - // then - assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); - assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); - } - - @Test - @DisplayName("졸이 옆으로 한 칸 이동한다.") - void 졸이_옆으로_한_칸_이동한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(1, 7); - Position target = Position.of(2, 7); - - // when - board.movePiece(new Turn(Side.CHO), source, target); - - // then - assertThat(board.getBoard().get(target)).isInstanceOf(Soldier.class); - } + @DisplayName("기물을 이동하면 목적지에 기물이 위치한다.") + @Test + void 기물을_이동하면_목적지에_기물이_위치한다() { + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(1, 3); - @Test - @DisplayName("졸이 뒤로 이동하면 예외가 발생한다.") - void 졸이_뒤로_이동하면_예외가_발생한다() { - // given - CHO 졸은 아래로 이동 불가 - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(1, 7); - Position target = Position.of(1, 8); + board.movePiece(source, target); - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.CHO), source, target)) - .isInstanceOf(IllegalArgumentException.class); - } + assertThat(board.getBoard().get(target)).isInstanceOf(Chariot.class); } - @Nested - @DisplayName("적 기물 잡기") - class CapturePiece { - - @Test - @DisplayName("적 기물을 잡을 수 있다.") - void 적_기물을_잡을_수_있다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - - // 차를 이동시켜 적 졸을 잡기 - board.movePiece(new Turn(Side.HAN), Position.of(1, 4), Position.of(2, 4)); - board.movePiece(new Turn(Side.HAN), Position.of(1, 1), Position.of(1, 7)); - - // then - assertThat(board.getBoard().get(Position.of(1, 7))).isInstanceOf(Chariot.class); - assertThat(board.getBoard().get(Position.of(1, 7)).isSameSide(Side.HAN)).isTrue(); - } + @DisplayName("기물을 이동하면 출발지는 빈칸이 된다.") + @Test + void 기물을_이동하면_출발지는_빈칸이_된다() { + Board board = new Board(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(1, 3); - @Test - @DisplayName("아군 기물을 잡으면 예외가 발생한다.") - void 아군_기물을_잡으면_예외가_발생한다() { - // given - Board board = new Board(Formation.from("1"), Formation.from("1")); - Position source = Position.of(1, 1); - Position target = Position.of(1, 4); // HAN 졸 위치 + board.movePiece(source, target); - // when & then - Assertions.assertThatThrownBy(() -> board.movePiece(new Turn(Side.HAN), source, target)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("아군 기물은 잡을 수 없습니다."); - } + assertThat(board.getBoard().get(source)).isInstanceOf(Empty.class); } } } diff --git a/src/test/java/GameTest.java b/src/test/java/GameTest.java new file mode 100644 index 0000000000..51d4a09170 --- /dev/null +++ b/src/test/java/GameTest.java @@ -0,0 +1,403 @@ +import static org.assertj.core.api.Assertions.assertThat; + +import domain.Formation; +import domain.Game; +import domain.Position; +import domain.Side; +import domain.piece.Cannon; +import domain.piece.Chariot; +import domain.piece.Elephant; +import domain.piece.Empty; +import domain.piece.Guard; +import domain.piece.Horse; +import domain.piece.King; +import domain.piece.Soldier; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class GameTest { + + @DisplayName("초 턴에 한 기물을 선택한 경우 이동할 수 없다.") + @Test + void 초_턴에_한_기물을_선택한_경우_이동할_수_없다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position sourcePosition = Position.of(1, 4); + Position targetPosition = Position.of(1, 1); + + Assertions.assertThatThrownBy(() -> game.move(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("선택한 기물은 아군 기물이 아닙니다."); + } + + @DisplayName("한 턴에 초 기물을 선택한 경우 이동할 수 없다.") + @Test + void 한_턴에_초_기물을_선택한_경우_이동할_수_없다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + moveBySide(game, Side.CHO, Position.of(1, 7), Position.of(1, 6)); + + Assertions.assertThatThrownBy(() -> game.move(Position.of(3, 7), Position.of(3, 6))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("선택한 기물은 아군 기물이 아닙니다."); + } + + @DisplayName("출발지에 기물이 존재하지 않는 경우 이동할 수 없다.") + @Test + void 기물이_존재하지_않는_경우_이동할_수_없다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position sourcePosition = Position.of(5, 5); + Position targetPosition = Position.of(1, 1); + + Assertions.assertThatThrownBy(() -> game.move(sourcePosition, targetPosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("해당 위치에 기물이 존재하지 않습니다."); + } + + @Nested + @DisplayName("기물 이동") + class MovePiece { + + @Nested + @DisplayName("차") + class ChariotMove { + + @Test + @DisplayName("차가 세로로 이동한다.") + void 차가_세로로_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(1, 3); + + moveBySide(game, Side.HAN, source, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(Chariot.class); + assertThat(game.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("차가 가로로 이동한다.") + void 차가_가로로_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position mid = Position.of(1, 2); + Position target = Position.of(3, 2); + + moveBySide(game, Side.HAN, source, mid); + moveBySide(game, Side.HAN, mid, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(Chariot.class); + } + + @Test + @DisplayName("차가 대각선으로 이동하면 예외가 발생한다.") + void 차가_대각선으로_이동하면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(3, 3); + + Assertions.assertThatThrownBy(() -> moveBySide(game, Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("상") + class ElephantMove { + + @Test + @DisplayName("상이 대각선 방향으로 이동한다.") + void 상이_대각선_방향으로_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(2, 1); + Position target = Position.of(4, 4); + + moveBySide(game, Side.HAN, source, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(Elephant.class); + assertThat(game.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("상이 이동 불가능한 위치로 이동하면 예외가 발생한다.") + void 상이_이동_불가능한_위치로_이동하면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(2, 1); + Position target = Position.of(3, 2); + + Assertions.assertThatThrownBy(() -> moveBySide(game, Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("마") + class HorseMove { + + @Test + @DisplayName("마가 날 일자로 이동한다.") + void 마가_날_일자로_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("4")); + Position source = Position.of(2, 1); + Position target = Position.of(3, 3); + + moveBySide(game, Side.HAN, source, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(Horse.class); + assertThat(game.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("마가 이동 불가능한 위치로 이동하면 예외가 발생한다.") + void 마가_이동_불가능한_위치로_이동하면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("4")); + Position source = Position.of(2, 1); + Position target = Position.of(4, 4); + + Assertions.assertThatThrownBy(() -> moveBySide(game, Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("포") + class CannonMove { + + @Test + @DisplayName("포가 기물을 하나 뛰어넘어 이동한다.") + void 포가_기물을_하나_뛰어넘어_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + + moveBySide(game, Side.HAN, Position.of(4, 1), Position.of(4, 2)); + moveBySide(game, Side.HAN, Position.of(4, 2), Position.of(4, 3)); + + moveBySide(game, Side.HAN, Position.of(2, 3), Position.of(5, 3)); + + assertThat(game.getBoard().get(Position.of(5, 3))).isInstanceOf(Cannon.class); + } + + @Test + @DisplayName("포가 뛰어넘을 기물이 없으면 예외가 발생한다.") + void 포가_뛰어넘을_기물이_없으면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(2, 3); + Position target = Position.of(5, 3); + + Assertions.assertThatThrownBy(() -> moveBySide(game, Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("포가 포를 뛰어넘으면 예외가 발생한다.") + void 포가_포를_뛰어넘으면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + + moveBySide(game, Side.HAN, Position.of(6, 1), Position.of(6, 2)); + moveBySide(game, Side.HAN, Position.of(6, 2), Position.of(6, 3)); + moveBySide(game, Side.HAN, Position.of(8, 3), Position.of(5, 3)); + + Assertions.assertThatThrownBy( + () -> moveBySide(game, Side.HAN, Position.of(2, 3), Position.of(7, 3))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("포를 넘어갈 수 없습니다."); + } + } + + @Nested + @DisplayName("사") + class GuardMove { + + @Test + @DisplayName("사가 한 칸 이동한다.") + void 사가_한_칸_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(4, 1); + Position target = Position.of(5, 1); + + moveBySide(game, Side.HAN, source, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(Guard.class); + assertThat(game.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("사가 두 칸 이상 이동하면 예외가 발생한다.") + void 사가_두_칸_이상_이동하면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(4, 1); + Position target = Position.of(4, 3); + + Assertions.assertThatThrownBy(() -> moveBySide(game, Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("궁") + class KingMove { + + @Test + @DisplayName("궁이 한 칸 이동한다.") + void 궁이_한_칸_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(5, 2); + Position target = Position.of(5, 1); + + moveBySide(game, Side.HAN, source, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(King.class); + assertThat(game.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("궁이 두 칸 이상 이동하면 예외가 발생한다.") + void 궁이_두_칸_이상_이동하면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(5, 2); + Position target = Position.of(5, 4); + + Assertions.assertThatThrownBy(() -> moveBySide(game, Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("졸/병") + class SoldierMove { + + @Test + @DisplayName("졸이 앞으로 한 칸 이동한다.") + void 졸이_앞으로_한_칸_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 7); + Position target = Position.of(1, 6); + + moveBySide(game, Side.CHO, source, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(Soldier.class); + assertThat(game.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("병이 앞으로 한 칸 이동한다.") + void 병이_앞으로_한_칸_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 4); + Position target = Position.of(1, 5); + + moveBySide(game, Side.HAN, source, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(Soldier.class); + assertThat(game.getBoard().get(source)).isInstanceOf(Empty.class); + } + + @Test + @DisplayName("졸이 옆으로 한 칸 이동한다.") + void 졸이_옆으로_한_칸_이동한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 7); + Position target = Position.of(2, 7); + + moveBySide(game, Side.CHO, source, target); + + assertThat(game.getBoard().get(target)).isInstanceOf(Soldier.class); + } + + @Test + @DisplayName("졸이 뒤로 이동하면 예외가 발생한다.") + void 졸이_뒤로_이동하면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 7); + Position target = Position.of(1, 8); + + Assertions.assertThatThrownBy(() -> moveBySide(game, Side.CHO, source, target)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("적 기물 잡기") + class CapturePiece { + + @Test + @DisplayName("적 기물을 잡을 수 있다.") + void 적_기물을_잡을_수_있다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + + moveBySide(game, Side.HAN, Position.of(1, 4), Position.of(2, 4)); + moveBySide(game, Side.HAN, Position.of(1, 1), Position.of(1, 7)); + + assertThat(game.getBoard().get(Position.of(1, 7))).isInstanceOf(Chariot.class); + assertThat(game.getBoard().get(Position.of(1, 7)).isSameSide(Side.HAN)).isTrue(); + } + + @Test + @DisplayName("아군 기물을 잡으면 예외가 발생한다.") + void 아군_기물을_잡으면_예외가_발생한다() { + Game game = new Game(Formation.from("1"), Formation.from("1")); + Position source = Position.of(1, 1); + Position target = Position.of(1, 4); + + Assertions.assertThatThrownBy(() -> moveBySide(game, Side.HAN, source, target)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("아군 기물은 잡을 수 없습니다."); + } + } + } + + private void moveBySide(Game game, Side side, Position source, Position target) { + if (game.getCurrentTurn() != side) { + passCurrentTurn(game); + } + game.move(source, target); + } + + private void passCurrentTurn(Game game) { + if (game.getCurrentTurn() == Side.CHO) { + moveOneOf(game, new MoveCandidate[] { + new MoveCandidate(9, 7, 9, 6), + new MoveCandidate(9, 6, 8, 6), + new MoveCandidate(8, 6, 8, 5), + new MoveCandidate(8, 5, 7, 5), + new MoveCandidate(7, 7, 7, 6), + new MoveCandidate(5, 7, 5, 6), + new MoveCandidate(3, 7, 3, 6), + new MoveCandidate(1, 7, 1, 6) + }); + return; + } + + moveOneOf(game, new MoveCandidate[] { + new MoveCandidate(9, 4, 9, 5), + new MoveCandidate(9, 5, 8, 5), + new MoveCandidate(8, 5, 8, 6), + new MoveCandidate(8, 6, 7, 6), + new MoveCandidate(7, 4, 7, 5), + new MoveCandidate(5, 4, 5, 5), + new MoveCandidate(3, 4, 3, 5), + new MoveCandidate(1, 4, 1, 5) + }); + } + + private void moveOneOf(Game game, MoveCandidate[] candidates) { + for (MoveCandidate candidate : candidates) { + try { + game.move(candidate.source, candidate.target); + return; + } catch (IllegalArgumentException ignored) { + } + } + throw new IllegalStateException("턴 전환을 위한 이동 가능한 수가 없습니다."); + } + + private static class MoveCandidate { + + private final Position source; + private final Position target; + + private MoveCandidate(int sourceX, int sourceY, int targetX, int targetY) { + this.source = Position.of(sourceX, sourceY); + this.target = Position.of(targetX, targetY); + } + } +} From bd6c89a86cde4694adf87d592878e2afb7c6145d Mon Sep 17 00:00:00 2001 From: yeo-li Date: Thu, 2 Apr 2026 20:55:34 +0900 Subject: [PATCH 29/34] =?UTF-8?q?refactor:=20BoardFactory=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85=20=EB=B0=8F=20Game/Board=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EB=B0=A9=EC=8B=9D=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 6 +- src/main/java/domain/Board.java | 64 +---------------- src/main/java/domain/BoardFactory.java | 72 +++++++++++++++++++ src/main/java/domain/Game.java | 4 +- src/test/java/BoardFactoryTest.java | 70 ++++++++++++++++++ src/test/java/BoardTest.java | 49 +------------ src/test/java/GameTest.java | 53 ++++++++------ 7 files changed, 185 insertions(+), 133 deletions(-) create mode 100644 src/main/java/domain/BoardFactory.java create mode 100644 src/test/java/BoardFactoryTest.java diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 91ed4aeecf..23ab520368 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,5 +1,7 @@ package controller; +import domain.Board; +import domain.BoardFactory; import domain.Formation; import domain.Game; import domain.Position; @@ -65,7 +67,9 @@ private Position readSourcePosition() { private Game initGame() { Formation choformation = readChoFormation(); Formation hanformation = readHanFormation(); - Game game = new Game(choformation, hanformation); + + Board board = BoardFactory.createBoard(choformation, hanformation); + Game game = new Game(board); outputView.printBoardStatus(game.getBoard()); return game; } diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 63bf54ec4b..dbe5fbda06 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -2,30 +2,16 @@ import domain.piece.Empty; import domain.piece.Piece; -import domain.piece.PieceType; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; public class Board { - private static final int BACK_Y = 0; - private static final int KING_Y = 1; - private static final int CANNON_Y = 2; - private static final int SOLDIER_Y = 3; + private final Map board; - private static final List CANNON_X = List.of(2, 8); - private static final List CHARIOT_X = List.of(1, 9); - private static final List GUARD_X = List.of(4, 6); - private static final List KING_X = List.of(5); - private static final List SOLIDER_X = List.of(1, 3, 5, 7, 9); - private static final List FORMATION_X = List.of(2, 3, 7, 8); - - private final Map board = new HashMap<>(); - - public Board(Formation choFormation, Formation hanFormation) { - createBoard(choFormation, hanFormation); + public Board(Map board) { + this.board = board; } public void movePiece(Position sourcePosition, Position targetPosition) { @@ -37,50 +23,6 @@ public Map getBoard() { return Map.copyOf(board); } - private void createBoard(Formation choFormation, Formation hanFormation) { - for (int x = 1; x <= 9; x++) { - for (int y = 1; y <= 10; y++) { - placePiece(Position.of(x, y), PieceType.EMPTY.create(Side.NONE)); - } - } - placePieces(choFormation, Side.CHO); - placePieces(hanFormation, Side.HAN); - } - - private void placePieces(Formation formation, Side side) { - List rows = getRowForSide(side); - placePiece(CANNON_X, rows.get(CANNON_Y), PieceType.CANNON, side); - placePiece(CHARIOT_X, rows.get(BACK_Y), PieceType.CHARIOT, side); - placePiece(GUARD_X, rows.get(BACK_Y), PieceType.GUARD, side); - placePiece(KING_X, rows.get(KING_Y), PieceType.KING, side); - placePiece(SOLIDER_X, rows.get(SOLDIER_Y), PieceType.SOLDIER, side); - placeFormationPiece(formation, FORMATION_X, rows.get(BACK_Y), side); - } - - private List getRowForSide(Side side) { - if (side == Side.HAN) { - return List.of(1, 2, 3, 4); - } - return List.of(10, 9, 8, 7); - } - - private void placePiece(Position position, Piece piece) { - board.put(position, piece); - } - - private void placePiece(List xPositions, int y, PieceType pieceType, Side side) { - for (int x : xPositions) { - board.put(Position.of(x, y), pieceType.create(side)); - } - } - - private void placeFormationPiece(Formation formation, List formationX, int y, Side side) { - List pieceTypes = formation.getPieceTypes(); - for (int i = 0; i < pieceTypes.size(); i++) { - board.put(Position.of(formationX.get(i), y), pieceTypes.get(i).create(side)); - } - } - public List findPiecesOnRoute(List route, Position targetPosition) { List pieces = new ArrayList<>(); for (Position position : route) { diff --git a/src/main/java/domain/BoardFactory.java b/src/main/java/domain/BoardFactory.java new file mode 100644 index 0000000000..1d49f77c13 --- /dev/null +++ b/src/main/java/domain/BoardFactory.java @@ -0,0 +1,72 @@ +package domain; + +import domain.piece.Piece; +import domain.piece.PieceType; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BoardFactory { + + private static final int BACK_Y = 0; + private static final int KING_Y = 1; + private static final int CANNON_Y = 2; + private static final int SOLDIER_Y = 3; + + private static final List CANNON_X = List.of(2, 8); + private static final List CHARIOT_X = List.of(1, 9); + private static final List GUARD_X = List.of(4, 6); + private static final List KING_X = List.of(5); + private static final List SOLIDER_X = List.of(1, 3, 5, 7, 9); + private static final List FORMATION_X = List.of(2, 3, 7, 8); + + public static Board createBoard(Formation choFormation, Formation hanFormation) { + Map board = new HashMap<>(); + for (int x = 1; x <= 9; x++) { + for (int y = 1; y <= 10; y++) { + placePiece(board, Position.of(x, y), PieceType.EMPTY.create(Side.NONE)); + } + } + placePieces(board, choFormation, Side.CHO); + placePieces(board, hanFormation, Side.HAN); + + return new Board(board); + } + + private static void placePieces(Map board, Formation formation, Side side) { + List rows = getRowForSide(side); + placePiece(board, CANNON_X, rows.get(CANNON_Y), PieceType.CANNON, side); + placePiece(board, CHARIOT_X, rows.get(BACK_Y), PieceType.CHARIOT, side); + placePiece(board, GUARD_X, rows.get(BACK_Y), PieceType.GUARD, side); + placePiece(board, KING_X, rows.get(KING_Y), PieceType.KING, side); + placePiece(board, SOLIDER_X, rows.get(SOLDIER_Y), PieceType.SOLDIER, side); + placeFormationPiece(board, formation, FORMATION_X, rows.get(BACK_Y), side); + } + + private static List getRowForSide(Side side) { + if (side == Side.HAN) { + return List.of(1, 2, 3, 4); + } + return List.of(10, 9, 8, 7); + } + + private static void placePiece(Map board, Position position, Piece piece) { + board.put(position, piece); + } + + private static void placePiece(Map board, List xPositions, int y, PieceType pieceType, + Side side) { + for (int x : xPositions) { + board.put(Position.of(x, y), pieceType.create(side)); + } + } + + private static void placeFormationPiece(Map board, Formation formation, List xPositions, + int y, + Side side) { + List pieceTypes = formation.getPieceTypes(); + for (int i = 0; i < pieceTypes.size(); i++) { + board.put(Position.of(xPositions.get(i), y), pieceTypes.get(i).create(side)); + } + } +} diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index f53376dc46..d5dc618fcd 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -9,8 +9,8 @@ public class Game { private final Board board; private final Turn turn; - public Game(Formation choFormation, Formation hanFormation) { - this.board = new Board(choFormation, hanFormation); + public Game(Board board) { + this.board = board; this.turn = new Turn(Side.CHO); } diff --git a/src/test/java/BoardFactoryTest.java b/src/test/java/BoardFactoryTest.java new file mode 100644 index 0000000000..9b79fa4f80 --- /dev/null +++ b/src/test/java/BoardFactoryTest.java @@ -0,0 +1,70 @@ +import static org.assertj.core.api.Assertions.assertThat; + +import domain.Board; +import domain.BoardFactory; +import domain.Formation; +import domain.Position; +import domain.Side; +import domain.piece.Elephant; +import domain.piece.Horse; +import domain.piece.Piece; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class BoardFactoryTest { + + @Nested + @DisplayName("장기판 생성") + class CreateBoard { + + @DisplayName("장기판의 모든 좌표를 초기화한다.") + @Test + void 장기판의_모든_좌표를_초기화한다() { + Board board = BoardFactory.createBoard(Formation.from("1"), Formation.from("1")); + Map pieces = board.getBoard(); + + assertThat(pieces).hasSize(90); + for (int x = 1; x <= 9; x++) { + for (int y = 1; y <= 10; y++) { + assertThat(pieces.get(Position.of(x, y))).isNotNull(); + } + } + } + + @DisplayName("초나라 포진을 상마마상으로 배치한다.") + @Test + void 초나라_포진을_상마마상으로_배치한다() { + Board board = BoardFactory.createBoard(Formation.from("1"), Formation.from("2")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(2, 10))).isInstanceOf(Elephant.class); + assertThat(pieces.get(Position.of(3, 10))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(7, 10))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(8, 10))).isInstanceOf(Elephant.class); + } + + @DisplayName("한나라 포진을 마상마상으로 배치한다.") + @Test + void 한나라_포진을_마상마상으로_배치한다() { + Board board = BoardFactory.createBoard(Formation.from("1"), Formation.from("4")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(2, 1))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(3, 1))).isInstanceOf(Elephant.class); + assertThat(pieces.get(Position.of(7, 1))).isInstanceOf(Horse.class); + assertThat(pieces.get(Position.of(8, 1))).isInstanceOf(Elephant.class); + } + + @DisplayName("각 궁이 올바른 진영에 배치된다.") + @Test + void 각_궁이_올바른_진영에_배치된다() { + Board board = BoardFactory.createBoard(Formation.from("1"), Formation.from("2")); + Map pieces = board.getBoard(); + + assertThat(pieces.get(Position.of(5, 2)).isSameSide(Side.HAN)).isTrue(); + assertThat(pieces.get(Position.of(5, 9)).isSameSide(Side.CHO)).isTrue(); + } + } +} diff --git a/src/test/java/BoardTest.java b/src/test/java/BoardTest.java index b2dba3ecdf..ee78670a5d 100644 --- a/src/test/java/BoardTest.java +++ b/src/test/java/BoardTest.java @@ -1,60 +1,17 @@ import static org.assertj.core.api.Assertions.assertThat; import domain.Board; +import domain.BoardFactory; import domain.Formation; import domain.Position; -import domain.Side; import domain.piece.Chariot; -import domain.piece.Elephant; import domain.piece.Empty; -import domain.piece.Horse; -import domain.piece.Piece; -import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; public class BoardTest { - @Nested - @DisplayName("장기판 초기화") - class InitBoard { - - @DisplayName("초나라 포진을 상마마상으로 배치한다.") - @Test - void 초나라_포진을_상마마상으로_배치한다() { - Board board = new Board(Formation.from("1"), Formation.from("2")); - Map pieces = board.getBoard(); - - assertThat(pieces.get(Position.of(2, 10))).isInstanceOf(Elephant.class); - assertThat(pieces.get(Position.of(3, 10))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(7, 10))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(8, 10))).isInstanceOf(Elephant.class); - } - - @DisplayName("한나라 포진을 마상마상으로 배치한다.") - @Test - void 한나라_포진을_마상마상으로_배치한다() { - Board board = new Board(Formation.from("1"), Formation.from("4")); - Map pieces = board.getBoard(); - - assertThat(pieces.get(Position.of(2, 1))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(3, 1))).isInstanceOf(Elephant.class); - assertThat(pieces.get(Position.of(7, 1))).isInstanceOf(Horse.class); - assertThat(pieces.get(Position.of(8, 1))).isInstanceOf(Elephant.class); - } - - @DisplayName("각 궁이 올바른 진영에 배치된다.") - @Test - void 각_궁이_올바른_진영에_배치된다() { - Board board = new Board(Formation.from("1"), Formation.from("2")); - Map pieces = board.getBoard(); - - assertThat(pieces.get(Position.of(5, 2)).isSameSide(Side.HAN)).isTrue(); - assertThat(pieces.get(Position.of(5, 9)).isSameSide(Side.CHO)).isTrue(); - } - } - @Nested @DisplayName("기물 이동") class MovePiece { @@ -62,7 +19,7 @@ class MovePiece { @DisplayName("기물을 이동하면 목적지에 기물이 위치한다.") @Test void 기물을_이동하면_목적지에_기물이_위치한다() { - Board board = new Board(Formation.from("1"), Formation.from("1")); + Board board = BoardFactory.createBoard(Formation.from("1"), Formation.from("1")); Position source = Position.of(1, 1); Position target = Position.of(1, 3); @@ -74,7 +31,7 @@ class MovePiece { @DisplayName("기물을 이동하면 출발지는 빈칸이 된다.") @Test void 기물을_이동하면_출발지는_빈칸이_된다() { - Board board = new Board(Formation.from("1"), Formation.from("1")); + Board board = BoardFactory.createBoard(Formation.from("1"), Formation.from("1")); Position source = Position.of(1, 1); Position target = Position.of(1, 3); diff --git a/src/test/java/GameTest.java b/src/test/java/GameTest.java index 51d4a09170..ce75d065f2 100644 --- a/src/test/java/GameTest.java +++ b/src/test/java/GameTest.java @@ -1,5 +1,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import domain.Board; +import domain.BoardFactory; import domain.Formation; import domain.Game; import domain.Position; @@ -22,7 +24,7 @@ public class GameTest { @DisplayName("초 턴에 한 기물을 선택한 경우 이동할 수 없다.") @Test void 초_턴에_한_기물을_선택한_경우_이동할_수_없다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position sourcePosition = Position.of(1, 4); Position targetPosition = Position.of(1, 1); @@ -34,7 +36,7 @@ public class GameTest { @DisplayName("한 턴에 초 기물을 선택한 경우 이동할 수 없다.") @Test void 한_턴에_초_기물을_선택한_경우_이동할_수_없다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); moveBySide(game, Side.CHO, Position.of(1, 7), Position.of(1, 6)); Assertions.assertThatThrownBy(() -> game.move(Position.of(3, 7), Position.of(3, 6))) @@ -45,7 +47,7 @@ public class GameTest { @DisplayName("출발지에 기물이 존재하지 않는 경우 이동할 수 없다.") @Test void 기물이_존재하지_않는_경우_이동할_수_없다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position sourcePosition = Position.of(5, 5); Position targetPosition = Position.of(1, 1); @@ -65,7 +67,7 @@ class ChariotMove { @Test @DisplayName("차가 세로로 이동한다.") void 차가_세로로_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(1, 1); Position target = Position.of(1, 3); @@ -78,7 +80,7 @@ class ChariotMove { @Test @DisplayName("차가 가로로 이동한다.") void 차가_가로로_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(1, 1); Position mid = Position.of(1, 2); Position target = Position.of(3, 2); @@ -92,7 +94,7 @@ class ChariotMove { @Test @DisplayName("차가 대각선으로 이동하면 예외가 발생한다.") void 차가_대각선으로_이동하면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(1, 1); Position target = Position.of(3, 3); @@ -108,7 +110,7 @@ class ElephantMove { @Test @DisplayName("상이 대각선 방향으로 이동한다.") void 상이_대각선_방향으로_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(2, 1); Position target = Position.of(4, 4); @@ -121,7 +123,7 @@ class ElephantMove { @Test @DisplayName("상이 이동 불가능한 위치로 이동하면 예외가 발생한다.") void 상이_이동_불가능한_위치로_이동하면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(2, 1); Position target = Position.of(3, 2); @@ -137,7 +139,7 @@ class HorseMove { @Test @DisplayName("마가 날 일자로 이동한다.") void 마가_날_일자로_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("4")); + Game game = createGame("1", "4"); Position source = Position.of(2, 1); Position target = Position.of(3, 3); @@ -150,7 +152,7 @@ class HorseMove { @Test @DisplayName("마가 이동 불가능한 위치로 이동하면 예외가 발생한다.") void 마가_이동_불가능한_위치로_이동하면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("4")); + Game game = createGame("1", "4"); Position source = Position.of(2, 1); Position target = Position.of(4, 4); @@ -166,7 +168,7 @@ class CannonMove { @Test @DisplayName("포가 기물을 하나 뛰어넘어 이동한다.") void 포가_기물을_하나_뛰어넘어_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); moveBySide(game, Side.HAN, Position.of(4, 1), Position.of(4, 2)); moveBySide(game, Side.HAN, Position.of(4, 2), Position.of(4, 3)); @@ -179,7 +181,7 @@ class CannonMove { @Test @DisplayName("포가 뛰어넘을 기물이 없으면 예외가 발생한다.") void 포가_뛰어넘을_기물이_없으면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(2, 3); Position target = Position.of(5, 3); @@ -190,7 +192,7 @@ class CannonMove { @Test @DisplayName("포가 포를 뛰어넘으면 예외가 발생한다.") void 포가_포를_뛰어넘으면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); moveBySide(game, Side.HAN, Position.of(6, 1), Position.of(6, 2)); moveBySide(game, Side.HAN, Position.of(6, 2), Position.of(6, 3)); @@ -210,7 +212,7 @@ class GuardMove { @Test @DisplayName("사가 한 칸 이동한다.") void 사가_한_칸_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(4, 1); Position target = Position.of(5, 1); @@ -223,7 +225,7 @@ class GuardMove { @Test @DisplayName("사가 두 칸 이상 이동하면 예외가 발생한다.") void 사가_두_칸_이상_이동하면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(4, 1); Position target = Position.of(4, 3); @@ -239,7 +241,7 @@ class KingMove { @Test @DisplayName("궁이 한 칸 이동한다.") void 궁이_한_칸_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(5, 2); Position target = Position.of(5, 1); @@ -252,7 +254,7 @@ class KingMove { @Test @DisplayName("궁이 두 칸 이상 이동하면 예외가 발생한다.") void 궁이_두_칸_이상_이동하면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(5, 2); Position target = Position.of(5, 4); @@ -268,7 +270,7 @@ class SoldierMove { @Test @DisplayName("졸이 앞으로 한 칸 이동한다.") void 졸이_앞으로_한_칸_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(1, 7); Position target = Position.of(1, 6); @@ -281,7 +283,7 @@ class SoldierMove { @Test @DisplayName("병이 앞으로 한 칸 이동한다.") void 병이_앞으로_한_칸_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(1, 4); Position target = Position.of(1, 5); @@ -294,7 +296,7 @@ class SoldierMove { @Test @DisplayName("졸이 옆으로 한 칸 이동한다.") void 졸이_옆으로_한_칸_이동한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(1, 7); Position target = Position.of(2, 7); @@ -306,7 +308,7 @@ class SoldierMove { @Test @DisplayName("졸이 뒤로 이동하면 예외가 발생한다.") void 졸이_뒤로_이동하면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(1, 7); Position target = Position.of(1, 8); @@ -322,7 +324,7 @@ class CapturePiece { @Test @DisplayName("적 기물을 잡을 수 있다.") void 적_기물을_잡을_수_있다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); moveBySide(game, Side.HAN, Position.of(1, 4), Position.of(2, 4)); moveBySide(game, Side.HAN, Position.of(1, 1), Position.of(1, 7)); @@ -334,7 +336,7 @@ class CapturePiece { @Test @DisplayName("아군 기물을 잡으면 예외가 발생한다.") void 아군_기물을_잡으면_예외가_발생한다() { - Game game = new Game(Formation.from("1"), Formation.from("1")); + Game game = createGame("1", "1"); Position source = Position.of(1, 1); Position target = Position.of(1, 4); @@ -345,6 +347,11 @@ class CapturePiece { } } + private Game createGame(String choFormation, String hanFormation) { + Board board = BoardFactory.createBoard(Formation.from(choFormation), Formation.from(hanFormation)); + return new Game(board); + } + private void moveBySide(Game game, Side side, Position source, Position target) { if (game.getCurrentTurn() != side) { passCurrentTurn(game); From bfc79bcd2d777ac1a1f19efebee5b31b436eba99 Mon Sep 17 00:00:00 2001 From: yeo-li Date: Thu, 2 Apr 2026 21:08:50 +0900 Subject: [PATCH 30/34] =?UTF-8?q?refactor:=20Elephant=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A3=BC=EC=9E=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/piece/Elephant.java | 6 +++--- src/main/java/domain/piece/PieceType.java | 20 ++++++++++---------- src/test/java/domain/piece/ElephantTest.java | 20 ++++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/domain/piece/Elephant.java b/src/main/java/domain/piece/Elephant.java index a51db3faae..7a89db161b 100644 --- a/src/main/java/domain/piece/Elephant.java +++ b/src/main/java/domain/piece/Elephant.java @@ -3,7 +3,7 @@ import domain.Direction; import domain.Position; import domain.Side; -import domain.strategy.PathMovement; +import domain.strategy.MovementStrategy; import java.util.List; public class Elephant extends Piece { @@ -19,8 +19,8 @@ public class Elephant extends Piece { List.of(Direction.LEFT, Direction.DOWN_LEFT, Direction.DOWN_LEFT) ); - public Elephant(Side side) { - super(side, new PathMovement()); + public Elephant(Side side, MovementStrategy movementStrategy) { + super(side, movementStrategy); } @Override diff --git a/src/main/java/domain/piece/PieceType.java b/src/main/java/domain/piece/PieceType.java index 3731e14c0a..4795452225 100644 --- a/src/main/java/domain/piece/PieceType.java +++ b/src/main/java/domain/piece/PieceType.java @@ -5,54 +5,54 @@ import domain.strategy.PathMovement; public enum PieceType { - ELEPHANT{ + ELEPHANT { @Override public Piece create(Side side) { - return new Elephant(side); + return new Elephant(side, new PathMovement()); } }, - CANNON{ + CANNON { @Override public Piece create(Side side) { return new Cannon(side, new LinearMovement()); } }, - CHARIOT{ + CHARIOT { @Override public Piece create(Side side) { return new Chariot(side, new LinearMovement()); } }, - GUARD{ + GUARD { @Override public Piece create(Side side) { return new Guard(side, new PathMovement()); } }, - HORSE{ + HORSE { @Override public Piece create(Side side) { return new Horse(side, new PathMovement()); } }, - KING{ + KING { @Override public Piece create(Side side) { return new King(side, new PathMovement()); } }, - EMPTY{ + EMPTY { @Override public Piece create(Side side) { return new Empty(); } }, - SOLDIER{ + SOLDIER { @Override public Piece create(Side side) { return new Soldier(side, new PathMovement()); } }; - public abstract Piece create (Side side); + public abstract Piece create(Side side); } diff --git a/src/test/java/domain/piece/ElephantTest.java b/src/test/java/domain/piece/ElephantTest.java index 5f7bed4230..9b0a5b1a67 100644 --- a/src/test/java/domain/piece/ElephantTest.java +++ b/src/test/java/domain/piece/ElephantTest.java @@ -13,35 +13,35 @@ class ElephantTest { @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") @CsvSource({ - "3, 1, 5, 4", - "3, 1, 1, 4" + "3, 1, 5, 4", + "3, 1, 1, 4" }) @DisplayName("상이 이동 가능한 위치로 이동하면 경로를 반환한다") void 상이_이동_가능한_위치로_이동하면_경로를_반환한다( - int sourceX, int sourceY, int targetX, int targetY + int sourceX, int sourceY, int targetX, int targetY ) { Position source = Position.of(sourceX, sourceY); Position target = Position.of(targetX, targetY); - Piece piece = new Elephant(IRRELEVANT_SIDE); + Piece piece = PieceType.ELEPHANT.create(IRRELEVANT_SIDE); Assertions.assertThatCode(() -> piece.findRoute(source, target)) - .doesNotThrowAnyException(); + .doesNotThrowAnyException(); } @ParameterizedTest(name = "[{index}] ({0},{1}) -> ({2},{3})") @CsvSource({ - "3, 1, 3, 2", - "3, 1, 4, 1" + "3, 1, 3, 2", + "3, 1, 4, 1" }) @DisplayName("상이 이동 불가능한 위치로 이동하면 예외가 발생한다") void 상이_이동_불가능한_위치로_이동하면_예외가_발생한다( - int sourceX, int sourceY, int targetX, int targetY + int sourceX, int sourceY, int targetX, int targetY ) { Position source = Position.of(sourceX, sourceY); Position target = Position.of(targetX, targetY); - Piece piece = new Elephant(IRRELEVANT_SIDE); + Piece piece = PieceType.ELEPHANT.create(IRRELEVANT_SIDE); Assertions.assertThatThrownBy(() -> piece.findRoute(source, target)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } } From 9b1789923bd2eb49c04e02dd81cb25c5b876cc5e Mon Sep 17 00:00:00 2001 From: yeo-li Date: Thu, 2 Apr 2026 23:25:45 +0900 Subject: [PATCH 31/34] =?UTF-8?q?refactor:=20BoardSpec=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B3=B4=EB=93=9C=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=EC=99=80=20=EC=8B=9C=EC=9E=91=20=ED=84=B4=20=ED=95=98?= =?UTF-8?q?=EB=93=9C=EC=BD=94=EB=94=A9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/constant/BoardSpec.java | 13 +++++++++++++ src/main/java/domain/BoardFactory.java | 8 ++++---- src/main/java/domain/Game.java | 3 ++- src/main/java/domain/Position.java | 8 +++++--- src/main/java/util/Parser.java | 4 +++- src/main/java/view/InputView.java | 15 ++++++++++----- src/main/java/view/OutputView.java | 23 +++++++++++++++-------- 7 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 src/main/java/constant/BoardSpec.java diff --git a/src/main/java/constant/BoardSpec.java b/src/main/java/constant/BoardSpec.java new file mode 100644 index 0000000000..6f6bd2392c --- /dev/null +++ b/src/main/java/constant/BoardSpec.java @@ -0,0 +1,13 @@ +package constant; + +import domain.Side; + +public class BoardSpec { + + public static final int MIN_X = 1; + public static final int MAX_X = 9; + public static final int MIN_Y = 1; + public static final int MAX_Y = 10; + + public static final Side DEFAULT_STARTING_SIDE = Side.CHO; +} diff --git a/src/main/java/domain/BoardFactory.java b/src/main/java/domain/BoardFactory.java index 1d49f77c13..771c215fcd 100644 --- a/src/main/java/domain/BoardFactory.java +++ b/src/main/java/domain/BoardFactory.java @@ -1,5 +1,6 @@ package domain; +import constant.BoardSpec; import domain.piece.Piece; import domain.piece.PieceType; import java.util.HashMap; @@ -22,8 +23,8 @@ public class BoardFactory { public static Board createBoard(Formation choFormation, Formation hanFormation) { Map board = new HashMap<>(); - for (int x = 1; x <= 9; x++) { - for (int y = 1; y <= 10; y++) { + for (int x = BoardSpec.MIN_X; x <= BoardSpec.MAX_X; x++) { + for (int y = BoardSpec.MIN_Y; y <= BoardSpec.MAX_Y; y++) { placePiece(board, Position.of(x, y), PieceType.EMPTY.create(Side.NONE)); } } @@ -62,8 +63,7 @@ private static void placePiece(Map board, List xPositi } private static void placeFormationPiece(Map board, Formation formation, List xPositions, - int y, - Side side) { + int y, Side side) { List pieceTypes = formation.getPieceTypes(); for (int i = 0; i < pieceTypes.size(); i++) { board.put(Position.of(xPositions.get(i), y), pieceTypes.get(i).create(side)); diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index d5dc618fcd..b32f1f27b1 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -1,5 +1,6 @@ package domain; +import constant.BoardSpec; import domain.piece.Piece; import java.util.List; import java.util.Map; @@ -11,7 +12,7 @@ public class Game { public Game(Board board) { this.board = board; - this.turn = new Turn(Side.CHO); + this.turn = new Turn(BoardSpec.DEFAULT_STARTING_SIDE); } public void move(Position sourcePosition, Position targetPosition) { diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index fed1d2eda6..7390b6b1a6 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -1,11 +1,13 @@ package domain; +import constant.BoardSpec; import java.util.HashMap; import java.util.Map; public class Position { - private static final String INVALID_POSITION_RANGE = "x 좌표는 1~9, y 좌표는 1~10, 사이여야 합니다."; + private static final String INVALID_POSITION_RANGE = String.format("x 좌표는 %d~%d, y 좌표는 %d~%d, 사이여야 합니다.", + BoardSpec.MIN_X, BoardSpec.MAX_X, BoardSpec.MIN_Y, BoardSpec.MAX_Y); private static final Map CACHE = new HashMap<>(); private final int x; @@ -24,7 +26,7 @@ public static Position of(int x, int y) { public boolean canMove(int dx, int dy) { int nx = x + dx; int ny = y + dy; - return 1 <= nx && nx <= 9 && 1 <= ny && ny <= 10; + return BoardSpec.MIN_X <= nx && nx <= BoardSpec.MAX_X && BoardSpec.MIN_Y <= ny && ny <= BoardSpec.MAX_Y; } public Position createPosition(int dx, int dy) { @@ -32,7 +34,7 @@ public Position createPosition(int dx, int dy) { } private static void validateOutOfRange(int x, int y) { - if(!((1 <= x && x <= 9) && (1 <= y && y <= 10))) { + if (!((BoardSpec.MIN_X <= x && x <= BoardSpec.MAX_X) && (BoardSpec.MIN_Y <= y && y <= BoardSpec.MAX_Y))) { throw new IllegalArgumentException(INVALID_POSITION_RANGE); } } diff --git a/src/main/java/util/Parser.java b/src/main/java/util/Parser.java index b5a06e7141..1b14baf176 100644 --- a/src/main/java/util/Parser.java +++ b/src/main/java/util/Parser.java @@ -2,11 +2,13 @@ public class Parser { + private static final String PARSE_INPUT_MESSAGE = "유효한 입력값이 아닙니다."; + public static int parseInput(String input) { try { return Integer.parseInt(input); } catch (NumberFormatException e) { - throw new IllegalArgumentException("유효한 입력값이 아닙니다."); + throw new IllegalArgumentException(PARSE_INPUT_MESSAGE); } } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index c064e5b0f1..5a7287a391 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,11 +1,16 @@ package view; +import constant.BoardSpec; import domain.Formation; import java.util.Scanner; public class InputView { - private final String READ_FORMATION_MESSAGE = "[%s] 포진을 선택해주세요."; + private static final String READ_FORMATION_MESSAGE = "[%s] 포진을 선택해주세요."; + private static final String READ_SOURCE_X_POSITION = String.format("움직일 기물의 x 좌표를 입력해주세요. (x 범위 %d ~ %d)", + BoardSpec.MIN_X, BoardSpec.MAX_X); + private static final String READ_SOURCE_Y_POSITION = String.format("움직일 기물의 y 좌표를 입력해주세요. (y 범위 %d~ %d)", + BoardSpec.MIN_Y, BoardSpec.MAX_Y); private final Scanner scanner = new Scanner(System.in); @@ -23,23 +28,23 @@ public String readHanFormation() { public String readSourceXPosition() { System.out.println(); - System.out.println("움직일 기물의 x 좌표를 입력해주세요. (x 범위 1 ~ 9)"); + System.out.println(READ_SOURCE_X_POSITION); return scanner.nextLine(); } public String readSourceYPosition() { - System.out.println("움직일 기물의 y 좌표를 입력해주세요. (y 범위 1 ~ 10)"); + System.out.println(); return scanner.nextLine(); } public String readTargetXPosition() { System.out.println(); - System.out.println("목적지 x 좌표를 입력해주세요. (x 범위 1 ~ 9)"); + System.out.println(READ_SOURCE_X_POSITION); return scanner.nextLine(); } public String readTargetYPosition() { - System.out.println("목적지 y 좌표를 입력해주세요. (y 범위 1 ~ 10)"); + System.out.println(READ_SOURCE_Y_POSITION); return scanner.nextLine(); } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 1e4cbc7d8a..8f843025af 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,5 +1,6 @@ package view; +import constant.BoardSpec; import domain.Position; import domain.Side; import domain.piece.Piece; @@ -11,12 +12,18 @@ public class OutputView { private static final String BLUE = "\u001B[34m"; private static final String RESET = "\u001B[0m"; private static final String ERROR_PREFIX = "[ERROR] "; + private static final String BOARD_HEADER = " 1 2 3 4 5 6 7 8 9"; + private static final String CHO_TURN_MESSAGE = "초의 차례입니다."; + private static final String HAN_TURN_MESSAGE = "한의 차례입니다."; + private static final String SINGLE_DIGIT_ROW_FORMAT = " %d "; + private static final String DOUBLE_DIGIT_ROW_FORMAT = "%d "; + private static final String PIECE_FORMAT = "%s%s "; public void printBoardStatus(Map board) { System.out.println(); - System.out.println(" 1 2 3 4 5 6 7 8 9"); + System.out.println(BOARD_HEADER); - for (int y = 1; y <= 10; y++) { + for (int y = BoardSpec.MIN_Y; y <= BoardSpec.MAX_Y; y++) { printRowNumber(y); for (int x = 1; x <= 9; x++) { Piece piece = board.get(Position.of(x, y)); @@ -29,23 +36,23 @@ public void printBoardStatus(Map board) { public void printCurrentTurn(Side currentSide) { System.out.println(); if (currentSide == Side.CHO) { - System.out.println("초의 차례입니다."); + System.out.println(CHO_TURN_MESSAGE); return; } - System.out.println("한의 차례입니다."); + System.out.println(HAN_TURN_MESSAGE); } private void printRowNumber(int y) { - if (y < 10) { - System.out.print(" " + y + " "); + if (y < BoardSpec.MAX_Y) { + System.out.printf(SINGLE_DIGIT_ROW_FORMAT, y); } else { - System.out.print(y + " "); + System.out.printf(DOUBLE_DIGIT_ROW_FORMAT, y); } } private void printPiece(Piece piece) { String color = getColor(piece.getSide()); - System.out.print(color + piece.getName() + " "); + System.out.printf(PIECE_FORMAT, color, piece.getName()); } private String getColor(Side side) { From 0cf6e2ebef5dd5d3cb31394ed1a56cc965ac967f Mon Sep 17 00:00:00 2001 From: yeo-li Date: Thu, 2 Apr 2026 23:35:57 +0900 Subject: [PATCH 32/34] =?UTF-8?q?refactor:=20Position=20CACHE=20key-value?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=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/Position.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java index 7390b6b1a6..ee156718ab 100644 --- a/src/main/java/domain/Position.java +++ b/src/main/java/domain/Position.java @@ -9,7 +9,7 @@ public class Position { private static final String INVALID_POSITION_RANGE = String.format("x 좌표는 %d~%d, y 좌표는 %d~%d, 사이여야 합니다.", BoardSpec.MIN_X, BoardSpec.MAX_X, BoardSpec.MIN_Y, BoardSpec.MAX_Y); - private static final Map CACHE = new HashMap<>(); + private static final Map> CACHE = new HashMap<>(); private final int x; private final int y; @@ -20,7 +20,9 @@ private Position(int x, int y) { public static Position of(int x, int y) { validateOutOfRange(x, y); - return CACHE.computeIfAbsent(x * 100 + y, k -> new Position(x, y)); + return CACHE + .computeIfAbsent(x, k -> new HashMap<>()) + .computeIfAbsent(y, k -> new Position(x, y)); } public boolean canMove(int dx, int dy) { From 3f4be1ba6785fe79e4f32717546d16aac28bb212 Mon Sep 17 00:00:00 2001 From: yeo-li Date: Fri, 3 Apr 2026 01:45:22 +0900 Subject: [PATCH 33/34] =?UTF-8?q?refactor:=20Side.NONE=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EA=B2=8C=EC=9E=84=20=EC=A2=85=EB=A3=8C?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4(isGameEnd)=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 2 +- src/main/java/domain/Board.java | 10 +++++++ src/main/java/domain/BoardFactory.java | 3 +- src/main/java/domain/Game.java | 5 +++- src/main/java/domain/Side.java | 3 +- src/main/java/domain/piece/Cannon.java | 2 +- src/main/java/domain/piece/Empty.java | 6 ++-- src/main/java/domain/piece/Piece.java | 6 +--- src/main/java/domain/piece/PieceType.java | 6 ---- src/main/java/view/OutputView.java | 8 ++--- src/test/java/GameTest.java | 21 +++++++++++++ src/test/java/domain/piece/CannonTest.java | 30 +++++++++---------- 12 files changed, 62 insertions(+), 40 deletions(-) diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 23ab520368..1ecf69d922 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -26,7 +26,7 @@ public void run() { } private void processMove(Game game) { - while (true) { + while (!game.isGameEnd()) { Side currentTurn = game.getCurrentTurn(); outputView.printCurrentTurn(currentTurn); Position sourcePosition = readSourcePosition(); diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index dbe5fbda06..150d13a296 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -1,6 +1,7 @@ package domain; import domain.piece.Empty; +import domain.piece.King; import domain.piece.Piece; import java.util.ArrayList; import java.util.List; @@ -36,4 +37,13 @@ public List findPiecesOnRoute(List route, Position targetPositi public Piece getPiece(Position position) { return board.get(position); } + + public boolean hasKing(Side side) { + for (Piece piece : board.values()) { + if (piece instanceof King && piece.isSameSide(side)) { + return false; + } + } + return true; + } } diff --git a/src/main/java/domain/BoardFactory.java b/src/main/java/domain/BoardFactory.java index 771c215fcd..f6d9c1c3ce 100644 --- a/src/main/java/domain/BoardFactory.java +++ b/src/main/java/domain/BoardFactory.java @@ -1,6 +1,7 @@ package domain; import constant.BoardSpec; +import domain.piece.Empty; import domain.piece.Piece; import domain.piece.PieceType; import java.util.HashMap; @@ -25,7 +26,7 @@ public static Board createBoard(Formation choFormation, Formation hanFormation) Map board = new HashMap<>(); for (int x = BoardSpec.MIN_X; x <= BoardSpec.MAX_X; x++) { for (int y = BoardSpec.MIN_Y; y <= BoardSpec.MAX_Y; y++) { - placePiece(board, Position.of(x, y), PieceType.EMPTY.create(Side.NONE)); + placePiece(board, Position.of(x, y), new Empty()); } } placePieces(board, choFormation, Side.CHO); diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index b32f1f27b1..b1c8134353 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -21,7 +21,6 @@ public void move(Position sourcePosition, Position targetPosition) { turn.next(); } - private void validateMovement(Position sourcePosition, Position targetPosition) { Piece sourcePiece = board.getPiece(sourcePosition); Piece targetPiece = board.getPiece(targetPosition); @@ -31,6 +30,10 @@ private void validateMovement(Position sourcePosition, Position targetPosition) sourcePiece.checkRoute(pieces); } + public boolean isGameEnd() { + return board.hasKing(Side.CHO) || board.hasKing(Side.HAN); + } + public Side getCurrentTurn() { return turn.current(); } diff --git a/src/main/java/domain/Side.java b/src/main/java/domain/Side.java index f954a3af07..16ac088c11 100644 --- a/src/main/java/domain/Side.java +++ b/src/main/java/domain/Side.java @@ -2,8 +2,7 @@ public enum Side { CHO, - HAN, - NONE; + HAN; public Side opposite() { if (this == CHO) { diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index e3bf2cadeb..a8ec9d3500 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -42,7 +42,7 @@ public void checkRoute(List pieces) { @Override public void checkTarget(Piece piece) { - if (piece.getSide().equals(side)) { + if (piece.isSameSide(side)) { throw new IllegalArgumentException(CANNOT_CAPTURE_OWN_PIECE); } diff --git a/src/main/java/domain/piece/Empty.java b/src/main/java/domain/piece/Empty.java index 2e270a28cb..0a83615af6 100644 --- a/src/main/java/domain/piece/Empty.java +++ b/src/main/java/domain/piece/Empty.java @@ -1,14 +1,12 @@ package domain.piece; import domain.Position; -import domain.Side; - import java.util.List; -public class Empty extends Piece{ +public class Empty extends Piece { public Empty() { - super(Side.NONE, null); + super(null, null); } @Override diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 2c1d8c0222..2005e2640d 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -25,12 +25,8 @@ public boolean isSameSide(Side side) { return this.side == side; } - public Side getSide() { - return side; - } - public void checkTarget(Piece piece) { - if (side.equals(piece.side)) { + if (piece.isSameSide(side)) { throw new IllegalArgumentException(CANNOT_CAPTURE_OWN_PIECE); } } diff --git a/src/main/java/domain/piece/PieceType.java b/src/main/java/domain/piece/PieceType.java index 4795452225..96f7ce0402 100644 --- a/src/main/java/domain/piece/PieceType.java +++ b/src/main/java/domain/piece/PieceType.java @@ -41,12 +41,6 @@ public Piece create(Side side) { return new King(side, new PathMovement()); } }, - EMPTY { - @Override - public Piece create(Side side) { - return new Empty(); - } - }, SOLDIER { @Override public Piece create(Side side) { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 8f843025af..38ffdf5072 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -51,15 +51,15 @@ private void printRowNumber(int y) { } private void printPiece(Piece piece) { - String color = getColor(piece.getSide()); + String color = getColor(piece); System.out.printf(PIECE_FORMAT, color, piece.getName()); } - private String getColor(Side side) { - if (side == Side.HAN) { + private String getColor(Piece piece) { + if (piece.isSameSide(Side.HAN)) { return RED; } - if (side == Side.CHO) { + if (piece.isSameSide(Side.CHO)) { return BLUE; } return RESET; diff --git a/src/test/java/GameTest.java b/src/test/java/GameTest.java index ce75d065f2..cb2046818e 100644 --- a/src/test/java/GameTest.java +++ b/src/test/java/GameTest.java @@ -14,6 +14,8 @@ import domain.piece.Horse; import domain.piece.King; import domain.piece.Soldier; +import domain.piece.PieceType; +import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -56,6 +58,25 @@ public class GameTest { .hasMessageContaining("해당 위치에 기물이 존재하지 않습니다."); } + @DisplayName("양측 궁이 모두 존재하면 게임이 종료되지 않는다.") + @Test + void 양측_궁이_모두_존재하면_게임이_종료되지_않는다() { + Game game = createGame("1", "1"); + + assertThat(game.isGameEnd()).isFalse(); + } + + @DisplayName("한쪽 궁이 없으면 게임이 종료된다.") + @Test + void 한쪽_궁이_없으면_게임이_종료된다() { + Board board = new Board(Map.of( + Position.of(5, 2), PieceType.KING.create(Side.HAN) + )); + Game game = new Game(board); + + assertThat(game.isGameEnd()).isTrue(); + } + @Nested @DisplayName("기물 이동") class MovePiece { diff --git a/src/test/java/domain/piece/CannonTest.java b/src/test/java/domain/piece/CannonTest.java index 0943b0fa13..b798f2edab 100644 --- a/src/test/java/domain/piece/CannonTest.java +++ b/src/test/java/domain/piece/CannonTest.java @@ -2,20 +2,19 @@ import domain.Position; import domain.Side; +import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.List; - class CannonTest { @DisplayName("포는 이동한 경로를 반환한다.") @Test void 포는_이동한_경로를_반환한다() { // given - Position sourcePosition = Position.of(1,1); - Position targetPosition = Position.of(1,5); + Position sourcePosition = Position.of(1, 1); + Position targetPosition = Position.of(1, 5); Piece piece = PieceType.CANNON.create(Side.HAN); // when @@ -23,7 +22,7 @@ class CannonTest { // then List expected = List.of(Position.of(1, 2), Position.of(1, 3), Position.of(1, 4)); - for(int i = 0; i < 3; i++) { + for (int i = 0; i < 3; i++) { Assertions.assertThat(positions.get(i)).isEqualTo(expected.get(i)); } } @@ -32,14 +31,14 @@ class CannonTest { @Test void 포는_목적지까지_이동할_수_없으면_예외를_발생한다() { // given - Position sourcePosition = Position.of(1,1); - Position targetPosition = Position.of(2,5); + Position sourcePosition = Position.of(1, 1); + Position targetPosition = Position.of(2, 5); Piece piece = PieceType.CANNON.create(Side.CHO); // when & then Assertions.assertThatThrownBy(() -> piece.findRoute(sourcePosition, targetPosition)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("이동할 수 없는 목적지입니다."); + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("이동할 수 없는 목적지입니다."); } @DisplayName("포 이동 경로에 포를 제외한 기물이 하나 존재해야 한다.") @@ -50,7 +49,7 @@ class CannonTest { // when Piece cannon = PieceType.CANNON.create(Side.CHO); Assertions.assertThatCode(() -> cannon.checkRoute(pieces)) - .doesNotThrowAnyException(); + .doesNotThrowAnyException(); } @DisplayName("포 이동 경로에 포가 존재하면 예외를 발생한다.") @@ -62,20 +61,21 @@ class CannonTest { // when & then Assertions.assertThatThrownBy(() -> cannon.checkRoute(pieces)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("포를 넘어갈 수 없습니다."); + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("포를 넘어갈 수 없습니다."); } @DisplayName("포 이동 경로에 기물이 두 개 이상 존재하면 예외를 발생한다.") @Test void 포_이동_경로에_기물이_두_개_이상_존재하면_예외를_발생한다() { // given - List pieces = List.of(PieceType.EMPTY.create(Side.NONE), PieceType.HORSE.create(Side.CHO), PieceType.ELEPHANT.create(Side.CHO)); + List pieces = List.of(new Empty(), PieceType.HORSE.create(Side.CHO), + PieceType.ELEPHANT.create(Side.CHO)); Piece cannon = PieceType.CANNON.create(Side.HAN); // when & then Assertions.assertThatThrownBy(() -> cannon.checkRoute(pieces)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("이동할 수 없는 목적지입니다."); + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("이동할 수 없는 목적지입니다."); } } From 85697734b802af1367cd8349b59d8ffa2403e79e Mon Sep 17 00:00:00 2001 From: yeo-li Date: Fri, 3 Apr 2026 02:04:01 +0900 Subject: [PATCH 34/34] =?UTF-8?q?style:=20=EA=B3=B5=EB=B0=B1=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=9C=EC=84=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 32 +++++++++---------- src/main/java/domain/Board.java | 12 +++---- src/main/java/domain/Formation.java | 5 ++- src/main/java/domain/Game.java | 18 +++++------ src/main/java/domain/piece/Cannon.java | 1 - src/main/java/domain/piece/Piece.java | 27 ++++++++-------- .../java/domain/strategy/PathMovement.java | 3 +- src/main/java/view/OutputView.java | 8 ++--- src/test/java/PositionTest.java | 23 +++++++------ src/test/java/domain/piece/PieceTest.java | 10 +++--- 10 files changed, 66 insertions(+), 73 deletions(-) diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 1ecf69d922..7855b27747 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -25,6 +25,16 @@ public void run() { processMove(game); } + private Game initGame() { + Formation choformation = readChoFormation(); + Formation hanformation = readHanFormation(); + + Board board = BoardFactory.createBoard(choformation, hanformation); + Game game = new Game(board); + outputView.printBoardStatus(game.getBoard()); + return game; + } + private void processMove(Game game) { while (!game.isGameEnd()) { Side currentTurn = game.getCurrentTurn(); @@ -40,11 +50,11 @@ private void processMove(Game game) { } } - private Position readTargetPosition() { + private Position readSourcePosition() { while (true) { try { - int x = Parser.parseInput(inputView.readTargetXPosition()); - int y = Parser.parseInput(inputView.readTargetYPosition()); + int x = Parser.parseInput(inputView.readSourceXPosition()); + int y = Parser.parseInput(inputView.readSourceYPosition()); return Position.of(x, y); } catch (IllegalArgumentException e) { outputView.printErrorMessage(e.getMessage()); @@ -52,11 +62,11 @@ private Position readTargetPosition() { } } - private Position readSourcePosition() { + private Position readTargetPosition() { while (true) { try { - int x = Parser.parseInput(inputView.readSourceXPosition()); - int y = Parser.parseInput(inputView.readSourceYPosition()); + int x = Parser.parseInput(inputView.readTargetXPosition()); + int y = Parser.parseInput(inputView.readTargetYPosition()); return Position.of(x, y); } catch (IllegalArgumentException e) { outputView.printErrorMessage(e.getMessage()); @@ -64,16 +74,6 @@ private Position readSourcePosition() { } } - private Game initGame() { - Formation choformation = readChoFormation(); - Formation hanformation = readHanFormation(); - - Board board = BoardFactory.createBoard(choformation, hanformation); - Game game = new Game(board); - outputView.printBoardStatus(game.getBoard()); - return game; - } - private Formation readChoFormation() { while (true) { try { diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 150d13a296..a090a36d9c 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -20,8 +20,8 @@ public void movePiece(Position sourcePosition, Position targetPosition) { board.put(sourcePosition, new Empty()); } - public Map getBoard() { - return Map.copyOf(board); + public Piece getPiece(Position position) { + return board.get(position); } public List findPiecesOnRoute(List route, Position targetPosition) { @@ -34,10 +34,6 @@ public List findPiecesOnRoute(List route, Position targetPositi return pieces; } - public Piece getPiece(Position position) { - return board.get(position); - } - public boolean hasKing(Side side) { for (Piece piece : board.values()) { if (piece instanceof King && piece.isSameSide(side)) { @@ -46,4 +42,8 @@ public boolean hasKing(Side side) { } return true; } + + public Map getBoard() { + return Map.copyOf(board); + } } diff --git a/src/main/java/domain/Formation.java b/src/main/java/domain/Formation.java index a46bdbf916..45645d03d9 100644 --- a/src/main/java/domain/Formation.java +++ b/src/main/java/domain/Formation.java @@ -1,7 +1,6 @@ package domain; import domain.piece.PieceType; - import java.util.List; public enum Formation { @@ -21,8 +20,8 @@ public enum Formation { } public static Formation from(String input) { - for(Formation formation : Formation.values()) { - if(formation.option.equals(input)) { + for (Formation formation : Formation.values()) { + if (formation.option.equals(input)) { return formation; } } diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index b1c8134353..3e705b2985 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -21,15 +21,6 @@ public void move(Position sourcePosition, Position targetPosition) { turn.next(); } - private void validateMovement(Position sourcePosition, Position targetPosition) { - Piece sourcePiece = board.getPiece(sourcePosition); - Piece targetPiece = board.getPiece(targetPosition); - sourcePiece.validateMovement(turn.current(), targetPiece); - List route = sourcePiece.findRoute(sourcePosition, targetPosition); - List pieces = board.findPiecesOnRoute(route, targetPosition); - sourcePiece.checkRoute(pieces); - } - public boolean isGameEnd() { return board.hasKing(Side.CHO) || board.hasKing(Side.HAN); } @@ -41,4 +32,13 @@ public Side getCurrentTurn() { public Map getBoard() { return board.getBoard(); } + + private void validateMovement(Position sourcePosition, Position targetPosition) { + Piece sourcePiece = board.getPiece(sourcePosition); + Piece targetPiece = board.getPiece(targetPosition); + sourcePiece.validateMovement(turn.current(), targetPiece); + List route = sourcePiece.findRoute(sourcePosition, targetPosition); + List pieces = board.findPiecesOnRoute(route, targetPosition); + sourcePiece.checkRoute(pieces); + } } diff --git a/src/main/java/domain/piece/Cannon.java b/src/main/java/domain/piece/Cannon.java index a8ec9d3500..cc3e9fbc52 100644 --- a/src/main/java/domain/piece/Cannon.java +++ b/src/main/java/domain/piece/Cannon.java @@ -18,7 +18,6 @@ public Cannon(Side side, MovementStrategy movementStrategy) { super(side, movementStrategy); } - @Override public List findRoute(Position sourcePosition, Position targetPosition) { return movementStrategy.findRoute(paths, sourcePosition, targetPosition); diff --git a/src/main/java/domain/piece/Piece.java b/src/main/java/domain/piece/Piece.java index 2005e2640d..46906d318d 100644 --- a/src/main/java/domain/piece/Piece.java +++ b/src/main/java/domain/piece/Piece.java @@ -9,7 +9,6 @@ public abstract class Piece { private static final String PIECE_NOT_FOUND = "해당 위치에 기물이 존재하지 않습니다."; private static final String NOT_OWN_PIECE = "선택한 기물은 아군 기물이 아닙니다."; - protected static final String CANNOT_CAPTURE_OWN_PIECE = "아군 기물은 잡을 수 없습니다."; protected static final String INVALID_TARGET_POSITION = "이동할 수 없는 목적지입니다."; @@ -21,8 +20,15 @@ protected Piece(Side side, MovementStrategy movementStrategy) { this.movementStrategy = movementStrategy; } - public boolean isSameSide(Side side) { - return this.side == side; + public void validateMovement(Side currentTurn, Piece targetPiece) { + if (this instanceof Empty) { + throw new IllegalArgumentException(PIECE_NOT_FOUND); + } + if (!side.equals(currentTurn)) { + throw new IllegalArgumentException(NOT_OWN_PIECE); + } + + checkTarget(targetPiece); } public void checkTarget(Piece piece) { @@ -31,8 +37,6 @@ public void checkTarget(Piece piece) { } } - public abstract List findRoute(Position sourcePosition, Position targetPosition); - public void checkRoute(List pieces) { for (Piece piece : pieces) { if (!(piece instanceof Empty)) { @@ -41,16 +45,11 @@ public void checkRoute(List pieces) { } } - public void validateMovement(Side currentTurn, Piece targetPiece) { - if (this instanceof Empty) { - throw new IllegalArgumentException(PIECE_NOT_FOUND); - } - if (!side.equals(currentTurn)) { - throw new IllegalArgumentException(NOT_OWN_PIECE); - } - - checkTarget(targetPiece); + public boolean isSameSide(Side side) { + return this.side == side; } + public abstract List findRoute(Position sourcePosition, Position targetPosition); + public abstract String getName(); } diff --git a/src/main/java/domain/strategy/PathMovement.java b/src/main/java/domain/strategy/PathMovement.java index 3e73f9e9a5..5d1a2c5ff0 100644 --- a/src/main/java/domain/strategy/PathMovement.java +++ b/src/main/java/domain/strategy/PathMovement.java @@ -2,11 +2,10 @@ import domain.Direction; import domain.Position; - import java.util.ArrayList; import java.util.List; -public class PathMovement extends MovementStrategy{ +public class PathMovement extends MovementStrategy { @Override protected List buildRoute(List path, Position source, Position target) { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 38ffdf5072..7796bb4c63 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -42,6 +42,10 @@ public void printCurrentTurn(Side currentSide) { System.out.println(HAN_TURN_MESSAGE); } + public void printErrorMessage(String errorMessage) { + System.out.println(ERROR_PREFIX + errorMessage); + } + private void printRowNumber(int y) { if (y < BoardSpec.MAX_Y) { System.out.printf(SINGLE_DIGIT_ROW_FORMAT, y); @@ -64,8 +68,4 @@ private String getColor(Piece piece) { } return RESET; } - - public void printErrorMessage(String errorMessage) { - System.out.println(ERROR_PREFIX + errorMessage); - } } diff --git a/src/test/java/PositionTest.java b/src/test/java/PositionTest.java index 5777519c08..8b0d78196c 100644 --- a/src/test/java/PositionTest.java +++ b/src/test/java/PositionTest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; - public class PositionTest { @Nested @@ -12,14 +11,14 @@ class FromTest { @ParameterizedTest @CsvSource({ - "0, 10", - "1, 11" + "0, 10", + "1, 11" }) void 목적지_좌표가_가로_1_9_세로_1_10을_벗어나면_예외가_발생한다(int x, int y) { // when & then Assertions.assertThatThrownBy(() -> Position.of(x, y)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("x 좌표는 1~9, y 좌표는 1~10, 사이여야 합니다."); + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("x 좌표는 1~9, y 좌표는 1~10, 사이여야 합니다."); } } @@ -28,9 +27,9 @@ class CamMoveTest { @ParameterizedTest @CsvSource({ - "1, 1, 0, 1", - "5, 5, 1, 0", - "9, 10, -1, -1" + "1, 1, 0, 1", + "5, 5, 1, 0", + "9, 10, -1, -1" }) void 이동_가능한_좌표이면_true를_반환한다(int x, int y, int dx, int dy) { // given @@ -43,10 +42,10 @@ class CamMoveTest { @ParameterizedTest @CsvSource({ - "1, 1, -1, 0", - "1, 1, 0, -1", - "9, 10, 1, 0", - "9, 10, 0, 1" + "1, 1, -1, 0", + "1, 1, 0, -1", + "9, 10, 1, 0", + "9, 10, 0, 1" }) void 이동_불가능한_좌표이면_false를_반환한다(int x, int y, int dx, int dy) { // given diff --git a/src/test/java/domain/piece/PieceTest.java b/src/test/java/domain/piece/PieceTest.java index fceac9af60..f3278dc739 100644 --- a/src/test/java/domain/piece/PieceTest.java +++ b/src/test/java/domain/piece/PieceTest.java @@ -3,15 +3,14 @@ import domain.Position; import domain.Side; import domain.strategy.PathMovement; +import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.List; - class PieceTest { - static class TestPiece extends Piece{ + static class TestPiece extends Piece { public TestPiece() { super(Side.CHO, new PathMovement()); @@ -28,7 +27,6 @@ public String getName() { } } - @DisplayName("기물 이동 경로는 모두 비어있어야한다.") @Test void 기물_이동_경로는_모두_비어있어야한다() { @@ -37,7 +35,7 @@ public String getName() { // when Piece piece = new TestPiece(); Assertions.assertThatCode(() -> piece.checkRoute(pieces)) - .doesNotThrowAnyException(); + .doesNotThrowAnyException(); // then } -} \ No newline at end of file +}