From 31156ca6c2df26175e50d85c8fbd812299689cf9 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Tue, 24 Mar 2026 20:28:30 +0900 Subject: [PATCH 01/26] =?UTF-8?q?docs:=201=EB=8B=A8=EA=B3=84=20=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9775dda0ae..c4ed62fcfd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,52 @@ -# java-janggi +# 1단계 - 보드 초기화 -장기 미션 저장소 +``` +한나라의 기물차림을 선택해주세요. +1. 왼상차림(상마상마) +2.오른상차림(마상마상) +3. 안상차림(마상상마) +4. 바깥상차림(상마마상) + +3 + +초나라의 기물차림을 선택해주세요. +1. 왼상차림(상마상마) +2. 오른상차림(마상마상) +3. 안상차림(마상상마) +4. 바깥상차림(상마마상) + +3 + +   0 1 2 3 4 5 6 7 8 + +-------------------+ +0| 車 馬 象 士 * 士 象 馬 車 | +1| * * * * 漢 * * * * | +2| * 包 * * * * * 包 * | +3| 兵 * 兵 * 兵 * 兵 * 兵 | +4| * * * * * * * * * | +5| * * * * * * * * * | +6| 卒 * 卒 * 卒 * 卒 * 卒 | +7| * 包 * * * * * 包 * | +8| * * * * 楚 * * * * | +9| 車 馬 象 士 * 士 象 馬 車 | + +-------------------+ +``` + +- 기물차림 순서 관리 + - 한나라부터 기물차림 입력을 받는다. + - 다음으로 초나라 기물차림 입력을 받는다. + - 잘못된 입력시 예외 +- 기물차림 별 상태 관리 + - 상마상마 + - 마상마상 + - 마상상마 + - 상마마상 +- 초기 배치를 위한 보드 정보 + - 위치 + - 격자 범위 + - 배치될 장기말 +- 장기말 정보 + - 차, 마, 상, 사, 졸(병), 포, 장 + - 자신의 나라 정보 +- 나라 정보 + - 한, 초 From 479c1d24582423f4260515b61de7a01047c3380e Mon Sep 17 00:00:00 2001 From: sauter001 Date: Wed, 25 Mar 2026 15:36:37 +0900 Subject: [PATCH 02/26] =?UTF-8?q?feat:=20=ED=95=9C/=EC=B4=88=20=EC=83=81?= =?UTF-8?q?=EC=B0=A8=EB=A6=BC=20=EC=9E=85=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 ++++++++++--------- src/main/java/Application.java | 10 ++++++ .../java/controller/JanggiController.java | 32 ++++++++++++++++++ src/main/java/controller/Retrier.java | 19 +++++++++++ src/main/java/model/JanggiFormation.java | 33 +++++++++++++++++++ src/main/java/model/Team.java | 15 +++++++++ src/main/java/view/InputView.java | 24 ++++++++++++++ src/main/java/view/OutputView.java | 7 ++++ src/main/java/view/parser/NumberParser.java | 11 +++++++ src/test/java/model/JanggiFormationTest.java | 32 ++++++++++++++++++ 10 files changed, 201 insertions(+), 15 deletions(-) create mode 100644 src/main/java/Application.java create mode 100644 src/main/java/controller/JanggiController.java create mode 100644 src/main/java/controller/Retrier.java create mode 100644 src/main/java/model/JanggiFormation.java create mode 100644 src/main/java/model/Team.java create mode 100644 src/main/java/view/InputView.java create mode 100644 src/main/java/view/OutputView.java create mode 100644 src/main/java/view/parser/NumberParser.java create mode 100644 src/test/java/model/JanggiFormationTest.java diff --git a/README.md b/README.md index c4ed62fcfd..e75dae6e5c 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,25 @@ # 1단계 - 보드 초기화 ``` -한나라의 기물차림을 선택해주세요. -1. 왼상차림(상마상마) -2.오른상차림(마상마상) -3. 안상차림(마상상마) -4. 바깥상차림(상마마상) +한나라의 상차림을 선택해주세요. +1. 상마상마 +2. 마상마상 +3. 마상상마 +4. 상마마상 3 -초나라의 기물차림을 선택해주세요. -1. 왼상차림(상마상마) -2. 오른상차림(마상마상) -3. 안상차림(마상상마) -4. 바깥상차림(상마마상) +초나라의 상차림을 선택해주세요. +1. 상마상마 +2. 마상마상 +3. 마상상마 +4. 상마마상 + +5 +[ERROR] 유효하지 않은 상차림 번호입니다. 3 - +    0 1 2 3 4 5 6 7 8  +-------------------+ 0| 車 馬 象 士 * 士 象 馬 車 | @@ -32,11 +35,11 @@  +-------------------+ ``` -- 기물차림 순서 관리 - - 한나라부터 기물차림 입력을 받는다. - - 다음으로 초나라 기물차림 입력을 받는다. +- 상차림 순서 관리 + - 한나라부터 상차림 입력을 받는다. + - 다음으로 초나라 상차림 입력을 받는다. - 잘못된 입력시 예외 -- 기물차림 별 상태 관리 +- 상차림 별 상태 관리 - 상마상마 - 마상마상 - 마상상마 diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 0000000000..c4e80ce0a4 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,10 @@ +import controller.JanggiController; +import view.InputView; +import view.OutputView; + +public class Application { + public static void main(String[] args) { + 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 new file mode 100644 index 0000000000..dd478eaa79 --- /dev/null +++ b/src/main/java/controller/JanggiController.java @@ -0,0 +1,32 @@ +package controller; + +import model.JanggiFormation; +import model.Team; +import view.InputView; +import view.OutputView; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +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() { + List formations = Arrays.asList(JanggiFormation.values()); + JanggiFormation hanFormation = Retrier.retry(() -> + inputView.readFormationNumber(Team.HAN, formations), processError()); + JanggiFormation choFormation = Retrier.retry(() -> + inputView.readFormationNumber(Team.CHO, formations), processError()); + } + + private Consumer processError() { + return (e) -> outputView.displayError(e.getMessage()); + } +} diff --git a/src/main/java/controller/Retrier.java b/src/main/java/controller/Retrier.java new file mode 100644 index 0000000000..22fcf7f770 --- /dev/null +++ b/src/main/java/controller/Retrier.java @@ -0,0 +1,19 @@ +package controller; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class Retrier { + private Retrier() { + } + + public static T retry(Supplier task, Consumer consumer) { + while (true) { + try { + return task.get(); + } catch (IllegalArgumentException e) { + consumer.accept(e); + } + } + } +} diff --git a/src/main/java/model/JanggiFormation.java b/src/main/java/model/JanggiFormation.java new file mode 100644 index 0000000000..c1326f3ab7 --- /dev/null +++ b/src/main/java/model/JanggiFormation.java @@ -0,0 +1,33 @@ +package model; + +import java.util.stream.Stream; + +public enum JanggiFormation { + SANG_MA_SANG_MA(1, "상마상마"), + MA_SANG_MA_SANG(2, "마상마상"), + MA_SANG_SANG_MA(3, "마상상마"), + SANG_MA_MA_SANG(4, "상마마상"); + + private final int order; + private final String formation; + + JanggiFormation(int order, String formation) { + this.order = order; + this.formation = formation; + } + + public static JanggiFormation from(int order) { + return Stream.of(values()) + .filter(formation -> formation.order == order) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 상차림 번호입니다.")); + } + + public int getOrder() { + return order; + } + + public String getFormation() { + return formation; + } +} diff --git a/src/main/java/model/Team.java b/src/main/java/model/Team.java new file mode 100644 index 0000000000..33bcddb297 --- /dev/null +++ b/src/main/java/model/Team.java @@ -0,0 +1,15 @@ +package model; + +public enum Team { + HAN("한나라"), CHO("초나라"); + + private final String name; + + Team(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 0000000000..7b36853554 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,24 @@ +package view; + +import model.JanggiFormation; +import model.Team; +import view.parser.NumberParser; + +import java.util.List; +import java.util.Scanner; + +public class InputView { + private static final Scanner SCANNER = new Scanner(System.in); + private static final NumberParser NUMBER_PARSER = new NumberParser(); + + public JanggiFormation readFormationNumber(Team team, List janggiFormations) { + System.out.printf("%n%s의 상차림을 선택해주세요.%n", team.getName()); + for (JanggiFormation formation : janggiFormations) { + System.out.printf("%d. %s%n", formation.getOrder(), formation.getFormation()); + } + + String input = SCANNER.nextLine(); + int order = NUMBER_PARSER.parse(input); + return JanggiFormation.from(order); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 0000000000..d8ed217852 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,7 @@ +package view; + +public class OutputView { + public void displayError(String message) { + System.out.println("[ERROR] " + message); + } +} diff --git a/src/main/java/view/parser/NumberParser.java b/src/main/java/view/parser/NumberParser.java new file mode 100644 index 0000000000..872a1610c4 --- /dev/null +++ b/src/main/java/view/parser/NumberParser.java @@ -0,0 +1,11 @@ +package view.parser; + +public class NumberParser { + public int parse(String input) { + try { + return Integer.parseInt(input.strip()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("잘못된 입력입니다. 수를 입력해주세요: " + input); + } + } +} diff --git a/src/test/java/model/JanggiFormationTest.java b/src/test/java/model/JanggiFormationTest.java new file mode 100644 index 0000000000..1eefd771b6 --- /dev/null +++ b/src/test/java/model/JanggiFormationTest.java @@ -0,0 +1,32 @@ +package model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +class JanggiFormationTest { + + @ParameterizedTest + @CsvSource({ + "1, SANG_MA_SANG_MA", + "2, MA_SANG_MA_SANG", + "3, MA_SANG_SANG_MA", + "4, SANG_MA_MA_SANG" + }) + void 번호로_상차림을_찾는다(int order, JanggiFormation expected) { + JanggiFormation formation = JanggiFormation.from(order); + + assertThat(formation).isEqualTo(expected); + } + + @ParameterizedTest + @ValueSource(ints = {0, 5}) + void 잘못된_번호_입력시_예외가_발생한다(int invalidOrder) { + assertThatThrownBy(() -> JanggiFormation.from(invalidOrder)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효하지 않은 상차림 번호입니다."); + } +} From d48f48580cd7a31c17f951330e1b748a7cc73dfd Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Wed, 25 Mar 2026 19:43:51 +0900 Subject: [PATCH 03/26] =?UTF-8?q?feat:=20=EC=83=81=EC=B0=A8=EB=A6=BC=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++++ .../java/controller/JanggiController.java | 26 ++++--- src/main/java/model/Board.java | 21 ++++++ src/main/java/model/BoardFactory.java | 54 ++++++++++++++ src/main/java/model/Position.java | 27 +++++++ .../model/formation/FormationFactory.java | 31 ++++++++ .../model/formation/FormationStrategy.java | 20 ++++++ .../{ => formation}/JanggiFormation.java | 2 +- .../model/formation/MaSangMaSangStrategy.java | 39 +++++++++++ .../model/formation/MaSangSangMaStrategy.java | 39 +++++++++++ .../model/formation/SangMaMaSangStrategy.java | 39 +++++++++++ .../model/formation/SangMaSangMaStrategy.java | 39 +++++++++++ src/main/java/model/piece/Cannon.java | 10 +++ src/main/java/model/piece/Chariot.java | 10 +++ src/main/java/model/piece/Elephant.java | 10 +++ src/main/java/model/piece/General.java | 10 +++ src/main/java/model/piece/Guard.java | 10 +++ src/main/java/model/piece/Horse.java | 10 +++ src/main/java/model/piece/Piece.java | 22 ++++++ src/main/java/model/piece/PieceType.java | 6 ++ src/main/java/model/piece/Soldier.java | 10 +++ src/main/java/view/InputView.java | 7 +- src/main/java/view/OutputView.java | 49 ++++++++++++- .../java/view/formater/BoardFormatter.java | 46 ++++++++++++ src/main/java/view/mapper/ViewMapper.java | 28 ++++++++ src/test/java/model/JanggiFormationTest.java | 1 + src/test/java/model/PositionTest.java | 36 ++++++++++ .../model/fixture/FormationTestFixture.java | 45 ++++++++++++ .../model/formation/FormationFactoryTest.java | 30 ++++++++ .../formation/FormationStrategyTest.java | 70 +++++++++++++++++++ 30 files changed, 748 insertions(+), 15 deletions(-) create mode 100644 src/main/java/model/Board.java create mode 100644 src/main/java/model/BoardFactory.java create mode 100644 src/main/java/model/Position.java create mode 100644 src/main/java/model/formation/FormationFactory.java create mode 100644 src/main/java/model/formation/FormationStrategy.java rename src/main/java/model/{ => formation}/JanggiFormation.java (97%) create mode 100644 src/main/java/model/formation/MaSangMaSangStrategy.java create mode 100644 src/main/java/model/formation/MaSangSangMaStrategy.java create mode 100644 src/main/java/model/formation/SangMaMaSangStrategy.java create mode 100644 src/main/java/model/formation/SangMaSangMaStrategy.java create mode 100644 src/main/java/model/piece/Cannon.java create mode 100644 src/main/java/model/piece/Chariot.java create mode 100644 src/main/java/model/piece/Elephant.java create mode 100644 src/main/java/model/piece/General.java create mode 100644 src/main/java/model/piece/Guard.java create mode 100644 src/main/java/model/piece/Horse.java create mode 100644 src/main/java/model/piece/Piece.java create mode 100644 src/main/java/model/piece/PieceType.java create mode 100644 src/main/java/model/piece/Soldier.java create mode 100644 src/main/java/view/formater/BoardFormatter.java create mode 100644 src/main/java/view/mapper/ViewMapper.java create mode 100644 src/test/java/model/PositionTest.java create mode 100644 src/test/java/model/fixture/FormationTestFixture.java create mode 100644 src/test/java/model/formation/FormationFactoryTest.java create mode 100644 src/test/java/model/formation/FormationStrategyTest.java diff --git a/README.md b/README.md index e75dae6e5c..77611c231e 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@  +-------------------+ ``` +## 기능 정리 + - 상차림 순서 관리 - 한나라부터 상차림 입력을 받는다. - 다음으로 초나라 상차림 입력을 받는다. @@ -53,3 +55,17 @@ - 자신의 나라 정보 - 나라 정보 - 한, 초 + +## 구현 순서 + +* 초나라/한나라 상차림 입력 + * 나라 별 유효한 상차림을 입력받는다. +* 상차림에 따른 장기판 초기 배치 + * 장기판에 위치할 장기말이 필요하다. + * 차, 상, 마, 포, 졸, 사, 장 + * 이름 + 정보: [나무위키](https://namu.wiki/w/%EC%9E%A5%EA%B8%B0/%EA%B8%B0%EB%AC%BC%20%EB%B0%8F%20%ED%96%89%EB%A7%88%EB%B2%95) + 참조 + * 장기말이 위치할 위치 정보가 필요하다. + * 장기판 위치 범위를 초과하면 예외가 발생해야한다. + * 장기판이 필요하다. \ No newline at end of file diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index dd478eaa79..e59eaa3370 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,13 +1,21 @@ package controller; -import model.JanggiFormation; -import model.Team; -import view.InputView; -import view.OutputView; +import static controller.Retrier.retry; +import static model.Team.CHO; +import static model.Team.HAN; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.function.Consumer; +import model.Board; +import model.BoardFactory; +import model.Position; +import model.formation.FormationFactory; +import model.formation.JanggiFormation; +import model.piece.Piece; +import view.InputView; +import view.OutputView; public class JanggiController { private final InputView inputView; @@ -20,10 +28,12 @@ public JanggiController(InputView inputView, OutputView outputView) { public void run() { List formations = Arrays.asList(JanggiFormation.values()); - JanggiFormation hanFormation = Retrier.retry(() -> - inputView.readFormationNumber(Team.HAN, formations), processError()); - JanggiFormation choFormation = Retrier.retry(() -> - inputView.readFormationNumber(Team.CHO, formations), processError()); + JanggiFormation hanFormation = retry(() -> inputView.readFormationNumber(HAN, formations), processError()); + JanggiFormation choFormation = retry(() -> inputView.readFormationNumber(CHO, formations), processError()); + + Map pieceByFormation = FormationFactory.generateFormation(hanFormation, choFormation); + Board board = BoardFactory.generatePieces(pieceByFormation); + outputView.displayBoard(board.getBoard()); } private Consumer processError() { diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java new file mode 100644 index 0000000000..fe3d550a9e --- /dev/null +++ b/src/main/java/model/Board.java @@ -0,0 +1,21 @@ +package model; + +import java.util.HashMap; +import java.util.Map; +import model.piece.Piece; + +public class Board { + + public static final int BOARD_ROW = 10; + public static final int BOARD_COL = 9; + + private final Map board; + + public Board(Map board) { + this.board = new HashMap<>(board); + } + + public Map getBoard() { + return Map.copyOf(board); + } +} diff --git a/src/main/java/model/BoardFactory.java b/src/main/java/model/BoardFactory.java new file mode 100644 index 0000000000..5d1d72d6dd --- /dev/null +++ b/src/main/java/model/BoardFactory.java @@ -0,0 +1,54 @@ +package model; + +import static model.Team.CHO; +import static model.Team.HAN; + +import java.util.HashMap; +import java.util.Map; +import model.piece.Cannon; +import model.piece.Chariot; +import model.piece.General; +import model.piece.Guard; +import model.piece.Piece; +import model.piece.Soldier; + +public class BoardFactory { + + private static final Map RED_PIECES = Map.ofEntries( + Map.entry(new Position(0, 0), new Chariot(HAN)), + Map.entry(new Position(0, 3), new Guard(HAN)), + Map.entry(new Position(0, 5), new Guard(HAN)), + Map.entry(new Position(0, 8), new Chariot(HAN)), + Map.entry(new Position(1, 4), new General(HAN)), + Map.entry(new Position(2, 1), new Cannon(HAN)), + Map.entry(new Position(2, 7), new Cannon(HAN)), + Map.entry(new Position(3, 0), new Soldier(HAN)), + Map.entry(new Position(3, 2), new Soldier(HAN)), + Map.entry(new Position(3, 4), new Soldier(HAN)), + Map.entry(new Position(3, 6), new Soldier(HAN)), + Map.entry(new Position(3, 8), new Soldier(HAN)) + ); + + private static final Map GREEN_PIECES = Map.ofEntries( + Map.entry(new Position(9, 0), new Chariot(CHO)), + Map.entry(new Position(9, 3), new Guard(CHO)), + Map.entry(new Position(9, 5), new Guard(CHO)), + Map.entry(new Position(9, 8), new Chariot(CHO)), + Map.entry(new Position(8, 4), new General(CHO)), + Map.entry(new Position(7, 1), new Cannon(CHO)), + Map.entry(new Position(7, 7), new Cannon(CHO)), + Map.entry(new Position(6, 0), new Soldier(CHO)), + Map.entry(new Position(6, 2), new Soldier(CHO)), + Map.entry(new Position(6, 4), new Soldier(CHO)), + Map.entry(new Position(6, 6), new Soldier(CHO)), + Map.entry(new Position(6, 8), new Soldier(CHO)) + ); + + public static Board generatePieces(Map pieceByFormation) { + Map allPieces = new HashMap<>(); + allPieces.putAll(RED_PIECES); + allPieces.putAll(GREEN_PIECES); + allPieces.putAll(pieceByFormation); + return new Board(allPieces); + } +} diff --git a/src/main/java/model/Position.java b/src/main/java/model/Position.java new file mode 100644 index 0000000000..de658e800b --- /dev/null +++ b/src/main/java/model/Position.java @@ -0,0 +1,27 @@ +package model; + +import static model.Board.BOARD_COL; +import static model.Board.BOARD_ROW; + +public record Position(int row, int col) { + + public static final Position HAN_LEFT_OUTER = new Position(0, 1); + public static final Position HAN_LEFT_INNER = new Position(0, 2); + public static final Position HAN_RIGHT_INNER = new Position(0, 6); + public static final Position HAN_RIGHT_OUTER = new Position(0, 7); + + public static final Position CHO_LEFT_OUTER = new Position(9, 1); + public static final Position CHO_LEFT_INNER = new Position(9, 2); + public static final Position CHO_RIGHT_INNER = new Position(9, 6); + public static final Position CHO_RIGHT_OUTER = new Position(9, 7); + + public Position { + if (row < 0 || row >= BOARD_ROW) { + throw new IllegalArgumentException("장기판의 행 범위를 벗어났습니다. 최대 범위: " + BOARD_ROW); + } + + if (col < 0 || col >= BOARD_COL) { + throw new IllegalArgumentException("장기판의 열 범위를 벗어났습니다. 최대 범위: " + BOARD_COL); + } + } +} diff --git a/src/main/java/model/formation/FormationFactory.java b/src/main/java/model/formation/FormationFactory.java new file mode 100644 index 0000000000..1b16aa585b --- /dev/null +++ b/src/main/java/model/formation/FormationFactory.java @@ -0,0 +1,31 @@ +package model.formation; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import model.Position; +import model.Team; +import model.piece.Piece; + +public class FormationFactory { + + private static final Map FORMATION_STRATEGIES = Map.of( + JanggiFormation.SANG_MA_SANG_MA, new SangMaSangMaStrategy(), + JanggiFormation.MA_SANG_MA_SANG, new MaSangMaSangStrategy(), + JanggiFormation.MA_SANG_SANG_MA, new MaSangSangMaStrategy(), + JanggiFormation.SANG_MA_MA_SANG, new SangMaMaSangStrategy() + ); + + public static Map generateFormation(JanggiFormation hanFormation, JanggiFormation choFormation) { + Map formations = new HashMap<>(); + formations.putAll(extractFormationByTeam(hanFormation, Team.HAN)); + formations.putAll(extractFormationByTeam(choFormation, Team.CHO)); + return Map.copyOf(formations); + } + + private static Map extractFormationByTeam(JanggiFormation formation, Team team) { + return Optional.ofNullable(FORMATION_STRATEGIES.get(formation)) + .map(strategy -> strategy.generateFormation(team)) + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 상차림 생성 전략입니다.")); + } +} diff --git a/src/main/java/model/formation/FormationStrategy.java b/src/main/java/model/formation/FormationStrategy.java new file mode 100644 index 0000000000..c0fba5f1d1 --- /dev/null +++ b/src/main/java/model/formation/FormationStrategy.java @@ -0,0 +1,20 @@ +package model.formation; + +import java.util.Map; +import model.Position; +import model.Team; +import model.piece.Piece; + +public abstract class FormationStrategy { + + public Map generateFormation(Team team) { + if (team == Team.HAN) { + return generateHanFormation(); + } + return generateChoFormation(); + } + + protected abstract Map generateHanFormation(); + + protected abstract Map generateChoFormation(); +} diff --git a/src/main/java/model/JanggiFormation.java b/src/main/java/model/formation/JanggiFormation.java similarity index 97% rename from src/main/java/model/JanggiFormation.java rename to src/main/java/model/formation/JanggiFormation.java index c1326f3ab7..93686b5107 100644 --- a/src/main/java/model/JanggiFormation.java +++ b/src/main/java/model/formation/JanggiFormation.java @@ -1,4 +1,4 @@ -package model; +package model.formation; import java.util.stream.Stream; diff --git a/src/main/java/model/formation/MaSangMaSangStrategy.java b/src/main/java/model/formation/MaSangMaSangStrategy.java new file mode 100644 index 0000000000..a871075e4a --- /dev/null +++ b/src/main/java/model/formation/MaSangMaSangStrategy.java @@ -0,0 +1,39 @@ +package model.formation; + +import static model.Position.CHO_LEFT_INNER; +import static model.Position.CHO_LEFT_OUTER; +import static model.Position.CHO_RIGHT_INNER; +import static model.Position.CHO_RIGHT_OUTER; +import static model.Position.HAN_LEFT_INNER; +import static model.Position.HAN_LEFT_OUTER; +import static model.Position.HAN_RIGHT_INNER; +import static model.Position.HAN_RIGHT_OUTER; + +import java.util.Map; +import model.Position; +import model.Team; +import model.piece.Elephant; +import model.piece.Horse; +import model.piece.Piece; + +public class MaSangMaSangStrategy extends FormationStrategy { + @Override + protected Map generateHanFormation() { + return Map.of( + HAN_LEFT_OUTER, new Horse(Team.HAN), + HAN_LEFT_INNER, new Elephant(Team.HAN), + HAN_RIGHT_INNER, new Horse(Team.HAN), + HAN_RIGHT_OUTER, new Elephant(Team.HAN) + ); + } + + @Override + protected Map generateChoFormation() { + return Map.of( + CHO_LEFT_OUTER, new Horse(Team.CHO), + CHO_LEFT_INNER, new Elephant(Team.CHO), + CHO_RIGHT_INNER, new Horse(Team.CHO), + CHO_RIGHT_OUTER, new Elephant(Team.CHO) + ); + } +} diff --git a/src/main/java/model/formation/MaSangSangMaStrategy.java b/src/main/java/model/formation/MaSangSangMaStrategy.java new file mode 100644 index 0000000000..1dce8eff7b --- /dev/null +++ b/src/main/java/model/formation/MaSangSangMaStrategy.java @@ -0,0 +1,39 @@ +package model.formation; + +import static model.Position.CHO_LEFT_INNER; +import static model.Position.CHO_LEFT_OUTER; +import static model.Position.CHO_RIGHT_INNER; +import static model.Position.CHO_RIGHT_OUTER; +import static model.Position.HAN_LEFT_INNER; +import static model.Position.HAN_LEFT_OUTER; +import static model.Position.HAN_RIGHT_INNER; +import static model.Position.HAN_RIGHT_OUTER; + +import java.util.Map; +import model.Position; +import model.Team; +import model.piece.Elephant; +import model.piece.Horse; +import model.piece.Piece; + +public class MaSangSangMaStrategy extends FormationStrategy { + @Override + protected Map generateHanFormation() { + return Map.of( + HAN_LEFT_OUTER, new Horse(Team.HAN), + HAN_LEFT_INNER, new Elephant(Team.HAN), + HAN_RIGHT_INNER, new Elephant(Team.HAN), + HAN_RIGHT_OUTER, new Horse(Team.HAN) + ); + } + + @Override + protected Map generateChoFormation() { + return Map.of( + CHO_LEFT_OUTER, new Horse(Team.CHO), + CHO_LEFT_INNER, new Elephant(Team.CHO), + CHO_RIGHT_INNER, new Elephant(Team.CHO), + CHO_RIGHT_OUTER, new Horse(Team.CHO) + ); + } +} diff --git a/src/main/java/model/formation/SangMaMaSangStrategy.java b/src/main/java/model/formation/SangMaMaSangStrategy.java new file mode 100644 index 0000000000..1ef7171d2c --- /dev/null +++ b/src/main/java/model/formation/SangMaMaSangStrategy.java @@ -0,0 +1,39 @@ +package model.formation; + +import static model.Position.CHO_LEFT_INNER; +import static model.Position.CHO_LEFT_OUTER; +import static model.Position.CHO_RIGHT_INNER; +import static model.Position.CHO_RIGHT_OUTER; +import static model.Position.HAN_LEFT_INNER; +import static model.Position.HAN_LEFT_OUTER; +import static model.Position.HAN_RIGHT_INNER; +import static model.Position.HAN_RIGHT_OUTER; + +import java.util.Map; +import model.Position; +import model.Team; +import model.piece.Elephant; +import model.piece.Horse; +import model.piece.Piece; + +public class SangMaMaSangStrategy extends FormationStrategy { + @Override + protected Map generateHanFormation() { + return Map.of( + HAN_LEFT_OUTER, new Elephant(Team.HAN), + HAN_LEFT_INNER, new Horse(Team.HAN), + HAN_RIGHT_INNER, new Horse(Team.HAN), + HAN_RIGHT_OUTER, new Elephant(Team.HAN) + ); + } + + @Override + protected Map generateChoFormation() { + return Map.of( + CHO_LEFT_OUTER, new Elephant(Team.CHO), + CHO_LEFT_INNER, new Horse(Team.CHO), + CHO_RIGHT_INNER, new Horse(Team.CHO), + CHO_RIGHT_OUTER, new Elephant(Team.CHO) + ); + } +} diff --git a/src/main/java/model/formation/SangMaSangMaStrategy.java b/src/main/java/model/formation/SangMaSangMaStrategy.java new file mode 100644 index 0000000000..a0c6bde01e --- /dev/null +++ b/src/main/java/model/formation/SangMaSangMaStrategy.java @@ -0,0 +1,39 @@ +package model.formation; + +import static model.Position.CHO_LEFT_INNER; +import static model.Position.CHO_LEFT_OUTER; +import static model.Position.CHO_RIGHT_INNER; +import static model.Position.CHO_RIGHT_OUTER; +import static model.Position.HAN_LEFT_INNER; +import static model.Position.HAN_LEFT_OUTER; +import static model.Position.HAN_RIGHT_INNER; +import static model.Position.HAN_RIGHT_OUTER; + +import java.util.Map; +import model.Position; +import model.Team; +import model.piece.Elephant; +import model.piece.Horse; +import model.piece.Piece; + +public class SangMaSangMaStrategy extends FormationStrategy { + @Override + protected Map generateHanFormation() { + return Map.of( + HAN_LEFT_OUTER, new Elephant(Team.HAN), + HAN_LEFT_INNER, new Horse(Team.HAN), + HAN_RIGHT_INNER, new Elephant(Team.HAN), + HAN_RIGHT_OUTER, new Horse(Team.HAN) + ); + } + + @Override + protected Map generateChoFormation() { + return Map.of( + CHO_LEFT_OUTER, new Elephant(Team.CHO), + CHO_LEFT_INNER, new Horse(Team.CHO), + CHO_RIGHT_INNER, new Elephant(Team.CHO), + CHO_RIGHT_OUTER, new Horse(Team.CHO) + ); + } +} diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java new file mode 100644 index 0000000000..cba8575194 --- /dev/null +++ b/src/main/java/model/piece/Cannon.java @@ -0,0 +1,10 @@ +package model.piece; + +import model.Team; + +public class Cannon extends Piece { + + public Cannon(Team team) { + super(team, PieceType.CANNON); + } +} diff --git a/src/main/java/model/piece/Chariot.java b/src/main/java/model/piece/Chariot.java new file mode 100644 index 0000000000..55502fa126 --- /dev/null +++ b/src/main/java/model/piece/Chariot.java @@ -0,0 +1,10 @@ +package model.piece; + +import model.Team; + +public class Chariot extends Piece { + + public Chariot(Team team) { + super(team, PieceType.CHARIOT); + } +} diff --git a/src/main/java/model/piece/Elephant.java b/src/main/java/model/piece/Elephant.java new file mode 100644 index 0000000000..436af4c056 --- /dev/null +++ b/src/main/java/model/piece/Elephant.java @@ -0,0 +1,10 @@ +package model.piece; + +import model.Team; + +public class Elephant extends Piece { + + public Elephant(Team team) { + super(team, PieceType.ELEPHANT); + } +} diff --git a/src/main/java/model/piece/General.java b/src/main/java/model/piece/General.java new file mode 100644 index 0000000000..3bb5edd300 --- /dev/null +++ b/src/main/java/model/piece/General.java @@ -0,0 +1,10 @@ +package model.piece; + +import model.Team; + +public class General extends Piece { + + public General(Team team) { + super(team, PieceType.GENERAL); + } +} diff --git a/src/main/java/model/piece/Guard.java b/src/main/java/model/piece/Guard.java new file mode 100644 index 0000000000..c5383a3fce --- /dev/null +++ b/src/main/java/model/piece/Guard.java @@ -0,0 +1,10 @@ +package model.piece; + +import model.Team; + +public class Guard extends Piece { + + public Guard(Team team) { + super(team, PieceType.GUARD); + } +} diff --git a/src/main/java/model/piece/Horse.java b/src/main/java/model/piece/Horse.java new file mode 100644 index 0000000000..3f48a0d4f9 --- /dev/null +++ b/src/main/java/model/piece/Horse.java @@ -0,0 +1,10 @@ +package model.piece; + +import model.Team; + +public class Horse extends Piece { + + public Horse(Team team) { + super(team, PieceType.HORSE); + } +} diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java new file mode 100644 index 0000000000..67ac1debb6 --- /dev/null +++ b/src/main/java/model/piece/Piece.java @@ -0,0 +1,22 @@ +package model.piece; + +import model.Team; + +public abstract class Piece { + + private final Team team; + private final PieceType type; + + protected Piece(Team team, PieceType type) { + this.team = team; + this.type = type; + } + + public Team getTeam() { + return team; + } + + public PieceType getType() { + return type; + } +} diff --git a/src/main/java/model/piece/PieceType.java b/src/main/java/model/piece/PieceType.java new file mode 100644 index 0000000000..8c38624bf1 --- /dev/null +++ b/src/main/java/model/piece/PieceType.java @@ -0,0 +1,6 @@ +package model.piece; + +public enum PieceType { + + CANNON, CHARIOT, ELEPHANT, GENERAL, GUARD, HORSE, SOLDIER +} diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java new file mode 100644 index 0000000000..3e1536889f --- /dev/null +++ b/src/main/java/model/piece/Soldier.java @@ -0,0 +1,10 @@ +package model.piece; + +import model.Team; + +public class Soldier extends Piece { + + public Soldier(Team team) { + super(team, PieceType.SOLDIER); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 7b36853554..e951045ca3 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,11 +1,10 @@ package view; -import model.JanggiFormation; -import model.Team; -import view.parser.NumberParser; - import java.util.List; import java.util.Scanner; +import model.Team; +import model.formation.JanggiFormation; +import view.parser.NumberParser; public class InputView { private static final Scanner SCANNER = new Scanner(System.in); diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index d8ed217852..6d577610a3 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,7 +1,52 @@ package view; +import static view.formater.BoardFormatter.COL_NUM; +import static view.formater.BoardFormatter.RED; +import static view.formater.BoardFormatter.RESET; +import static view.formater.BoardFormatter.ROW_NUM; +import static view.formater.BoardFormatter.SPACE; +import static view.formater.BoardFormatter.VERTICAL_LINE; +import static view.formater.BoardFormatter.formatHorizon; +import static view.formater.BoardFormatter.formatSymbol; + +import java.util.Map; +import model.Board; +import model.Position; +import model.piece.Piece; + public class OutputView { + + private static void displayPositionByPiece(Map board) { + for (int row = 0; row < Board.BOARD_ROW; row++) { + System.out.print(ROW_NUM[row] + " " + VERTICAL_LINE); + for (int col = 0; col < Board.BOARD_COL; col++) { + Piece piece = board.get(new Position(row, col)); + System.out.print(SPACE + formatSymbol(piece)); + } + System.out.println(SPACE + VERTICAL_LINE); + } + } + + private static void displayColIndex() { + System.out.println(); + System.out.print(SPACE + SPACE + SPACE); + for (String column : COL_NUM) { + System.out.print(SPACE + column); + } + System.out.println(); + } + + public void displayBoard(Map board) { + displayColIndex(); + String border = formatHorizon(Board.BOARD_COL); + System.out.println(border); + + displayPositionByPiece(board); + + System.out.println(border); + } + public void displayError(String message) { - System.out.println("[ERROR] " + message); + System.out.println(RED + "[ERROR] " + message + RESET); } -} +} \ No newline at end of file diff --git a/src/main/java/view/formater/BoardFormatter.java b/src/main/java/view/formater/BoardFormatter.java new file mode 100644 index 0000000000..d46f1cb8f3 --- /dev/null +++ b/src/main/java/view/formater/BoardFormatter.java @@ -0,0 +1,46 @@ +package view.formater; + +import static view.mapper.ViewMapper.getSymbol; + +import model.Team; +import model.piece.Piece; + +public class BoardFormatter { + + public static final String RED = "\u001B[31m"; + public static final String GREEN = "\u001B[32m"; + public static final String RESET = "\u001B[0m"; + + public static final String SPACE = " "; + public static final String VERTICAL_LINE = "|"; + + public static final String[] COL_NUM = {"0", "1", "2", "3", "4", "5", "6", "7", "8"}; + public static final String[] ROW_NUM = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + + private static final String HORIZON_LINE = "-"; + private static final String CORNER = "+"; + private static final String EMPTY = "*"; + + private BoardFormatter() { + } + + public static String formatHorizon(int colSize) { + return SPACE + " " + CORNER + HORIZON_LINE.repeat(colSize * 2 + 1) + CORNER; + } + + public static String formatSymbol(Piece piece) { + if (piece == null) { + return EMPTY; + } + String color = extractColor(piece.getTeam()); + String symbol = getSymbol(piece.getType(), piece.getTeam()); + return color + symbol + RESET; + } + + private static String extractColor(Team team) { + if (team == Team.HAN) { + return RED; + } + return GREEN; + } +} diff --git a/src/main/java/view/mapper/ViewMapper.java b/src/main/java/view/mapper/ViewMapper.java new file mode 100644 index 0000000000..5e34c18bac --- /dev/null +++ b/src/main/java/view/mapper/ViewMapper.java @@ -0,0 +1,28 @@ +package view.mapper; + +import java.util.EnumMap; +import java.util.Map; +import model.Team; +import model.piece.PieceType; + +public class ViewMapper { + + private static final Map> SYMBOL_MAP = new EnumMap<>(PieceType.class); + + static { + SYMBOL_MAP.put(PieceType.CHARIOT, Map.of(Team.HAN, "車", Team.CHO, "車")); + SYMBOL_MAP.put(PieceType.CANNON, Map.of(Team.HAN, "包", Team.CHO, "包")); + SYMBOL_MAP.put(PieceType.GENERAL, Map.of(Team.HAN, "漢", Team.CHO, "楚")); + SYMBOL_MAP.put(PieceType.HORSE, Map.of(Team.HAN, "馬", Team.CHO, "馬")); + SYMBOL_MAP.put(PieceType.ELEPHANT, Map.of(Team.HAN, "象", Team.CHO, "象")); + SYMBOL_MAP.put(PieceType.GUARD, Map.of(Team.HAN, "士", Team.CHO, "士")); + SYMBOL_MAP.put(PieceType.SOLDIER, Map.of(Team.HAN, "兵", Team.CHO, "卒")); + } + + private ViewMapper() { + } + + public static String getSymbol(PieceType type, Team team) { + return SYMBOL_MAP.get(type).get(team); + } +} diff --git a/src/test/java/model/JanggiFormationTest.java b/src/test/java/model/JanggiFormationTest.java index 1eefd771b6..1d3c7fc928 100644 --- a/src/test/java/model/JanggiFormationTest.java +++ b/src/test/java/model/JanggiFormationTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import model.formation.JanggiFormation; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; diff --git a/src/test/java/model/PositionTest.java b/src/test/java/model/PositionTest.java new file mode 100644 index 0000000000..cfc19bcf36 --- /dev/null +++ b/src/test/java/model/PositionTest.java @@ -0,0 +1,36 @@ +package model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class PositionTest { + + @ParameterizedTest + @CsvSource({ + "0, 0", // 장기판 최소 좌표 꼭짓점 + "9, 8" // 장기판 최대 좌표 꼭짓점 + }) + void 장기판_위치가_유효한_범위라면_정상적으로_생성할_수_있다(int row, int col) { + // when + Position position = new Position(row, col); + + // then + assertThat(position.row()).isEqualTo(row); + assertThat(position.col()).isEqualTo(col); + } + + @ParameterizedTest + @CsvSource({ + "-1, 0", // 행이 상측으로 벗어난 경우 + "10, 0", // 행이 하측으로 벗어난 경우 + "0, -1", // 열이 좌측으로 벗어난 경우 + "0, 9" // 열이 우측으로 벗어난 경우 + }) + void 장기판_위치가_유효하지_않다면_예외가_발생한다(int row, int col) { + assertThatThrownBy(() -> new Position(row, col)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/model/fixture/FormationTestFixture.java b/src/test/java/model/fixture/FormationTestFixture.java new file mode 100644 index 0000000000..134f10dbf3 --- /dev/null +++ b/src/test/java/model/fixture/FormationTestFixture.java @@ -0,0 +1,45 @@ +package model.fixture; + +import java.util.stream.Stream; +import model.Team; +import model.formation.MaSangMaSangStrategy; +import model.formation.MaSangSangMaStrategy; +import model.formation.SangMaMaSangStrategy; +import model.formation.SangMaSangMaStrategy; +import model.piece.Elephant; +import model.piece.Horse; +import org.junit.jupiter.params.provider.Arguments; + +public class FormationTestFixture { + + public static Stream 한나라_상차림_전략() { + return Stream.of( + Arguments.of(new SangMaSangMaStrategy(), Elephant.class, Horse.class, Elephant.class, Horse.class), + Arguments.of(new MaSangMaSangStrategy(), Horse.class, Elephant.class, Horse.class, Elephant.class), + Arguments.of(new MaSangSangMaStrategy(), Horse.class, Elephant.class, Elephant.class, Horse.class), + Arguments.of(new SangMaMaSangStrategy(), Elephant.class, Horse.class, Horse.class, Elephant.class) + ); + } + + public static Stream 초나라_상차림_전략() { + return Stream.of( + Arguments.of(new SangMaSangMaStrategy(), Elephant.class, Horse.class, Elephant.class, Horse.class), + Arguments.of(new MaSangMaSangStrategy(), Horse.class, Elephant.class, Horse.class, Elephant.class), + Arguments.of(new MaSangSangMaStrategy(), Horse.class, Elephant.class, Elephant.class, Horse.class), + Arguments.of(new SangMaMaSangStrategy(), Elephant.class, Horse.class, Horse.class, Elephant.class) + ); + } + + public static Stream 나라별_상차림_전략() { + return Stream.of( + Arguments.of(new SangMaSangMaStrategy(), Team.HAN), + Arguments.of(new SangMaSangMaStrategy(), Team.CHO), + Arguments.of(new MaSangMaSangStrategy(), Team.HAN), + Arguments.of(new MaSangMaSangStrategy(), Team.CHO), + Arguments.of(new MaSangSangMaStrategy(), Team.HAN), + Arguments.of(new MaSangSangMaStrategy(), Team.CHO), + Arguments.of(new SangMaMaSangStrategy(), Team.HAN), + Arguments.of(new SangMaMaSangStrategy(), Team.CHO) + ); + } +} diff --git a/src/test/java/model/formation/FormationFactoryTest.java b/src/test/java/model/formation/FormationFactoryTest.java new file mode 100644 index 0000000000..1004d1d322 --- /dev/null +++ b/src/test/java/model/formation/FormationFactoryTest.java @@ -0,0 +1,30 @@ +package model.formation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import java.util.stream.Stream; +import model.Position; +import model.piece.Piece; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class FormationFactoryTest { + + static Stream validFormationCombinations() { + return Stream.of(JanggiFormation.values()) + .flatMap(han -> Stream.of(JanggiFormation.values()) + .map(cho -> Arguments.of(han, cho))); + } + + @ParameterizedTest + @MethodSource("validFormationCombinations") + void 두_팀의_상차림을_합쳐서_8개_기물을_생성한다(JanggiFormation han, JanggiFormation cho) { + // when + Map result = FormationFactory.generateFormation(han, cho); + + // then + assertThat(result).hasSize(8); + } +} \ No newline at end of file diff --git a/src/test/java/model/formation/FormationStrategyTest.java b/src/test/java/model/formation/FormationStrategyTest.java new file mode 100644 index 0000000000..057527417b --- /dev/null +++ b/src/test/java/model/formation/FormationStrategyTest.java @@ -0,0 +1,70 @@ +package model.formation; + + +import static model.Position.CHO_LEFT_INNER; +import static model.Position.CHO_LEFT_OUTER; +import static model.Position.CHO_RIGHT_INNER; +import static model.Position.CHO_RIGHT_OUTER; +import static model.Position.HAN_LEFT_INNER; +import static model.Position.HAN_LEFT_OUTER; +import static model.Position.HAN_RIGHT_INNER; +import static model.Position.HAN_RIGHT_OUTER; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import model.Position; +import model.Team; +import model.piece.Piece; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class FormationStrategyTest { + + @ParameterizedTest + @MethodSource("model.fixture.FormationTestFixture#한나라_상차림_전략") + void HAN팀_상차림_전략별_기물_배치가_올바르다( + FormationStrategy strategy, + Class leftOuter, + Class leftInner, + Class rightInner, + Class rightOuter + ) { + // when + Map formation = strategy.generateFormation(Team.HAN); + + // then + assertThat(formation.get(HAN_LEFT_OUTER)).isInstanceOf(leftOuter); + assertThat(formation.get(HAN_LEFT_INNER)).isInstanceOf(leftInner); + assertThat(formation.get(HAN_RIGHT_INNER)).isInstanceOf(rightInner); + assertThat(formation.get(HAN_RIGHT_OUTER)).isInstanceOf(rightOuter); + } + + @ParameterizedTest + @MethodSource("model.fixture.FormationTestFixture#초나라_상차림_전략") + void CHO팀_상차림_전략별_기물_배치가_올바르다( + FormationStrategy strategy, + Class leftOuter, + Class leftInner, + Class rightInner, + Class rightOuter + ) { + // when + Map formation = strategy.generateFormation(Team.CHO); + + // then + assertThat(formation.get(CHO_LEFT_OUTER)).isInstanceOf(leftOuter); + assertThat(formation.get(CHO_LEFT_INNER)).isInstanceOf(leftInner); + assertThat(formation.get(CHO_RIGHT_INNER)).isInstanceOf(rightInner); + assertThat(formation.get(CHO_RIGHT_OUTER)).isInstanceOf(rightOuter); + } + + @ParameterizedTest + @MethodSource("model.fixture.FormationTestFixture#나라별_상차림_전략") + void 상차림_기물_수는_4개다(FormationStrategy strategy, Team team) { + // when + Map pieceByFormation = strategy.generateFormation(team); + + // then + assertThat(pieceByFormation).hasSize(4); + } +} \ No newline at end of file From e3deb3ed5eaa8ddd64cfdd486f40b83a82a67f98 Mon Sep 17 00:00:00 2001 From: sauter001 Date: Wed, 25 Mar 2026 20:28:57 +0900 Subject: [PATCH 04/26] =?UTF-8?q?docs:=202=EB=8B=A8=EA=B3=84=20=EA=B8=B0?= =?UTF-8?q?=EB=AC=BC=20=EC=9D=B4=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20=EB=AA=85?= =?UTF-8?q?=EC=84=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 77611c231e..c5a483f489 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,103 @@ 참조 * 장기말이 위치할 위치 정보가 필요하다. * 장기판 위치 범위를 초과하면 예외가 발생해야한다. - * 장기판이 필요하다. \ No newline at end of file + * 장기판이 필요하다. + +# 2단계 - 기물 이동 + +- 2단계에서는 기물 이동 구현이 목적이니까 종료는 `Ctrl+C`로 강제 종료한다. +## 잘못된 기물 선택 시나리오 +1. 다른 나라의 기물을 선택했을 때 + 1. 예외: 다른나라의 기물 선택 + 2. `[ERROR] 다른 나라의 기물을 선택할 수 없습니다.` +2. 기물이 없는 곳을 선택했다. (빈칸 or 장기판 외부) + 1. 예외: 기물이 없는 곳을 선택 + 2. `[ERROR] 현재 위치에 존재하는 기물이 없습니다.` + +## 기물 이동 규칙 +- 궁성 영역 - 고려하지 않는다. (사이클 2) +- 기물 이동 규칙 공통 + - 현재 위치로 이동할 수 없다. + - 보드 영역 밖으로 이동할 수 없다. + - 목적지에 아군이 있으면 이동할 수 없다. + - 목적지에 적군이 있으면 잡아낸다. + +- `차`: 4방위로 이동한다. + - 목적지까지의 경로에 기물이 있으면 이동할 수 없다. +- `포`: 4방위로 이동한다. + - 무조건 목적지까지의 경로에 포를 제외한 기물이 하나만 존재해야 한다. +- `마`: 4방위 중 한 방향으로 한 칸 이동하고 대각선의 한 방향으로 1칸 이동해야 한다. + - 목적지까지의 경로에 기물이 있으면 이동할 수 없다. +- `상`: 4방위 중 한 방향으로 한 칸 이동하고 대각선의 한 방향으로 2칸 이동해야 한다. + - 목적지까지의 경로에 기물이 있으면 이동할 수 없다. +- `졸`: 직진과 좌, 우로만 한 칸 이동해야 한다. + - 한 칸이라서 경로에 기물이 있을 리가 없다. +- `사, 장`: 궁성 영역이므로 구현하지 않는다. + +## 입출력 예시 +``` +1단계 ... + +   0 1 2 3 4 5 6 7 8 + +-------------------+ +0| 車 馬 象 士 * 士 象 馬 車 | +1| * * * * 漢 * * * * | +2| * 包 * * * * * 包 * | +3| 兵 * 兵 * 兵 * 兵 * 兵 | +4| * * * * * * * * * | +5| * * * * * * * * * | +6| 卒 * 卒 * 卒 * 卒 * 卒 | +7| * 包 * * * * * 包 * | +8| * * * * 楚 * * * * | +9| 車 馬 象 士 * 士 象 馬 車 | + +-------------------+ + +[초나라] 이동할 기물을 선택해주세요. (쉼표 기준으로 분리) +기물: 6,8 + +[초나라] 기물 卒의 다음 위치를 선택해주세요. (쉼표 기준으로 분리) +위치: 6,7 + + +   0 1 2 3 4 5 6 7 8 + +-------------------+ +0| 車 馬 象 士 * 士 象 馬 車 | +1| * * * * 漢 * * * * | +2| * 包 * * * * * 包 * | +3| 兵 * 兵 * 兵 * 兵 * 兵 | +4| * * * * * * * * * | +5| * * * * * * * * * | +6| 卒 * 卒 * 卒 * 卒 卒 * | +7| * 包 * * * * * 包 * | +8| * * * * 楚 * * * * | +9| 車 馬 象 士 * 士 象 馬 車 | + +-------------------+ + +[한나라] 이동할 기물을 선택해주세요. (쉼표 기준으로 분리) +기물: 3,1 + +[ERROR] 현재 위치에 존재하는 기물이 없습니다. + + +[한나라] 이동할 기물을 선택해주세요. (쉼표 기준으로 분리) +기물: 3,0 + + +[한나라] 기물 兵의 다음 위치를 선택해주세요. (쉼표 기준으로 분리) +위치: 3,1 + +   0 1 2 3 4 5 6 7 8 + +-------------------+ +0| 車 馬 象 士 * 士 象 馬 車 | +1| * * * * 漢 * * * * | +2| * 包 * * * * * 包 * | +3| * 兵 兵 * 兵 * 兵 * 兵 | +4| * * * * * * * * * | +5| * * * * * * * * * | +6| 卒 * 卒 * 卒 * 卒 卒 * | +7| * 包 * * * * * 包 * | +8| * * * * 楚 * * * * | +9| 車 馬 象 士 * 士 象 馬 車 | + +-------------------+ +``` + From 43b6a3418f753fc318efcca84648b29526c39e33 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Thu, 26 Mar 2026 19:22:20 +0900 Subject: [PATCH 05/26] =?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=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++ .../java/controller/JanggiController.java | 18 +++ src/main/java/controller/Retrier.java | 12 ++ src/main/java/model/Board.java | 23 ++++ src/main/java/model/Janggi.java | 36 +++++ src/main/java/model/Position.java | 8 ++ src/main/java/model/Team.java | 7 + src/main/java/model/piece/Cannon.java | 5 + src/main/java/model/piece/Chariot.java | 5 + src/main/java/model/piece/Elephant.java | 5 + src/main/java/model/piece/General.java | 5 + src/main/java/model/piece/Guard.java | 5 + src/main/java/model/piece/Horse.java | 5 + src/main/java/model/piece/Piece.java | 17 +++ src/main/java/model/piece/Soldier.java | 18 +++ src/main/java/view/InputView.java | 31 ++++- src/main/java/view/parser/InputParser.java | 22 +++ src/main/java/view/parser/NumberParser.java | 11 -- src/test/java/model/BoardTest.java | 57 ++++++++ .../java/model/fixture/PieceTestFixture.java | 127 ++++++++++++++++++ src/test/java/model/piece/CannonTest.java | 33 +++++ src/test/java/model/piece/ChariotTest.java | 33 +++++ src/test/java/model/piece/ElephantTest.java | 33 +++++ src/test/java/model/piece/HorseTest.java | 33 +++++ src/test/java/model/piece/SoldierTest.java | 55 ++++++++ 25 files changed, 606 insertions(+), 14 deletions(-) create mode 100644 src/main/java/model/Janggi.java create mode 100644 src/main/java/view/parser/InputParser.java delete mode 100644 src/main/java/view/parser/NumberParser.java create mode 100644 src/test/java/model/BoardTest.java create mode 100644 src/test/java/model/fixture/PieceTestFixture.java create mode 100644 src/test/java/model/piece/CannonTest.java create mode 100644 src/test/java/model/piece/ChariotTest.java create mode 100644 src/test/java/model/piece/ElephantTest.java create mode 100644 src/test/java/model/piece/HorseTest.java create mode 100644 src/test/java/model/piece/SoldierTest.java diff --git a/README.md b/README.md index c5a483f489..9003d49411 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,9 @@ # 2단계 - 기물 이동 - 2단계에서는 기물 이동 구현이 목적이니까 종료는 `Ctrl+C`로 강제 종료한다. + ## 잘못된 기물 선택 시나리오 + 1. 다른 나라의 기물을 선택했을 때 1. 예외: 다른나라의 기물 선택 2. `[ERROR] 다른 나라의 기물을 선택할 수 없습니다.` @@ -82,6 +84,7 @@ 2. `[ERROR] 현재 위치에 존재하는 기물이 없습니다.` ## 기물 이동 규칙 + - 궁성 영역 - 고려하지 않는다. (사이클 2) - 기물 이동 규칙 공통 - 현재 위치로 이동할 수 없다. @@ -102,6 +105,7 @@ - `사, 장`: 궁성 영역이므로 구현하지 않는다. ## 입출력 예시 + ``` 1단계 ... @@ -168,3 +172,15 @@  +-------------------+ ``` +## 구현 순서 + +1. 기물 이동 기능 추가 + * 나라 별 입력 + * 이동할 기물의 위치를 입력 받는다. + * 해당 위치에 기물이 존재하지 않다면 예외가 발생한다. + * 해당 위치의 기물이 다른 팀이라면 예외가 발생한다. + * 잘못된 범위라면 예외를 발생한다. (보드 범위 벗어남) + * 기물의 이동할 위치를 입력 받는다. + * 잘못된 범위라면 예외를 발생한다. + * 해당 위치에 아군의 기물이 존재한다면 예외가 발생한다. + * 기물 별로 이동할 수 없는 목적지라면 예외가 발생한다. \ No newline at end of file diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index e59eaa3370..7b96108023 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -10,7 +10,9 @@ import java.util.function.Consumer; import model.Board; import model.BoardFactory; +import model.Janggi; import model.Position; +import model.Team; import model.formation.FormationFactory; import model.formation.JanggiFormation; import model.piece.Piece; @@ -34,6 +36,22 @@ public void run() { Map pieceByFormation = FormationFactory.generateFormation(hanFormation, choFormation); Board board = BoardFactory.generatePieces(pieceByFormation); outputView.displayBoard(board.getBoard()); + + Janggi janggi = new Janggi(board); + while (true) { + retry(() -> playByTurn(janggi), processError()); + outputView.displayBoard(board.getBoard()); + } + } + + private void playByTurn(Janggi janggi) { + Team nowTurn = janggi.getTurn(); + + Position current = inputView.readSource(nowTurn); + Piece piece = janggi.findPieceAt(current, nowTurn); + + Position next = inputView.readDestination(nowTurn, piece); + janggi.move(current, next); } private Consumer processError() { diff --git a/src/main/java/controller/Retrier.java b/src/main/java/controller/Retrier.java index 22fcf7f770..76e3f8e2c5 100644 --- a/src/main/java/controller/Retrier.java +++ b/src/main/java/controller/Retrier.java @@ -16,4 +16,16 @@ public static T retry(Supplier task, Consumer c } } } + + + public static void retry(Runnable task, Consumer consumer) { + while (true) { + try { + task.run(); + return; + } catch (IllegalArgumentException e) { + consumer.accept(e); + } + } + } } diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index fe3d550a9e..5cb446c851 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import model.piece.Piece; public class Board { @@ -18,4 +19,26 @@ public Board(Map board) { public Map getBoard() { return Map.copyOf(board); } + + public Piece pickPiece(Position position) { + if (!board.containsKey(position)) { + throw new IllegalArgumentException("해당 위치에 존재하는 장기말이 없습니다."); + } + return board.get(position); + } + + public void movePiece(Position current, Position next) { + Piece piece = pickPiece(current); + Optional.ofNullable(board.get(next)) + .ifPresent(otherPiece -> checkAlly(piece, otherPiece)); + + board.remove(current); + board.put(next, piece); + } + + private void checkAlly(Piece piece, Piece otherPiece) { + if (piece.isSameTeam(otherPiece)) { + throw new IllegalArgumentException("해당 위치는 아군이 존재하는 위치입니다."); + } + } } diff --git a/src/main/java/model/Janggi.java b/src/main/java/model/Janggi.java new file mode 100644 index 0000000000..48c13cfaef --- /dev/null +++ b/src/main/java/model/Janggi.java @@ -0,0 +1,36 @@ +package model; + +import model.piece.Piece; + +public class Janggi { + + private final Board board; + private Team turn; + + public Janggi(Board board) { + this.board = board; + this.turn = Team.CHO; + } + + public Piece findPieceAt(Position position, Team turn) { + Piece piece = board.pickPiece(position); + if (piece.isEnemy(turn)) { + throw new IllegalArgumentException(turn.getName() + "의 기물이 아닙니다."); + } + return piece; + } + + public void move(Position current, Position next) { + Piece piece = findPieceAt(current, turn); + if (!piece.canMove(current, next)) { + throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); + } + + board.movePiece(current, next); + this.turn = turn.next(); + } + + public Team getTurn() { + return turn; + } +} diff --git a/src/main/java/model/Position.java b/src/main/java/model/Position.java index de658e800b..15ec9b9538 100644 --- a/src/main/java/model/Position.java +++ b/src/main/java/model/Position.java @@ -24,4 +24,12 @@ public record Position(int row, int col) { throw new IllegalArgumentException("장기판의 열 범위를 벗어났습니다. 최대 범위: " + BOARD_COL); } } + + public int calculateRowDiff(Position other) { + return this.row() - other.row(); + } + + public int calculateColDiff(Position other) { + return this.col - other.col(); + } } diff --git a/src/main/java/model/Team.java b/src/main/java/model/Team.java index 33bcddb297..5d8c88ab58 100644 --- a/src/main/java/model/Team.java +++ b/src/main/java/model/Team.java @@ -12,4 +12,11 @@ public enum Team { public String getName() { return name; } + + public Team next() { + if (this == HAN) { + return CHO; + } + return HAN; + } } diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index cba8575194..cb39548deb 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -7,4 +7,9 @@ public class Cannon extends Piece { public Cannon(Team team) { super(team, PieceType.CANNON); } + + @Override + protected boolean comparePosition(int rowDiff, int colDiff) { + return (colDiff >= 1 && rowDiff == 0) || (colDiff == 0 && rowDiff >= 1); + } } diff --git a/src/main/java/model/piece/Chariot.java b/src/main/java/model/piece/Chariot.java index 55502fa126..2c4d131365 100644 --- a/src/main/java/model/piece/Chariot.java +++ b/src/main/java/model/piece/Chariot.java @@ -7,4 +7,9 @@ public class Chariot extends Piece { public Chariot(Team team) { super(team, PieceType.CHARIOT); } + + @Override + protected boolean comparePosition(int rowDiff, int colDiff) { + return (colDiff >= 1 && rowDiff == 0) || (colDiff == 0 && rowDiff >= 1); + } } diff --git a/src/main/java/model/piece/Elephant.java b/src/main/java/model/piece/Elephant.java index 436af4c056..6dfb1fafe8 100644 --- a/src/main/java/model/piece/Elephant.java +++ b/src/main/java/model/piece/Elephant.java @@ -7,4 +7,9 @@ public class Elephant extends Piece { public Elephant(Team team) { super(team, PieceType.ELEPHANT); } + + @Override + protected boolean comparePosition(int rowDiff, int colDiff) { + return (colDiff == 3 && rowDiff == 2) || (colDiff == 2 && rowDiff == 3); + } } diff --git a/src/main/java/model/piece/General.java b/src/main/java/model/piece/General.java index 3bb5edd300..e012368d42 100644 --- a/src/main/java/model/piece/General.java +++ b/src/main/java/model/piece/General.java @@ -7,4 +7,9 @@ public class General extends Piece { public General(Team team) { super(team, PieceType.GENERAL); } + + @Override + protected boolean comparePosition(int rowDiff, int colDiff) { + throw new IllegalArgumentException("1단계 궁성 영역 미구현"); + } } diff --git a/src/main/java/model/piece/Guard.java b/src/main/java/model/piece/Guard.java index c5383a3fce..059d9d0036 100644 --- a/src/main/java/model/piece/Guard.java +++ b/src/main/java/model/piece/Guard.java @@ -7,4 +7,9 @@ public class Guard extends Piece { public Guard(Team team) { super(team, PieceType.GUARD); } + + @Override + protected boolean comparePosition(int rowDiff, int colDiff) { + throw new IllegalArgumentException("1단계 궁성 영역 미구현"); + } } diff --git a/src/main/java/model/piece/Horse.java b/src/main/java/model/piece/Horse.java index 3f48a0d4f9..a00c0ac82e 100644 --- a/src/main/java/model/piece/Horse.java +++ b/src/main/java/model/piece/Horse.java @@ -7,4 +7,9 @@ public class Horse extends Piece { public Horse(Team team) { super(team, PieceType.HORSE); } + + @Override + protected boolean comparePosition(int rowDiff, int colDiff) { + return (colDiff == 1 && rowDiff == 2) || (colDiff == 2 && rowDiff == 1); + } } diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 67ac1debb6..69046ef9e1 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -1,5 +1,6 @@ package model.piece; +import model.Position; import model.Team; public abstract class Piece { @@ -12,6 +13,14 @@ protected Piece(Team team, PieceType type) { this.type = type; } + public boolean isSameTeam(Piece other) { + return !isEnemy(other.team); + } + + public boolean isEnemy(Team team) { + return this.team != team; + } + public Team getTeam() { return team; } @@ -19,4 +28,12 @@ public Team getTeam() { public PieceType getType() { return type; } + + public boolean canMove(Position current, Position next) { + int rowDiff = next.calculateRowDiff(current); + int colDiff = next.calculateColDiff(current); + return comparePosition(Math.abs(rowDiff), Math.abs(colDiff)); + } + + protected abstract boolean comparePosition(int rowDiff, int colDiff); } diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java index 3e1536889f..31017c444b 100644 --- a/src/main/java/model/piece/Soldier.java +++ b/src/main/java/model/piece/Soldier.java @@ -1,5 +1,8 @@ package model.piece; +import static model.Team.CHO; + +import model.Position; import model.Team; public class Soldier extends Piece { @@ -7,4 +10,19 @@ public class Soldier extends Piece { public Soldier(Team team) { super(team, PieceType.SOLDIER); } + + @Override + public boolean canMove(Position current, Position next) { + int rowDiff = next.calculateRowDiff(current); + int colDiff = Math.abs(next.calculateColDiff(current)); + return comparePosition(rowDiff, colDiff); + } + + @Override + protected boolean comparePosition(int rowDiff, int colDiff) { + if (getTeam() == CHO) { + return (rowDiff == -1 && colDiff == 0) || (rowDiff == 0 && colDiff == 1); + } + return (rowDiff == 1 && colDiff == 0) || (rowDiff == 0 && colDiff == 1); + } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index e951045ca3..cb0daa712b 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,14 +1,18 @@ package view; +import static view.formater.BoardFormatter.formatSymbol; + import java.util.List; import java.util.Scanner; +import model.Position; import model.Team; import model.formation.JanggiFormation; -import view.parser.NumberParser; +import model.piece.Piece; +import view.parser.InputParser; public class InputView { private static final Scanner SCANNER = new Scanner(System.in); - private static final NumberParser NUMBER_PARSER = new NumberParser(); + private static final InputParser PARSER = new InputParser(); public JanggiFormation readFormationNumber(Team team, List janggiFormations) { System.out.printf("%n%s의 상차림을 선택해주세요.%n", team.getName()); @@ -17,7 +21,28 @@ public JanggiFormation readFormationNumber(Team team, List jang } String input = SCANNER.nextLine(); - int order = NUMBER_PARSER.parse(input); + int order = PARSER.parseNumber(input); return JanggiFormation.from(order); } + + public Position readSource(Team turn) { + System.out.println(); + System.out.printf("[%s] 이동할 기물을 선택해주세요. (쉼표 기준으로 분리)%n", turn.getName()); + System.out.print("기물: "); + return extractPosition(); + } + + public Position readDestination(Team turn, Piece piece) { + System.out.println(); + System.out.printf("[%s] 기물 %s의 다음 위치를 선택해주세요. (쉼표 기준으로 분리)%n", turn.getName(), formatSymbol(piece)); + System.out.print("기물: "); + return extractPosition(); + } + + private Position extractPosition() { + List tokens = PARSER.parseToken(SCANNER.nextLine(), ","); + int row = PARSER.parseNumber(tokens.get(0)); + int col = PARSER.parseNumber(tokens.get(1)); + return new Position(row, col); + } } diff --git a/src/main/java/view/parser/InputParser.java b/src/main/java/view/parser/InputParser.java new file mode 100644 index 0000000000..bd60f1d13b --- /dev/null +++ b/src/main/java/view/parser/InputParser.java @@ -0,0 +1,22 @@ +package view.parser; + +import java.util.Arrays; +import java.util.List; + +public class InputParser { + + public int parseNumber(String input) { + try { + return Integer.parseInt(input.strip()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("잘못된 입력입니다. 수를 입력해주세요: " + input); + } + } + + public List parseToken(String input, String delimiter) { + return Arrays.stream(input.strip() + .split(delimiter)) + .map(String::strip) + .toList(); + } +} diff --git a/src/main/java/view/parser/NumberParser.java b/src/main/java/view/parser/NumberParser.java deleted file mode 100644 index 872a1610c4..0000000000 --- a/src/main/java/view/parser/NumberParser.java +++ /dev/null @@ -1,11 +0,0 @@ -package view.parser; - -public class NumberParser { - public int parse(String input) { - try { - return Integer.parseInt(input.strip()); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("잘못된 입력입니다. 수를 입력해주세요: " + input); - } - } -} diff --git a/src/test/java/model/BoardTest.java b/src/test/java/model/BoardTest.java new file mode 100644 index 0000000000..eab682d206 --- /dev/null +++ b/src/test/java/model/BoardTest.java @@ -0,0 +1,57 @@ +package model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashMap; +import java.util.Map; +import model.piece.Chariot; +import model.piece.Piece; +import org.junit.jupiter.api.Test; + +class BoardTest { + + @Test + void 아군_위치로_이동하면_예외가_발생한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(0, 0), new Chariot(Team.HAN)); + pieces.put(new Position(0, 5), new Chariot(Team.HAN)); + Board board = new Board(pieces); + + // when & then + assertThatThrownBy(() -> board.movePiece(new Position(0, 0), new Position(0, 5))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 빈_위치를_선택하면_예외가_발생한다() { + // given + Board board = new Board(new HashMap<>()); + + // when & then + assertThatThrownBy(() -> board.pickPiece(new Position(0, 0))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 적군_위치로_이동하면_기물이_교체된다() { + // given + Piece han = new Chariot(Team.HAN); + Piece cho = new Chariot(Team.CHO); + Position cur = new Position(0, 0); + Position next = new Position(0, 5); + + Map pieces = new HashMap<>(); + pieces.put(cur, han); + pieces.put(next, cho); + + Board board = new Board(pieces); + + // when + board.movePiece(cur, next); + + // then + assertThat(board.pickPiece(next)).isEqualTo(han); + } +} \ No newline at end of file diff --git a/src/test/java/model/fixture/PieceTestFixture.java b/src/test/java/model/fixture/PieceTestFixture.java new file mode 100644 index 0000000000..237a3ec659 --- /dev/null +++ b/src/test/java/model/fixture/PieceTestFixture.java @@ -0,0 +1,127 @@ +package model.fixture; + +import java.util.stream.Stream; +import model.Position; +import org.junit.jupiter.params.provider.Arguments; + +public class PieceTestFixture { + + // ============================ + // 직선 이동 (車, 包 공통) + // ============================ + + public static Stream 직선_이동_가능한_위치() { + return Stream.of( + Arguments.of(new Position(0, 0), new Position(0, 5)), // 우 이동 + Arguments.of(new Position(0, 0), new Position(5, 0)), // 하 이동 + Arguments.of(new Position(5, 5), new Position(0, 5)), // 상 이동 + Arguments.of(new Position(5, 5), new Position(5, 0)) // 좌 이동 + ); + } + + public static Stream 직선_이동_불가능한_위치() { + return Stream.of( + Arguments.of(new Position(0, 0), new Position(1, 1)), // 정대각선 (1:1) + Arguments.of(new Position(0, 0), new Position(2, 1)), // rowDiff > colDiff + Arguments.of(new Position(0, 0), new Position(1, 2)), // rowDiff < colDiff + Arguments.of(new Position(5, 5), new Position(7, 3)), // 음수 방향 대각선 + Arguments.of(new Position(5, 5), new Position(9, 8)) // 둘 다 다름 + ); + } + + // ============================ + // 馬 + // ============================ + + public static Stream 마_이동_가능한_위치() { + return Stream.of( + Arguments.of(new Position(5, 5), new Position(3, 4)), // 상+좌 (row-2, col-1) + Arguments.of(new Position(5, 5), new Position(3, 6)), // 상+우 (row-2, col+1) + Arguments.of(new Position(5, 5), new Position(7, 4)), // 하+좌 (row+2, col-1) + Arguments.of(new Position(5, 5), new Position(7, 6)), // 하+우 (row+2, col+1) + Arguments.of(new Position(5, 5), new Position(4, 3)), // 좌+상 (row-1, col-2) + Arguments.of(new Position(5, 5), new Position(6, 3)), // 좌+하 (row+1, col-2) + Arguments.of(new Position(5, 5), new Position(4, 7)), // 우+상 (row-1, col+2) + Arguments.of(new Position(5, 5), new Position(6, 7)) // 우+하 (row+1, col+2) + ); + } + + public static Stream 마_이동_불가능한_위치() { + return Stream.of( + Arguments.of(new Position(5, 5), new Position(5, 7)), // 직선 이동 + Arguments.of(new Position(5, 5), new Position(7, 5)), // 직선 이동 + Arguments.of(new Position(5, 5), new Position(7, 7)), // 정대각선 + Arguments.of(new Position(5, 5), new Position(8, 7)), // 상 이동 (row+3, col+2) + Arguments.of(new Position(5, 5), new Position(5, 5)) // 제자리 + ); + } + + // ============================ + // 象 + // ============================ + + public static Stream 상_이동_가능한_위치() { + return Stream.of( + Arguments.of(new Position(5, 5), new Position(2, 3)), // 상+좌 (row-3, col-2) + Arguments.of(new Position(5, 5), new Position(2, 7)), // 상+우 (row-3, col+2) + Arguments.of(new Position(5, 5), new Position(8, 3)), // 하+좌 (row+3, col-2) + Arguments.of(new Position(5, 5), new Position(8, 7)), // 하+우 (row+3, col+2) + Arguments.of(new Position(5, 5), new Position(3, 2)), // 좌+상 (row-2, col-3) + Arguments.of(new Position(5, 5), new Position(7, 2)), // 좌+하 (row+2, col-3) + Arguments.of(new Position(5, 5), new Position(3, 8)), // 우+상 (row-2, col+3) + Arguments.of(new Position(5, 5), new Position(7, 8)) // 우+하 (row+2, col+3) + ); + } + + public static Stream 상_이동_불가능한_위치() { + return Stream.of( + Arguments.of(new Position(5, 5), new Position(3, 4)), // 마 이동 (row-2, col-1) + Arguments.of(new Position(5, 5), new Position(5, 8)), // 직선 이동 + Arguments.of(new Position(5, 5), new Position(8, 8)), // 정대각선 + Arguments.of(new Position(5, 5), new Position(4, 4)), // 1칸 대각선 + Arguments.of(new Position(5, 5), new Position(5, 5)) // 제자리 + ); + } + + // ============================ + // 兵 (HAN) + // ============================ + + public static Stream 한나라_병_이동_가능한_위치() { + return Stream.of( + Arguments.of(new Position(3, 2), new Position(4, 2)), // 전진 (하) + Arguments.of(new Position(3, 2), new Position(3, 1)), // 좌 + Arguments.of(new Position(3, 2), new Position(3, 3)) // 우 + ); + } + + public static Stream 한나라_병_이동_불가능한_위치() { + return Stream.of( + Arguments.of(new Position(3, 2), new Position(2, 2)), // 후퇴 (상) + Arguments.of(new Position(3, 2), new Position(5, 2)), // 두 칸 전진 + Arguments.of(new Position(3, 2), new Position(3, 4)), // 두 칸 옆 + Arguments.of(new Position(3, 2), new Position(4, 3)) // 대각선 + ); + } + + // ============================ + // 卒 (CHO) + // ============================ + + public static Stream 초나라_졸_이동_가능한_위치() { + return Stream.of( + Arguments.of(new Position(6, 2), new Position(5, 2)), // 전진 (상) + Arguments.of(new Position(6, 2), new Position(6, 1)), // 좌 + Arguments.of(new Position(6, 2), new Position(6, 3)) // 우 + ); + } + + public static Stream 초나라_졸_이동_불가능한_위치() { + return Stream.of( + Arguments.of(new Position(6, 2), new Position(7, 2)), // 후퇴 (하) + Arguments.of(new Position(6, 2), new Position(4, 2)), // 두 칸 전진 + Arguments.of(new Position(6, 2), new Position(6, 4)), // 두 칸 옆 + Arguments.of(new Position(6, 2), new Position(5, 3)) // 대각선 + ); + } +} \ No newline at end of file diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java new file mode 100644 index 0000000000..7ef000119a --- /dev/null +++ b/src/test/java/model/piece/CannonTest.java @@ -0,0 +1,33 @@ +package model.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import model.Position; +import model.Team; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class CannonTest { + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#직선_이동_가능한_위치") + void 포는_직선으로_이동할_수_있다(Position current, Position next) { + // given + Piece cannon = new Cannon(Team.HAN); + // when + boolean canMove = cannon.canMove(current, next); + // then + assertThat(canMove).isTrue(); + } + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#직선_이동_불가능한_위치") + void 포는_대각선으로_이동할_수_없다(Position current, Position next) { + // given + Piece cannon = new Cannon(Team.HAN); + // when + boolean canMove = cannon.canMove(current, next); + // then + assertThat(canMove).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java new file mode 100644 index 0000000000..3f5b03d34b --- /dev/null +++ b/src/test/java/model/piece/ChariotTest.java @@ -0,0 +1,33 @@ +package model.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import model.Position; +import model.Team; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class ChariotTest { + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#직선_이동_가능한_위치") + void 차는_직선으로_이동할_수_있다(Position current, Position next) { + // given + Piece chariot = new Chariot(Team.HAN); + // when + boolean canMove = chariot.canMove(current, next); + // then + assertThat(canMove).isTrue(); + } + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#직선_이동_불가능한_위치") + void 차는_대각선으로_이동할_수_없다(Position current, Position next) { + // given + Piece chariot = new Chariot(Team.HAN); + // when + boolean canMove = chariot.canMove(current, next); + // then + assertThat(canMove).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java new file mode 100644 index 0000000000..13fe752681 --- /dev/null +++ b/src/test/java/model/piece/ElephantTest.java @@ -0,0 +1,33 @@ +package model.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import model.Position; +import model.Team; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class ElephantTest { + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#상_이동_가능한_위치") + void 상은_두칸_직진_후_대각선으로_이동할_수_있다(Position current, Position next) { + // given + Piece elephant = new Elephant(Team.HAN); + // when + boolean canMove = elephant.canMove(current, next); + // then + assertThat(canMove).isTrue(); + } + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#상_이동_불가능한_위치") + void 상은_이동_규칙에_맞지_않으면_이동할_수_없다(Position current, Position next) { + // given + Piece elephant = new Elephant(Team.HAN); + // when + boolean canMove = elephant.canMove(current, next); + // then + assertThat(canMove).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java new file mode 100644 index 0000000000..e2ee138547 --- /dev/null +++ b/src/test/java/model/piece/HorseTest.java @@ -0,0 +1,33 @@ +package model.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import model.Position; +import model.Team; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class HorseTest { + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#마_이동_가능한_위치") + void 마는_한칸_직진_후_대각선으로_이동할_수_있다(Position current, Position next) { + // given + Piece horse = new Horse(Team.HAN); + // when + boolean canMove = horse.canMove(current, next); + // then + assertThat(canMove).isTrue(); + } + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#마_이동_불가능한_위치") + void 마는_이동_규칙에_맞지_않으면_이동할_수_없다(Position current, Position next) { + // given + Piece horse = new Horse(Team.HAN); + // when + boolean canMove = horse.canMove(current, next); + // then + assertThat(canMove).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/model/piece/SoldierTest.java b/src/test/java/model/piece/SoldierTest.java new file mode 100644 index 0000000000..1dc562c66f --- /dev/null +++ b/src/test/java/model/piece/SoldierTest.java @@ -0,0 +1,55 @@ +package model.piece; + +import static org.assertj.core.api.Assertions.assertThat; + +import model.Position; +import model.Team; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class SoldierTest { + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#한나라_병_이동_가능한_위치") + void 한나라_병은_전진과_좌우로_이동할_수_있다(Position current, Position next) { + // given + Piece soldier = new Soldier(Team.HAN); + // when + boolean canMove = soldier.canMove(current, next); + // then + assertThat(canMove).isTrue(); + } + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#한나라_병_이동_불가능한_위치") + void 한나라_병은_후퇴와_두칸_이동을_할_수_없다(Position current, Position next) { + // given + Piece soldier = new Soldier(Team.HAN); + // when + boolean canMove = soldier.canMove(current, next); + // then + assertThat(canMove).isFalse(); + } + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#초나라_졸_이동_가능한_위치") + void 초나라_졸은_전진과_좌우로_이동할_수_있다(Position current, Position next) { + // given + Piece soldier = new Soldier(Team.CHO); + // when + boolean canMove = soldier.canMove(current, next); + // then + assertThat(canMove).isTrue(); + } + + @ParameterizedTest + @MethodSource("model.fixture.PieceTestFixture#초나라_졸_이동_불가능한_위치") + void 초나라_졸은_후퇴와_두칸_이동을_할_수_없다(Position current, Position next) { + // given + Piece soldier = new Soldier(Team.CHO); + // when + boolean canMove = soldier.canMove(current, next); + // then + assertThat(canMove).isFalse(); + } +} \ No newline at end of file From 4aa1071ea16cee21c995d58a4084ea2e3405b04b Mon Sep 17 00:00:00 2001 From: sauter001 Date: Fri, 27 Mar 2026 08:10:46 +0900 Subject: [PATCH 06/26] =?UTF-8?q?=EC=B0=A8/=EC=83=81/=EB=A7=88=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EB=A9=B1=20=EA=B8=B0=EC=A4=80=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 2 +- src/main/java/model/Board.java | 18 +++- src/main/java/model/BoardFactory.java | 2 + src/main/java/model/Janggi.java | 4 + src/main/java/model/coordinate/Direction.java | 87 +++++++++++++++++ .../java/model/coordinate/Directions.java | 24 +++++ .../java/model/coordinate/Displacement.java | 4 + .../model/coordinate/MovablePositions.java | 23 +++++ .../java/model/{ => coordinate}/Position.java | 10 +- .../model/formation/FormationFactory.java | 2 +- .../model/formation/FormationStrategy.java | 2 +- .../model/formation/MaSangMaSangStrategy.java | 18 ++-- .../model/formation/MaSangSangMaStrategy.java | 18 ++-- .../model/formation/SangMaMaSangStrategy.java | 18 ++-- .../model/formation/SangMaSangMaStrategy.java | 18 ++-- src/main/java/model/piece/Cannon.java | 7 ++ src/main/java/model/piece/Chariot.java | 18 ++++ src/main/java/model/piece/Elephant.java | 18 ++++ src/main/java/model/piece/General.java | 7 ++ src/main/java/model/piece/Guard.java | 7 ++ src/main/java/model/piece/Horse.java | 18 ++++ src/main/java/model/piece/Piece.java | 5 +- src/main/java/model/piece/Soldier.java | 8 +- src/main/java/view/InputView.java | 2 +- src/main/java/view/OutputView.java | 2 +- src/test/java/model/BoardTest.java | 2 + src/test/java/model/DirectionTest.java | 44 +++++++++ src/test/java/model/PositionTest.java | 1 + .../java/model/fixture/PieceTestFixture.java | 2 +- .../model/formation/FormationFactoryTest.java | 2 +- .../formation/FormationStrategyTest.java | 18 ++-- src/test/java/model/piece/CannonTest.java | 2 +- src/test/java/model/piece/ChariotTest.java | 94 ++++++++++++++++++- src/test/java/model/piece/ElephantTest.java | 2 +- src/test/java/model/piece/HorseTest.java | 2 +- src/test/java/model/piece/SoldierTest.java | 2 +- 36 files changed, 452 insertions(+), 61 deletions(-) create mode 100644 src/main/java/model/coordinate/Direction.java create mode 100644 src/main/java/model/coordinate/Directions.java create mode 100644 src/main/java/model/coordinate/Displacement.java create mode 100644 src/main/java/model/coordinate/MovablePositions.java rename src/main/java/model/{ => coordinate}/Position.java (81%) create mode 100644 src/test/java/model/DirectionTest.java diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 7b96108023..5e6394e872 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -11,7 +11,7 @@ import model.Board; import model.BoardFactory; import model.Janggi; -import model.Position; +import model.coordinate.Position; import model.Team; import model.formation.FormationFactory; import model.formation.JanggiFormation; diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index 5cb446c851..2f99b042bf 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -1,9 +1,12 @@ package model; +import model.coordinate.MovablePositions; +import model.coordinate.Position; +import model.piece.Piece; + import java.util.HashMap; import java.util.Map; import java.util.Optional; -import model.piece.Piece; public class Board { @@ -41,4 +44,17 @@ private void checkAlly(Piece piece, Piece otherPiece) { throw new IllegalArgumentException("해당 위치는 아군이 존재하는 위치입니다."); } } + + public boolean hasPieceAt(MovablePositions positions) { + for (Position position : positions) { + if (hasPieceAt(position)) { + return true; + } + } + return false; + } + + private boolean hasPieceAt(Position position) { + return board.containsKey(position); + } } diff --git a/src/main/java/model/BoardFactory.java b/src/main/java/model/BoardFactory.java index 5d1d72d6dd..9efc868389 100644 --- a/src/main/java/model/BoardFactory.java +++ b/src/main/java/model/BoardFactory.java @@ -5,6 +5,8 @@ import java.util.HashMap; import java.util.Map; + +import model.coordinate.Position; import model.piece.Cannon; import model.piece.Chariot; import model.piece.General; diff --git a/src/main/java/model/Janggi.java b/src/main/java/model/Janggi.java index 48c13cfaef..28b974d2bb 100644 --- a/src/main/java/model/Janggi.java +++ b/src/main/java/model/Janggi.java @@ -1,5 +1,6 @@ package model; +import model.coordinate.Position; import model.piece.Piece; public class Janggi { @@ -25,6 +26,9 @@ public void move(Position current, Position next) { if (!piece.canMove(current, next)) { throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); } + if (board.hasPieceAt(piece.extractPath(current, next))) { + throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); + } board.movePiece(current, next); this.turn = turn.next(); diff --git a/src/main/java/model/coordinate/Direction.java b/src/main/java/model/coordinate/Direction.java new file mode 100644 index 0000000000..6cce6c791f --- /dev/null +++ b/src/main/java/model/coordinate/Direction.java @@ -0,0 +1,87 @@ +package model.coordinate; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +public enum Direction { + EAST(0, 1), + WEST(0, -1), + NORTH(-1, 0), + SOUTH(1, 0), + NORTH_EAST(-1, 1), + SOUTH_EAST(1, 1), + NORTH_WEST(-1, -1), + SOUTH_WEST(1, -1); + + private final int row; + private final int col; + + Direction(int row, int col) { + this.row = row; + this.col = col; + } + + public static Direction from(Position start, Position end) { + Displacement displacement = end.minus(start); + return Stream.of(values()) + .filter(direction -> hasSameDirection(direction, displacement)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("직선 방향이 아닙니다.")); + } + + /** + * 변위를 직선 1칸 + 대각선 N칸으로 분해한다. + * 마: [직선, 대각선], 상: [직선, 대각선, 대각선] + */ + public static List decomposeToCardinalAndDiagonal(Position start, Position end) { + Displacement displacement = end.minus(start); + Direction cardinal = resolveCardinal(displacement); + Direction diagonal = resolveDiagonal(displacement); + int diagonalCount = Math.min(Math.abs(displacement.row()), Math.abs(displacement.col())); + + List directions = new ArrayList<>(); + directions.add(cardinal); + for (int i = 0; i < diagonalCount; i++) { + directions.add(diagonal); + } + return directions; + } + + private static Direction resolveCardinal(Displacement displacement) { + if (Math.abs(displacement.row()) > Math.abs(displacement.col())) { + return findByRowCol(Integer.signum(displacement.row()), 0); + } + return findByRowCol(0, Integer.signum(displacement.col())); + } + + private static Direction resolveDiagonal(Displacement displacement) { + return findByRowCol(Integer.signum(displacement.row()), Integer.signum(displacement.col())); + } + + private static Direction findByRowCol(int row, int col) { + return Stream.of(values()) + .filter(d -> d.row == row && d.col == col) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("이동할 수 없는 방향입니다.")); + } + + private static boolean hasSameDirection(Direction direction, Displacement displacement) { + /* + 두 방향 벡터 (a,b), (c,d)가 실수배이려면 비례 관계 a:c = b:d 가 성립해야 한다. + 나눗셈의 0 처리 문제를 피하기 위해 ad = bc 형태로 변환하여 판별하고, + 부호 곱 >= 0 조건으로 반대 방향을 걸러낸다. + */ + return direction.row * displacement.col() == direction.col * displacement.row() + && direction.row * displacement.row() >= 0 + && direction.col * displacement.col() >= 0; + } + + public int row() { + return row; + } + + public int col() { + return col; + } +} diff --git a/src/main/java/model/coordinate/Directions.java b/src/main/java/model/coordinate/Directions.java new file mode 100644 index 0000000000..fe83996213 --- /dev/null +++ b/src/main/java/model/coordinate/Directions.java @@ -0,0 +1,24 @@ +package model.coordinate; + +import java.util.Iterator; +import java.util.Set; + +public class Directions implements Iterable { + public static final Directions CARDINAL_DIRECTIONS = new Directions(Set.of( + Direction.EAST, Direction.WEST, + Direction.SOUTH, Direction.NORTH)); + public static final Directions DIAGONAL_DIRECTIONS = new Directions(Set.of( + Direction.NORTH_EAST, Direction.NORTH_WEST, Direction.SOUTH_EAST, Direction.SOUTH_WEST + )); + private final Set directions; + + public Directions(Set directions) { + this.directions = Set.copyOf(directions); + } + + @Override + public Iterator iterator() { + return directions.iterator(); + } +} + diff --git a/src/main/java/model/coordinate/Displacement.java b/src/main/java/model/coordinate/Displacement.java new file mode 100644 index 0000000000..d275b3b523 --- /dev/null +++ b/src/main/java/model/coordinate/Displacement.java @@ -0,0 +1,4 @@ +package model.coordinate; + +public record Displacement(int row, int col) { +} diff --git a/src/main/java/model/coordinate/MovablePositions.java b/src/main/java/model/coordinate/MovablePositions.java new file mode 100644 index 0000000000..d5933fbaf4 --- /dev/null +++ b/src/main/java/model/coordinate/MovablePositions.java @@ -0,0 +1,23 @@ +package model.coordinate; + +import java.util.Iterator; +import java.util.List; + +public class MovablePositions implements Iterable { + private final List paths ; + + private static final MovablePositions EMPTY = new MovablePositions(List.of()); + + public MovablePositions(List paths) { + this.paths = List.copyOf(paths); + } + + public static MovablePositions empty() { + return EMPTY; + } + + @Override + public Iterator iterator() { + return paths.iterator(); + } +} diff --git a/src/main/java/model/Position.java b/src/main/java/model/coordinate/Position.java similarity index 81% rename from src/main/java/model/Position.java rename to src/main/java/model/coordinate/Position.java index 15ec9b9538..0ef5633004 100644 --- a/src/main/java/model/Position.java +++ b/src/main/java/model/coordinate/Position.java @@ -1,4 +1,4 @@ -package model; +package model.coordinate; import static model.Board.BOARD_COL; import static model.Board.BOARD_ROW; @@ -32,4 +32,12 @@ public int calculateRowDiff(Position other) { public int calculateColDiff(Position other) { return this.col - other.col(); } + + public Displacement minus(Position other) { + return new Displacement(calculateRowDiff(other), calculateColDiff(other)); + } + + public Position move(Direction direction) { + return new Position(row + direction.row(), col + direction.col()); + } } diff --git a/src/main/java/model/formation/FormationFactory.java b/src/main/java/model/formation/FormationFactory.java index 1b16aa585b..e3e573e7fd 100644 --- a/src/main/java/model/formation/FormationFactory.java +++ b/src/main/java/model/formation/FormationFactory.java @@ -3,7 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import model.Position; +import model.coordinate.Position; import model.Team; import model.piece.Piece; diff --git a/src/main/java/model/formation/FormationStrategy.java b/src/main/java/model/formation/FormationStrategy.java index c0fba5f1d1..3dcf55376a 100644 --- a/src/main/java/model/formation/FormationStrategy.java +++ b/src/main/java/model/formation/FormationStrategy.java @@ -1,7 +1,7 @@ package model.formation; import java.util.Map; -import model.Position; +import model.coordinate.Position; import model.Team; import model.piece.Piece; diff --git a/src/main/java/model/formation/MaSangMaSangStrategy.java b/src/main/java/model/formation/MaSangMaSangStrategy.java index a871075e4a..7dd5a40a18 100644 --- a/src/main/java/model/formation/MaSangMaSangStrategy.java +++ b/src/main/java/model/formation/MaSangMaSangStrategy.java @@ -1,16 +1,16 @@ package model.formation; -import static model.Position.CHO_LEFT_INNER; -import static model.Position.CHO_LEFT_OUTER; -import static model.Position.CHO_RIGHT_INNER; -import static model.Position.CHO_RIGHT_OUTER; -import static model.Position.HAN_LEFT_INNER; -import static model.Position.HAN_LEFT_OUTER; -import static model.Position.HAN_RIGHT_INNER; -import static model.Position.HAN_RIGHT_OUTER; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; import java.util.Map; -import model.Position; +import model.coordinate.Position; import model.Team; import model.piece.Elephant; import model.piece.Horse; diff --git a/src/main/java/model/formation/MaSangSangMaStrategy.java b/src/main/java/model/formation/MaSangSangMaStrategy.java index 1dce8eff7b..89abdb55d4 100644 --- a/src/main/java/model/formation/MaSangSangMaStrategy.java +++ b/src/main/java/model/formation/MaSangSangMaStrategy.java @@ -1,16 +1,16 @@ package model.formation; -import static model.Position.CHO_LEFT_INNER; -import static model.Position.CHO_LEFT_OUTER; -import static model.Position.CHO_RIGHT_INNER; -import static model.Position.CHO_RIGHT_OUTER; -import static model.Position.HAN_LEFT_INNER; -import static model.Position.HAN_LEFT_OUTER; -import static model.Position.HAN_RIGHT_INNER; -import static model.Position.HAN_RIGHT_OUTER; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; import java.util.Map; -import model.Position; +import model.coordinate.Position; import model.Team; import model.piece.Elephant; import model.piece.Horse; diff --git a/src/main/java/model/formation/SangMaMaSangStrategy.java b/src/main/java/model/formation/SangMaMaSangStrategy.java index 1ef7171d2c..8295cb0b52 100644 --- a/src/main/java/model/formation/SangMaMaSangStrategy.java +++ b/src/main/java/model/formation/SangMaMaSangStrategy.java @@ -1,16 +1,16 @@ package model.formation; -import static model.Position.CHO_LEFT_INNER; -import static model.Position.CHO_LEFT_OUTER; -import static model.Position.CHO_RIGHT_INNER; -import static model.Position.CHO_RIGHT_OUTER; -import static model.Position.HAN_LEFT_INNER; -import static model.Position.HAN_LEFT_OUTER; -import static model.Position.HAN_RIGHT_INNER; -import static model.Position.HAN_RIGHT_OUTER; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; import java.util.Map; -import model.Position; +import model.coordinate.Position; import model.Team; import model.piece.Elephant; import model.piece.Horse; diff --git a/src/main/java/model/formation/SangMaSangMaStrategy.java b/src/main/java/model/formation/SangMaSangMaStrategy.java index a0c6bde01e..7d47a2181a 100644 --- a/src/main/java/model/formation/SangMaSangMaStrategy.java +++ b/src/main/java/model/formation/SangMaSangMaStrategy.java @@ -1,16 +1,16 @@ package model.formation; -import static model.Position.CHO_LEFT_INNER; -import static model.Position.CHO_LEFT_OUTER; -import static model.Position.CHO_RIGHT_INNER; -import static model.Position.CHO_RIGHT_OUTER; -import static model.Position.HAN_LEFT_INNER; -import static model.Position.HAN_LEFT_OUTER; -import static model.Position.HAN_RIGHT_INNER; -import static model.Position.HAN_RIGHT_OUTER; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; import java.util.Map; -import model.Position; +import model.coordinate.Position; import model.Team; import model.piece.Elephant; import model.piece.Horse; diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index cb39548deb..4368bc61c1 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -1,5 +1,7 @@ package model.piece; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; public class Cannon extends Piece { @@ -8,6 +10,11 @@ public Cannon(Team team) { super(team, PieceType.CANNON); } + @Override + public MovablePositions extractPath(Position current, Position next) { + return MovablePositions.empty(); + } + @Override protected boolean comparePosition(int rowDiff, int colDiff) { return (colDiff >= 1 && rowDiff == 0) || (colDiff == 0 && rowDiff >= 1); diff --git a/src/main/java/model/piece/Chariot.java b/src/main/java/model/piece/Chariot.java index 2c4d131365..2b00bf0731 100644 --- a/src/main/java/model/piece/Chariot.java +++ b/src/main/java/model/piece/Chariot.java @@ -1,13 +1,31 @@ package model.piece; +import model.coordinate.Direction; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; +import java.util.ArrayList; +import java.util.List; + public class Chariot extends Piece { public Chariot(Team team) { super(team, PieceType.CHARIOT); } + @Override + public MovablePositions extractPath(Position current, Position next) { + Direction direction = Direction.from(current, next); + List path = new ArrayList<>(); + Position step = current.move(direction); + while (!step.equals(next)) { + path.add(step); + step = step.move(direction); + } + return new MovablePositions(path); + } + @Override protected boolean comparePosition(int rowDiff, int colDiff) { return (colDiff >= 1 && rowDiff == 0) || (colDiff == 0 && rowDiff >= 1); diff --git a/src/main/java/model/piece/Elephant.java b/src/main/java/model/piece/Elephant.java index 6dfb1fafe8..d44d29b813 100644 --- a/src/main/java/model/piece/Elephant.java +++ b/src/main/java/model/piece/Elephant.java @@ -1,13 +1,31 @@ package model.piece; +import model.coordinate.Direction; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; +import java.util.ArrayList; +import java.util.List; + public class Elephant extends Piece { public Elephant(Team team) { super(team, PieceType.ELEPHANT); } + @Override + public MovablePositions extractPath(Position current, Position next) { + List directions = Direction.decomposeToCardinalAndDiagonal(current, next); + List path = new ArrayList<>(); + Position step = current; + for (int i = 0; i < directions.size() - 1; i++) { + step = step.move(directions.get(i)); + path.add(step); + } + return new MovablePositions(path); + } + @Override protected boolean comparePosition(int rowDiff, int colDiff) { return (colDiff == 3 && rowDiff == 2) || (colDiff == 2 && rowDiff == 3); diff --git a/src/main/java/model/piece/General.java b/src/main/java/model/piece/General.java index e012368d42..11f0a4a47a 100644 --- a/src/main/java/model/piece/General.java +++ b/src/main/java/model/piece/General.java @@ -1,5 +1,7 @@ package model.piece; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; public class General extends Piece { @@ -8,6 +10,11 @@ public General(Team team) { super(team, PieceType.GENERAL); } + @Override + public MovablePositions extractPath(Position current, Position next) { + return MovablePositions.empty(); + } + @Override protected boolean comparePosition(int rowDiff, int colDiff) { throw new IllegalArgumentException("1단계 궁성 영역 미구현"); diff --git a/src/main/java/model/piece/Guard.java b/src/main/java/model/piece/Guard.java index 059d9d0036..9f11a9b895 100644 --- a/src/main/java/model/piece/Guard.java +++ b/src/main/java/model/piece/Guard.java @@ -1,5 +1,7 @@ package model.piece; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; public class Guard extends Piece { @@ -8,6 +10,11 @@ public Guard(Team team) { super(team, PieceType.GUARD); } + @Override + public MovablePositions extractPath(Position current, Position next) { + return MovablePositions.empty(); + } + @Override protected boolean comparePosition(int rowDiff, int colDiff) { throw new IllegalArgumentException("1단계 궁성 영역 미구현"); diff --git a/src/main/java/model/piece/Horse.java b/src/main/java/model/piece/Horse.java index a00c0ac82e..75a299a413 100644 --- a/src/main/java/model/piece/Horse.java +++ b/src/main/java/model/piece/Horse.java @@ -1,13 +1,31 @@ package model.piece; +import model.coordinate.Direction; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; +import java.util.ArrayList; +import java.util.List; + public class Horse extends Piece { public Horse(Team team) { super(team, PieceType.HORSE); } + @Override + public MovablePositions extractPath(Position current, Position next) { + List directions = Direction.decomposeToCardinalAndDiagonal(current, next); + List path = new ArrayList<>(); + Position step = current; + for (int i = 0; i < directions.size() - 1; i++) { + step = step.move(directions.get(i)); + path.add(step); + } + return new MovablePositions(path); + } + @Override protected boolean comparePosition(int rowDiff, int colDiff) { return (colDiff == 1 && rowDiff == 2) || (colDiff == 2 && rowDiff == 1); diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 69046ef9e1..6fb8ac1ee6 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -1,6 +1,7 @@ package model.piece; -import model.Position; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; public abstract class Piece { @@ -29,6 +30,8 @@ public PieceType getType() { return type; } + public abstract MovablePositions extractPath(Position current, Position next); + public boolean canMove(Position current, Position next) { int rowDiff = next.calculateRowDiff(current); int colDiff = next.calculateColDiff(current); diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java index 31017c444b..ca14e988e9 100644 --- a/src/main/java/model/piece/Soldier.java +++ b/src/main/java/model/piece/Soldier.java @@ -2,7 +2,8 @@ import static model.Team.CHO; -import model.Position; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; public class Soldier extends Piece { @@ -11,6 +12,11 @@ public Soldier(Team team) { super(team, PieceType.SOLDIER); } + @Override + public MovablePositions extractPath(Position current, Position next) { + return MovablePositions.empty(); + } + @Override public boolean canMove(Position current, Position next) { int rowDiff = next.calculateRowDiff(current); diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index cb0daa712b..1a24ea690d 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Scanner; -import model.Position; +import model.coordinate.Position; import model.Team; import model.formation.JanggiFormation; import model.piece.Piece; diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 6d577610a3..3f2bea0eee 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -11,7 +11,7 @@ import java.util.Map; import model.Board; -import model.Position; +import model.coordinate.Position; import model.piece.Piece; public class OutputView { diff --git a/src/test/java/model/BoardTest.java b/src/test/java/model/BoardTest.java index eab682d206..936e50bf22 100644 --- a/src/test/java/model/BoardTest.java +++ b/src/test/java/model/BoardTest.java @@ -5,6 +5,8 @@ import java.util.HashMap; import java.util.Map; + +import model.coordinate.Position; import model.piece.Chariot; import model.piece.Piece; import org.junit.jupiter.api.Test; diff --git a/src/test/java/model/DirectionTest.java b/src/test/java/model/DirectionTest.java new file mode 100644 index 0000000000..bdcb16b09f --- /dev/null +++ b/src/test/java/model/DirectionTest.java @@ -0,0 +1,44 @@ +package model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import model.coordinate.Direction; +import model.coordinate.Position; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class DirectionTest { + + @ParameterizedTest + @CsvSource({ + "3, 4, 5, 4, SOUTH", // 아래로 직선 + "5, 4, 3, 4, NORTH", // 위로 직선 + "3, 4, 3, 6, EAST", // 오른쪽 직선 + "3, 6, 3, 4, WEST", // 왼쪽 직선 + "3, 4, 5, 6, SOUTH_EAST", // 오른쪽 아래 대각선 + "5, 6, 3, 4, NORTH_WEST", // 왼쪽 위 대각선 + "3, 6, 5, 4, SOUTH_WEST", // 왼쪽 아래 대각선 + "5, 4, 3, 6, NORTH_EAST", // 오른쪽 위 대각선 + }) + void 두_위치로부터_방향을_구할_수_있다(int startRow, int startCol, int endRow, int endCol, Direction expected) { + Position start = new Position(startRow, startCol); + Position end = new Position(endRow, endCol); + + Direction result = Direction.from(start, end); + + assertThat(result).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource({ + "0, 0, 1, 2", // 비례하지 않는 벡터 + }) + void 유효하지_않은_방향이면_예외가_발생한다(int startRow, int startCol, int endRow, int endCol) { + Position start = new Position(startRow, startCol); + Position end = new Position(endRow, endCol); + + assertThatThrownBy(() -> Direction.from(start, end)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/model/PositionTest.java b/src/test/java/model/PositionTest.java index cfc19bcf36..26b5a3d535 100644 --- a/src/test/java/model/PositionTest.java +++ b/src/test/java/model/PositionTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; diff --git a/src/test/java/model/fixture/PieceTestFixture.java b/src/test/java/model/fixture/PieceTestFixture.java index 237a3ec659..ed2ee6909b 100644 --- a/src/test/java/model/fixture/PieceTestFixture.java +++ b/src/test/java/model/fixture/PieceTestFixture.java @@ -1,7 +1,7 @@ package model.fixture; import java.util.stream.Stream; -import model.Position; +import model.coordinate.Position; import org.junit.jupiter.params.provider.Arguments; public class PieceTestFixture { diff --git a/src/test/java/model/formation/FormationFactoryTest.java b/src/test/java/model/formation/FormationFactoryTest.java index 1004d1d322..1463b9f29b 100644 --- a/src/test/java/model/formation/FormationFactoryTest.java +++ b/src/test/java/model/formation/FormationFactoryTest.java @@ -4,7 +4,7 @@ import java.util.Map; import java.util.stream.Stream; -import model.Position; +import model.coordinate.Position; import model.piece.Piece; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/src/test/java/model/formation/FormationStrategyTest.java b/src/test/java/model/formation/FormationStrategyTest.java index 057527417b..1d830090fa 100644 --- a/src/test/java/model/formation/FormationStrategyTest.java +++ b/src/test/java/model/formation/FormationStrategyTest.java @@ -1,18 +1,18 @@ package model.formation; -import static model.Position.CHO_LEFT_INNER; -import static model.Position.CHO_LEFT_OUTER; -import static model.Position.CHO_RIGHT_INNER; -import static model.Position.CHO_RIGHT_OUTER; -import static model.Position.HAN_LEFT_INNER; -import static model.Position.HAN_LEFT_OUTER; -import static model.Position.HAN_RIGHT_INNER; -import static model.Position.HAN_RIGHT_OUTER; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; import static org.assertj.core.api.Assertions.assertThat; import java.util.Map; -import model.Position; +import model.coordinate.Position; import model.Team; import model.piece.Piece; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index 7ef000119a..e7fd4b66c9 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import model.Position; +import model.coordinate.Position; import model.Team; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index 3f5b03d34b..126cca5b61 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -2,8 +2,13 @@ import static org.assertj.core.api.Assertions.assertThat; -import model.Position; +import java.util.HashMap; +import java.util.Map; +import model.Board; +import model.coordinate.MovablePositions; +import model.coordinate.Position; import model.Team; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -30,4 +35,91 @@ public class ChariotTest { // then assertThat(canMove).isFalse(); } + + @Test + void 차는_이동_경로의_중간_위치를_반환한다() { + // given + Piece chariot = new Chariot(Team.HAN); + Position current = new Position(0, 0); + Position next = new Position(0, 5); + + // when + MovablePositions path = chariot.extractPath(current, next); + + // then + assertThat(path).containsExactly( + new Position(0, 1), + new Position(0, 2), + new Position(0, 3), + new Position(0, 4) + ); + } + + @Test + void 차가_한_칸_이동하면_중간_경로가_없다() { + // given + Piece chariot = new Chariot(Team.HAN); + Position current = new Position(3, 4); + Position next = new Position(3, 5); + + // when + MovablePositions path = chariot.extractPath(current, next); + + // then + assertThat(path).isEmpty(); + } + + @Test + void 이동_경로에_기물이_있으면_멱이_적용된다() { + // given + Piece chariot = new Chariot(Team.HAN); + Position current = new Position(0, 0); + Position next = new Position(0, 5); + + Map pieces = new HashMap<>(); + pieces.put(current, chariot); + pieces.put(new Position(0, 3), new Chariot(Team.CHO)); + Board board = new Board(pieces); + + // when + MovablePositions path = chariot.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isTrue(); + } + + @Test + void 이동_경로에_기물이_없으면_멱이_적용되지_않는다() { + // given + Piece chariot = new Chariot(Team.HAN); + Position current = new Position(0, 0); + Position next = new Position(0, 5); + + Map pieces = new HashMap<>(); + pieces.put(current, chariot); + Board board = new Board(pieces); + + // when + MovablePositions path = chariot.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isFalse(); + } + + @Test + void 한_칸_이동은_경로가_없으므로_멱이_적용되지_않는다() { + // given + Piece chariot = new Chariot(Team.HAN); + Position current = new Position(0, 0); + Position next = new Position(0, 1); + + Board board = new Board(new HashMap<>()); + + // when + MovablePositions path = chariot.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isFalse(); + } + } \ No newline at end of file diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index 13fe752681..973209582b 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import model.Position; +import model.coordinate.Position; import model.Team; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index e2ee138547..8402867c7d 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import model.Position; +import model.coordinate.Position; import model.Team; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/model/piece/SoldierTest.java b/src/test/java/model/piece/SoldierTest.java index 1dc562c66f..767409f580 100644 --- a/src/test/java/model/piece/SoldierTest.java +++ b/src/test/java/model/piece/SoldierTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import model.Position; +import model.coordinate.Position; import model.Team; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; From c0b0c98cd9700b75e3559e4704ab4010fcec41c6 Mon Sep 17 00:00:00 2001 From: sauter001 Date: Fri, 27 Mar 2026 08:25:11 +0900 Subject: [PATCH 07/26] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=EC=9D=98?= =?UTF-8?q?=20=EC=A7=84=EC=98=81=20=ED=8C=90=EB=8B=A8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B6=84=EB=A6=AC(is?= =?UTF-8?q?Cho)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/piece/Piece.java | 4 ++++ src/main/java/model/piece/Soldier.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 6fb8ac1ee6..ee2ea5c13c 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -38,5 +38,9 @@ public boolean canMove(Position current, Position next) { return comparePosition(Math.abs(rowDiff), Math.abs(colDiff)); } + public boolean isCho() { + return getTeam() == Team.CHO; + } + protected abstract boolean comparePosition(int rowDiff, int colDiff); } diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java index ca14e988e9..76fa271182 100644 --- a/src/main/java/model/piece/Soldier.java +++ b/src/main/java/model/piece/Soldier.java @@ -26,7 +26,7 @@ public boolean canMove(Position current, Position next) { @Override protected boolean comparePosition(int rowDiff, int colDiff) { - if (getTeam() == CHO) { + if (isCho()) { return (rowDiff == -1 && colDiff == 0) || (rowDiff == 0 && colDiff == 1); } return (rowDiff == 1 && colDiff == 0) || (rowDiff == 0 && colDiff == 1); From 6dc5899af561c141c8c879fdbddda3ca0c5a7bd9 Mon Sep 17 00:00:00 2001 From: sauter001 Date: Fri, 27 Mar 2026 09:26:46 +0900 Subject: [PATCH 08/26] =?UTF-8?q?feat:=20=ED=8F=AC=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=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 | 8 +- src/main/java/model/Board.java | 23 ++++ src/main/java/model/Janggi.java | 31 +++++- src/main/java/model/piece/Cannon.java | 13 ++- src/main/java/model/piece/Piece.java | 6 +- src/test/java/model/piece/CannonTest.java | 104 +++++++++++++++++- 6 files changed, 172 insertions(+), 13 deletions(-) diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 5e6394e872..2e1ef772f9 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -45,12 +45,12 @@ public void run() { } private void playByTurn(Janggi janggi) { - Team nowTurn = janggi.getTurn(); + Team currentTurn = janggi.getTurn(); - Position current = inputView.readSource(nowTurn); - Piece piece = janggi.findPieceAt(current, nowTurn); + Position current = inputView.readSource(currentTurn); + Piece piece = janggi.findPieceAt(current, currentTurn); - Position next = inputView.readDestination(nowTurn, piece); + Position next = inputView.readDestination(currentTurn, piece); janggi.move(current, next); } diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index 2f99b042bf..ec86f1941e 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -45,6 +45,16 @@ private void checkAlly(Piece piece, Piece otherPiece) { } } + public int countPiecesAt(MovablePositions positions) { + int count = 0; + for (Position position : positions) { + if (hasPieceAt(position)) { + count++; + } + } + return count; + } + public boolean hasPieceAt(MovablePositions positions) { for (Position position : positions) { if (hasPieceAt(position)) { @@ -57,4 +67,17 @@ public boolean hasPieceAt(MovablePositions positions) { private boolean hasPieceAt(Position position) { return board.containsKey(position); } + + public boolean hasCannon(MovablePositions positions) { + for (Position position : positions) { + if (!hasPieceAt(position)) { + continue; + } + Piece piece = pickPiece(position); + if (piece.isCannon()) { + return true; + } + } + return false; + } } diff --git a/src/main/java/model/Janggi.java b/src/main/java/model/Janggi.java index 28b974d2bb..0ec166e6d0 100644 --- a/src/main/java/model/Janggi.java +++ b/src/main/java/model/Janggi.java @@ -1,10 +1,12 @@ package model; +import model.coordinate.MovablePositions; import model.coordinate.Position; import model.piece.Piece; public class Janggi { + public static final int CANNON_HURDLE_COUNT = 1; private final Board board; private Team turn; @@ -13,6 +15,14 @@ public Janggi(Board board) { this.turn = Team.CHO; } + public void move(Position current, Position next) { + Piece piece = findPieceAt(current, turn); + validateMovement(current, next, piece); + + board.movePiece(current, next); + this.turn = turn.next(); + } + public Piece findPieceAt(Position position, Team turn) { Piece piece = board.pickPiece(position); if (piece.isEnemy(turn)) { @@ -21,17 +31,30 @@ public Piece findPieceAt(Position position, Team turn) { return piece; } - public void move(Position current, Position next) { - Piece piece = findPieceAt(current, turn); + private void validateMovement(Position current, Position next, Piece piece) { if (!piece.canMove(current, next)) { throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); } + + if (piece.isCannon()) { + validateMovementOfCannon(current, next, piece); + return; + } + if (board.hasPieceAt(piece.extractPath(current, next))) { throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); } + } - board.movePiece(current, next); - this.turn = turn.next(); + private void validateMovementOfCannon(Position current, Position next, Piece piece) { + MovablePositions positions = piece.extractPath(current, next); + int countedPieces = board.countPiecesAt(positions); + if (countedPieces != CANNON_HURDLE_COUNT) { + throw new IllegalArgumentException("포는 1개의 기물만 건너 뛰어야 합니다."); + } + if (board.hasCannon(positions)) { + throw new IllegalArgumentException("포는 포를 건너뛸 수 없습니다."); + } } public Team getTurn() { diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index 4368bc61c1..db2b97e290 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -1,9 +1,13 @@ package model.piece; +import model.coordinate.Direction; import model.coordinate.MovablePositions; import model.coordinate.Position; import model.Team; +import java.util.ArrayList; +import java.util.List; + public class Cannon extends Piece { public Cannon(Team team) { @@ -12,7 +16,14 @@ public Cannon(Team team) { @Override public MovablePositions extractPath(Position current, Position next) { - return MovablePositions.empty(); + Direction direction = Direction.from(current, next); + List path = new ArrayList<>(); + Position step = current.move(direction); + while (!step.equals(next)) { + path.add(step); + step = step.move(direction); + } + return new MovablePositions(path); } @Override diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index ee2ea5c13c..6baf523375 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -1,8 +1,8 @@ package model.piece; +import model.Team; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; public abstract class Piece { @@ -42,5 +42,9 @@ public boolean isCho() { return getTeam() == Team.CHO; } + public boolean isCannon() { + return getType() == PieceType.CANNON; + } + protected abstract boolean comparePosition(int rowDiff, int colDiff); } diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index e7fd4b66c9..1f3e673f6a 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -1,12 +1,19 @@ package model.piece; -import static org.assertj.core.api.Assertions.assertThat; - -import model.coordinate.Position; +import model.Board; +import model.Janggi; import model.Team; +import model.coordinate.Position; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + public class CannonTest { @ParameterizedTest @@ -30,4 +37,95 @@ public class CannonTest { // then assertThat(canMove).isFalse(); } + + @Test + void 포가_1개의_기물을_뛰어넘어_이동한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(3, 0), new Soldier(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when + janggi.move(new Position(5, 0), new Position(1, 0)); + + // then + assertThat(board.pickPiece(new Position(1, 0)).isCannon()).isTrue(); + } + + @Test + void 포가_가로로_1개의_기물을_뛰어넘어_이동한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(5, 3), new Soldier(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when + janggi.move(new Position(5, 0), new Position(5, 6)); + + // then + assertThat(board.pickPiece(new Position(5, 6)).isCannon()).isTrue(); + } + + @Test + void 포가_경로에_기물이_없으면_예외가_발생한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_경로에_기물이_2개_이상이면_예외가_발생한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(4, 0), new Soldier(Team.HAN)); + pieces.put(new Position(3, 0), new Soldier(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_경로에_포를_뛰어넘으면_예외가_발생한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(3, 0), new Cannon(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_적군_기물을_잡을_수_있다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(3, 0), new Soldier(Team.HAN)); + pieces.put(new Position(1, 0), new Chariot(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when + janggi.move(new Position(5, 0), new Position(1, 0)); + + // then + assertThat(board.pickPiece(new Position(1, 0)).isCannon()).isTrue(); + } } \ No newline at end of file From f389157d3cac9efc185b867cc52d6ef0f991c09d Mon Sep 17 00:00:00 2001 From: sauter001 Date: Fri, 27 Mar 2026 09:37:23 +0900 Subject: [PATCH 09/26] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?step-down=20=EB=B0=A9=EC=8B=9D=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 19 ++++++----- src/main/java/model/Board.java | 22 ++++++------ src/main/java/model/BoardFactory.java | 13 +++---- src/main/java/model/coordinate/Direction.java | 22 ++++++------ .../java/model/coordinate/Directions.java | 24 ------------- .../model/coordinate/MovablePositions.java | 3 +- src/main/java/model/coordinate/Position.java | 8 ++--- .../model/formation/FormationFactory.java | 7 ++-- .../model/formation/FormationStrategy.java | 5 +-- .../model/formation/MaSangMaSangStrategy.java | 16 +++------ .../model/formation/MaSangSangMaStrategy.java | 16 +++------ .../model/formation/SangMaMaSangStrategy.java | 16 +++------ .../model/formation/SangMaSangMaStrategy.java | 16 +++------ src/main/java/model/piece/Cannon.java | 2 +- src/main/java/model/piece/Chariot.java | 2 +- src/main/java/model/piece/Elephant.java | 2 +- src/main/java/model/piece/General.java | 2 +- src/main/java/model/piece/Guard.java | 2 +- src/main/java/model/piece/Horse.java | 2 +- src/main/java/model/piece/Piece.java | 22 ++++++------ src/main/java/model/piece/Soldier.java | 4 +-- src/main/java/view/InputView.java | 25 +++++++------- src/main/java/view/OutputView.java | 34 ++++++++----------- .../java/view/formater/BoardFormatter.java | 4 +-- src/main/java/view/mapper/ViewMapper.java | 5 +-- src/test/java/model/BoardTest.java | 12 +++---- src/test/java/model/DirectionTest.java | 6 ++-- src/test/java/model/JanggiFormationTest.java | 6 ++-- src/test/java/model/PositionTest.java | 6 ++-- .../model/fixture/FormationTestFixture.java | 3 +- .../java/model/fixture/PieceTestFixture.java | 3 +- .../model/formation/FormationFactoryTest.java | 9 ++--- .../formation/FormationStrategyTest.java | 18 ++++------ src/test/java/model/piece/ChariotTest.java | 11 +++--- src/test/java/model/piece/ElephantTest.java | 6 ++-- src/test/java/model/piece/HorseTest.java | 6 ++-- src/test/java/model/piece/SoldierTest.java | 6 ++-- 37 files changed, 163 insertions(+), 222 deletions(-) delete mode 100644 src/main/java/model/coordinate/Directions.java diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 2e1ef772f9..3df7b1581c 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,24 +1,25 @@ package controller; -import static controller.Retrier.retry; -import static model.Team.CHO; -import static model.Team.HAN; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; import model.Board; import model.BoardFactory; import model.Janggi; -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import model.formation.FormationFactory; import model.formation.JanggiFormation; import model.piece.Piece; import view.InputView; import view.OutputView; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import static controller.Retrier.retry; +import static model.Team.CHO; +import static model.Team.HAN; + public class JanggiController { private final InputView inputView; private final OutputView outputView; diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index ec86f1941e..5e6db17f67 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -23,13 +23,6 @@ public Map getBoard() { return Map.copyOf(board); } - public Piece pickPiece(Position position) { - if (!board.containsKey(position)) { - throw new IllegalArgumentException("해당 위치에 존재하는 장기말이 없습니다."); - } - return board.get(position); - } - public void movePiece(Position current, Position next) { Piece piece = pickPiece(current); Optional.ofNullable(board.get(next)) @@ -39,6 +32,13 @@ public void movePiece(Position current, Position next) { board.put(next, piece); } + public Piece pickPiece(Position position) { + if (!board.containsKey(position)) { + throw new IllegalArgumentException("해당 위치에 존재하는 장기말이 없습니다."); + } + return board.get(position); + } + private void checkAlly(Piece piece, Piece otherPiece) { if (piece.isSameTeam(otherPiece)) { throw new IllegalArgumentException("해당 위치는 아군이 존재하는 위치입니다."); @@ -55,6 +55,10 @@ public int countPiecesAt(MovablePositions positions) { return count; } + private boolean hasPieceAt(Position position) { + return board.containsKey(position); + } + public boolean hasPieceAt(MovablePositions positions) { for (Position position : positions) { if (hasPieceAt(position)) { @@ -64,10 +68,6 @@ public boolean hasPieceAt(MovablePositions positions) { return false; } - private boolean hasPieceAt(Position position) { - return board.containsKey(position); - } - public boolean hasCannon(MovablePositions positions) { for (Position position : positions) { if (!hasPieceAt(position)) { diff --git a/src/main/java/model/BoardFactory.java b/src/main/java/model/BoardFactory.java index 9efc868389..f87c122ac1 100644 --- a/src/main/java/model/BoardFactory.java +++ b/src/main/java/model/BoardFactory.java @@ -1,18 +1,13 @@ package model; -import static model.Team.CHO; -import static model.Team.HAN; +import model.coordinate.Position; +import model.piece.*; import java.util.HashMap; import java.util.Map; -import model.coordinate.Position; -import model.piece.Cannon; -import model.piece.Chariot; -import model.piece.General; -import model.piece.Guard; -import model.piece.Piece; -import model.piece.Soldier; +import static model.Team.CHO; +import static model.Team.HAN; public class BoardFactory { diff --git a/src/main/java/model/coordinate/Direction.java b/src/main/java/model/coordinate/Direction.java index 6cce6c791f..e4eca2ef2c 100644 --- a/src/main/java/model/coordinate/Direction.java +++ b/src/main/java/model/coordinate/Direction.java @@ -30,6 +30,17 @@ public static Direction from(Position start, Position end) { .orElseThrow(() -> new IllegalArgumentException("직선 방향이 아닙니다.")); } + private static boolean hasSameDirection(Direction direction, Displacement displacement) { + /* + 두 방향 벡터 (a,b), (c,d)가 실수배이려면 비례 관계 a:c = b:d 가 성립해야 한다. + 나눗셈의 0 처리 문제를 피하기 위해 ad = bc 형태로 변환하여 판별하고, + 부호 곱 >= 0 조건으로 반대 방향을 걸러낸다. + */ + return direction.row * displacement.col() == direction.col * displacement.row() + && direction.row * displacement.row() >= 0 + && direction.col * displacement.col() >= 0; + } + /** * 변위를 직선 1칸 + 대각선 N칸으로 분해한다. * 마: [직선, 대각선], 상: [직선, 대각선, 대각선] @@ -66,17 +77,6 @@ private static Direction findByRowCol(int row, int col) { .orElseThrow(() -> new IllegalArgumentException("이동할 수 없는 방향입니다.")); } - private static boolean hasSameDirection(Direction direction, Displacement displacement) { - /* - 두 방향 벡터 (a,b), (c,d)가 실수배이려면 비례 관계 a:c = b:d 가 성립해야 한다. - 나눗셈의 0 처리 문제를 피하기 위해 ad = bc 형태로 변환하여 판별하고, - 부호 곱 >= 0 조건으로 반대 방향을 걸러낸다. - */ - return direction.row * displacement.col() == direction.col * displacement.row() - && direction.row * displacement.row() >= 0 - && direction.col * displacement.col() >= 0; - } - public int row() { return row; } diff --git a/src/main/java/model/coordinate/Directions.java b/src/main/java/model/coordinate/Directions.java deleted file mode 100644 index fe83996213..0000000000 --- a/src/main/java/model/coordinate/Directions.java +++ /dev/null @@ -1,24 +0,0 @@ -package model.coordinate; - -import java.util.Iterator; -import java.util.Set; - -public class Directions implements Iterable { - public static final Directions CARDINAL_DIRECTIONS = new Directions(Set.of( - Direction.EAST, Direction.WEST, - Direction.SOUTH, Direction.NORTH)); - public static final Directions DIAGONAL_DIRECTIONS = new Directions(Set.of( - Direction.NORTH_EAST, Direction.NORTH_WEST, Direction.SOUTH_EAST, Direction.SOUTH_WEST - )); - private final Set directions; - - public Directions(Set directions) { - this.directions = Set.copyOf(directions); - } - - @Override - public Iterator iterator() { - return directions.iterator(); - } -} - diff --git a/src/main/java/model/coordinate/MovablePositions.java b/src/main/java/model/coordinate/MovablePositions.java index d5933fbaf4..60ed14ae12 100644 --- a/src/main/java/model/coordinate/MovablePositions.java +++ b/src/main/java/model/coordinate/MovablePositions.java @@ -4,9 +4,8 @@ import java.util.List; public class MovablePositions implements Iterable { - private final List paths ; - private static final MovablePositions EMPTY = new MovablePositions(List.of()); + private final List paths; public MovablePositions(List paths) { this.paths = List.copyOf(paths); diff --git a/src/main/java/model/coordinate/Position.java b/src/main/java/model/coordinate/Position.java index 0ef5633004..085dfdb2a9 100644 --- a/src/main/java/model/coordinate/Position.java +++ b/src/main/java/model/coordinate/Position.java @@ -25,6 +25,10 @@ public record Position(int row, int col) { } } + public Displacement minus(Position other) { + return new Displacement(calculateRowDiff(other), calculateColDiff(other)); + } + public int calculateRowDiff(Position other) { return this.row() - other.row(); } @@ -33,10 +37,6 @@ public int calculateColDiff(Position other) { return this.col - other.col(); } - public Displacement minus(Position other) { - return new Displacement(calculateRowDiff(other), calculateColDiff(other)); - } - public Position move(Direction direction) { return new Position(row + direction.row(), col + direction.col()); } diff --git a/src/main/java/model/formation/FormationFactory.java b/src/main/java/model/formation/FormationFactory.java index e3e573e7fd..3650299701 100644 --- a/src/main/java/model/formation/FormationFactory.java +++ b/src/main/java/model/formation/FormationFactory.java @@ -1,11 +1,12 @@ package model.formation; +import model.Team; +import model.coordinate.Position; +import model.piece.Piece; + import java.util.HashMap; import java.util.Map; import java.util.Optional; -import model.coordinate.Position; -import model.Team; -import model.piece.Piece; public class FormationFactory { diff --git a/src/main/java/model/formation/FormationStrategy.java b/src/main/java/model/formation/FormationStrategy.java index 3dcf55376a..d802879fdf 100644 --- a/src/main/java/model/formation/FormationStrategy.java +++ b/src/main/java/model/formation/FormationStrategy.java @@ -1,10 +1,11 @@ package model.formation; -import java.util.Map; -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import model.piece.Piece; +import java.util.Map; + public abstract class FormationStrategy { public Map generateFormation(Team team) { diff --git a/src/main/java/model/formation/MaSangMaSangStrategy.java b/src/main/java/model/formation/MaSangMaSangStrategy.java index 7dd5a40a18..3931f7f657 100644 --- a/src/main/java/model/formation/MaSangMaSangStrategy.java +++ b/src/main/java/model/formation/MaSangMaSangStrategy.java @@ -1,21 +1,15 @@ package model.formation; -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; - -import java.util.Map; -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import model.piece.Elephant; import model.piece.Horse; import model.piece.Piece; +import java.util.Map; + +import static model.coordinate.Position.*; + public class MaSangMaSangStrategy extends FormationStrategy { @Override protected Map generateHanFormation() { diff --git a/src/main/java/model/formation/MaSangSangMaStrategy.java b/src/main/java/model/formation/MaSangSangMaStrategy.java index 89abdb55d4..378f02f389 100644 --- a/src/main/java/model/formation/MaSangSangMaStrategy.java +++ b/src/main/java/model/formation/MaSangSangMaStrategy.java @@ -1,21 +1,15 @@ package model.formation; -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; - -import java.util.Map; -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import model.piece.Elephant; import model.piece.Horse; import model.piece.Piece; +import java.util.Map; + +import static model.coordinate.Position.*; + public class MaSangSangMaStrategy extends FormationStrategy { @Override protected Map generateHanFormation() { diff --git a/src/main/java/model/formation/SangMaMaSangStrategy.java b/src/main/java/model/formation/SangMaMaSangStrategy.java index 8295cb0b52..2f75f96334 100644 --- a/src/main/java/model/formation/SangMaMaSangStrategy.java +++ b/src/main/java/model/formation/SangMaMaSangStrategy.java @@ -1,21 +1,15 @@ package model.formation; -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; - -import java.util.Map; -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import model.piece.Elephant; import model.piece.Horse; import model.piece.Piece; +import java.util.Map; + +import static model.coordinate.Position.*; + public class SangMaMaSangStrategy extends FormationStrategy { @Override protected Map generateHanFormation() { diff --git a/src/main/java/model/formation/SangMaSangMaStrategy.java b/src/main/java/model/formation/SangMaSangMaStrategy.java index 7d47a2181a..f3a8948528 100644 --- a/src/main/java/model/formation/SangMaSangMaStrategy.java +++ b/src/main/java/model/formation/SangMaSangMaStrategy.java @@ -1,21 +1,15 @@ package model.formation; -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; - -import java.util.Map; -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import model.piece.Elephant; import model.piece.Horse; import model.piece.Piece; +import java.util.Map; + +import static model.coordinate.Position.*; + public class SangMaSangMaStrategy extends FormationStrategy { @Override protected Map generateHanFormation() { diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index db2b97e290..0a1071cd12 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -1,9 +1,9 @@ package model.piece; +import model.Team; import model.coordinate.Direction; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/model/piece/Chariot.java b/src/main/java/model/piece/Chariot.java index 2b00bf0731..1ec79471f2 100644 --- a/src/main/java/model/piece/Chariot.java +++ b/src/main/java/model/piece/Chariot.java @@ -1,9 +1,9 @@ package model.piece; +import model.Team; import model.coordinate.Direction; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/model/piece/Elephant.java b/src/main/java/model/piece/Elephant.java index d44d29b813..7a56b5639e 100644 --- a/src/main/java/model/piece/Elephant.java +++ b/src/main/java/model/piece/Elephant.java @@ -1,9 +1,9 @@ package model.piece; +import model.Team; import model.coordinate.Direction; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/model/piece/General.java b/src/main/java/model/piece/General.java index 11f0a4a47a..32f8061585 100644 --- a/src/main/java/model/piece/General.java +++ b/src/main/java/model/piece/General.java @@ -1,8 +1,8 @@ package model.piece; +import model.Team; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; public class General extends Piece { diff --git a/src/main/java/model/piece/Guard.java b/src/main/java/model/piece/Guard.java index 9f11a9b895..0c38913d45 100644 --- a/src/main/java/model/piece/Guard.java +++ b/src/main/java/model/piece/Guard.java @@ -1,8 +1,8 @@ package model.piece; +import model.Team; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; public class Guard extends Piece { diff --git a/src/main/java/model/piece/Horse.java b/src/main/java/model/piece/Horse.java index 75a299a413..313f308d25 100644 --- a/src/main/java/model/piece/Horse.java +++ b/src/main/java/model/piece/Horse.java @@ -1,9 +1,9 @@ package model.piece; +import model.Team; import model.coordinate.Direction; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 6baf523375..51bc3ddb75 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -14,6 +14,8 @@ protected Piece(Team team, PieceType type) { this.type = type; } + public abstract MovablePositions extractPath(Position current, Position next); + public boolean isSameTeam(Piece other) { return !isEnemy(other.team); } @@ -22,29 +24,27 @@ public boolean isEnemy(Team team) { return this.team != team; } - public Team getTeam() { - return team; - } - - public PieceType getType() { - return type; - } - - public abstract MovablePositions extractPath(Position current, Position next); - public boolean canMove(Position current, Position next) { int rowDiff = next.calculateRowDiff(current); int colDiff = next.calculateColDiff(current); return comparePosition(Math.abs(rowDiff), Math.abs(colDiff)); } + protected abstract boolean comparePosition(int rowDiff, int colDiff); + public boolean isCho() { return getTeam() == Team.CHO; } + public Team getTeam() { + return team; + } + public boolean isCannon() { return getType() == PieceType.CANNON; } - protected abstract boolean comparePosition(int rowDiff, int colDiff); + public PieceType getType() { + return type; + } } diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java index 76fa271182..3206d9697c 100644 --- a/src/main/java/model/piece/Soldier.java +++ b/src/main/java/model/piece/Soldier.java @@ -1,10 +1,8 @@ package model.piece; -import static model.Team.CHO; - +import model.Team; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; public class Soldier extends Piece { diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 1a24ea690d..248d52c621 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,15 +1,16 @@ package view; -import static view.formater.BoardFormatter.formatSymbol; - -import java.util.List; -import java.util.Scanner; -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import model.formation.JanggiFormation; import model.piece.Piece; import view.parser.InputParser; +import java.util.List; +import java.util.Scanner; + +import static view.formater.BoardFormatter.formatSymbol; + public class InputView { private static final Scanner SCANNER = new Scanner(System.in); private static final InputParser PARSER = new InputParser(); @@ -31,13 +32,6 @@ public Position readSource(Team turn) { System.out.print("기물: "); return extractPosition(); } - - public Position readDestination(Team turn, Piece piece) { - System.out.println(); - System.out.printf("[%s] 기물 %s의 다음 위치를 선택해주세요. (쉼표 기준으로 분리)%n", turn.getName(), formatSymbol(piece)); - System.out.print("기물: "); - return extractPosition(); - } private Position extractPosition() { List tokens = PARSER.parseToken(SCANNER.nextLine(), ","); @@ -45,4 +39,11 @@ private Position extractPosition() { int col = PARSER.parseNumber(tokens.get(1)); return new Position(row, col); } + + public Position readDestination(Team turn, Piece piece) { + System.out.println(); + System.out.printf("[%s] 기물 %s의 다음 위치를 선택해주세요. (쉼표 기준으로 분리)%n", turn.getName(), formatSymbol(piece)); + System.out.print("기물: "); + return extractPosition(); + } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 3f2bea0eee..1ef68a6c0f 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,21 +1,25 @@ package view; -import static view.formater.BoardFormatter.COL_NUM; -import static view.formater.BoardFormatter.RED; -import static view.formater.BoardFormatter.RESET; -import static view.formater.BoardFormatter.ROW_NUM; -import static view.formater.BoardFormatter.SPACE; -import static view.formater.BoardFormatter.VERTICAL_LINE; -import static view.formater.BoardFormatter.formatHorizon; -import static view.formater.BoardFormatter.formatSymbol; - -import java.util.Map; import model.Board; import model.coordinate.Position; import model.piece.Piece; +import java.util.Map; + +import static view.formater.BoardFormatter.*; + public class OutputView { + public void displayBoard(Map board) { + displayColIndex(); + String border = formatHorizon(Board.BOARD_COL); + System.out.println(border); + + displayPositionByPiece(board); + + System.out.println(border); + } + private static void displayPositionByPiece(Map board) { for (int row = 0; row < Board.BOARD_ROW; row++) { System.out.print(ROW_NUM[row] + " " + VERTICAL_LINE); @@ -36,16 +40,6 @@ private static void displayColIndex() { System.out.println(); } - public void displayBoard(Map board) { - displayColIndex(); - String border = formatHorizon(Board.BOARD_COL); - System.out.println(border); - - displayPositionByPiece(board); - - System.out.println(border); - } - public void displayError(String message) { System.out.println(RED + "[ERROR] " + message + RESET); } diff --git a/src/main/java/view/formater/BoardFormatter.java b/src/main/java/view/formater/BoardFormatter.java index d46f1cb8f3..a91a117f46 100644 --- a/src/main/java/view/formater/BoardFormatter.java +++ b/src/main/java/view/formater/BoardFormatter.java @@ -1,10 +1,10 @@ package view.formater; -import static view.mapper.ViewMapper.getSymbol; - import model.Team; import model.piece.Piece; +import static view.mapper.ViewMapper.getSymbol; + public class BoardFormatter { public static final String RED = "\u001B[31m"; diff --git a/src/main/java/view/mapper/ViewMapper.java b/src/main/java/view/mapper/ViewMapper.java index 5e34c18bac..d4bad8d2bc 100644 --- a/src/main/java/view/mapper/ViewMapper.java +++ b/src/main/java/view/mapper/ViewMapper.java @@ -1,10 +1,11 @@ package view.mapper; -import java.util.EnumMap; -import java.util.Map; import model.Team; import model.piece.PieceType; +import java.util.EnumMap; +import java.util.Map; + public class ViewMapper { private static final Map> SYMBOL_MAP = new EnumMap<>(PieceType.class); diff --git a/src/test/java/model/BoardTest.java b/src/test/java/model/BoardTest.java index 936e50bf22..f3375a4134 100644 --- a/src/test/java/model/BoardTest.java +++ b/src/test/java/model/BoardTest.java @@ -1,16 +1,16 @@ package model; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.HashMap; -import java.util.Map; - import model.coordinate.Position; import model.piece.Chariot; import model.piece.Piece; import org.junit.jupiter.api.Test; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + class BoardTest { @Test diff --git a/src/test/java/model/DirectionTest.java b/src/test/java/model/DirectionTest.java index bdcb16b09f..ec576362d8 100644 --- a/src/test/java/model/DirectionTest.java +++ b/src/test/java/model/DirectionTest.java @@ -1,13 +1,13 @@ package model; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - import model.coordinate.Direction; import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + class DirectionTest { @ParameterizedTest diff --git a/src/test/java/model/JanggiFormationTest.java b/src/test/java/model/JanggiFormationTest.java index 1d3c7fc928..c26b9613d9 100644 --- a/src/test/java/model/JanggiFormationTest.java +++ b/src/test/java/model/JanggiFormationTest.java @@ -1,13 +1,13 @@ package model; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - import model.formation.JanggiFormation; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + class JanggiFormationTest { @ParameterizedTest diff --git a/src/test/java/model/PositionTest.java b/src/test/java/model/PositionTest.java index 26b5a3d535..4ccc7557ae 100644 --- a/src/test/java/model/PositionTest.java +++ b/src/test/java/model/PositionTest.java @@ -1,12 +1,12 @@ package model; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + class PositionTest { @ParameterizedTest diff --git a/src/test/java/model/fixture/FormationTestFixture.java b/src/test/java/model/fixture/FormationTestFixture.java index 134f10dbf3..edf452e824 100644 --- a/src/test/java/model/fixture/FormationTestFixture.java +++ b/src/test/java/model/fixture/FormationTestFixture.java @@ -1,6 +1,5 @@ package model.fixture; -import java.util.stream.Stream; import model.Team; import model.formation.MaSangMaSangStrategy; import model.formation.MaSangSangMaStrategy; @@ -10,6 +9,8 @@ import model.piece.Horse; import org.junit.jupiter.params.provider.Arguments; +import java.util.stream.Stream; + public class FormationTestFixture { public static Stream 한나라_상차림_전략() { diff --git a/src/test/java/model/fixture/PieceTestFixture.java b/src/test/java/model/fixture/PieceTestFixture.java index ed2ee6909b..0db9c40791 100644 --- a/src/test/java/model/fixture/PieceTestFixture.java +++ b/src/test/java/model/fixture/PieceTestFixture.java @@ -1,9 +1,10 @@ package model.fixture; -import java.util.stream.Stream; import model.coordinate.Position; import org.junit.jupiter.params.provider.Arguments; +import java.util.stream.Stream; + public class PieceTestFixture { // ============================ diff --git a/src/test/java/model/formation/FormationFactoryTest.java b/src/test/java/model/formation/FormationFactoryTest.java index 1463b9f29b..978fa40503 100644 --- a/src/test/java/model/formation/FormationFactoryTest.java +++ b/src/test/java/model/formation/FormationFactoryTest.java @@ -1,15 +1,16 @@ package model.formation; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Map; -import java.util.stream.Stream; import model.coordinate.Position; import model.piece.Piece; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + class FormationFactoryTest { static Stream validFormationCombinations() { diff --git a/src/test/java/model/formation/FormationStrategyTest.java b/src/test/java/model/formation/FormationStrategyTest.java index 1d830090fa..54a9143bcf 100644 --- a/src/test/java/model/formation/FormationStrategyTest.java +++ b/src/test/java/model/formation/FormationStrategyTest.java @@ -1,23 +1,17 @@ package model.formation; -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Map; -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import model.piece.Piece; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.util.Map; + +import static model.coordinate.Position.*; +import static org.assertj.core.api.Assertions.assertThat; + class FormationStrategyTest { @ParameterizedTest diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index 126cca5b61..7f39d8b2a2 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -1,17 +1,18 @@ package model.piece; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.HashMap; -import java.util.Map; import model.Board; +import model.Team; import model.coordinate.MovablePositions; import model.coordinate.Position; -import model.Team; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + public class ChariotTest { @ParameterizedTest diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index 973209582b..c70d9c2c15 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -1,12 +1,12 @@ package model.piece; -import static org.assertj.core.api.Assertions.assertThat; - -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import static org.assertj.core.api.Assertions.assertThat; + public class ElephantTest { @ParameterizedTest diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index 8402867c7d..ade3a314ab 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -1,12 +1,12 @@ package model.piece; -import static org.assertj.core.api.Assertions.assertThat; - -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import static org.assertj.core.api.Assertions.assertThat; + public class HorseTest { @ParameterizedTest diff --git a/src/test/java/model/piece/SoldierTest.java b/src/test/java/model/piece/SoldierTest.java index 767409f580..86731d6d53 100644 --- a/src/test/java/model/piece/SoldierTest.java +++ b/src/test/java/model/piece/SoldierTest.java @@ -1,12 +1,12 @@ package model.piece; -import static org.assertj.core.api.Assertions.assertThat; - -import model.coordinate.Position; import model.Team; +import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import static org.assertj.core.api.Assertions.assertThat; + public class SoldierTest { @ParameterizedTest From a994b25c59b7947f11ec969730381473de28d68d Mon Sep 17 00:00:00 2001 From: sauter001 Date: Fri, 27 Mar 2026 09:39:05 +0900 Subject: [PATCH 10/26] =?UTF-8?q?style:=20=EC=A3=BC=EC=84=9D=20javadocs?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/coordinate/Direction.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/model/coordinate/Direction.java b/src/main/java/model/coordinate/Direction.java index e4eca2ef2c..4f6750db6e 100644 --- a/src/main/java/model/coordinate/Direction.java +++ b/src/main/java/model/coordinate/Direction.java @@ -30,12 +30,12 @@ public static Direction from(Position start, Position end) { .orElseThrow(() -> new IllegalArgumentException("직선 방향이 아닙니다.")); } + /** + * 두 방향 벡터 (a,b), (c,d)가 실수배이려면 비례 관계 a:c = b:d 가 성립해야 한다. + * 나눗셈의 0 처리 문제를 피하기 위해 ad = bc 형태로 변환하여 판별하고, + * 부호 곱 >= 0 조건으로 반대 방향을 걸러낸다. + */ private static boolean hasSameDirection(Direction direction, Displacement displacement) { - /* - 두 방향 벡터 (a,b), (c,d)가 실수배이려면 비례 관계 a:c = b:d 가 성립해야 한다. - 나눗셈의 0 처리 문제를 피하기 위해 ad = bc 형태로 변환하여 판별하고, - 부호 곱 >= 0 조건으로 반대 방향을 걸러낸다. - */ return direction.row * displacement.col() == direction.col * displacement.row() && direction.row * displacement.row() >= 0 && direction.col * displacement.col() >= 0; From 8a9035dc452e9e0d96178519f089055ba3114150 Mon Sep 17 00:00:00 2001 From: sauter001 Date: Fri, 27 Mar 2026 09:47:31 +0900 Subject: [PATCH 11/26] =?UTF-8?q?refactor:=20import.*=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/BoardFactory.java | 7 ++++++- src/main/java/model/formation/MaSangMaSangStrategy.java | 9 ++++++++- src/main/java/model/formation/MaSangSangMaStrategy.java | 9 ++++++++- src/main/java/model/formation/SangMaMaSangStrategy.java | 9 ++++++++- src/main/java/model/formation/SangMaSangMaStrategy.java | 9 ++++++++- src/main/java/view/OutputView.java | 9 ++++++++- src/test/java/model/formation/FormationStrategyTest.java | 9 ++++++++- 7 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/main/java/model/BoardFactory.java b/src/main/java/model/BoardFactory.java index f87c122ac1..e95ae3952c 100644 --- a/src/main/java/model/BoardFactory.java +++ b/src/main/java/model/BoardFactory.java @@ -1,7 +1,12 @@ package model; import model.coordinate.Position; -import model.piece.*; +import model.piece.Cannon; +import model.piece.Chariot; +import model.piece.General; +import model.piece.Guard; +import model.piece.Piece; +import model.piece.Soldier; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/model/formation/MaSangMaSangStrategy.java b/src/main/java/model/formation/MaSangMaSangStrategy.java index 3931f7f657..55469d9804 100644 --- a/src/main/java/model/formation/MaSangMaSangStrategy.java +++ b/src/main/java/model/formation/MaSangMaSangStrategy.java @@ -8,7 +8,14 @@ import java.util.Map; -import static model.coordinate.Position.*; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; public class MaSangMaSangStrategy extends FormationStrategy { @Override diff --git a/src/main/java/model/formation/MaSangSangMaStrategy.java b/src/main/java/model/formation/MaSangSangMaStrategy.java index 378f02f389..fe0ffdd156 100644 --- a/src/main/java/model/formation/MaSangSangMaStrategy.java +++ b/src/main/java/model/formation/MaSangSangMaStrategy.java @@ -8,7 +8,14 @@ import java.util.Map; -import static model.coordinate.Position.*; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; public class MaSangSangMaStrategy extends FormationStrategy { @Override diff --git a/src/main/java/model/formation/SangMaMaSangStrategy.java b/src/main/java/model/formation/SangMaMaSangStrategy.java index 2f75f96334..cc3be623b1 100644 --- a/src/main/java/model/formation/SangMaMaSangStrategy.java +++ b/src/main/java/model/formation/SangMaMaSangStrategy.java @@ -8,7 +8,14 @@ import java.util.Map; -import static model.coordinate.Position.*; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; public class SangMaMaSangStrategy extends FormationStrategy { @Override diff --git a/src/main/java/model/formation/SangMaSangMaStrategy.java b/src/main/java/model/formation/SangMaSangMaStrategy.java index f3a8948528..b6ab972cb8 100644 --- a/src/main/java/model/formation/SangMaSangMaStrategy.java +++ b/src/main/java/model/formation/SangMaSangMaStrategy.java @@ -8,7 +8,14 @@ import java.util.Map; -import static model.coordinate.Position.*; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; public class SangMaSangMaStrategy extends FormationStrategy { @Override diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 1ef68a6c0f..c65b66952e 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -6,7 +6,14 @@ import java.util.Map; -import static view.formater.BoardFormatter.*; +import static view.formater.BoardFormatter.COL_NUM; +import static view.formater.BoardFormatter.RED; +import static view.formater.BoardFormatter.RESET; +import static view.formater.BoardFormatter.ROW_NUM; +import static view.formater.BoardFormatter.SPACE; +import static view.formater.BoardFormatter.VERTICAL_LINE; +import static view.formater.BoardFormatter.formatHorizon; +import static view.formater.BoardFormatter.formatSymbol; public class OutputView { diff --git a/src/test/java/model/formation/FormationStrategyTest.java b/src/test/java/model/formation/FormationStrategyTest.java index 54a9143bcf..d7b055a78d 100644 --- a/src/test/java/model/formation/FormationStrategyTest.java +++ b/src/test/java/model/formation/FormationStrategyTest.java @@ -9,7 +9,14 @@ import java.util.Map; -import static model.coordinate.Position.*; +import static model.coordinate.Position.CHO_LEFT_INNER; +import static model.coordinate.Position.CHO_LEFT_OUTER; +import static model.coordinate.Position.CHO_RIGHT_INNER; +import static model.coordinate.Position.CHO_RIGHT_OUTER; +import static model.coordinate.Position.HAN_LEFT_INNER; +import static model.coordinate.Position.HAN_LEFT_OUTER; +import static model.coordinate.Position.HAN_RIGHT_INNER; +import static model.coordinate.Position.HAN_RIGHT_OUTER; import static org.assertj.core.api.Assertions.assertThat; class FormationStrategyTest { From ca929e7232783703e442c24b6105462d0502094e Mon Sep 17 00:00:00 2001 From: sauter001 Date: Fri, 27 Mar 2026 09:59:46 +0900 Subject: [PATCH 12/26] =?UTF-8?q?refactor:=20=EC=83=81,=20=EB=A7=88?= =?UTF-8?q?=EA=B0=80=20=EB=A9=B1=EC=9D=BC=20=EB=95=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 | 4 +- src/main/java/model/Board.java | 7 +- src/main/java/view/parser/InputParser.java | 4 +- src/test/java/model/piece/ElephantTest.java | 92 +++++++++++++++++++ src/test/java/model/piece/HorseTest.java | 59 ++++++++++++ 5 files changed, 158 insertions(+), 8 deletions(-) diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 3df7b1581c..24feb55f78 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -36,12 +36,12 @@ public void run() { Map pieceByFormation = FormationFactory.generateFormation(hanFormation, choFormation); Board board = BoardFactory.generatePieces(pieceByFormation); - outputView.displayBoard(board.getBoard()); + outputView.displayBoard(board.board()); Janggi janggi = new Janggi(board); while (true) { retry(() -> playByTurn(janggi), processError()); - outputView.displayBoard(board.getBoard()); + outputView.displayBoard(board.board()); } } diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index 5e6db17f67..bf30c964f3 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -8,18 +8,17 @@ import java.util.Map; import java.util.Optional; -public class Board { +public record Board(Map board) { public static final int BOARD_ROW = 10; public static final int BOARD_COL = 9; - private final Map board; - public Board(Map board) { this.board = new HashMap<>(board); } - public Map getBoard() { + @Override + public Map board() { return Map.copyOf(board); } diff --git a/src/main/java/view/parser/InputParser.java b/src/main/java/view/parser/InputParser.java index bd60f1d13b..8eeb8874f1 100644 --- a/src/main/java/view/parser/InputParser.java +++ b/src/main/java/view/parser/InputParser.java @@ -4,7 +4,7 @@ import java.util.List; public class InputParser { - + public int parseNumber(String input) { try { return Integer.parseInt(input.strip()); @@ -15,7 +15,7 @@ public int parseNumber(String input) { public List parseToken(String input, String delimiter) { return Arrays.stream(input.strip() - .split(delimiter)) + .split(delimiter)) .map(String::strip) .toList(); } diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index c70d9c2c15..658c8e5a17 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -1,11 +1,19 @@ package model.piece; +import model.Board; +import model.Janggi; import model.Team; +import model.coordinate.MovablePositions; import model.coordinate.Position; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.util.HashMap; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ElephantTest { @@ -30,4 +38,88 @@ public class ElephantTest { // then assertThat(canMove).isFalse(); } + + @Test + void 상의_직선_경로에_기물이_있으면_멱이_적용된다() { + // given + Piece elephant = new Elephant(Team.HAN); + Position current = new Position(5, 4); + Position next = new Position(2, 6); + + Map pieces = new HashMap<>(); + pieces.put(current, elephant); + pieces.put(new Position(4, 4), new Soldier(Team.CHO)); + Board board = new Board(pieces); + + // when + MovablePositions path = elephant.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isTrue(); + } + + @Test + void 상의_대각선_경로에_기물이_있으면_멱이_적용된다() { + // given + Piece elephant = new Elephant(Team.HAN); + Position current = new Position(5, 4); + Position next = new Position(2, 6); + + Map pieces = new HashMap<>(); + pieces.put(current, elephant); + pieces.put(new Position(3, 5), new Soldier(Team.CHO)); + Board board = new Board(pieces); + + // when + MovablePositions path = elephant.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isTrue(); + } + + @Test + void 상의_직선_경로에_기물이_있으면_이동할_수_없다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 4), new Elephant(Team.CHO)); + pieces.put(new Position(4, 4), new Soldier(Team.CHO)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 4), new Position(2, 6))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 상의_대각선_경로에_기물이_있으면_이동할_수_없다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 4), new Elephant(Team.CHO)); + pieces.put(new Position(3, 5), new Soldier(Team.CHO)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 4), new Position(2, 6))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 상의_경로에_기물이_없으면_멱이_적용되지_않는다() { + // given + Piece elephant = new Elephant(Team.HAN); + Position current = new Position(5, 4); + Position next = new Position(2, 6); + + Map pieces = new HashMap<>(); + pieces.put(current, elephant); + Board board = new Board(pieces); + + // when + MovablePositions path = elephant.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isFalse(); + } } \ No newline at end of file diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index ade3a314ab..ea4c20ed10 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -1,11 +1,19 @@ package model.piece; +import model.Board; +import model.Janggi; import model.Team; +import model.coordinate.MovablePositions; import model.coordinate.Position; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.util.HashMap; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class HorseTest { @@ -30,4 +38,55 @@ public class HorseTest { // then assertThat(canMove).isFalse(); } + + @Test + void 마의_직선_경로에_기물이_있으면_멱이_적용된다() { + // given + Piece horse = new Horse(Team.HAN); + Position current = new Position(5, 4); + Position next = new Position(3, 5); + + Map pieces = new HashMap<>(); + pieces.put(current, horse); + pieces.put(new Position(4, 4), new Soldier(Team.CHO)); + Board board = new Board(pieces); + + // when + MovablePositions path = horse.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isTrue(); + } + + @Test + void 마의_경로에_기물이_있으면_이동할_수_없다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 4), new Horse(Team.CHO)); + pieces.put(new Position(4, 4), new Soldier(Team.CHO)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 4), new Position(3, 5))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 마의_경로에_기물이_없으면_멱이_적용되지_않는다() { + // given + Piece horse = new Horse(Team.HAN); + Position current = new Position(5, 4); + Position next = new Position(3, 5); + + Map pieces = new HashMap<>(); + pieces.put(current, horse); + Board board = new Board(pieces); + + // when + MovablePositions path = horse.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isFalse(); + } } \ No newline at end of file From e980fe6ab2f95db42ea8b002252a9e54ebe40a84 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Fri, 27 Mar 2026 13:37:20 +0900 Subject: [PATCH 13/26] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=88=9C=EC=84=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/Board.java | 42 ++++++++++++++-------------- src/main/java/model/Janggi.java | 9 +++--- src/main/java/model/piece/Piece.java | 12 ++++---- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index bf30c964f3..72a610fc10 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -1,27 +1,23 @@ package model; -import model.coordinate.MovablePositions; -import model.coordinate.Position; -import model.piece.Piece; - import java.util.HashMap; import java.util.Map; import java.util.Optional; +import model.coordinate.MovablePositions; +import model.coordinate.Position; +import model.piece.Piece; -public record Board(Map board) { +public class Board { public static final int BOARD_ROW = 10; public static final int BOARD_COL = 9; + private final Map board; + public Board(Map board) { this.board = new HashMap<>(board); } - @Override - public Map board() { - return Map.copyOf(board); - } - public void movePiece(Position current, Position next) { Piece piece = pickPiece(current); Optional.ofNullable(board.get(next)) @@ -32,18 +28,12 @@ public void movePiece(Position current, Position next) { } public Piece pickPiece(Position position) { - if (!board.containsKey(position)) { + if (!hasPieceAt(position)) { throw new IllegalArgumentException("해당 위치에 존재하는 장기말이 없습니다."); } return board.get(position); } - private void checkAlly(Piece piece, Piece otherPiece) { - if (piece.isSameTeam(otherPiece)) { - throw new IllegalArgumentException("해당 위치는 아군이 존재하는 위치입니다."); - } - } - public int countPiecesAt(MovablePositions positions) { int count = 0; for (Position position : positions) { @@ -54,10 +44,6 @@ public int countPiecesAt(MovablePositions positions) { return count; } - private boolean hasPieceAt(Position position) { - return board.containsKey(position); - } - public boolean hasPieceAt(MovablePositions positions) { for (Position position : positions) { if (hasPieceAt(position)) { @@ -79,4 +65,18 @@ public boolean hasCannon(MovablePositions positions) { } return false; } + + private void checkAlly(Piece piece, Piece otherPiece) { + if (piece.isSameTeam(otherPiece)) { + throw new IllegalArgumentException("해당 위치는 아군이 존재하는 위치입니다."); + } + } + + private boolean hasPieceAt(Position position) { + return board.containsKey(position); + } + + public Map board() { + return Map.copyOf(board); + } } diff --git a/src/main/java/model/Janggi.java b/src/main/java/model/Janggi.java index 0ec166e6d0..1131622f83 100644 --- a/src/main/java/model/Janggi.java +++ b/src/main/java/model/Janggi.java @@ -7,6 +7,7 @@ public class Janggi { public static final int CANNON_HURDLE_COUNT = 1; + private final Board board; private Team turn; @@ -31,6 +32,10 @@ public Piece findPieceAt(Position position, Team turn) { return piece; } + public Team getTurn() { + return turn; + } + private void validateMovement(Position current, Position next, Piece piece) { if (!piece.canMove(current, next)) { throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); @@ -56,8 +61,4 @@ private void validateMovementOfCannon(Position current, Position next, Piece pie throw new IllegalArgumentException("포는 포를 건너뛸 수 없습니다."); } } - - public Team getTurn() { - return turn; - } } diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 51bc3ddb75..bd14a21aab 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -16,6 +16,8 @@ protected Piece(Team team, PieceType type) { public abstract MovablePositions extractPath(Position current, Position next); + protected abstract boolean comparePosition(int rowDiff, int colDiff); + public boolean isSameTeam(Piece other) { return !isEnemy(other.team); } @@ -30,20 +32,18 @@ public boolean canMove(Position current, Position next) { return comparePosition(Math.abs(rowDiff), Math.abs(colDiff)); } - protected abstract boolean comparePosition(int rowDiff, int colDiff); - public boolean isCho() { return getTeam() == Team.CHO; } - public Team getTeam() { - return team; - } - public boolean isCannon() { return getType() == PieceType.CANNON; } + public Team getTeam() { + return team; + } + public PieceType getType() { return type; } From ebba9f4a432419455f90b0741a4c5d71a9acc09f Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Fri, 27 Mar 2026 15:01:42 +0900 Subject: [PATCH 14/26] =?UTF-8?q?refactor:=20=EC=9E=A5=EA=B8=B0=ED=8C=90?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=A4=91=EA=B4=84=ED=98=B8=20Depth=20?= =?UTF-8?q?=EA=B0=90=EC=86=8C=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/Board.java | 39 ++++++------------- src/main/java/model/Janggi.java | 11 +++--- .../model/coordinate/MovablePositions.java | 22 ----------- src/main/java/model/piece/Cannon.java | 10 ++--- src/main/java/model/piece/Chariot.java | 10 ++--- src/main/java/model/piece/Elephant.java | 10 ++--- src/main/java/model/piece/General.java | 6 +-- src/main/java/model/piece/Guard.java | 6 +-- src/main/java/model/piece/Horse.java | 10 ++--- src/main/java/model/piece/Piece.java | 4 +- src/main/java/model/piece/Soldier.java | 6 +-- src/test/java/model/piece/ChariotTest.java | 21 +++++----- src/test/java/model/piece/ElephantTest.java | 19 +++++---- src/test/java/model/piece/HorseTest.java | 17 ++++---- 14 files changed, 72 insertions(+), 119 deletions(-) delete mode 100644 src/main/java/model/coordinate/MovablePositions.java diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index 72a610fc10..d50a79ee8c 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -1,9 +1,9 @@ package model; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; -import model.coordinate.MovablePositions; import model.coordinate.Position; import model.piece.Piece; @@ -34,36 +34,21 @@ public Piece pickPiece(Position position) { return board.get(position); } - public int countPiecesAt(MovablePositions positions) { - int count = 0; - for (Position position : positions) { - if (hasPieceAt(position)) { - count++; - } - } - return count; + public int countPiecesAt(List path) { + return (int) path.stream() + .filter(this::hasPieceAt) + .count(); } - public boolean hasPieceAt(MovablePositions positions) { - for (Position position : positions) { - if (hasPieceAt(position)) { - return true; - } - } - return false; + public boolean hasPieceAt(List path) { + return path.stream() + .anyMatch(this::hasPieceAt); } - public boolean hasCannon(MovablePositions positions) { - for (Position position : positions) { - if (!hasPieceAt(position)) { - continue; - } - Piece piece = pickPiece(position); - if (piece.isCannon()) { - return true; - } - } - return false; + public boolean hasCannon(List path) { + return path.stream() + .filter(this::hasPieceAt) + .anyMatch(position -> pickPiece(position).isCannon()); } private void checkAlly(Piece piece, Piece otherPiece) { diff --git a/src/main/java/model/Janggi.java b/src/main/java/model/Janggi.java index 1131622f83..a12b842df7 100644 --- a/src/main/java/model/Janggi.java +++ b/src/main/java/model/Janggi.java @@ -1,6 +1,6 @@ package model; -import model.coordinate.MovablePositions; +import java.util.List; import model.coordinate.Position; import model.piece.Piece; @@ -46,18 +46,19 @@ private void validateMovement(Position current, Position next, Piece piece) { return; } - if (board.hasPieceAt(piece.extractPath(current, next))) { + List path = piece.extractPath(current, next); + if (board.hasPieceAt(path)) { throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); } } private void validateMovementOfCannon(Position current, Position next, Piece piece) { - MovablePositions positions = piece.extractPath(current, next); - int countedPieces = board.countPiecesAt(positions); + List path = piece.extractPath(current, next); + int countedPieces = board.countPiecesAt(path); if (countedPieces != CANNON_HURDLE_COUNT) { throw new IllegalArgumentException("포는 1개의 기물만 건너 뛰어야 합니다."); } - if (board.hasCannon(positions)) { + if (board.hasCannon(path)) { throw new IllegalArgumentException("포는 포를 건너뛸 수 없습니다."); } } diff --git a/src/main/java/model/coordinate/MovablePositions.java b/src/main/java/model/coordinate/MovablePositions.java deleted file mode 100644 index 60ed14ae12..0000000000 --- a/src/main/java/model/coordinate/MovablePositions.java +++ /dev/null @@ -1,22 +0,0 @@ -package model.coordinate; - -import java.util.Iterator; -import java.util.List; - -public class MovablePositions implements Iterable { - private static final MovablePositions EMPTY = new MovablePositions(List.of()); - private final List paths; - - public MovablePositions(List paths) { - this.paths = List.copyOf(paths); - } - - public static MovablePositions empty() { - return EMPTY; - } - - @Override - public Iterator iterator() { - return paths.iterator(); - } -} diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index 0a1071cd12..9d75514ec4 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -1,13 +1,11 @@ package model.piece; +import java.util.ArrayList; +import java.util.List; import model.Team; import model.coordinate.Direction; -import model.coordinate.MovablePositions; import model.coordinate.Position; -import java.util.ArrayList; -import java.util.List; - public class Cannon extends Piece { public Cannon(Team team) { @@ -15,7 +13,7 @@ public Cannon(Team team) { } @Override - public MovablePositions extractPath(Position current, Position next) { + public List extractPath(Position current, Position next) { Direction direction = Direction.from(current, next); List path = new ArrayList<>(); Position step = current.move(direction); @@ -23,7 +21,7 @@ public MovablePositions extractPath(Position current, Position next) { path.add(step); step = step.move(direction); } - return new MovablePositions(path); + return path; } @Override diff --git a/src/main/java/model/piece/Chariot.java b/src/main/java/model/piece/Chariot.java index 1ec79471f2..cc009e8a73 100644 --- a/src/main/java/model/piece/Chariot.java +++ b/src/main/java/model/piece/Chariot.java @@ -1,13 +1,11 @@ package model.piece; +import java.util.ArrayList; +import java.util.List; import model.Team; import model.coordinate.Direction; -import model.coordinate.MovablePositions; import model.coordinate.Position; -import java.util.ArrayList; -import java.util.List; - public class Chariot extends Piece { public Chariot(Team team) { @@ -15,7 +13,7 @@ public Chariot(Team team) { } @Override - public MovablePositions extractPath(Position current, Position next) { + public List extractPath(Position current, Position next) { Direction direction = Direction.from(current, next); List path = new ArrayList<>(); Position step = current.move(direction); @@ -23,7 +21,7 @@ public MovablePositions extractPath(Position current, Position next) { path.add(step); step = step.move(direction); } - return new MovablePositions(path); + return path; } @Override diff --git a/src/main/java/model/piece/Elephant.java b/src/main/java/model/piece/Elephant.java index 7a56b5639e..a06fc7ea4c 100644 --- a/src/main/java/model/piece/Elephant.java +++ b/src/main/java/model/piece/Elephant.java @@ -1,13 +1,11 @@ package model.piece; +import java.util.ArrayList; +import java.util.List; import model.Team; import model.coordinate.Direction; -import model.coordinate.MovablePositions; import model.coordinate.Position; -import java.util.ArrayList; -import java.util.List; - public class Elephant extends Piece { public Elephant(Team team) { @@ -15,7 +13,7 @@ public Elephant(Team team) { } @Override - public MovablePositions extractPath(Position current, Position next) { + public List extractPath(Position current, Position next) { List directions = Direction.decomposeToCardinalAndDiagonal(current, next); List path = new ArrayList<>(); Position step = current; @@ -23,7 +21,7 @@ public MovablePositions extractPath(Position current, Position next) { step = step.move(directions.get(i)); path.add(step); } - return new MovablePositions(path); + return path; } @Override diff --git a/src/main/java/model/piece/General.java b/src/main/java/model/piece/General.java index 32f8061585..a99c128c62 100644 --- a/src/main/java/model/piece/General.java +++ b/src/main/java/model/piece/General.java @@ -1,7 +1,7 @@ package model.piece; +import java.util.List; import model.Team; -import model.coordinate.MovablePositions; import model.coordinate.Position; public class General extends Piece { @@ -11,8 +11,8 @@ public General(Team team) { } @Override - public MovablePositions extractPath(Position current, Position next) { - return MovablePositions.empty(); + public List extractPath(Position current, Position next) { + throw new IllegalArgumentException("1단계 궁성 영역 미구현"); } @Override diff --git a/src/main/java/model/piece/Guard.java b/src/main/java/model/piece/Guard.java index 0c38913d45..1564dedf11 100644 --- a/src/main/java/model/piece/Guard.java +++ b/src/main/java/model/piece/Guard.java @@ -1,7 +1,7 @@ package model.piece; +import java.util.List; import model.Team; -import model.coordinate.MovablePositions; import model.coordinate.Position; public class Guard extends Piece { @@ -11,8 +11,8 @@ public Guard(Team team) { } @Override - public MovablePositions extractPath(Position current, Position next) { - return MovablePositions.empty(); + public List extractPath(Position current, Position next) { + throw new IllegalArgumentException("1단계 궁성 영역 미구현"); } @Override diff --git a/src/main/java/model/piece/Horse.java b/src/main/java/model/piece/Horse.java index 313f308d25..74d3bd43fd 100644 --- a/src/main/java/model/piece/Horse.java +++ b/src/main/java/model/piece/Horse.java @@ -1,13 +1,11 @@ package model.piece; +import java.util.ArrayList; +import java.util.List; import model.Team; import model.coordinate.Direction; -import model.coordinate.MovablePositions; import model.coordinate.Position; -import java.util.ArrayList; -import java.util.List; - public class Horse extends Piece { public Horse(Team team) { @@ -15,7 +13,7 @@ public Horse(Team team) { } @Override - public MovablePositions extractPath(Position current, Position next) { + public List extractPath(Position current, Position next) { List directions = Direction.decomposeToCardinalAndDiagonal(current, next); List path = new ArrayList<>(); Position step = current; @@ -23,7 +21,7 @@ public MovablePositions extractPath(Position current, Position next) { step = step.move(directions.get(i)); path.add(step); } - return new MovablePositions(path); + return path; } @Override diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index bd14a21aab..68c0ee6001 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -1,7 +1,7 @@ package model.piece; +import java.util.List; import model.Team; -import model.coordinate.MovablePositions; import model.coordinate.Position; public abstract class Piece { @@ -14,7 +14,7 @@ protected Piece(Team team, PieceType type) { this.type = type; } - public abstract MovablePositions extractPath(Position current, Position next); + public abstract List extractPath(Position current, Position next); protected abstract boolean comparePosition(int rowDiff, int colDiff); diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java index 3206d9697c..d3126261b6 100644 --- a/src/main/java/model/piece/Soldier.java +++ b/src/main/java/model/piece/Soldier.java @@ -1,7 +1,7 @@ package model.piece; +import java.util.List; import model.Team; -import model.coordinate.MovablePositions; import model.coordinate.Position; public class Soldier extends Piece { @@ -11,8 +11,8 @@ public Soldier(Team team) { } @Override - public MovablePositions extractPath(Position current, Position next) { - return MovablePositions.empty(); + public List extractPath(Position current, Position next) { + return List.of(); } @Override diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index 7f39d8b2a2..5a87bdb7a4 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -1,18 +1,17 @@ package model.piece; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; import model.Board; import model.Team; -import model.coordinate.MovablePositions; import model.coordinate.Position; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - public class ChariotTest { @ParameterizedTest @@ -45,7 +44,7 @@ public class ChariotTest { Position next = new Position(0, 5); // when - MovablePositions path = chariot.extractPath(current, next); + List path = chariot.extractPath(current, next); // then assertThat(path).containsExactly( @@ -64,7 +63,7 @@ public class ChariotTest { Position next = new Position(3, 5); // when - MovablePositions path = chariot.extractPath(current, next); + List path = chariot.extractPath(current, next); // then assertThat(path).isEmpty(); @@ -83,7 +82,7 @@ public class ChariotTest { Board board = new Board(pieces); // when - MovablePositions path = chariot.extractPath(current, next); + List path = chariot.extractPath(current, next); // then assertThat(board.hasPieceAt(path)).isTrue(); @@ -101,7 +100,7 @@ public class ChariotTest { Board board = new Board(pieces); // when - MovablePositions path = chariot.extractPath(current, next); + List path = chariot.extractPath(current, next); // then assertThat(board.hasPieceAt(path)).isFalse(); @@ -117,7 +116,7 @@ public class ChariotTest { Board board = new Board(new HashMap<>()); // when - MovablePositions path = chariot.extractPath(current, next); + List path = chariot.extractPath(current, next); // then assertThat(board.hasPieceAt(path)).isFalse(); diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index 658c8e5a17..f560edb782 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -1,20 +1,19 @@ package model.piece; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; import model.Board; import model.Janggi; import model.Team; -import model.coordinate.MovablePositions; import model.coordinate.Position; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public class ElephantTest { @ParameterizedTest @@ -52,7 +51,7 @@ public class ElephantTest { Board board = new Board(pieces); // when - MovablePositions path = elephant.extractPath(current, next); + List path = elephant.extractPath(current, next); // then assertThat(board.hasPieceAt(path)).isTrue(); @@ -71,7 +70,7 @@ public class ElephantTest { Board board = new Board(pieces); // when - MovablePositions path = elephant.extractPath(current, next); + List path = elephant.extractPath(current, next); // then assertThat(board.hasPieceAt(path)).isTrue(); @@ -117,7 +116,7 @@ public class ElephantTest { Board board = new Board(pieces); // when - MovablePositions path = elephant.extractPath(current, next); + List path = elephant.extractPath(current, next); // then assertThat(board.hasPieceAt(path)).isFalse(); diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index ea4c20ed10..51d1ff9ec2 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -1,20 +1,19 @@ package model.piece; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; import model.Board; import model.Janggi; import model.Team; -import model.coordinate.MovablePositions; import model.coordinate.Position; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public class HorseTest { @ParameterizedTest @@ -52,7 +51,7 @@ public class HorseTest { Board board = new Board(pieces); // when - MovablePositions path = horse.extractPath(current, next); + List path = horse.extractPath(current, next); // then assertThat(board.hasPieceAt(path)).isTrue(); @@ -84,7 +83,7 @@ public class HorseTest { Board board = new Board(pieces); // when - MovablePositions path = horse.extractPath(current, next); + List path = horse.extractPath(current, next); // then assertThat(board.hasPieceAt(path)).isFalse(); From c26b932ed15dd3d04b98b62cfdd6f7f28b4c5a21 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Fri, 27 Mar 2026 15:15:24 +0900 Subject: [PATCH 15/26] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=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/model/coordinate/Direction.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/model/coordinate/Direction.java b/src/main/java/model/coordinate/Direction.java index 4f6750db6e..972f980cbe 100644 --- a/src/main/java/model/coordinate/Direction.java +++ b/src/main/java/model/coordinate/Direction.java @@ -30,21 +30,12 @@ public static Direction from(Position start, Position end) { .orElseThrow(() -> new IllegalArgumentException("직선 방향이 아닙니다.")); } - /** - * 두 방향 벡터 (a,b), (c,d)가 실수배이려면 비례 관계 a:c = b:d 가 성립해야 한다. - * 나눗셈의 0 처리 문제를 피하기 위해 ad = bc 형태로 변환하여 판별하고, - * 부호 곱 >= 0 조건으로 반대 방향을 걸러낸다. - */ private static boolean hasSameDirection(Direction direction, Displacement displacement) { return direction.row * displacement.col() == direction.col * displacement.row() && direction.row * displacement.row() >= 0 && direction.col * displacement.col() >= 0; } - /** - * 변위를 직선 1칸 + 대각선 N칸으로 분해한다. - * 마: [직선, 대각선], 상: [직선, 대각선, 대각선] - */ public static List decomposeToCardinalAndDiagonal(Position start, Position end) { Displacement displacement = end.minus(start); Direction cardinal = resolveCardinal(displacement); From 7015c8b3ccf82fe82938ddd194c36e67d0f22b21 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Mon, 30 Mar 2026 14:58:23 +0900 Subject: [PATCH 16/26] =?UTF-8?q?test:=20=EC=B0=A8=EC=99=80=20=ED=8F=AC=20?= =?UTF-8?q?=EA=B8=B0=EB=AC=BC=20=EC=9D=B4=EB=8F=99=20test=20fixture=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=85=8B=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/model/piece/Piece.java | 12 +++++----- .../java/model/fixture/PieceTestFixture.java | 21 ++++++++++-------- src/test/java/model/piece/CannonTest.java | 22 +++++++++++-------- src/test/java/model/piece/ChariotTest.java | 4 ++-- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 68c0ee6001..e08d543e14 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -16,8 +16,6 @@ protected Piece(Team team, PieceType type) { public abstract List extractPath(Position current, Position next); - protected abstract boolean comparePosition(int rowDiff, int colDiff); - public boolean isSameTeam(Piece other) { return !isEnemy(other.team); } @@ -32,18 +30,20 @@ public boolean canMove(Position current, Position next) { return comparePosition(Math.abs(rowDiff), Math.abs(colDiff)); } + protected abstract boolean comparePosition(int rowDiff, int colDiff); + public boolean isCho() { return getTeam() == Team.CHO; } - public boolean isCannon() { - return getType() == PieceType.CANNON; - } - public Team getTeam() { return team; } + public boolean isCannon() { + return getType() == PieceType.CANNON; + } + public PieceType getType() { return type; } diff --git a/src/test/java/model/fixture/PieceTestFixture.java b/src/test/java/model/fixture/PieceTestFixture.java index 0db9c40791..9594120737 100644 --- a/src/test/java/model/fixture/PieceTestFixture.java +++ b/src/test/java/model/fixture/PieceTestFixture.java @@ -1,17 +1,21 @@ package model.fixture; +import java.util.stream.Stream; import model.coordinate.Position; import org.junit.jupiter.params.provider.Arguments; -import java.util.stream.Stream; - public class PieceTestFixture { + // 공통 + public static Stream 제자리_이동_케이스() { + return Stream.of(Arguments.of(new Position(0, 0), new Position(0, 0))); + } + // ============================ // 직선 이동 (車, 包 공통) // ============================ - public static Stream 직선_이동_가능한_위치() { + public static Stream 사방위_이동_방향_케이스() { return Stream.of( Arguments.of(new Position(0, 0), new Position(0, 5)), // 우 이동 Arguments.of(new Position(0, 0), new Position(5, 0)), // 하 이동 @@ -20,13 +24,12 @@ public class PieceTestFixture { ); } - public static Stream 직선_이동_불가능한_위치() { + public static Stream 사간방_대각선_이동_방향_케이스() { return Stream.of( - Arguments.of(new Position(0, 0), new Position(1, 1)), // 정대각선 (1:1) - Arguments.of(new Position(0, 0), new Position(2, 1)), // rowDiff > colDiff - Arguments.of(new Position(0, 0), new Position(1, 2)), // rowDiff < colDiff - Arguments.of(new Position(5, 5), new Position(7, 3)), // 음수 방향 대각선 - Arguments.of(new Position(5, 5), new Position(9, 8)) // 둘 다 다름 + Arguments.of(new Position(2, 2), new Position(4, 4)), // 1. 북동 (NE): row 증가, col 증가 + Arguments.of(new Position(5, 5), new Position(3, 7)), // 2. 남동 (SE): row 감소, col 증가 + Arguments.of(new Position(5, 5), new Position(2, 2)), // 3. 남서 (SW): row 감소, col 감소 + Arguments.of(new Position(2, 5), new Position(4, 3)) // 4. 북서 (NW): row 증가, col 감소 ); } diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index 1f3e673f6a..b91eb1d0e3 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -1,5 +1,10 @@ package model.piece; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashMap; +import java.util.Map; import model.Board; import model.Janggi; import model.Team; @@ -8,32 +13,31 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public class CannonTest { @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#직선_이동_가능한_위치") + @MethodSource("model.fixture.PieceTestFixture#사방위_이동_방향_케이스") void 포는_직선으로_이동할_수_있다(Position current, Position next) { // given Piece cannon = new Cannon(Team.HAN); + // when boolean canMove = cannon.canMove(current, next); + // then assertThat(canMove).isTrue(); } @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#직선_이동_불가능한_위치") - void 포는_대각선으로_이동할_수_없다(Position current, Position next) { + @MethodSource("model.fixture.PieceTestFixture#사간방_대각선_이동_방향_케이스") + @MethodSource("model.fixture.PieceTestFixture#제자리_이동_케이스") + void 포는_대각선이나_제자리로_이동할_수_없다(Position current, Position next) { // given Piece cannon = new Cannon(Team.HAN); + // when boolean canMove = cannon.canMove(current, next); + // then assertThat(canMove).isFalse(); } diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index 5a87bdb7a4..916525cf55 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -15,7 +15,7 @@ public class ChariotTest { @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#직선_이동_가능한_위치") + @MethodSource("model.fixture.PieceTestFixture#사방위_이동_방향_케이스") void 차는_직선으로_이동할_수_있다(Position current, Position next) { // given Piece chariot = new Chariot(Team.HAN); @@ -26,7 +26,7 @@ public class ChariotTest { } @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#직선_이동_불가능한_위치") + @MethodSource("model.fixture.PieceTestFixture#사간방_대각선_이동_방향_케이스") void 차는_대각선으로_이동할_수_없다(Position current, Position next) { // given Piece chariot = new Chariot(Team.HAN); From d6a8dd6b0ed9e75fbacea8292fb67d4aec131645 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Mon, 30 Mar 2026 15:43:51 +0900 Subject: [PATCH 17/26] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=98=AC?= =?UTF-8?q?=EB=B0=94=EB=A5=B8=20=EA=B0=9D=EC=B2=B4=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/model/BoardTest.java | 66 ++++++++++-- src/test/java/model/JanggiTest.java | 107 ++++++++++++++++++++ src/test/java/model/piece/CannonTest.java | 97 ------------------ src/test/java/model/piece/ChariotTest.java | 60 +---------- src/test/java/model/piece/ElephantTest.java | 95 +---------------- src/test/java/model/piece/HorseTest.java | 62 +----------- src/test/java/model/piece/SoldierTest.java | 12 ++- 7 files changed, 187 insertions(+), 312 deletions(-) create mode 100644 src/test/java/model/JanggiTest.java diff --git a/src/test/java/model/BoardTest.java b/src/test/java/model/BoardTest.java index f3375a4134..fd53f24088 100644 --- a/src/test/java/model/BoardTest.java +++ b/src/test/java/model/BoardTest.java @@ -1,16 +1,16 @@ package model; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; import model.coordinate.Position; import model.piece.Chariot; import model.piece.Piece; import org.junit.jupiter.api.Test; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - class BoardTest { @Test @@ -56,4 +56,58 @@ class BoardTest { // then assertThat(board.pickPiece(next)).isEqualTo(han); } + + @Test + void 이동_경로에_기물이_있으면_멱이_적용된다() { + // given + Piece chariot = new Chariot(Team.HAN); + Position current = new Position(0, 0); + Position next = new Position(0, 5); + + Map pieces = new HashMap<>(); + pieces.put(current, chariot); + pieces.put(new Position(0, 3), new Chariot(Team.CHO)); + Board board = new Board(pieces); + + // when + List path = chariot.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isTrue(); + } + + + @Test + void 한_칸_이동은_경로가_없으므로_멱이_적용되지_않는다() { + // given + Piece chariot = new Chariot(Team.HAN); + Position current = new Position(0, 0); + Position next = new Position(0, 1); + + Board board = new Board(new HashMap<>()); + + // when + List path = chariot.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isFalse(); + } + + @Test + void 이동_경로에_기물이_없으면_멱이_적용되지_않는다() { + // given + Piece chariot = new Chariot(Team.HAN); + Position current = new Position(0, 0); + Position next = new Position(0, 5); + + Map pieces = new HashMap<>(); + pieces.put(current, chariot); + Board board = new Board(pieces); + + // when + List path = chariot.extractPath(current, next); + + // then + assertThat(board.hasPieceAt(path)).isFalse(); + } } \ No newline at end of file diff --git a/src/test/java/model/JanggiTest.java b/src/test/java/model/JanggiTest.java new file mode 100644 index 0000000000..23341909c5 --- /dev/null +++ b/src/test/java/model/JanggiTest.java @@ -0,0 +1,107 @@ +package model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashMap; +import java.util.Map; +import model.coordinate.Position; +import model.piece.Cannon; +import model.piece.Chariot; +import model.piece.Piece; +import model.piece.Soldier; +import org.junit.jupiter.api.Test; + +class JanggiTest { + + @Test + void 포가_1개의_기물을_뛰어넘어_이동한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(3, 0), new Soldier(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when + janggi.move(new Position(5, 0), new Position(1, 0)); + + // then + assertThat(board.pickPiece(new Position(1, 0)).isCannon()).isTrue(); + } + + @Test + void 포가_가로로_1개의_기물을_뛰어넘어_이동한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(5, 3), new Soldier(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when + janggi.move(new Position(5, 0), new Position(5, 6)); + + // then + assertThat(board.pickPiece(new Position(5, 6)).isCannon()).isTrue(); + } + + @Test + void 포가_경로에_기물이_없으면_예외가_발생한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_경로에_기물이_2개_이상이면_예외가_발생한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(4, 0), new Soldier(Team.HAN)); + pieces.put(new Position(3, 0), new Soldier(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_경로에_포를_뛰어넘으면_예외가_발생한다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(3, 0), new Cannon(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when & then + assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_적군_기물을_잡을_수_있다() { + // given + Map pieces = new HashMap<>(); + pieces.put(new Position(5, 0), new Cannon(Team.CHO)); + pieces.put(new Position(3, 0), new Soldier(Team.HAN)); + pieces.put(new Position(1, 0), new Chariot(Team.HAN)); + Board board = new Board(pieces); + Janggi janggi = new Janggi(board); + + // when + janggi.move(new Position(5, 0), new Position(1, 0)); + + // then + assertThat(board.pickPiece(new Position(1, 0)).isCannon()).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index b91eb1d0e3..44e634d6a2 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -1,15 +1,9 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.HashMap; -import java.util.Map; -import model.Board; -import model.Janggi; import model.Team; import model.coordinate.Position; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -41,95 +35,4 @@ public class CannonTest { // then assertThat(canMove).isFalse(); } - - @Test - void 포가_1개의_기물을_뛰어넘어_이동한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(3, 0), new Soldier(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when - janggi.move(new Position(5, 0), new Position(1, 0)); - - // then - assertThat(board.pickPiece(new Position(1, 0)).isCannon()).isTrue(); - } - - @Test - void 포가_가로로_1개의_기물을_뛰어넘어_이동한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(5, 3), new Soldier(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when - janggi.move(new Position(5, 0), new Position(5, 6)); - - // then - assertThat(board.pickPiece(new Position(5, 6)).isCannon()).isTrue(); - } - - @Test - void 포가_경로에_기물이_없으면_예외가_발생한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 포가_경로에_기물이_2개_이상이면_예외가_발생한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(4, 0), new Soldier(Team.HAN)); - pieces.put(new Position(3, 0), new Soldier(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 포가_경로에_포를_뛰어넘으면_예외가_발생한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(3, 0), new Cannon(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 포가_적군_기물을_잡을_수_있다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(3, 0), new Soldier(Team.HAN)); - pieces.put(new Position(1, 0), new Chariot(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when - janggi.move(new Position(5, 0), new Position(1, 0)); - - // then - assertThat(board.pickPiece(new Position(1, 0)).isCannon()).isTrue(); - } } \ No newline at end of file diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index 916525cf55..6af9e150e2 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -2,10 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import model.Board; import model.Team; import model.coordinate.Position; import org.junit.jupiter.api.Test; @@ -27,7 +24,8 @@ public class ChariotTest { @ParameterizedTest @MethodSource("model.fixture.PieceTestFixture#사간방_대각선_이동_방향_케이스") - void 차는_대각선으로_이동할_수_없다(Position current, Position next) { + @MethodSource("model.fixture.PieceTestFixture#제자리_이동_케이스") + void 차는_대각선_또는_제자리로_이동할_수_없다(Position current, Position next) { // given Piece chariot = new Chariot(Team.HAN); // when @@ -68,58 +66,4 @@ public class ChariotTest { // then assertThat(path).isEmpty(); } - - @Test - void 이동_경로에_기물이_있으면_멱이_적용된다() { - // given - Piece chariot = new Chariot(Team.HAN); - Position current = new Position(0, 0); - Position next = new Position(0, 5); - - Map pieces = new HashMap<>(); - pieces.put(current, chariot); - pieces.put(new Position(0, 3), new Chariot(Team.CHO)); - Board board = new Board(pieces); - - // when - List path = chariot.extractPath(current, next); - - // then - assertThat(board.hasPieceAt(path)).isTrue(); - } - - @Test - void 이동_경로에_기물이_없으면_멱이_적용되지_않는다() { - // given - Piece chariot = new Chariot(Team.HAN); - Position current = new Position(0, 0); - Position next = new Position(0, 5); - - Map pieces = new HashMap<>(); - pieces.put(current, chariot); - Board board = new Board(pieces); - - // when - List path = chariot.extractPath(current, next); - - // then - assertThat(board.hasPieceAt(path)).isFalse(); - } - - @Test - void 한_칸_이동은_경로가_없으므로_멱이_적용되지_않는다() { - // given - Piece chariot = new Chariot(Team.HAN); - Position current = new Position(0, 0); - Position next = new Position(0, 1); - - Board board = new Board(new HashMap<>()); - - // when - List path = chariot.extractPath(current, next); - - // then - assertThat(board.hasPieceAt(path)).isFalse(); - } - } \ No newline at end of file diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index f560edb782..65ee47cb07 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -1,16 +1,9 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import model.Board; -import model.Janggi; import model.Team; import model.coordinate.Position; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -21,8 +14,10 @@ public class ElephantTest { void 상은_두칸_직진_후_대각선으로_이동할_수_있다(Position current, Position next) { // given Piece elephant = new Elephant(Team.HAN); + // when boolean canMove = elephant.canMove(current, next); + // then assertThat(canMove).isTrue(); } @@ -32,93 +27,11 @@ public class ElephantTest { void 상은_이동_규칙에_맞지_않으면_이동할_수_없다(Position current, Position next) { // given Piece elephant = new Elephant(Team.HAN); - // when - boolean canMove = elephant.canMove(current, next); - // then - assertThat(canMove).isFalse(); - } - - @Test - void 상의_직선_경로에_기물이_있으면_멱이_적용된다() { - // given - Piece elephant = new Elephant(Team.HAN); - Position current = new Position(5, 4); - Position next = new Position(2, 6); - - Map pieces = new HashMap<>(); - pieces.put(current, elephant); - pieces.put(new Position(4, 4), new Soldier(Team.CHO)); - Board board = new Board(pieces); - - // when - List path = elephant.extractPath(current, next); - - // then - assertThat(board.hasPieceAt(path)).isTrue(); - } - - @Test - void 상의_대각선_경로에_기물이_있으면_멱이_적용된다() { - // given - Piece elephant = new Elephant(Team.HAN); - Position current = new Position(5, 4); - Position next = new Position(2, 6); - - Map pieces = new HashMap<>(); - pieces.put(current, elephant); - pieces.put(new Position(3, 5), new Soldier(Team.CHO)); - Board board = new Board(pieces); // when - List path = elephant.extractPath(current, next); - - // then - assertThat(board.hasPieceAt(path)).isTrue(); - } - - @Test - void 상의_직선_경로에_기물이_있으면_이동할_수_없다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 4), new Elephant(Team.CHO)); - pieces.put(new Position(4, 4), new Soldier(Team.CHO)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 4), new Position(2, 6))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 상의_대각선_경로에_기물이_있으면_이동할_수_없다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 4), new Elephant(Team.CHO)); - pieces.put(new Position(3, 5), new Soldier(Team.CHO)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 4), new Position(2, 6))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 상의_경로에_기물이_없으면_멱이_적용되지_않는다() { - // given - Piece elephant = new Elephant(Team.HAN); - Position current = new Position(5, 4); - Position next = new Position(2, 6); - - Map pieces = new HashMap<>(); - pieces.put(current, elephant); - Board board = new Board(pieces); - - // when - List path = elephant.extractPath(current, next); + boolean canMove = elephant.canMove(current, next); // then - assertThat(board.hasPieceAt(path)).isFalse(); + assertThat(canMove).isFalse(); } } \ No newline at end of file diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index 51d1ff9ec2..9c977ee32e 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -1,16 +1,9 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import model.Board; -import model.Janggi; import model.Team; import model.coordinate.Position; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -21,8 +14,10 @@ public class HorseTest { void 마는_한칸_직진_후_대각선으로_이동할_수_있다(Position current, Position next) { // given Piece horse = new Horse(Team.HAN); + // when boolean canMove = horse.canMove(current, next); + // then assertThat(canMove).isTrue(); } @@ -32,60 +27,11 @@ public class HorseTest { void 마는_이동_규칙에_맞지_않으면_이동할_수_없다(Position current, Position next) { // given Piece horse = new Horse(Team.HAN); - // when - boolean canMove = horse.canMove(current, next); - // then - assertThat(canMove).isFalse(); - } - - @Test - void 마의_직선_경로에_기물이_있으면_멱이_적용된다() { - // given - Piece horse = new Horse(Team.HAN); - Position current = new Position(5, 4); - Position next = new Position(3, 5); - - Map pieces = new HashMap<>(); - pieces.put(current, horse); - pieces.put(new Position(4, 4), new Soldier(Team.CHO)); - Board board = new Board(pieces); - - // when - List path = horse.extractPath(current, next); - - // then - assertThat(board.hasPieceAt(path)).isTrue(); - } - - @Test - void 마의_경로에_기물이_있으면_이동할_수_없다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 4), new Horse(Team.CHO)); - pieces.put(new Position(4, 4), new Soldier(Team.CHO)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 4), new Position(3, 5))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 마의_경로에_기물이_없으면_멱이_적용되지_않는다() { - // given - Piece horse = new Horse(Team.HAN); - Position current = new Position(5, 4); - Position next = new Position(3, 5); - - Map pieces = new HashMap<>(); - pieces.put(current, horse); - Board board = new Board(pieces); // when - List path = horse.extractPath(current, next); + boolean canMove = horse.canMove(current, next); // then - assertThat(board.hasPieceAt(path)).isFalse(); + assertThat(canMove).isFalse(); } } \ No newline at end of file diff --git a/src/test/java/model/piece/SoldierTest.java b/src/test/java/model/piece/SoldierTest.java index 86731d6d53..5fe6ca5810 100644 --- a/src/test/java/model/piece/SoldierTest.java +++ b/src/test/java/model/piece/SoldierTest.java @@ -1,12 +1,12 @@ package model.piece; +import static org.assertj.core.api.Assertions.assertThat; + import model.Team; import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import static org.assertj.core.api.Assertions.assertThat; - public class SoldierTest { @ParameterizedTest @@ -14,8 +14,10 @@ public class SoldierTest { void 한나라_병은_전진과_좌우로_이동할_수_있다(Position current, Position next) { // given Piece soldier = new Soldier(Team.HAN); + // when boolean canMove = soldier.canMove(current, next); + // then assertThat(canMove).isTrue(); } @@ -25,8 +27,10 @@ public class SoldierTest { void 한나라_병은_후퇴와_두칸_이동을_할_수_없다(Position current, Position next) { // given Piece soldier = new Soldier(Team.HAN); + // when boolean canMove = soldier.canMove(current, next); + // then assertThat(canMove).isFalse(); } @@ -36,8 +40,10 @@ public class SoldierTest { void 초나라_졸은_전진과_좌우로_이동할_수_있다(Position current, Position next) { // given Piece soldier = new Soldier(Team.CHO); + // when boolean canMove = soldier.canMove(current, next); + // then assertThat(canMove).isTrue(); } @@ -47,8 +53,10 @@ public class SoldierTest { void 초나라_졸은_후퇴와_두칸_이동을_할_수_없다(Position current, Position next) { // given Piece soldier = new Soldier(Team.CHO); + // when boolean canMove = soldier.canMove(current, next); + // then assertThat(canMove).isFalse(); } From 7fba74609aba219aac9128a2d54f69f3bbceeb76 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Tue, 31 Mar 2026 14:36:54 +0900 Subject: [PATCH 18/26] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B2=BD=EB=A1=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/fixture/PieceMovePathFixture.java | 82 +++++++++++++++++++ ...ure.java => PieceMovePositionFixture.java} | 66 +++++++-------- src/test/java/model/piece/CannonTest.java | 20 ++++- src/test/java/model/piece/ChariotTest.java | 35 ++------ src/test/java/model/piece/ElephantTest.java | 18 +++- src/test/java/model/piece/HorseTest.java | 18 +++- src/test/java/model/piece/SoldierTest.java | 56 +++++-------- 7 files changed, 188 insertions(+), 107 deletions(-) create mode 100644 src/test/java/model/fixture/PieceMovePathFixture.java rename src/test/java/model/fixture/{PieceTestFixture.java => PieceMovePositionFixture.java} (71%) diff --git a/src/test/java/model/fixture/PieceMovePathFixture.java b/src/test/java/model/fixture/PieceMovePathFixture.java new file mode 100644 index 0000000000..a9caa54335 --- /dev/null +++ b/src/test/java/model/fixture/PieceMovePathFixture.java @@ -0,0 +1,82 @@ +package model.fixture; + +import java.util.List; +import java.util.stream.Stream; +import model.Team; +import model.coordinate.Position; +import org.junit.jupiter.params.provider.Arguments; + +public class PieceMovePathFixture { + + static Stream 사방위_이동_경로_테스트_데이터() { + return Stream.of( + // 1. 여러 칸 이동 (중간 경로 존재) + Arguments.of( + new Position(0, 0), + new Position(0, 5), + List.of(new Position(0, 1), new Position(0, 2), new Position(0, 3), new Position(0, 4)) + ), + // 2. 한 칸 이동 (중간 경로 없음) + Arguments.of( + new Position(3, 4), + new Position(3, 5), + List.of() + ), + // 3. 수직 이동 (행 방향 이동) + Arguments.of( + new Position(0, 0), + new Position(3, 0), + List.of(new Position(1, 0), new Position(2, 0)) + ) + ); + } + + // === 마 이동 데이터 === + static Stream 마_이동_경로_테스트_데이터() { + return Stream.of( + // 1. 세로(row)로 먼저 움직이는 경우 (rowDiff=2, colDiff=1) + Arguments.of(new Position(5, 4), new Position(7, 3), List.of(new Position(6, 4))), // 북서 + Arguments.of(new Position(5, 4), new Position(7, 5), List.of(new Position(6, 4))), // 북동 + Arguments.of(new Position(5, 4), new Position(3, 3), List.of(new Position(4, 4))), // 남서 + Arguments.of(new Position(5, 4), new Position(3, 5), List.of(new Position(4, 4))), // 남동 + + // 2. 가로(col)로 먼저 움직이는 경우 (rowDiff=1, colDiff=2) + Arguments.of(new Position(5, 4), new Position(6, 6), List.of(new Position(5, 5))), // 동북 + Arguments.of(new Position(5, 4), new Position(4, 6), List.of(new Position(5, 5))), // 동남 + Arguments.of(new Position(5, 4), new Position(6, 2), List.of(new Position(5, 3))), // 서북 + Arguments.of(new Position(5, 4), new Position(4, 2), List.of(new Position(5, 3))) // 서남 + ); + } + + // === 상 이동 데이터 === + static Stream 상_이동_경로_테스트_데이터() { + return Stream.of( + // 1. 세로(row)로 먼저 움직이는 경우 (rowDiff=3, colDiff=2) + // 북서, 북동, 남서, 남동 순서 + Arguments.of(new Position(5, 4), new Position(8, 2), List.of(new Position(6, 4), new Position(7, 3))), + Arguments.of(new Position(5, 4), new Position(8, 6), List.of(new Position(6, 4), new Position(7, 5))), + Arguments.of(new Position(5, 4), new Position(2, 2), List.of(new Position(4, 4), new Position(3, 3))), + Arguments.of(new Position(5, 4), new Position(2, 6), List.of(new Position(4, 4), new Position(3, 5))), + + // 2. 가로(col)로 먼저 움직이는 경우 (rowDiff=2, colDiff=3) + // 동북, 동남, 서북, 서남 순서 + Arguments.of(new Position(5, 4), new Position(7, 7), List.of(new Position(5, 5), new Position(6, 6))), + Arguments.of(new Position(5, 4), new Position(3, 7), List.of(new Position(5, 5), new Position(4, 6))), + Arguments.of(new Position(5, 4), new Position(7, 1), List.of(new Position(5, 3), new Position(6, 2))), + Arguments.of(new Position(5, 4), new Position(3, 1), List.of(new Position(5, 3), new Position(4, 2))) + ); + } + + static Stream 졸_병_이동_경로_테스트_데이터() { + return Stream.of( + // 한나라 전진/좌/우 + Arguments.of(Team.HAN, new Position(3, 2), new Position(4, 2)), + Arguments.of(Team.HAN, new Position(3, 2), new Position(3, 1)), + Arguments.of(Team.HAN, new Position(3, 2), new Position(3, 3)), + // 초나라 전진/좌/우 + Arguments.of(Team.CHO, new Position(6, 2), new Position(5, 2)), + Arguments.of(Team.CHO, new Position(6, 2), new Position(6, 1)), + Arguments.of(Team.CHO, new Position(6, 2), new Position(6, 3)) + ); + } +} diff --git a/src/test/java/model/fixture/PieceTestFixture.java b/src/test/java/model/fixture/PieceMovePositionFixture.java similarity index 71% rename from src/test/java/model/fixture/PieceTestFixture.java rename to src/test/java/model/fixture/PieceMovePositionFixture.java index 9594120737..63f5c31528 100644 --- a/src/test/java/model/fixture/PieceTestFixture.java +++ b/src/test/java/model/fixture/PieceMovePositionFixture.java @@ -1,10 +1,11 @@ package model.fixture; import java.util.stream.Stream; +import model.Team; import model.coordinate.Position; import org.junit.jupiter.params.provider.Arguments; -public class PieceTestFixture { +public class PieceMovePositionFixture { // 공통 public static Stream 제자리_이동_케이스() { @@ -88,44 +89,39 @@ public class PieceTestFixture { } // ============================ - // 兵 (HAN) + // 兵 & 卒 (Soldier) 통합 데이터 // ============================ - - public static Stream 한나라_병_이동_가능한_위치() { - return Stream.of( - Arguments.of(new Position(3, 2), new Position(4, 2)), // 전진 (하) - Arguments.of(new Position(3, 2), new Position(3, 1)), // 좌 - Arguments.of(new Position(3, 2), new Position(3, 3)) // 우 - ); - } - - public static Stream 한나라_병_이동_불가능한_위치() { - return Stream.of( - Arguments.of(new Position(3, 2), new Position(2, 2)), // 후퇴 (상) - Arguments.of(new Position(3, 2), new Position(5, 2)), // 두 칸 전진 - Arguments.of(new Position(3, 2), new Position(3, 4)), // 두 칸 옆 - Arguments.of(new Position(3, 2), new Position(4, 3)) // 대각선 - ); - } - - // ============================ - // 卒 (CHO) - // ============================ - - public static Stream 초나라_졸_이동_가능한_위치() { - return Stream.of( - Arguments.of(new Position(6, 2), new Position(5, 2)), // 전진 (상) - Arguments.of(new Position(6, 2), new Position(6, 1)), // 좌 - Arguments.of(new Position(6, 2), new Position(6, 3)) // 우 + public static Stream 졸_병_이동_가능한_위치() { + return Stream.concat( + // 한나라 (HAN): 전진(row+1), 좌우 + Stream.of( + Arguments.of(Team.HAN, new Position(3, 2), new Position(4, 2)), // 전진 + Arguments.of(Team.HAN, new Position(3, 2), new Position(3, 1)), // 좌 + Arguments.of(Team.HAN, new Position(3, 2), new Position(3, 3)) // 우 + ), + // 초나라 (CHO): 전진(row-1), 좌우 + Stream.of( + Arguments.of(Team.CHO, new Position(6, 2), new Position(5, 2)), // 전진 + Arguments.of(Team.CHO, new Position(6, 2), new Position(6, 1)), // 좌 + Arguments.of(Team.CHO, new Position(6, 2), new Position(6, 3)) // 우 + ) ); } - public static Stream 초나라_졸_이동_불가능한_위치() { - return Stream.of( - Arguments.of(new Position(6, 2), new Position(7, 2)), // 후퇴 (하) - Arguments.of(new Position(6, 2), new Position(4, 2)), // 두 칸 전진 - Arguments.of(new Position(6, 2), new Position(6, 4)), // 두 칸 옆 - Arguments.of(new Position(6, 2), new Position(5, 3)) // 대각선 + public static Stream 졸_병_이동_불가능한_위치() { + return Stream.concat( + // 한나라 (HAN) 불가능 + Stream.of( + Arguments.of(Team.HAN, new Position(3, 2), new Position(2, 2)), // 후퇴 + Arguments.of(Team.HAN, new Position(3, 2), new Position(5, 2)), // 2칸 전진 + Arguments.of(Team.HAN, new Position(3, 2), new Position(4, 3)) // 대각선 + ), + // 초나라 (CHO) 불가능 + Stream.of( + Arguments.of(Team.CHO, new Position(6, 2), new Position(7, 2)), // 후퇴 + Arguments.of(Team.CHO, new Position(6, 2), new Position(4, 2)), // 2칸 전진 + Arguments.of(Team.CHO, new Position(6, 2), new Position(5, 3)) // 대각선 + ) ); } } \ No newline at end of file diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index 44e634d6a2..9417545b5f 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; import model.Team; import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; @@ -10,7 +11,7 @@ public class CannonTest { @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#사방위_이동_방향_케이스") + @MethodSource("model.fixture.PieceMovePositionFixture#사방위_이동_방향_케이스") void 포는_직선으로_이동할_수_있다(Position current, Position next) { // given Piece cannon = new Cannon(Team.HAN); @@ -23,8 +24,8 @@ public class CannonTest { } @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#사간방_대각선_이동_방향_케이스") - @MethodSource("model.fixture.PieceTestFixture#제자리_이동_케이스") + @MethodSource("model.fixture.PieceMovePositionFixture#사간방_대각선_이동_방향_케이스") + @MethodSource("model.fixture.PieceMovePositionFixture#제자리_이동_케이스") void 포는_대각선이나_제자리로_이동할_수_없다(Position current, Position next) { // given Piece cannon = new Cannon(Team.HAN); @@ -35,4 +36,17 @@ public class CannonTest { // then assertThat(canMove).isFalse(); } + + @ParameterizedTest + @MethodSource("model.fixture.PieceMovePathFixture#사방위_이동_경로_테스트_데이터") + void 포에_대한_다음_위치까지의_이동_경로를_반환한다(Position current, Position next, List expectedPath) { + // given + Piece chariot = new Cannon(Team.HAN); + + // when + List path = chariot.extractPath(current, next); + + // then + assertThat(path).isEqualTo(expectedPath); + } } \ No newline at end of file diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index 6af9e150e2..324fac2bbf 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -5,14 +5,13 @@ import java.util.List; import model.Team; import model.coordinate.Position; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; public class ChariotTest { @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#사방위_이동_방향_케이스") + @MethodSource("model.fixture.PieceMovePositionFixture#사방위_이동_방향_케이스") void 차는_직선으로_이동할_수_있다(Position current, Position next) { // given Piece chariot = new Chariot(Team.HAN); @@ -23,8 +22,8 @@ public class ChariotTest { } @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#사간방_대각선_이동_방향_케이스") - @MethodSource("model.fixture.PieceTestFixture#제자리_이동_케이스") + @MethodSource("model.fixture.PieceMovePositionFixture#사간방_대각선_이동_방향_케이스") + @MethodSource("model.fixture.PieceMovePositionFixture#제자리_이동_케이스") void 차는_대각선_또는_제자리로_이동할_수_없다(Position current, Position next) { // given Piece chariot = new Chariot(Team.HAN); @@ -34,36 +33,16 @@ public class ChariotTest { assertThat(canMove).isFalse(); } - @Test - void 차는_이동_경로의_중간_위치를_반환한다() { - // given - Piece chariot = new Chariot(Team.HAN); - Position current = new Position(0, 0); - Position next = new Position(0, 5); - - // when - List path = chariot.extractPath(current, next); - - // then - assertThat(path).containsExactly( - new Position(0, 1), - new Position(0, 2), - new Position(0, 3), - new Position(0, 4) - ); - } - - @Test - void 차가_한_칸_이동하면_중간_경로가_없다() { + @ParameterizedTest + @MethodSource("model.fixture.PieceMovePathFixture#사방위_이동_경로_테스트_데이터") + void 차에_대한_다음_위치까지의_이동_경로를_반환한다(Position current, Position next, List expectedPath) { // given Piece chariot = new Chariot(Team.HAN); - Position current = new Position(3, 4); - Position next = new Position(3, 5); // when List path = chariot.extractPath(current, next); // then - assertThat(path).isEmpty(); + assertThat(path).isEqualTo(expectedPath); } } \ No newline at end of file diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index 65ee47cb07..4745c30e32 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; import model.Team; import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; @@ -10,7 +11,7 @@ public class ElephantTest { @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#상_이동_가능한_위치") + @MethodSource("model.fixture.PieceMovePositionFixture#상_이동_가능한_위치") void 상은_두칸_직진_후_대각선으로_이동할_수_있다(Position current, Position next) { // given Piece elephant = new Elephant(Team.HAN); @@ -23,7 +24,7 @@ public class ElephantTest { } @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#상_이동_불가능한_위치") + @MethodSource("model.fixture.PieceMovePositionFixture#상_이동_불가능한_위치") void 상은_이동_규칙에_맞지_않으면_이동할_수_없다(Position current, Position next) { // given Piece elephant = new Elephant(Team.HAN); @@ -34,4 +35,17 @@ public class ElephantTest { // then assertThat(canMove).isFalse(); } + + @ParameterizedTest + @MethodSource("model.fixture.PieceMovePathFixture#상_이동_경로_테스트_데이터") + void 상에_대한_다음_위치까지의_이동_경로를_반환한다(Position current, Position next, List expectedPath) { + // given + Piece chariot = new Elephant(Team.HAN); + + // when + List path = chariot.extractPath(current, next); + + // then + assertThat(path).isEqualTo(expectedPath); + } } \ No newline at end of file diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index 9c977ee32e..bfca563d3c 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; import model.Team; import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; @@ -10,7 +11,7 @@ public class HorseTest { @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#마_이동_가능한_위치") + @MethodSource("model.fixture.PieceMovePositionFixture#마_이동_가능한_위치") void 마는_한칸_직진_후_대각선으로_이동할_수_있다(Position current, Position next) { // given Piece horse = new Horse(Team.HAN); @@ -23,7 +24,7 @@ public class HorseTest { } @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#마_이동_불가능한_위치") + @MethodSource("model.fixture.PieceMovePositionFixture#마_이동_불가능한_위치") void 마는_이동_규칙에_맞지_않으면_이동할_수_없다(Position current, Position next) { // given Piece horse = new Horse(Team.HAN); @@ -34,4 +35,17 @@ public class HorseTest { // then assertThat(canMove).isFalse(); } + + @ParameterizedTest + @MethodSource("model.fixture.PieceMovePathFixture#마_이동_경로_테스트_데이터") + void 말에_대한_다음_위치까지의_이동_경로를_반환한다(Position current, Position next, List expectedPath) { + // given + Piece chariot = new Horse(Team.HAN); + + // when + List path = chariot.extractPath(current, next); + + // then + assertThat(path).isEqualTo(expectedPath); + } } \ No newline at end of file diff --git a/src/test/java/model/piece/SoldierTest.java b/src/test/java/model/piece/SoldierTest.java index 5fe6ca5810..28d3a7cf61 100644 --- a/src/test/java/model/piece/SoldierTest.java +++ b/src/test/java/model/piece/SoldierTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; import model.Team; import model.coordinate.Position; import org.junit.jupiter.params.ParameterizedTest; @@ -9,55 +10,36 @@ public class SoldierTest { - @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#한나라_병_이동_가능한_위치") - void 한나라_병은_전진과_좌우로_이동할_수_있다(Position current, Position next) { + @ParameterizedTest(name = "{0}나라 졸 일 때, {1}에서 {2}로 이동할 수 있다.") + @MethodSource("model.fixture.PieceMovePositionFixture#졸_병_이동_가능한_위치") + void 졸_병_이동_성공_테스트(Team team, Position current, Position next) { // given - Piece soldier = new Soldier(Team.HAN); + Piece soldier = new Soldier(team); - // when - boolean canMove = soldier.canMove(current, next); - - // then - assertThat(canMove).isTrue(); + // when & then + assertThat(soldier.canMove(current, next)).isTrue(); } - @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#한나라_병_이동_불가능한_위치") - void 한나라_병은_후퇴와_두칸_이동을_할_수_없다(Position current, Position next) { + @ParameterizedTest(name = "{0}나라 졸 일 때, {1}에서 {2}로 이동할 수 없다.") + @MethodSource("model.fixture.PieceMovePositionFixture#졸_병_이동_불가능한_위치") + void 졸_병_이동_실패_테스트(Team team, Position current, Position next) { // given - Piece soldier = new Soldier(Team.HAN); + Piece soldier = new Soldier(team); - // when - boolean canMove = soldier.canMove(current, next); - - // then - assertThat(canMove).isFalse(); - } - - @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#초나라_졸_이동_가능한_위치") - void 초나라_졸은_전진과_좌우로_이동할_수_있다(Position current, Position next) { - // given - Piece soldier = new Soldier(Team.CHO); - - // when - boolean canMove = soldier.canMove(current, next); - - // then - assertThat(canMove).isTrue(); + // when & then + assertThat(soldier.canMove(current, next)).isFalse(); } - @ParameterizedTest - @MethodSource("model.fixture.PieceTestFixture#초나라_졸_이동_불가능한_위치") - void 초나라_졸은_후퇴와_두칸_이동을_할_수_없다(Position current, Position next) { + @ParameterizedTest(name = "{0}나라 졸 일 때, {1}에서 {2}로 이동 시 무조건 경로가 비어있다.") + @MethodSource("model.fixture.PieceMovePathFixture#졸_병_이동_경로_테스트_데이터") + void 졸_병_경로_테스트(Team team, Position current, Position next) { // given - Piece soldier = new Soldier(Team.CHO); + Piece soldier = new Soldier(team); // when - boolean canMove = soldier.canMove(current, next); + List path = soldier.extractPath(current, next); // then - assertThat(canMove).isFalse(); + assertThat(path).isEmpty(); } } \ No newline at end of file From 6e7083e9169474c359a6f72f01f07e374930bd31 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Tue, 31 Mar 2026 21:39:51 +0900 Subject: [PATCH 19/26] =?UTF-8?q?refactor:=20=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EC=9D=BC=EA=B8=89=20=EC=BB=AC=EB=A0=89=EC=85=98=EA=B3=BC=20?= =?UTF-8?q?=EC=9E=A5=EA=B8=B0=20AggregateRoot=20=EB=AA=85=ED=99=95?= =?UTF-8?q?=ED=9E=88=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/JanggiController.java | 31 ++-- src/main/java/model/Board.java | 62 ++++--- src/main/java/model/Janggi.java | 65 ------- src/main/java/model/JanggiGame.java | 37 ++++ src/main/java/model/piece/Piece.java | 12 +- src/test/java/model/BoardTest.java | 168 ++++++++++++------ src/test/java/model/JanggiGameTest.java | 64 +++++++ src/test/java/model/JanggiTest.java | 107 ----------- src/test/java/model/testdouble/FakePiece.java | 45 +++++ src/test/java/model/testdouble/SpyBoard.java | 38 ++++ 10 files changed, 362 insertions(+), 267 deletions(-) delete mode 100644 src/main/java/model/Janggi.java create mode 100644 src/main/java/model/JanggiGame.java create mode 100644 src/test/java/model/JanggiGameTest.java delete mode 100644 src/test/java/model/JanggiTest.java create mode 100644 src/test/java/model/testdouble/FakePiece.java create mode 100644 src/test/java/model/testdouble/SpyBoard.java diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 24feb55f78..797cd6be91 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,8 +1,16 @@ package controller; +import static controller.Retrier.retry; +import static model.Team.CHO; +import static model.Team.HAN; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import model.Board; import model.BoardFactory; -import model.Janggi; +import model.JanggiGame; import model.Team; import model.coordinate.Position; import model.formation.FormationFactory; @@ -11,15 +19,6 @@ import view.InputView; import view.OutputView; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import static controller.Retrier.retry; -import static model.Team.CHO; -import static model.Team.HAN; - public class JanggiController { private final InputView inputView; private final OutputView outputView; @@ -38,21 +37,21 @@ public void run() { Board board = BoardFactory.generatePieces(pieceByFormation); outputView.displayBoard(board.board()); - Janggi janggi = new Janggi(board); + JanggiGame janggiGame = new JanggiGame(board); while (true) { - retry(() -> playByTurn(janggi), processError()); + retry(() -> playByTurn(janggiGame), processError()); outputView.displayBoard(board.board()); } } - private void playByTurn(Janggi janggi) { - Team currentTurn = janggi.getTurn(); + private void playByTurn(JanggiGame janggiGame) { + Team currentTurn = janggiGame.getTurn(); Position current = inputView.readSource(currentTurn); - Piece piece = janggi.findPieceAt(current, currentTurn); + Piece piece = janggiGame.selectPiece(current); Position next = inputView.readDestination(currentTurn, piece); - janggi.move(current, next); + janggiGame.movePiece(current, next); } private Consumer processError() { diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index d50a79ee8c..38875210e6 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -11,6 +11,7 @@ public class Board { public static final int BOARD_ROW = 10; public static final int BOARD_COL = 9; + private static final int CANNON_HURDLE_COUNT = 1; private final Map board; @@ -18,49 +19,70 @@ public Board(Map board) { this.board = new HashMap<>(board); } - public void movePiece(Position current, Position next) { + public void move(Position current, Position next) { Piece piece = pickPiece(current); - Optional.ofNullable(board.get(next)) - .ifPresent(otherPiece -> checkAlly(piece, otherPiece)); + if (!piece.canMove(current, next)) { + throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); + } + + findByPosition(next).ifPresent(otherPiece -> { + if (otherPiece.isSameTeam(piece)) { + throw new IllegalArgumentException("아군이 있는 위치로 이동할 수 없습니다."); + } + }); + + List path = piece.extractPath(current, next); + if (piece.isCannon()) { + int countedPieces = countPiecesAt(path); + if (countedPieces != CANNON_HURDLE_COUNT) { + throw new IllegalArgumentException("포는" + CANNON_HURDLE_COUNT + "개의 기물만 건너 뛰어야 합니다."); + } + if (hasCannon(path)) { + throw new IllegalArgumentException("포는 포를 건너뛸 수 없습니다."); + } + board.remove(current); + board.put(next, piece); + return; + } + + if (hasPieceAt(path)) { + throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); + } board.remove(current); board.put(next, piece); } public Piece pickPiece(Position position) { - if (!hasPieceAt(position)) { - throw new IllegalArgumentException("해당 위치에 존재하는 장기말이 없습니다."); - } - return board.get(position); + return findByPosition(position) + .orElseThrow(() -> new IllegalArgumentException("해당 위치에 존재하는 장기말이 없습니다.")); + } + + public boolean hasPieceAt(Position position) { + return board.containsKey(position); + } + + private Optional findByPosition(Position position) { + return Optional.ofNullable(board.get(position)); } - public int countPiecesAt(List path) { + private int countPiecesAt(List path) { return (int) path.stream() .filter(this::hasPieceAt) .count(); } - public boolean hasPieceAt(List path) { + private boolean hasPieceAt(List path) { return path.stream() .anyMatch(this::hasPieceAt); } - public boolean hasCannon(List path) { + private boolean hasCannon(List path) { return path.stream() .filter(this::hasPieceAt) .anyMatch(position -> pickPiece(position).isCannon()); } - private void checkAlly(Piece piece, Piece otherPiece) { - if (piece.isSameTeam(otherPiece)) { - throw new IllegalArgumentException("해당 위치는 아군이 존재하는 위치입니다."); - } - } - - private boolean hasPieceAt(Position position) { - return board.containsKey(position); - } - public Map board() { return Map.copyOf(board); } diff --git a/src/main/java/model/Janggi.java b/src/main/java/model/Janggi.java deleted file mode 100644 index a12b842df7..0000000000 --- a/src/main/java/model/Janggi.java +++ /dev/null @@ -1,65 +0,0 @@ -package model; - -import java.util.List; -import model.coordinate.Position; -import model.piece.Piece; - -public class Janggi { - - public static final int CANNON_HURDLE_COUNT = 1; - - private final Board board; - private Team turn; - - public Janggi(Board board) { - this.board = board; - this.turn = Team.CHO; - } - - public void move(Position current, Position next) { - Piece piece = findPieceAt(current, turn); - validateMovement(current, next, piece); - - board.movePiece(current, next); - this.turn = turn.next(); - } - - public Piece findPieceAt(Position position, Team turn) { - Piece piece = board.pickPiece(position); - if (piece.isEnemy(turn)) { - throw new IllegalArgumentException(turn.getName() + "의 기물이 아닙니다."); - } - return piece; - } - - public Team getTurn() { - return turn; - } - - private void validateMovement(Position current, Position next, Piece piece) { - if (!piece.canMove(current, next)) { - throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); - } - - if (piece.isCannon()) { - validateMovementOfCannon(current, next, piece); - return; - } - - List path = piece.extractPath(current, next); - if (board.hasPieceAt(path)) { - throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); - } - } - - private void validateMovementOfCannon(Position current, Position next, Piece piece) { - List path = piece.extractPath(current, next); - int countedPieces = board.countPiecesAt(path); - if (countedPieces != CANNON_HURDLE_COUNT) { - throw new IllegalArgumentException("포는 1개의 기물만 건너 뛰어야 합니다."); - } - if (board.hasCannon(path)) { - throw new IllegalArgumentException("포는 포를 건너뛸 수 없습니다."); - } - } -} diff --git a/src/main/java/model/JanggiGame.java b/src/main/java/model/JanggiGame.java new file mode 100644 index 0000000000..f4d5500154 --- /dev/null +++ b/src/main/java/model/JanggiGame.java @@ -0,0 +1,37 @@ +package model; + +import model.coordinate.Position; +import model.piece.Piece; + +public class JanggiGame { + + private final Board board; + private Team turn; + + public JanggiGame(Board board) { + this.board = board; + this.turn = Team.CHO; + } + + public void movePiece(Position current, Position next) { + validateTurn(board.pickPiece(current)); + board.move(current, next); + this.turn = turn.next(); + } + + public Piece selectPiece(Position position) { + Piece piece = board.pickPiece(position); + validateTurn(piece); + return piece; + } + + private void validateTurn(Piece piece) { + if (piece.isOtherTeam(turn)) { + throw new IllegalArgumentException(turn.getName() + "의 기물이 아닙니다."); + } + } + + public Team getTurn() { + return turn; + } +} diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index e08d543e14..561512be09 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -17,10 +17,10 @@ protected Piece(Team team, PieceType type) { public abstract List extractPath(Position current, Position next); public boolean isSameTeam(Piece other) { - return !isEnemy(other.team); + return !isOtherTeam(other.team); } - public boolean isEnemy(Team team) { + public boolean isOtherTeam(Team team) { return this.team != team; } @@ -36,14 +36,14 @@ public boolean isCho() { return getTeam() == Team.CHO; } - public Team getTeam() { - return team; - } - public boolean isCannon() { return getType() == PieceType.CANNON; } + public Team getTeam() { + return team; + } + public PieceType getType() { return type; } diff --git a/src/test/java/model/BoardTest.java b/src/test/java/model/BoardTest.java index fd53f24088..c42d17f4f0 100644 --- a/src/test/java/model/BoardTest.java +++ b/src/test/java/model/BoardTest.java @@ -3,111 +3,173 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.HashMap; import java.util.List; import java.util.Map; import model.coordinate.Position; -import model.piece.Chariot; import model.piece.Piece; +import model.testdouble.FakePiece; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; class BoardTest { + private Position source = new Position(5, 0); + private Position destination = new Position(5, 4); + + @Test + void 해당_위치에_기물이_있으면_반환한다() { + // given + FakePiece piece = FakePiece.이동_가능(Team.CHO); + Board board = new Board(Map.of(source, piece)); + + // when + Piece pick = board.pickPiece(source); + + // then + assertThat(pick).isEqualTo(piece); + } + @Test - void 아군_위치로_이동하면_예외가_발생한다() { + void 해당_위치에_기물이_없으면_예외가_발생한다() { // given - Map pieces = new HashMap<>(); - pieces.put(new Position(0, 0), new Chariot(Team.HAN)); - pieces.put(new Position(0, 5), new Chariot(Team.HAN)); - Board board = new Board(pieces); + Board board = new Board(Map.of()); // when & then - assertThatThrownBy(() -> board.movePiece(new Position(0, 0), new Position(0, 5))) + assertThatThrownBy(() -> board.pickPiece(source)) .isInstanceOf(IllegalArgumentException.class); } @Test - void 빈_위치를_선택하면_예외가_발생한다() { + void 이동_가능한_기물은_정상적으로_이동한다() { + // given + FakePiece piece = FakePiece.이동_가능(Team.CHO); + Board board = new Board(Map.of(source, piece)); + + // when + board.move(source, destination); + + // then + assertSuccessMoved(board, destination, piece, source); + } + + @Test + void 이동_불가능한_기물을_이동하면_예외가_발생한다() { // given - Board board = new Board(new HashMap<>()); + FakePiece piece = FakePiece.이동_불가(Team.CHO); + Board board = new Board(Map.of(source, piece)); // when & then - assertThatThrownBy(() -> board.pickPiece(new Position(0, 0))) + assertThatThrownBy(() -> board.move(source, destination)) .isInstanceOf(IllegalArgumentException.class); } @Test - void 적군_위치로_이동하면_기물이_교체된다() { + void 도착_위치에_아군이_있으면_예외가_발생한다() { // given - Piece han = new Chariot(Team.HAN); - Piece cho = new Chariot(Team.CHO); - Position cur = new Position(0, 0); - Position next = new Position(0, 5); + FakePiece piece = FakePiece.이동_가능(Team.CHO); + FakePiece ally = FakePiece.이동_가능(Team.CHO); + + Board board = new Board(Map.of(source, piece, destination, ally)); - Map pieces = new HashMap<>(); - pieces.put(cur, han); - pieces.put(next, cho); + // when & then + assertThatThrownBy(() -> board.move(source, destination)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 도착_위치에_적군이_있으면_이동한다() { + // given + FakePiece piece = FakePiece.이동_가능(Team.CHO); + FakePiece enemy = FakePiece.이동_가능(Team.HAN); - Board board = new Board(pieces); + Board board = new Board(Map.of(source, piece, destination, enemy)); // when - board.movePiece(cur, next); + board.move(source, destination); // then - assertThat(board.pickPiece(next)).isEqualTo(han); + assertSuccessMoved(board, destination, piece, source); } @Test - void 이동_경로에_기물이_있으면_멱이_적용된다() { + void 이동_경로에_기물이_있으면_예외가_발생한다() { // given - Piece chariot = new Chariot(Team.HAN); - Position current = new Position(0, 0); - Position next = new Position(0, 5); + Position vertex = new Position(5, 2); + FakePiece piece = FakePiece.이동_가능하지만_경로_있는_기물(Team.CHO, List.of(vertex)); + FakePiece barricade = FakePiece.이동_가능(Team.HAN); - Map pieces = new HashMap<>(); - pieces.put(current, chariot); - pieces.put(new Position(0, 3), new Chariot(Team.CHO)); - Board board = new Board(pieces); + Board board = new Board(Map.of(source, piece, vertex, barricade)); + + // when & then + assertThatThrownBy(() -> board.move(source, destination)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포는_정확히_1개의_기물을_넘어야_한다() { + // given + Position vertex = new Position(5, 2); + FakePiece cannon = FakePiece.이동_가능한_포(Team.CHO, List.of(vertex)); + FakePiece hurdle = FakePiece.이동_가능(Team.HAN); + + Board board = new Board(Map.of(source, cannon, vertex, hurdle)); // when - List path = chariot.extractPath(current, next); + board.move(source, destination); // then - assertThat(board.hasPieceAt(path)).isTrue(); + assertSuccessMoved(board, source, cannon, destination); } - @Test - void 한_칸_이동은_경로가_없으므로_멱이_적용되지_않는다() { + void 포는_넘을_기물이_없으면_예외가_발생한다() { // given - Piece chariot = new Chariot(Team.HAN); - Position current = new Position(0, 0); - Position next = new Position(0, 1); + Position vertex = new Position(5, 2); + FakePiece cannon = FakePiece.이동_가능한_포(Team.CHO, List.of(vertex)); - Board board = new Board(new HashMap<>()); + Board board = new Board(Map.of(source, cannon)); - // when - List path = chariot.extractPath(current, next); + // when & then + assertThatThrownBy(() -> board.move(source, destination)) + .isInstanceOf(IllegalArgumentException.class); + } - // then - assertThat(board.hasPieceAt(path)).isFalse(); + @ParameterizedTest + @EnumSource(Team.class) + void 포는_포를_넘을_수_없다(Team team) { + // given + Position vertex = new Position(5, 2); + FakePiece cannon = FakePiece.이동_가능한_포(Team.CHO, List.of(vertex)); + FakePiece otherCannon = FakePiece.이동_가능한_포(team, List.of()); + + Board board = new Board(Map.of(source, cannon, vertex, otherCannon)); + + // when & then + assertThatThrownBy(() -> board.move(source, destination)) + .isInstanceOf(IllegalArgumentException.class); } @Test - void 이동_경로에_기물이_없으면_멱이_적용되지_않는다() { + void 포는_2개_이상의_기물을_넘으면_예외가_발생한다() { // given - Piece chariot = new Chariot(Team.HAN); - Position current = new Position(0, 0); - Position next = new Position(0, 5); + Position firstVertex = new Position(5, 1); + Position secondVertex = new Position(5, 2); + FakePiece cannon = FakePiece.이동_가능한_포(Team.CHO, List.of(firstVertex, secondVertex)); - Map pieces = new HashMap<>(); - pieces.put(current, chariot); - Board board = new Board(pieces); + Board board = new Board(Map.of( + source, cannon, + firstVertex, FakePiece.이동_가능(Team.HAN), + secondVertex, FakePiece.이동_가능(Team.HAN) + )); - // when - List path = chariot.extractPath(current, next); + // when & then + assertThatThrownBy(() -> board.move(source, destination)) + .isInstanceOf(IllegalArgumentException.class); + } - // then - assertThat(board.hasPieceAt(path)).isFalse(); + private void assertSuccessMoved(Board board, Position source, FakePiece piece, Position destination) { + assertThat(board.pickPiece(source)).isEqualTo(piece); + assertThat(board.hasPieceAt(destination)).isFalse(); } } \ No newline at end of file diff --git a/src/test/java/model/JanggiGameTest.java b/src/test/java/model/JanggiGameTest.java new file mode 100644 index 0000000000..9e6b421fc3 --- /dev/null +++ b/src/test/java/model/JanggiGameTest.java @@ -0,0 +1,64 @@ +package model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import model.coordinate.Position; +import model.piece.Horse; +import model.piece.Piece; +import model.testdouble.SpyBoard; +import org.junit.jupiter.api.Test; + +class JanggiGameTest { + + @Test + void 장기에서_장기말을_옮기면_보드에서_장기말이_이동하고_다음_차례가_된다() { + // given + SpyBoard board = SpyBoard.cho(); + JanggiGame janggiGame = new JanggiGame(board); + Team prevTurn = janggiGame.getTurn(); + + // when + janggiGame.movePiece(new Position(1, 1), new Position(2, 2)); + + // then + assertThat(board.isMoved).isTrue(); + assertThat(janggiGame.getTurn()).isEqualTo(prevTurn.next()); + } + + @Test + void 장기에서_장기말을_옮길_때_다른_팀의_장기말을_옮기면_예외가_발생한다() { + // given + SpyBoard board = SpyBoard.han(); + JanggiGame janggiGame = new JanggiGame(board); + + // when & then + assertThatThrownBy(() -> janggiGame.movePiece(new Position(1, 1), new Position(2, 2))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 장기에서_현재_턴에_대한_장기물을_선택할_수_있다() { + // given + Piece piece = new Horse(Team.CHO); + SpyBoard board = new SpyBoard(piece); + JanggiGame janggiGame = new JanggiGame(board); + + // when + Piece selectedPiece = janggiGame.selectPiece(new Position(1, 1)); + + // then + assertThat(selectedPiece).isSameAs(piece); + } + + @Test + void 장기에서_다른_턴에_대한_장기물을_선택하면_예외가_발생한다() { + // given + SpyBoard board = new SpyBoard(new Horse(Team.HAN)); + JanggiGame janggiGame = new JanggiGame(board); + + // when & then + assertThatThrownBy(() -> janggiGame.selectPiece(new Position(1, 1))) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/model/JanggiTest.java b/src/test/java/model/JanggiTest.java deleted file mode 100644 index 23341909c5..0000000000 --- a/src/test/java/model/JanggiTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package model; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.HashMap; -import java.util.Map; -import model.coordinate.Position; -import model.piece.Cannon; -import model.piece.Chariot; -import model.piece.Piece; -import model.piece.Soldier; -import org.junit.jupiter.api.Test; - -class JanggiTest { - - @Test - void 포가_1개의_기물을_뛰어넘어_이동한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(3, 0), new Soldier(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when - janggi.move(new Position(5, 0), new Position(1, 0)); - - // then - assertThat(board.pickPiece(new Position(1, 0)).isCannon()).isTrue(); - } - - @Test - void 포가_가로로_1개의_기물을_뛰어넘어_이동한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(5, 3), new Soldier(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when - janggi.move(new Position(5, 0), new Position(5, 6)); - - // then - assertThat(board.pickPiece(new Position(5, 6)).isCannon()).isTrue(); - } - - @Test - void 포가_경로에_기물이_없으면_예외가_발생한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 포가_경로에_기물이_2개_이상이면_예외가_발생한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(4, 0), new Soldier(Team.HAN)); - pieces.put(new Position(3, 0), new Soldier(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 포가_경로에_포를_뛰어넘으면_예외가_발생한다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(3, 0), new Cannon(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when & then - assertThatThrownBy(() -> janggi.move(new Position(5, 0), new Position(1, 0))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 포가_적군_기물을_잡을_수_있다() { - // given - Map pieces = new HashMap<>(); - pieces.put(new Position(5, 0), new Cannon(Team.CHO)); - pieces.put(new Position(3, 0), new Soldier(Team.HAN)); - pieces.put(new Position(1, 0), new Chariot(Team.HAN)); - Board board = new Board(pieces); - Janggi janggi = new Janggi(board); - - // when - janggi.move(new Position(5, 0), new Position(1, 0)); - - // then - assertThat(board.pickPiece(new Position(1, 0)).isCannon()).isTrue(); - } -} \ No newline at end of file diff --git a/src/test/java/model/testdouble/FakePiece.java b/src/test/java/model/testdouble/FakePiece.java new file mode 100644 index 0000000000..353f063558 --- /dev/null +++ b/src/test/java/model/testdouble/FakePiece.java @@ -0,0 +1,45 @@ +package model.testdouble; + +import java.util.List; +import model.Team; +import model.coordinate.Position; +import model.piece.Piece; +import model.piece.PieceType; + +public class FakePiece extends Piece { + + private final boolean movable; + private final List path; + + FakePiece(Team team, PieceType type, boolean movable, List path) { + super(team, type); + this.movable = movable; + this.path = path; + } + + public static FakePiece 이동_가능(Team team) { + return new FakePiece(team, PieceType.SOLDIER, true, List.of()); + } + + public static FakePiece 이동_불가(Team team) { + return new FakePiece(team, PieceType.SOLDIER, false, List.of()); + } + + public static FakePiece 이동_가능한_포(Team team, List path) { + return new FakePiece(team, PieceType.CANNON, true, path); + } + + public static FakePiece 이동_가능하지만_경로_있는_기물(Team team, List path) { + return new FakePiece(team, PieceType.SOLDIER, true, path); + } + + @Override + protected boolean comparePosition(int rowDiff, int colDiff) { + return movable; + } + + @Override + public List extractPath(Position current, Position next) { + return path; + } +} diff --git a/src/test/java/model/testdouble/SpyBoard.java b/src/test/java/model/testdouble/SpyBoard.java new file mode 100644 index 0000000000..1e1c09d88c --- /dev/null +++ b/src/test/java/model/testdouble/SpyBoard.java @@ -0,0 +1,38 @@ +package model.testdouble; + +import java.util.Map; +import model.Board; +import model.Team; +import model.coordinate.Position; +import model.piece.Horse; +import model.piece.Piece; + +public class SpyBoard extends Board { + + // pickPiece 반환용 + private final Piece piece; + public boolean isMoved = false; + + public SpyBoard(Piece piece) { + super(Map.of()); + this.piece = piece; + } + + public static SpyBoard cho() { + return new SpyBoard(new Horse(Team.CHO)); + } + + public static SpyBoard han() { + return new SpyBoard(new Horse(Team.HAN)); + } + + @Override + public void move(Position current, Position next) { + isMoved = true; + } + + @Override + public Piece pickPiece(Position position) { + return piece; + } +} From b44e3fdc8f71f30f3f0663b4e9383efd5956beb1 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Tue, 31 Mar 2026 21:46:37 +0900 Subject: [PATCH 20/26] =?UTF-8?q?refactor:=20=EC=9E=A5=EA=B8=B0=ED=8C=90?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EB=B6=84=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/model/JanggiGame.java | 3 +- src/main/java/model/{ => board}/Board.java | 3 +- .../java/model/{ => board}/BoardFactory.java | 14 ++++----- .../{coordinate => board}/Direction.java | 2 +- .../{coordinate => board}/Displacement.java | 2 +- .../model/{coordinate => board}/Position.java | 6 ++-- .../model/formation/FormationFactory.java | 7 ++--- .../model/formation/FormationStrategy.java | 5 ++- .../model/formation/MaSangMaSangStrategy.java | 23 +++++++------- .../model/formation/MaSangSangMaStrategy.java | 23 +++++++------- .../model/formation/SangMaMaSangStrategy.java | 23 +++++++------- .../model/formation/SangMaSangMaStrategy.java | 23 +++++++------- src/main/java/model/piece/Cannon.java | 4 +-- src/main/java/model/piece/Chariot.java | 4 +-- src/main/java/model/piece/Elephant.java | 4 +-- src/main/java/model/piece/General.java | 2 +- src/main/java/model/piece/Guard.java | 2 +- src/main/java/model/piece/Horse.java | 4 +-- src/main/java/model/piece/Piece.java | 2 +- src/main/java/model/piece/Soldier.java | 2 +- src/main/java/view/InputView.java | 11 +++---- src/main/java/view/OutputView.java | 31 +++++++++---------- src/test/java/model/JanggiGameTest.java | 2 +- .../java/model/{ => board}/BoardTest.java | 4 +-- .../java/model/{ => board}/DirectionTest.java | 10 +++--- .../java/model/{ => board}/PositionTest.java | 9 +++--- .../model/fixture/PieceMovePathFixture.java | 2 +- .../fixture/PieceMovePositionFixture.java | 2 +- .../model/formation/FormationFactoryTest.java | 11 +++---- .../formation/FormationStrategyTest.java | 25 +++++++-------- .../{ => formation}/JanggiFormationTest.java | 9 +++--- src/test/java/model/piece/CannonTest.java | 2 +- src/test/java/model/piece/ChariotTest.java | 2 +- src/test/java/model/piece/ElephantTest.java | 2 +- src/test/java/model/piece/HorseTest.java | 2 +- src/test/java/model/piece/SoldierTest.java | 2 +- src/test/java/model/testdouble/FakePiece.java | 2 +- src/test/java/model/testdouble/SpyBoard.java | 4 +-- 39 files changed, 140 insertions(+), 156 deletions(-) rename src/main/java/model/{ => board}/Board.java (98%) rename src/main/java/model/{ => board}/BoardFactory.java (97%) rename src/main/java/model/{coordinate => board}/Direction.java (98%) rename src/main/java/model/{coordinate => board}/Displacement.java (65%) rename src/main/java/model/{coordinate => board}/Position.java (93%) rename src/test/java/model/{ => board}/BoardTest.java (99%) rename src/test/java/model/{ => board}/DirectionTest.java (95%) rename src/test/java/model/{ => board}/PositionTest.java (96%) rename src/test/java/model/{ => formation}/JanggiFormationTest.java (95%) diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 797cd6be91..34989087e1 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -8,11 +8,11 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; -import model.Board; -import model.BoardFactory; import model.JanggiGame; import model.Team; -import model.coordinate.Position; +import model.board.Board; +import model.board.BoardFactory; +import model.board.Position; import model.formation.FormationFactory; import model.formation.JanggiFormation; import model.piece.Piece; diff --git a/src/main/java/model/JanggiGame.java b/src/main/java/model/JanggiGame.java index f4d5500154..acd429aad7 100644 --- a/src/main/java/model/JanggiGame.java +++ b/src/main/java/model/JanggiGame.java @@ -1,6 +1,7 @@ package model; -import model.coordinate.Position; +import model.board.Board; +import model.board.Position; import model.piece.Piece; public class JanggiGame { diff --git a/src/main/java/model/Board.java b/src/main/java/model/board/Board.java similarity index 98% rename from src/main/java/model/Board.java rename to src/main/java/model/board/Board.java index 38875210e6..8cf6071b3c 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/board/Board.java @@ -1,10 +1,9 @@ -package model; +package model.board; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import model.coordinate.Position; import model.piece.Piece; public class Board { diff --git a/src/main/java/model/BoardFactory.java b/src/main/java/model/board/BoardFactory.java similarity index 97% rename from src/main/java/model/BoardFactory.java rename to src/main/java/model/board/BoardFactory.java index e95ae3952c..01ba35cbc3 100644 --- a/src/main/java/model/BoardFactory.java +++ b/src/main/java/model/board/BoardFactory.java @@ -1,6 +1,10 @@ -package model; +package model.board; -import model.coordinate.Position; +import static model.Team.CHO; +import static model.Team.HAN; + +import java.util.HashMap; +import java.util.Map; import model.piece.Cannon; import model.piece.Chariot; import model.piece.General; @@ -8,12 +12,6 @@ import model.piece.Piece; import model.piece.Soldier; -import java.util.HashMap; -import java.util.Map; - -import static model.Team.CHO; -import static model.Team.HAN; - public class BoardFactory { private static final Map RED_PIECES = Map.ofEntries( diff --git a/src/main/java/model/coordinate/Direction.java b/src/main/java/model/board/Direction.java similarity index 98% rename from src/main/java/model/coordinate/Direction.java rename to src/main/java/model/board/Direction.java index 972f980cbe..37ac02d91b 100644 --- a/src/main/java/model/coordinate/Direction.java +++ b/src/main/java/model/board/Direction.java @@ -1,4 +1,4 @@ -package model.coordinate; +package model.board; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/model/coordinate/Displacement.java b/src/main/java/model/board/Displacement.java similarity index 65% rename from src/main/java/model/coordinate/Displacement.java rename to src/main/java/model/board/Displacement.java index d275b3b523..c40e7417dc 100644 --- a/src/main/java/model/coordinate/Displacement.java +++ b/src/main/java/model/board/Displacement.java @@ -1,4 +1,4 @@ -package model.coordinate; +package model.board; public record Displacement(int row, int col) { } diff --git a/src/main/java/model/coordinate/Position.java b/src/main/java/model/board/Position.java similarity index 93% rename from src/main/java/model/coordinate/Position.java rename to src/main/java/model/board/Position.java index 085dfdb2a9..9db8971776 100644 --- a/src/main/java/model/coordinate/Position.java +++ b/src/main/java/model/board/Position.java @@ -1,7 +1,7 @@ -package model.coordinate; +package model.board; -import static model.Board.BOARD_COL; -import static model.Board.BOARD_ROW; +import static model.board.Board.BOARD_COL; +import static model.board.Board.BOARD_ROW; public record Position(int row, int col) { diff --git a/src/main/java/model/formation/FormationFactory.java b/src/main/java/model/formation/FormationFactory.java index 3650299701..e94fcd2ce9 100644 --- a/src/main/java/model/formation/FormationFactory.java +++ b/src/main/java/model/formation/FormationFactory.java @@ -1,12 +1,11 @@ package model.formation; -import model.Team; -import model.coordinate.Position; -import model.piece.Piece; - import java.util.HashMap; import java.util.Map; import java.util.Optional; +import model.Team; +import model.board.Position; +import model.piece.Piece; public class FormationFactory { diff --git a/src/main/java/model/formation/FormationStrategy.java b/src/main/java/model/formation/FormationStrategy.java index d802879fdf..e19e462503 100644 --- a/src/main/java/model/formation/FormationStrategy.java +++ b/src/main/java/model/formation/FormationStrategy.java @@ -1,11 +1,10 @@ package model.formation; +import java.util.Map; import model.Team; -import model.coordinate.Position; +import model.board.Position; import model.piece.Piece; -import java.util.Map; - public abstract class FormationStrategy { public Map generateFormation(Team team) { diff --git a/src/main/java/model/formation/MaSangMaSangStrategy.java b/src/main/java/model/formation/MaSangMaSangStrategy.java index 55469d9804..17a2f309b4 100644 --- a/src/main/java/model/formation/MaSangMaSangStrategy.java +++ b/src/main/java/model/formation/MaSangMaSangStrategy.java @@ -1,22 +1,21 @@ package model.formation; +import static model.board.Position.CHO_LEFT_INNER; +import static model.board.Position.CHO_LEFT_OUTER; +import static model.board.Position.CHO_RIGHT_INNER; +import static model.board.Position.CHO_RIGHT_OUTER; +import static model.board.Position.HAN_LEFT_INNER; +import static model.board.Position.HAN_LEFT_OUTER; +import static model.board.Position.HAN_RIGHT_INNER; +import static model.board.Position.HAN_RIGHT_OUTER; + +import java.util.Map; import model.Team; -import model.coordinate.Position; +import model.board.Position; import model.piece.Elephant; import model.piece.Horse; import model.piece.Piece; -import java.util.Map; - -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; - public class MaSangMaSangStrategy extends FormationStrategy { @Override protected Map generateHanFormation() { diff --git a/src/main/java/model/formation/MaSangSangMaStrategy.java b/src/main/java/model/formation/MaSangSangMaStrategy.java index fe0ffdd156..08700de87b 100644 --- a/src/main/java/model/formation/MaSangSangMaStrategy.java +++ b/src/main/java/model/formation/MaSangSangMaStrategy.java @@ -1,22 +1,21 @@ package model.formation; +import static model.board.Position.CHO_LEFT_INNER; +import static model.board.Position.CHO_LEFT_OUTER; +import static model.board.Position.CHO_RIGHT_INNER; +import static model.board.Position.CHO_RIGHT_OUTER; +import static model.board.Position.HAN_LEFT_INNER; +import static model.board.Position.HAN_LEFT_OUTER; +import static model.board.Position.HAN_RIGHT_INNER; +import static model.board.Position.HAN_RIGHT_OUTER; + +import java.util.Map; import model.Team; -import model.coordinate.Position; +import model.board.Position; import model.piece.Elephant; import model.piece.Horse; import model.piece.Piece; -import java.util.Map; - -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; - public class MaSangSangMaStrategy extends FormationStrategy { @Override protected Map generateHanFormation() { diff --git a/src/main/java/model/formation/SangMaMaSangStrategy.java b/src/main/java/model/formation/SangMaMaSangStrategy.java index cc3be623b1..9471694234 100644 --- a/src/main/java/model/formation/SangMaMaSangStrategy.java +++ b/src/main/java/model/formation/SangMaMaSangStrategy.java @@ -1,22 +1,21 @@ package model.formation; +import static model.board.Position.CHO_LEFT_INNER; +import static model.board.Position.CHO_LEFT_OUTER; +import static model.board.Position.CHO_RIGHT_INNER; +import static model.board.Position.CHO_RIGHT_OUTER; +import static model.board.Position.HAN_LEFT_INNER; +import static model.board.Position.HAN_LEFT_OUTER; +import static model.board.Position.HAN_RIGHT_INNER; +import static model.board.Position.HAN_RIGHT_OUTER; + +import java.util.Map; import model.Team; -import model.coordinate.Position; +import model.board.Position; import model.piece.Elephant; import model.piece.Horse; import model.piece.Piece; -import java.util.Map; - -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; - public class SangMaMaSangStrategy extends FormationStrategy { @Override protected Map generateHanFormation() { diff --git a/src/main/java/model/formation/SangMaSangMaStrategy.java b/src/main/java/model/formation/SangMaSangMaStrategy.java index b6ab972cb8..f783cd5383 100644 --- a/src/main/java/model/formation/SangMaSangMaStrategy.java +++ b/src/main/java/model/formation/SangMaSangMaStrategy.java @@ -1,22 +1,21 @@ package model.formation; +import static model.board.Position.CHO_LEFT_INNER; +import static model.board.Position.CHO_LEFT_OUTER; +import static model.board.Position.CHO_RIGHT_INNER; +import static model.board.Position.CHO_RIGHT_OUTER; +import static model.board.Position.HAN_LEFT_INNER; +import static model.board.Position.HAN_LEFT_OUTER; +import static model.board.Position.HAN_RIGHT_INNER; +import static model.board.Position.HAN_RIGHT_OUTER; + +import java.util.Map; import model.Team; -import model.coordinate.Position; +import model.board.Position; import model.piece.Elephant; import model.piece.Horse; import model.piece.Piece; -import java.util.Map; - -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; - public class SangMaSangMaStrategy extends FormationStrategy { @Override protected Map generateHanFormation() { diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index 9d75514ec4..1a331493ea 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -3,8 +3,8 @@ import java.util.ArrayList; import java.util.List; import model.Team; -import model.coordinate.Direction; -import model.coordinate.Position; +import model.board.Direction; +import model.board.Position; public class Cannon extends Piece { diff --git a/src/main/java/model/piece/Chariot.java b/src/main/java/model/piece/Chariot.java index cc009e8a73..65415b97e0 100644 --- a/src/main/java/model/piece/Chariot.java +++ b/src/main/java/model/piece/Chariot.java @@ -3,8 +3,8 @@ import java.util.ArrayList; import java.util.List; import model.Team; -import model.coordinate.Direction; -import model.coordinate.Position; +import model.board.Direction; +import model.board.Position; public class Chariot extends Piece { diff --git a/src/main/java/model/piece/Elephant.java b/src/main/java/model/piece/Elephant.java index a06fc7ea4c..61e7a5ca20 100644 --- a/src/main/java/model/piece/Elephant.java +++ b/src/main/java/model/piece/Elephant.java @@ -3,8 +3,8 @@ import java.util.ArrayList; import java.util.List; import model.Team; -import model.coordinate.Direction; -import model.coordinate.Position; +import model.board.Direction; +import model.board.Position; public class Elephant extends Piece { diff --git a/src/main/java/model/piece/General.java b/src/main/java/model/piece/General.java index a99c128c62..85c25688db 100644 --- a/src/main/java/model/piece/General.java +++ b/src/main/java/model/piece/General.java @@ -2,7 +2,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; public class General extends Piece { diff --git a/src/main/java/model/piece/Guard.java b/src/main/java/model/piece/Guard.java index 1564dedf11..4e88347cc8 100644 --- a/src/main/java/model/piece/Guard.java +++ b/src/main/java/model/piece/Guard.java @@ -2,7 +2,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; public class Guard extends Piece { diff --git a/src/main/java/model/piece/Horse.java b/src/main/java/model/piece/Horse.java index 74d3bd43fd..90d5bd5d70 100644 --- a/src/main/java/model/piece/Horse.java +++ b/src/main/java/model/piece/Horse.java @@ -3,8 +3,8 @@ import java.util.ArrayList; import java.util.List; import model.Team; -import model.coordinate.Direction; -import model.coordinate.Position; +import model.board.Direction; +import model.board.Position; public class Horse extends Piece { diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 561512be09..1c76954c27 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -2,7 +2,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; public abstract class Piece { diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java index d3126261b6..5905f4cdf4 100644 --- a/src/main/java/model/piece/Soldier.java +++ b/src/main/java/model/piece/Soldier.java @@ -2,7 +2,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; public class Soldier extends Piece { diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 248d52c621..68e4e36c2c 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,16 +1,15 @@ package view; +import static view.formater.BoardFormatter.formatSymbol; + +import java.util.List; +import java.util.Scanner; import model.Team; -import model.coordinate.Position; +import model.board.Position; import model.formation.JanggiFormation; import model.piece.Piece; import view.parser.InputParser; -import java.util.List; -import java.util.Scanner; - -import static view.formater.BoardFormatter.formatSymbol; - public class InputView { private static final Scanner SCANNER = new Scanner(System.in); private static final InputParser PARSER = new InputParser(); diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index c65b66952e..dbbe2be93a 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,11 +1,5 @@ package view; -import model.Board; -import model.coordinate.Position; -import model.piece.Piece; - -import java.util.Map; - import static view.formater.BoardFormatter.COL_NUM; import static view.formater.BoardFormatter.RED; import static view.formater.BoardFormatter.RESET; @@ -15,17 +9,12 @@ import static view.formater.BoardFormatter.formatHorizon; import static view.formater.BoardFormatter.formatSymbol; -public class OutputView { - - public void displayBoard(Map board) { - displayColIndex(); - String border = formatHorizon(Board.BOARD_COL); - System.out.println(border); - - displayPositionByPiece(board); +import java.util.Map; +import model.board.Board; +import model.board.Position; +import model.piece.Piece; - System.out.println(border); - } +public class OutputView { private static void displayPositionByPiece(Map board) { for (int row = 0; row < Board.BOARD_ROW; row++) { @@ -47,6 +36,16 @@ private static void displayColIndex() { System.out.println(); } + public void displayBoard(Map board) { + displayColIndex(); + String border = formatHorizon(Board.BOARD_COL); + System.out.println(border); + + displayPositionByPiece(board); + + System.out.println(border); + } + public void displayError(String message) { System.out.println(RED + "[ERROR] " + message + RESET); } diff --git a/src/test/java/model/JanggiGameTest.java b/src/test/java/model/JanggiGameTest.java index 9e6b421fc3..311f424156 100644 --- a/src/test/java/model/JanggiGameTest.java +++ b/src/test/java/model/JanggiGameTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import model.coordinate.Position; +import model.board.Position; import model.piece.Horse; import model.piece.Piece; import model.testdouble.SpyBoard; diff --git a/src/test/java/model/BoardTest.java b/src/test/java/model/board/BoardTest.java similarity index 99% rename from src/test/java/model/BoardTest.java rename to src/test/java/model/board/BoardTest.java index c42d17f4f0..61e2595d6d 100644 --- a/src/test/java/model/BoardTest.java +++ b/src/test/java/model/board/BoardTest.java @@ -1,11 +1,11 @@ -package model; +package model.board; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; import java.util.Map; -import model.coordinate.Position; +import model.Team; import model.piece.Piece; import model.testdouble.FakePiece; import org.junit.jupiter.api.Test; diff --git a/src/test/java/model/DirectionTest.java b/src/test/java/model/board/DirectionTest.java similarity index 95% rename from src/test/java/model/DirectionTest.java rename to src/test/java/model/board/DirectionTest.java index ec576362d8..129357526b 100644 --- a/src/test/java/model/DirectionTest.java +++ b/src/test/java/model/board/DirectionTest.java @@ -1,13 +1,11 @@ -package model; - -import model.coordinate.Direction; -import model.coordinate.Position; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; +package model.board; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + class DirectionTest { @ParameterizedTest diff --git a/src/test/java/model/PositionTest.java b/src/test/java/model/board/PositionTest.java similarity index 96% rename from src/test/java/model/PositionTest.java rename to src/test/java/model/board/PositionTest.java index 4ccc7557ae..639fbf63b2 100644 --- a/src/test/java/model/PositionTest.java +++ b/src/test/java/model/board/PositionTest.java @@ -1,12 +1,11 @@ -package model; - -import model.coordinate.Position; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; +package model.board; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + class PositionTest { @ParameterizedTest diff --git a/src/test/java/model/fixture/PieceMovePathFixture.java b/src/test/java/model/fixture/PieceMovePathFixture.java index a9caa54335..a1aea7fc6f 100644 --- a/src/test/java/model/fixture/PieceMovePathFixture.java +++ b/src/test/java/model/fixture/PieceMovePathFixture.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.stream.Stream; import model.Team; -import model.coordinate.Position; +import model.board.Position; import org.junit.jupiter.params.provider.Arguments; public class PieceMovePathFixture { diff --git a/src/test/java/model/fixture/PieceMovePositionFixture.java b/src/test/java/model/fixture/PieceMovePositionFixture.java index 63f5c31528..acbd358e41 100644 --- a/src/test/java/model/fixture/PieceMovePositionFixture.java +++ b/src/test/java/model/fixture/PieceMovePositionFixture.java @@ -2,7 +2,7 @@ import java.util.stream.Stream; import model.Team; -import model.coordinate.Position; +import model.board.Position; import org.junit.jupiter.params.provider.Arguments; public class PieceMovePositionFixture { diff --git a/src/test/java/model/formation/FormationFactoryTest.java b/src/test/java/model/formation/FormationFactoryTest.java index 978fa40503..7502dee14a 100644 --- a/src/test/java/model/formation/FormationFactoryTest.java +++ b/src/test/java/model/formation/FormationFactoryTest.java @@ -1,16 +1,15 @@ package model.formation; -import model.coordinate.Position; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import java.util.stream.Stream; +import model.board.Position; import model.piece.Piece; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.Map; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - class FormationFactoryTest { static Stream validFormationCombinations() { diff --git a/src/test/java/model/formation/FormationStrategyTest.java b/src/test/java/model/formation/FormationStrategyTest.java index d7b055a78d..7889a43022 100644 --- a/src/test/java/model/formation/FormationStrategyTest.java +++ b/src/test/java/model/formation/FormationStrategyTest.java @@ -1,24 +1,23 @@ package model.formation; +import static model.board.Position.CHO_LEFT_INNER; +import static model.board.Position.CHO_LEFT_OUTER; +import static model.board.Position.CHO_RIGHT_INNER; +import static model.board.Position.CHO_RIGHT_OUTER; +import static model.board.Position.HAN_LEFT_INNER; +import static model.board.Position.HAN_LEFT_OUTER; +import static model.board.Position.HAN_RIGHT_INNER; +import static model.board.Position.HAN_RIGHT_OUTER; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; import model.Team; -import model.coordinate.Position; +import model.board.Position; import model.piece.Piece; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.Map; - -import static model.coordinate.Position.CHO_LEFT_INNER; -import static model.coordinate.Position.CHO_LEFT_OUTER; -import static model.coordinate.Position.CHO_RIGHT_INNER; -import static model.coordinate.Position.CHO_RIGHT_OUTER; -import static model.coordinate.Position.HAN_LEFT_INNER; -import static model.coordinate.Position.HAN_LEFT_OUTER; -import static model.coordinate.Position.HAN_RIGHT_INNER; -import static model.coordinate.Position.HAN_RIGHT_OUTER; -import static org.assertj.core.api.Assertions.assertThat; - class FormationStrategyTest { @ParameterizedTest diff --git a/src/test/java/model/JanggiFormationTest.java b/src/test/java/model/formation/JanggiFormationTest.java similarity index 95% rename from src/test/java/model/JanggiFormationTest.java rename to src/test/java/model/formation/JanggiFormationTest.java index c26b9613d9..cafb56bdeb 100644 --- a/src/test/java/model/JanggiFormationTest.java +++ b/src/test/java/model/formation/JanggiFormationTest.java @@ -1,13 +1,12 @@ -package model; +package model.formation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; -import model.formation.JanggiFormation; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - class JanggiFormationTest { @ParameterizedTest diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index 9417545b5f..bb472955ba 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -4,7 +4,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index 324fac2bbf..b9d0540b2c 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -4,7 +4,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index 4745c30e32..00b114aeb4 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -4,7 +4,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index bfca563d3c..affe087e2b 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -4,7 +4,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/model/piece/SoldierTest.java b/src/test/java/model/piece/SoldierTest.java index 28d3a7cf61..24f9ba78a0 100644 --- a/src/test/java/model/piece/SoldierTest.java +++ b/src/test/java/model/piece/SoldierTest.java @@ -4,7 +4,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/model/testdouble/FakePiece.java b/src/test/java/model/testdouble/FakePiece.java index 353f063558..445f8f99a7 100644 --- a/src/test/java/model/testdouble/FakePiece.java +++ b/src/test/java/model/testdouble/FakePiece.java @@ -2,7 +2,7 @@ import java.util.List; import model.Team; -import model.coordinate.Position; +import model.board.Position; import model.piece.Piece; import model.piece.PieceType; diff --git a/src/test/java/model/testdouble/SpyBoard.java b/src/test/java/model/testdouble/SpyBoard.java index 1e1c09d88c..de1816869a 100644 --- a/src/test/java/model/testdouble/SpyBoard.java +++ b/src/test/java/model/testdouble/SpyBoard.java @@ -1,9 +1,9 @@ package model.testdouble; import java.util.Map; -import model.Board; import model.Team; -import model.coordinate.Position; +import model.board.Board; +import model.board.Position; import model.piece.Horse; import model.piece.Piece; From ba786370e26a1af74a412f79ce3e8bc1668bdd65 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Wed, 1 Apr 2026 10:37:49 +0900 Subject: [PATCH 21/26] =?UTF-8?q?refactor:=20board=20move=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/JanggiGame.java | 7 +-- src/main/java/model/board/Board.java | 55 ++++++++++++++++-------- src/test/java/model/board/BoardTest.java | 2 +- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/main/java/model/JanggiGame.java b/src/main/java/model/JanggiGame.java index acd429aad7..c3d6537138 100644 --- a/src/main/java/model/JanggiGame.java +++ b/src/main/java/model/JanggiGame.java @@ -15,18 +15,19 @@ public JanggiGame(Board board) { } public void movePiece(Position current, Position next) { - validateTurn(board.pickPiece(current)); + Piece piece = board.pickPiece(current); + validateCurrentTurn(piece); board.move(current, next); this.turn = turn.next(); } public Piece selectPiece(Position position) { Piece piece = board.pickPiece(position); - validateTurn(piece); + validateCurrentTurn(piece); return piece; } - private void validateTurn(Piece piece) { + private void validateCurrentTurn(Piece piece) { if (piece.isOtherTeam(turn)) { throw new IllegalArgumentException(turn.getName() + "의 기물이 아닙니다."); } diff --git a/src/main/java/model/board/Board.java b/src/main/java/model/board/Board.java index 8cf6071b3c..6a61e1112a 100644 --- a/src/main/java/model/board/Board.java +++ b/src/main/java/model/board/Board.java @@ -20,36 +20,53 @@ public Board(Map board) { public void move(Position current, Position next) { Piece piece = pickPiece(current); - if (!piece.canMove(current, next)) { - throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); - } + validateMove(current, next, piece); - findByPosition(next).ifPresent(otherPiece -> { - if (otherPiece.isSameTeam(piece)) { - throw new IllegalArgumentException("아군이 있는 위치로 이동할 수 없습니다."); - } - }); + board.remove(current); + board.put(next, piece); + } + + private void validateMove(Position current, Position next, Piece piece) { + validateCanMoveByPiece(current, next, piece); List path = piece.extractPath(current, next); if (piece.isCannon()) { - int countedPieces = countPiecesAt(path); - if (countedPieces != CANNON_HURDLE_COUNT) { - throw new IllegalArgumentException("포는" + CANNON_HURDLE_COUNT + "개의 기물만 건너 뛰어야 합니다."); - } - if (hasCannon(path)) { - throw new IllegalArgumentException("포는 포를 건너뛸 수 없습니다."); - } - board.remove(current); - board.put(next, piece); + validateContainOnlyOnePiece(path); + validateNotContainCannon(path); return; } + validateNotContainPiece(path); + } + + private void validateNotContainPiece(List path) { if (hasPieceAt(path)) { throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); } + } + + private void validateNotContainCannon(List path) { + if (hasCannon(path)) { + throw new IllegalArgumentException("포는 포를 건너뛸 수 없습니다."); + } + } - board.remove(current); - board.put(next, piece); + private void validateContainOnlyOnePiece(List path) { + if (countPiecesAt(path) != CANNON_HURDLE_COUNT) { + throw new IllegalArgumentException("포는" + CANNON_HURDLE_COUNT + "개의 기물만 건너 뛰어야 합니다."); + } + } + + private void validateCanMoveByPiece(Position current, Position next, Piece piece) { + if (!piece.canMove(current, next)) { + throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); + } + + findByPosition(next).ifPresent(otherPiece -> { + if (otherPiece.isSameTeam(piece)) { + throw new IllegalArgumentException("아군이 있는 위치로 이동할 수 없습니다."); + } + }); } public Piece pickPiece(Position position) { diff --git a/src/test/java/model/board/BoardTest.java b/src/test/java/model/board/BoardTest.java index 61e2595d6d..f7f550298f 100644 --- a/src/test/java/model/board/BoardTest.java +++ b/src/test/java/model/board/BoardTest.java @@ -119,7 +119,7 @@ class BoardTest { board.move(source, destination); // then - assertSuccessMoved(board, source, cannon, destination); + assertSuccessMoved(board, destination, cannon, source); } @Test From cb70ddc1cb7ee9a72f85b3f04c2a6e9dd61e6e67 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Thu, 2 Apr 2026 17:30:59 +0900 Subject: [PATCH 22/26] =?UTF-8?q?refactor:=20=EC=9E=A5=EA=B8=B0=20?= =?UTF-8?q?=ED=8F=AC=EB=A9=94=EC=9D=B4=EC=85=98=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 중복되는 로직 제거 * 뷰와 연관된 네이밍 수정 * 책임에 맞는 변수 위치 수정 --- .../java/controller/JanggiController.java | 23 ++--- src/main/java/model/Team.java | 6 +- src/main/java/model/board/Board.java | 4 + src/main/java/model/board/BoardFactory.java | 11 ++- .../java/model/board/FormationStrategy.java | 10 +++ .../java/model/board/JanggiFormation.java | 50 +++++++++++ .../model/formation/FormationFactory.java | 31 ------- .../model/formation/FormationStrategy.java | 20 ----- .../java/model/formation/JanggiFormation.java | 33 -------- .../model/formation/MaSangMaSangStrategy.java | 39 --------- .../model/formation/MaSangSangMaStrategy.java | 39 --------- .../model/formation/SangMaMaSangStrategy.java | 39 --------- .../model/formation/SangMaSangMaStrategy.java | 39 --------- src/main/java/view/InputView.java | 16 ++-- .../java/view/formater/BoardFormatter.java | 6 +- src/main/java/view/mapper/ViewMapper.java | 29 +++++-- .../java/model/board/JanggiFormationTest.java | 84 +++++++++++++++++++ .../model/fixture/FormationTestFixture.java | 46 ---------- .../model/formation/FormationFactoryTest.java | 30 ------- .../formation/FormationStrategyTest.java | 70 ---------------- .../model/formation/JanggiFormationTest.java | 32 ------- 21 files changed, 205 insertions(+), 452 deletions(-) create mode 100644 src/main/java/model/board/FormationStrategy.java create mode 100644 src/main/java/model/board/JanggiFormation.java delete mode 100644 src/main/java/model/formation/FormationFactory.java delete mode 100644 src/main/java/model/formation/FormationStrategy.java delete mode 100644 src/main/java/model/formation/JanggiFormation.java delete mode 100644 src/main/java/model/formation/MaSangMaSangStrategy.java delete mode 100644 src/main/java/model/formation/MaSangSangMaStrategy.java delete mode 100644 src/main/java/model/formation/SangMaMaSangStrategy.java delete mode 100644 src/main/java/model/formation/SangMaSangMaStrategy.java create mode 100644 src/test/java/model/board/JanggiFormationTest.java delete mode 100644 src/test/java/model/fixture/FormationTestFixture.java delete mode 100644 src/test/java/model/formation/FormationFactoryTest.java delete mode 100644 src/test/java/model/formation/FormationStrategyTest.java delete mode 100644 src/test/java/model/formation/JanggiFormationTest.java diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 34989087e1..59d5a0ca57 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -4,17 +4,13 @@ import static model.Team.CHO; import static model.Team.HAN; -import java.util.Arrays; -import java.util.List; -import java.util.Map; import java.util.function.Consumer; import model.JanggiGame; import model.Team; import model.board.Board; import model.board.BoardFactory; +import model.board.JanggiFormation; import model.board.Position; -import model.formation.FormationFactory; -import model.formation.JanggiFormation; import model.piece.Piece; import view.InputView; import view.OutputView; @@ -29,12 +25,7 @@ public JanggiController(InputView inputView, OutputView outputView) { } public void run() { - List formations = Arrays.asList(JanggiFormation.values()); - JanggiFormation hanFormation = retry(() -> inputView.readFormationNumber(HAN, formations), processError()); - JanggiFormation choFormation = retry(() -> inputView.readFormationNumber(CHO, formations), processError()); - - Map pieceByFormation = FormationFactory.generateFormation(hanFormation, choFormation); - Board board = BoardFactory.generatePieces(pieceByFormation); + Board board = createBoardByFormation(); outputView.displayBoard(board.board()); JanggiGame janggiGame = new JanggiGame(board); @@ -44,6 +35,16 @@ public void run() { } } + private Board createBoardByFormation() { + JanggiFormation hanFormation = retry(() -> inputView.readFormationByTeam(HAN), processError()); + JanggiFormation choFormation = retry(() -> inputView.readFormationByTeam(CHO), processError()); + + Board board = BoardFactory.generateDefaultPieces(); + board.arrangePieces(hanFormation.generateByTeam(HAN)); + board.arrangePieces(choFormation.generateByTeam(CHO)); + return board; + } + private void playByTurn(JanggiGame janggiGame) { Team currentTurn = janggiGame.getTurn(); diff --git a/src/main/java/model/Team.java b/src/main/java/model/Team.java index 5d8c88ab58..edf67d92cf 100644 --- a/src/main/java/model/Team.java +++ b/src/main/java/model/Team.java @@ -13,8 +13,12 @@ public String getName() { return name; } + public boolean isHan() { + return this == HAN; + } + public Team next() { - if (this == HAN) { + if (isHan()) { return CHO; } return HAN; diff --git a/src/main/java/model/board/Board.java b/src/main/java/model/board/Board.java index 6a61e1112a..ff4ad70ebd 100644 --- a/src/main/java/model/board/Board.java +++ b/src/main/java/model/board/Board.java @@ -78,6 +78,10 @@ public boolean hasPieceAt(Position position) { return board.containsKey(position); } + public void arrangePieces(Map pieces) { + board.putAll(pieces); + } + private Optional findByPosition(Position position) { return Optional.ofNullable(board.get(position)); } diff --git a/src/main/java/model/board/BoardFactory.java b/src/main/java/model/board/BoardFactory.java index 01ba35cbc3..b89731725c 100644 --- a/src/main/java/model/board/BoardFactory.java +++ b/src/main/java/model/board/BoardFactory.java @@ -14,7 +14,7 @@ public class BoardFactory { - private static final Map RED_PIECES = Map.ofEntries( + private static final Map HAN_PIECES = Map.ofEntries( Map.entry(new Position(0, 0), new Chariot(HAN)), Map.entry(new Position(0, 3), new Guard(HAN)), Map.entry(new Position(0, 5), new Guard(HAN)), @@ -29,7 +29,7 @@ public class BoardFactory { Map.entry(new Position(3, 8), new Soldier(HAN)) ); - private static final Map GREEN_PIECES = Map.ofEntries( + private static final Map CHO_PIECES = Map.ofEntries( Map.entry(new Position(9, 0), new Chariot(CHO)), Map.entry(new Position(9, 3), new Guard(CHO)), Map.entry(new Position(9, 5), new Guard(CHO)), @@ -44,11 +44,10 @@ public class BoardFactory { Map.entry(new Position(6, 8), new Soldier(CHO)) ); - public static Board generatePieces(Map pieceByFormation) { + public static Board generateDefaultPieces() { Map allPieces = new HashMap<>(); - allPieces.putAll(RED_PIECES); - allPieces.putAll(GREEN_PIECES); - allPieces.putAll(pieceByFormation); + allPieces.putAll(HAN_PIECES); + allPieces.putAll(CHO_PIECES); return new Board(allPieces); } } diff --git a/src/main/java/model/board/FormationStrategy.java b/src/main/java/model/board/FormationStrategy.java new file mode 100644 index 0000000000..9e6c2af3d3 --- /dev/null +++ b/src/main/java/model/board/FormationStrategy.java @@ -0,0 +1,10 @@ +package model.board; + +import java.util.List; +import model.Team; +import model.piece.Piece; + +public interface FormationStrategy { + + List generateByTeam(Team team); +} diff --git a/src/main/java/model/board/JanggiFormation.java b/src/main/java/model/board/JanggiFormation.java new file mode 100644 index 0000000000..b3bf475b70 --- /dev/null +++ b/src/main/java/model/board/JanggiFormation.java @@ -0,0 +1,50 @@ +package model.board; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import model.Team; +import model.piece.Elephant; +import model.piece.Horse; +import model.piece.Piece; + +public enum JanggiFormation { + SANG_MA_SANG_MA(team -> List.of(new Elephant(team), new Horse(team), new Elephant(team), new Horse(team))), + MA_SANG_MA_SANG(team -> List.of(new Horse(team), new Elephant(team), new Horse(team), new Elephant(team))), + MA_SANG_SANG_MA(team -> List.of(new Horse(team), new Elephant(team), new Elephant(team), new Horse(team))), + SANG_MA_MA_SANG(team -> List.of(new Elephant(team), new Horse(team), new Horse(team), new Elephant(team))); + + private static final Position HAN_LEFT_OUTER = new Position(0, 1); + private static final Position HAN_LEFT_INNER = new Position(0, 2); + private static final Position HAN_RIGHT_INNER = new Position(0, 6); + private static final Position HAN_RIGHT_OUTER = new Position(0, 7); + + private static final Position CHO_LEFT_OUTER = new Position(9, 1); + private static final Position CHO_LEFT_INNER = new Position(9, 2); + private static final Position CHO_RIGHT_INNER = new Position(9, 6); + private static final Position CHO_RIGHT_OUTER = new Position(9, 7); + + private final FormationStrategy strategy; + + JanggiFormation(FormationStrategy strategy) { + this.strategy = strategy; + } + + private static List extractPositionsByTeam(Team team) { + if (team == Team.HAN) { + return List.of(HAN_LEFT_OUTER, HAN_LEFT_INNER, HAN_RIGHT_INNER, HAN_RIGHT_OUTER); + } + return List.of(CHO_LEFT_OUTER, CHO_LEFT_INNER, CHO_RIGHT_INNER, CHO_RIGHT_OUTER); + } + + public Map generateByTeam(Team team) { + List positions = extractPositionsByTeam(team); + List pieces = strategy.generateByTeam(team); + + Map formation = new HashMap<>(); + for (int i = 0; i < positions.size(); i++) { + formation.put(positions.get(i), pieces.get(i)); + } + return formation; + } +} diff --git a/src/main/java/model/formation/FormationFactory.java b/src/main/java/model/formation/FormationFactory.java deleted file mode 100644 index e94fcd2ce9..0000000000 --- a/src/main/java/model/formation/FormationFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package model.formation; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import model.Team; -import model.board.Position; -import model.piece.Piece; - -public class FormationFactory { - - private static final Map FORMATION_STRATEGIES = Map.of( - JanggiFormation.SANG_MA_SANG_MA, new SangMaSangMaStrategy(), - JanggiFormation.MA_SANG_MA_SANG, new MaSangMaSangStrategy(), - JanggiFormation.MA_SANG_SANG_MA, new MaSangSangMaStrategy(), - JanggiFormation.SANG_MA_MA_SANG, new SangMaMaSangStrategy() - ); - - public static Map generateFormation(JanggiFormation hanFormation, JanggiFormation choFormation) { - Map formations = new HashMap<>(); - formations.putAll(extractFormationByTeam(hanFormation, Team.HAN)); - formations.putAll(extractFormationByTeam(choFormation, Team.CHO)); - return Map.copyOf(formations); - } - - private static Map extractFormationByTeam(JanggiFormation formation, Team team) { - return Optional.ofNullable(FORMATION_STRATEGIES.get(formation)) - .map(strategy -> strategy.generateFormation(team)) - .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 상차림 생성 전략입니다.")); - } -} diff --git a/src/main/java/model/formation/FormationStrategy.java b/src/main/java/model/formation/FormationStrategy.java deleted file mode 100644 index e19e462503..0000000000 --- a/src/main/java/model/formation/FormationStrategy.java +++ /dev/null @@ -1,20 +0,0 @@ -package model.formation; - -import java.util.Map; -import model.Team; -import model.board.Position; -import model.piece.Piece; - -public abstract class FormationStrategy { - - public Map generateFormation(Team team) { - if (team == Team.HAN) { - return generateHanFormation(); - } - return generateChoFormation(); - } - - protected abstract Map generateHanFormation(); - - protected abstract Map generateChoFormation(); -} diff --git a/src/main/java/model/formation/JanggiFormation.java b/src/main/java/model/formation/JanggiFormation.java deleted file mode 100644 index 93686b5107..0000000000 --- a/src/main/java/model/formation/JanggiFormation.java +++ /dev/null @@ -1,33 +0,0 @@ -package model.formation; - -import java.util.stream.Stream; - -public enum JanggiFormation { - SANG_MA_SANG_MA(1, "상마상마"), - MA_SANG_MA_SANG(2, "마상마상"), - MA_SANG_SANG_MA(3, "마상상마"), - SANG_MA_MA_SANG(4, "상마마상"); - - private final int order; - private final String formation; - - JanggiFormation(int order, String formation) { - this.order = order; - this.formation = formation; - } - - public static JanggiFormation from(int order) { - return Stream.of(values()) - .filter(formation -> formation.order == order) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 상차림 번호입니다.")); - } - - public int getOrder() { - return order; - } - - public String getFormation() { - return formation; - } -} diff --git a/src/main/java/model/formation/MaSangMaSangStrategy.java b/src/main/java/model/formation/MaSangMaSangStrategy.java deleted file mode 100644 index 17a2f309b4..0000000000 --- a/src/main/java/model/formation/MaSangMaSangStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -package model.formation; - -import static model.board.Position.CHO_LEFT_INNER; -import static model.board.Position.CHO_LEFT_OUTER; -import static model.board.Position.CHO_RIGHT_INNER; -import static model.board.Position.CHO_RIGHT_OUTER; -import static model.board.Position.HAN_LEFT_INNER; -import static model.board.Position.HAN_LEFT_OUTER; -import static model.board.Position.HAN_RIGHT_INNER; -import static model.board.Position.HAN_RIGHT_OUTER; - -import java.util.Map; -import model.Team; -import model.board.Position; -import model.piece.Elephant; -import model.piece.Horse; -import model.piece.Piece; - -public class MaSangMaSangStrategy extends FormationStrategy { - @Override - protected Map generateHanFormation() { - return Map.of( - HAN_LEFT_OUTER, new Horse(Team.HAN), - HAN_LEFT_INNER, new Elephant(Team.HAN), - HAN_RIGHT_INNER, new Horse(Team.HAN), - HAN_RIGHT_OUTER, new Elephant(Team.HAN) - ); - } - - @Override - protected Map generateChoFormation() { - return Map.of( - CHO_LEFT_OUTER, new Horse(Team.CHO), - CHO_LEFT_INNER, new Elephant(Team.CHO), - CHO_RIGHT_INNER, new Horse(Team.CHO), - CHO_RIGHT_OUTER, new Elephant(Team.CHO) - ); - } -} diff --git a/src/main/java/model/formation/MaSangSangMaStrategy.java b/src/main/java/model/formation/MaSangSangMaStrategy.java deleted file mode 100644 index 08700de87b..0000000000 --- a/src/main/java/model/formation/MaSangSangMaStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -package model.formation; - -import static model.board.Position.CHO_LEFT_INNER; -import static model.board.Position.CHO_LEFT_OUTER; -import static model.board.Position.CHO_RIGHT_INNER; -import static model.board.Position.CHO_RIGHT_OUTER; -import static model.board.Position.HAN_LEFT_INNER; -import static model.board.Position.HAN_LEFT_OUTER; -import static model.board.Position.HAN_RIGHT_INNER; -import static model.board.Position.HAN_RIGHT_OUTER; - -import java.util.Map; -import model.Team; -import model.board.Position; -import model.piece.Elephant; -import model.piece.Horse; -import model.piece.Piece; - -public class MaSangSangMaStrategy extends FormationStrategy { - @Override - protected Map generateHanFormation() { - return Map.of( - HAN_LEFT_OUTER, new Horse(Team.HAN), - HAN_LEFT_INNER, new Elephant(Team.HAN), - HAN_RIGHT_INNER, new Elephant(Team.HAN), - HAN_RIGHT_OUTER, new Horse(Team.HAN) - ); - } - - @Override - protected Map generateChoFormation() { - return Map.of( - CHO_LEFT_OUTER, new Horse(Team.CHO), - CHO_LEFT_INNER, new Elephant(Team.CHO), - CHO_RIGHT_INNER, new Elephant(Team.CHO), - CHO_RIGHT_OUTER, new Horse(Team.CHO) - ); - } -} diff --git a/src/main/java/model/formation/SangMaMaSangStrategy.java b/src/main/java/model/formation/SangMaMaSangStrategy.java deleted file mode 100644 index 9471694234..0000000000 --- a/src/main/java/model/formation/SangMaMaSangStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -package model.formation; - -import static model.board.Position.CHO_LEFT_INNER; -import static model.board.Position.CHO_LEFT_OUTER; -import static model.board.Position.CHO_RIGHT_INNER; -import static model.board.Position.CHO_RIGHT_OUTER; -import static model.board.Position.HAN_LEFT_INNER; -import static model.board.Position.HAN_LEFT_OUTER; -import static model.board.Position.HAN_RIGHT_INNER; -import static model.board.Position.HAN_RIGHT_OUTER; - -import java.util.Map; -import model.Team; -import model.board.Position; -import model.piece.Elephant; -import model.piece.Horse; -import model.piece.Piece; - -public class SangMaMaSangStrategy extends FormationStrategy { - @Override - protected Map generateHanFormation() { - return Map.of( - HAN_LEFT_OUTER, new Elephant(Team.HAN), - HAN_LEFT_INNER, new Horse(Team.HAN), - HAN_RIGHT_INNER, new Horse(Team.HAN), - HAN_RIGHT_OUTER, new Elephant(Team.HAN) - ); - } - - @Override - protected Map generateChoFormation() { - return Map.of( - CHO_LEFT_OUTER, new Elephant(Team.CHO), - CHO_LEFT_INNER, new Horse(Team.CHO), - CHO_RIGHT_INNER, new Horse(Team.CHO), - CHO_RIGHT_OUTER, new Elephant(Team.CHO) - ); - } -} diff --git a/src/main/java/model/formation/SangMaSangMaStrategy.java b/src/main/java/model/formation/SangMaSangMaStrategy.java deleted file mode 100644 index f783cd5383..0000000000 --- a/src/main/java/model/formation/SangMaSangMaStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -package model.formation; - -import static model.board.Position.CHO_LEFT_INNER; -import static model.board.Position.CHO_LEFT_OUTER; -import static model.board.Position.CHO_RIGHT_INNER; -import static model.board.Position.CHO_RIGHT_OUTER; -import static model.board.Position.HAN_LEFT_INNER; -import static model.board.Position.HAN_LEFT_OUTER; -import static model.board.Position.HAN_RIGHT_INNER; -import static model.board.Position.HAN_RIGHT_OUTER; - -import java.util.Map; -import model.Team; -import model.board.Position; -import model.piece.Elephant; -import model.piece.Horse; -import model.piece.Piece; - -public class SangMaSangMaStrategy extends FormationStrategy { - @Override - protected Map generateHanFormation() { - return Map.of( - HAN_LEFT_OUTER, new Elephant(Team.HAN), - HAN_LEFT_INNER, new Horse(Team.HAN), - HAN_RIGHT_INNER, new Elephant(Team.HAN), - HAN_RIGHT_OUTER, new Horse(Team.HAN) - ); - } - - @Override - protected Map generateChoFormation() { - return Map.of( - CHO_LEFT_OUTER, new Elephant(Team.CHO), - CHO_LEFT_INNER, new Horse(Team.CHO), - CHO_RIGHT_INNER, new Elephant(Team.CHO), - CHO_RIGHT_OUTER, new Horse(Team.CHO) - ); - } -} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 68e4e36c2c..05eaca3400 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,12 +1,15 @@ package view; import static view.formater.BoardFormatter.formatSymbol; +import static view.mapper.ViewMapper.FORMATION_DISPLAY_MAPPER; +import static view.mapper.ViewMapper.FORMATION_ORDER_MAPPER; import java.util.List; +import java.util.Optional; import java.util.Scanner; import model.Team; +import model.board.JanggiFormation; import model.board.Position; -import model.formation.JanggiFormation; import model.piece.Piece; import view.parser.InputParser; @@ -14,15 +17,16 @@ public class InputView { private static final Scanner SCANNER = new Scanner(System.in); private static final InputParser PARSER = new InputParser(); - public JanggiFormation readFormationNumber(Team team, List janggiFormations) { + public JanggiFormation readFormationByTeam(Team team) { System.out.printf("%n%s의 상차림을 선택해주세요.%n", team.getName()); - for (JanggiFormation formation : janggiFormations) { - System.out.printf("%d. %s%n", formation.getOrder(), formation.getFormation()); - } + FORMATION_ORDER_MAPPER.forEach((order, formation) -> + System.out.printf("%d. %s%n", order, FORMATION_DISPLAY_MAPPER.get(formation))); String input = SCANNER.nextLine(); int order = PARSER.parseNumber(input); - return JanggiFormation.from(order); + + return Optional.ofNullable(FORMATION_ORDER_MAPPER.get(order)) + .orElseThrow(() -> new IllegalArgumentException("올바른 상차림을 선택해주세요.")); } public Position readSource(Team turn) { diff --git a/src/main/java/view/formater/BoardFormatter.java b/src/main/java/view/formater/BoardFormatter.java index a91a117f46..a01a712350 100644 --- a/src/main/java/view/formater/BoardFormatter.java +++ b/src/main/java/view/formater/BoardFormatter.java @@ -1,10 +1,10 @@ package view.formater; +import static view.mapper.ViewMapper.SYMBOL_MAP; + import model.Team; import model.piece.Piece; -import static view.mapper.ViewMapper.getSymbol; - public class BoardFormatter { public static final String RED = "\u001B[31m"; @@ -33,7 +33,7 @@ public static String formatSymbol(Piece piece) { return EMPTY; } String color = extractColor(piece.getTeam()); - String symbol = getSymbol(piece.getType(), piece.getTeam()); + String symbol = SYMBOL_MAP.get(piece.getType()).get(piece.getTeam()); return color + symbol + RESET; } diff --git a/src/main/java/view/mapper/ViewMapper.java b/src/main/java/view/mapper/ViewMapper.java index d4bad8d2bc..04b5ebc991 100644 --- a/src/main/java/view/mapper/ViewMapper.java +++ b/src/main/java/view/mapper/ViewMapper.java @@ -1,14 +1,28 @@ package view.mapper; -import model.Team; -import model.piece.PieceType; +import static model.board.JanggiFormation.MA_SANG_MA_SANG; +import static model.board.JanggiFormation.MA_SANG_SANG_MA; +import static model.board.JanggiFormation.SANG_MA_MA_SANG; +import static model.board.JanggiFormation.SANG_MA_SANG_MA; import java.util.EnumMap; +import java.util.LinkedHashMap; import java.util.Map; +import model.Team; +import model.board.JanggiFormation; +import model.piece.PieceType; public class ViewMapper { - private static final Map> SYMBOL_MAP = new EnumMap<>(PieceType.class); + public static final Map> SYMBOL_MAP = new EnumMap<>(PieceType.class); + public static Map FORMATION_ORDER_MAPPER = new LinkedHashMap<>(); + + public static Map FORMATION_DISPLAY_MAPPER = Map.of( + SANG_MA_SANG_MA, "상마상마", + MA_SANG_MA_SANG, "마상마상", + MA_SANG_SANG_MA, "마상상마", + SANG_MA_MA_SANG, "상마마상" + ); static { SYMBOL_MAP.put(PieceType.CHARIOT, Map.of(Team.HAN, "車", Team.CHO, "車")); @@ -18,12 +32,13 @@ public class ViewMapper { SYMBOL_MAP.put(PieceType.ELEPHANT, Map.of(Team.HAN, "象", Team.CHO, "象")); SYMBOL_MAP.put(PieceType.GUARD, Map.of(Team.HAN, "士", Team.CHO, "士")); SYMBOL_MAP.put(PieceType.SOLDIER, Map.of(Team.HAN, "兵", Team.CHO, "卒")); - } - private ViewMapper() { + FORMATION_ORDER_MAPPER.put(1, SANG_MA_SANG_MA); + FORMATION_ORDER_MAPPER.put(2, MA_SANG_MA_SANG); + FORMATION_ORDER_MAPPER.put(3, MA_SANG_SANG_MA); + FORMATION_ORDER_MAPPER.put(4, SANG_MA_MA_SANG); } - public static String getSymbol(PieceType type, Team team) { - return SYMBOL_MAP.get(type).get(team); + private ViewMapper() { } } diff --git a/src/test/java/model/board/JanggiFormationTest.java b/src/test/java/model/board/JanggiFormationTest.java new file mode 100644 index 0000000000..41888b4a09 --- /dev/null +++ b/src/test/java/model/board/JanggiFormationTest.java @@ -0,0 +1,84 @@ +package model.board; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import java.util.stream.Stream; +import model.Team; +import model.piece.Elephant; +import model.piece.Horse; +import model.piece.Piece; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +class JanggiFormationTest { + + static Stream formationTestProvider() { + return Stream.of( + Arguments.of(JanggiFormation.SANG_MA_SANG_MA, Elephant.class, Horse.class, Elephant.class, Horse.class), + Arguments.of(JanggiFormation.MA_SANG_MA_SANG, Horse.class, Elephant.class, Horse.class, Elephant.class), + Arguments.of(JanggiFormation.MA_SANG_SANG_MA, Horse.class, Elephant.class, Elephant.class, Horse.class), + Arguments.of(JanggiFormation.SANG_MA_MA_SANG, Elephant.class, Horse.class, Horse.class, Elephant.class) + ); + } + + static Stream 포메이션_별_기물_위치() { + return Stream.of( + // formation, 좌외(1), 좌내(2), 우내(6), 우외(7) 순서 + Arguments.of(JanggiFormation.SANG_MA_SANG_MA, Elephant.class, Horse.class, Elephant.class, Horse.class), + Arguments.of(JanggiFormation.MA_SANG_MA_SANG, Horse.class, Elephant.class, Horse.class, Elephant.class), + Arguments.of(JanggiFormation.MA_SANG_SANG_MA, Horse.class, Elephant.class, Elephant.class, Horse.class), + Arguments.of(JanggiFormation.SANG_MA_MA_SANG, Elephant.class, Horse.class, Horse.class, Elephant.class) + ); + } + + static Stream choFormationProvider() { + return 포메이션_별_기물_위치(); + } + + @ParameterizedTest(name = "{0} 차림 일 때") + @MethodSource("포메이션_별_기물_위치") + void 한나라_상차림_기물_순서_테스트( + JanggiFormation formation, + Class leftOuter, + Class leftInner, + Class rightInner, + Class rightOuter + ) { + Map result = formation.generateByTeam(Team.HAN); + + assertThat(result.get(new Position(0, 1))).isInstanceOf(leftOuter); + assertThat(result.get(new Position(0, 2))).isInstanceOf(leftInner); + assertThat(result.get(new Position(0, 6))).isInstanceOf(rightInner); + assertThat(result.get(new Position(0, 7))).isInstanceOf(rightOuter); + } + + @ParameterizedTest(name = "{0} 차림일 때") + @MethodSource("choFormationProvider") + void 초나라_상차림_기물_순서_테스트( + JanggiFormation formation, + Class leftOuter, + Class leftInner, + Class rightInner, + Class rightOuter + ) { + Map result = formation.generateByTeam(Team.CHO); + + assertThat(result.get(new Position(9, 1))).isInstanceOf(leftOuter); + assertThat(result.get(new Position(9, 2))).isInstanceOf(leftInner); + assertThat(result.get(new Position(9, 6))).isInstanceOf(rightInner); + assertThat(result.get(new Position(9, 7))).isInstanceOf(rightOuter); + } + + @ParameterizedTest(name = "{1}나라 일 때 {0}포메이션에서 기물 수는 총 4개다.") + @CsvSource({ + "SANG_MA_SANG_MA, HAN", "MA_SANG_MA_SANG, HAN", "MA_SANG_SANG_MA, HAN", "SANG_MA_MA_SANG, HAN", + "SANG_MA_SANG_MA, CHO", "MA_SANG_MA_SANG, CHO", "MA_SANG_SANG_MA, CHO", "SANG_MA_MA_SANG, CHO" + }) + void 각_팀별_상차림의_기물_수는_4개여야_한다(JanggiFormation formation, Team team) { + // when + assertThat(formation.generateByTeam(team)).hasSize(4); + } +} diff --git a/src/test/java/model/fixture/FormationTestFixture.java b/src/test/java/model/fixture/FormationTestFixture.java deleted file mode 100644 index edf452e824..0000000000 --- a/src/test/java/model/fixture/FormationTestFixture.java +++ /dev/null @@ -1,46 +0,0 @@ -package model.fixture; - -import model.Team; -import model.formation.MaSangMaSangStrategy; -import model.formation.MaSangSangMaStrategy; -import model.formation.SangMaMaSangStrategy; -import model.formation.SangMaSangMaStrategy; -import model.piece.Elephant; -import model.piece.Horse; -import org.junit.jupiter.params.provider.Arguments; - -import java.util.stream.Stream; - -public class FormationTestFixture { - - public static Stream 한나라_상차림_전략() { - return Stream.of( - Arguments.of(new SangMaSangMaStrategy(), Elephant.class, Horse.class, Elephant.class, Horse.class), - Arguments.of(new MaSangMaSangStrategy(), Horse.class, Elephant.class, Horse.class, Elephant.class), - Arguments.of(new MaSangSangMaStrategy(), Horse.class, Elephant.class, Elephant.class, Horse.class), - Arguments.of(new SangMaMaSangStrategy(), Elephant.class, Horse.class, Horse.class, Elephant.class) - ); - } - - public static Stream 초나라_상차림_전략() { - return Stream.of( - Arguments.of(new SangMaSangMaStrategy(), Elephant.class, Horse.class, Elephant.class, Horse.class), - Arguments.of(new MaSangMaSangStrategy(), Horse.class, Elephant.class, Horse.class, Elephant.class), - Arguments.of(new MaSangSangMaStrategy(), Horse.class, Elephant.class, Elephant.class, Horse.class), - Arguments.of(new SangMaMaSangStrategy(), Elephant.class, Horse.class, Horse.class, Elephant.class) - ); - } - - public static Stream 나라별_상차림_전략() { - return Stream.of( - Arguments.of(new SangMaSangMaStrategy(), Team.HAN), - Arguments.of(new SangMaSangMaStrategy(), Team.CHO), - Arguments.of(new MaSangMaSangStrategy(), Team.HAN), - Arguments.of(new MaSangMaSangStrategy(), Team.CHO), - Arguments.of(new MaSangSangMaStrategy(), Team.HAN), - Arguments.of(new MaSangSangMaStrategy(), Team.CHO), - Arguments.of(new SangMaMaSangStrategy(), Team.HAN), - Arguments.of(new SangMaMaSangStrategy(), Team.CHO) - ); - } -} diff --git a/src/test/java/model/formation/FormationFactoryTest.java b/src/test/java/model/formation/FormationFactoryTest.java deleted file mode 100644 index 7502dee14a..0000000000 --- a/src/test/java/model/formation/FormationFactoryTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package model.formation; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Map; -import java.util.stream.Stream; -import model.board.Position; -import model.piece.Piece; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class FormationFactoryTest { - - static Stream validFormationCombinations() { - return Stream.of(JanggiFormation.values()) - .flatMap(han -> Stream.of(JanggiFormation.values()) - .map(cho -> Arguments.of(han, cho))); - } - - @ParameterizedTest - @MethodSource("validFormationCombinations") - void 두_팀의_상차림을_합쳐서_8개_기물을_생성한다(JanggiFormation han, JanggiFormation cho) { - // when - Map result = FormationFactory.generateFormation(han, cho); - - // then - assertThat(result).hasSize(8); - } -} \ No newline at end of file diff --git a/src/test/java/model/formation/FormationStrategyTest.java b/src/test/java/model/formation/FormationStrategyTest.java deleted file mode 100644 index 7889a43022..0000000000 --- a/src/test/java/model/formation/FormationStrategyTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package model.formation; - - -import static model.board.Position.CHO_LEFT_INNER; -import static model.board.Position.CHO_LEFT_OUTER; -import static model.board.Position.CHO_RIGHT_INNER; -import static model.board.Position.CHO_RIGHT_OUTER; -import static model.board.Position.HAN_LEFT_INNER; -import static model.board.Position.HAN_LEFT_OUTER; -import static model.board.Position.HAN_RIGHT_INNER; -import static model.board.Position.HAN_RIGHT_OUTER; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Map; -import model.Team; -import model.board.Position; -import model.piece.Piece; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -class FormationStrategyTest { - - @ParameterizedTest - @MethodSource("model.fixture.FormationTestFixture#한나라_상차림_전략") - void HAN팀_상차림_전략별_기물_배치가_올바르다( - FormationStrategy strategy, - Class leftOuter, - Class leftInner, - Class rightInner, - Class rightOuter - ) { - // when - Map formation = strategy.generateFormation(Team.HAN); - - // then - assertThat(formation.get(HAN_LEFT_OUTER)).isInstanceOf(leftOuter); - assertThat(formation.get(HAN_LEFT_INNER)).isInstanceOf(leftInner); - assertThat(formation.get(HAN_RIGHT_INNER)).isInstanceOf(rightInner); - assertThat(formation.get(HAN_RIGHT_OUTER)).isInstanceOf(rightOuter); - } - - @ParameterizedTest - @MethodSource("model.fixture.FormationTestFixture#초나라_상차림_전략") - void CHO팀_상차림_전략별_기물_배치가_올바르다( - FormationStrategy strategy, - Class leftOuter, - Class leftInner, - Class rightInner, - Class rightOuter - ) { - // when - Map formation = strategy.generateFormation(Team.CHO); - - // then - assertThat(formation.get(CHO_LEFT_OUTER)).isInstanceOf(leftOuter); - assertThat(formation.get(CHO_LEFT_INNER)).isInstanceOf(leftInner); - assertThat(formation.get(CHO_RIGHT_INNER)).isInstanceOf(rightInner); - assertThat(formation.get(CHO_RIGHT_OUTER)).isInstanceOf(rightOuter); - } - - @ParameterizedTest - @MethodSource("model.fixture.FormationTestFixture#나라별_상차림_전략") - void 상차림_기물_수는_4개다(FormationStrategy strategy, Team team) { - // when - Map pieceByFormation = strategy.generateFormation(team); - - // then - assertThat(pieceByFormation).hasSize(4); - } -} \ No newline at end of file diff --git a/src/test/java/model/formation/JanggiFormationTest.java b/src/test/java/model/formation/JanggiFormationTest.java deleted file mode 100644 index cafb56bdeb..0000000000 --- a/src/test/java/model/formation/JanggiFormationTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package model.formation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; - -class JanggiFormationTest { - - @ParameterizedTest - @CsvSource({ - "1, SANG_MA_SANG_MA", - "2, MA_SANG_MA_SANG", - "3, MA_SANG_SANG_MA", - "4, SANG_MA_MA_SANG" - }) - void 번호로_상차림을_찾는다(int order, JanggiFormation expected) { - JanggiFormation formation = JanggiFormation.from(order); - - assertThat(formation).isEqualTo(expected); - } - - @ParameterizedTest - @ValueSource(ints = {0, 5}) - void 잘못된_번호_입력시_예외가_발생한다(int invalidOrder) { - assertThatThrownBy(() -> JanggiFormation.from(invalidOrder)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("유효하지 않은 상차림 번호입니다."); - } -} From 528918af393cad8d72896a67bf3c73bffd4ce709 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Thu, 2 Apr 2026 17:44:20 +0900 Subject: [PATCH 23/26] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EB=B0=8F=20=EB=B0=B0=EC=B9=98=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=9E=85=EB=A0=A5=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=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/controller/JanggiController.java | 4 ++-- src/main/java/model/JanggiGame.java | 3 +-- src/main/java/view/InputView.java | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 59d5a0ca57..12f5c8d293 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -48,10 +48,10 @@ private Board createBoardByFormation() { private void playByTurn(JanggiGame janggiGame) { Team currentTurn = janggiGame.getTurn(); - Position current = inputView.readSource(currentTurn); + Position current = inputView.readPiecePositionForMove(currentTurn); Piece piece = janggiGame.selectPiece(current); - Position next = inputView.readDestination(currentTurn, piece); + Position next = inputView.readPiecePositionForArrange(currentTurn, piece); janggiGame.movePiece(current, next); } diff --git a/src/main/java/model/JanggiGame.java b/src/main/java/model/JanggiGame.java index c3d6537138..27f925fb01 100644 --- a/src/main/java/model/JanggiGame.java +++ b/src/main/java/model/JanggiGame.java @@ -15,8 +15,7 @@ public JanggiGame(Board board) { } public void movePiece(Position current, Position next) { - Piece piece = board.pickPiece(current); - validateCurrentTurn(piece); + selectPiece(current); board.move(current, next); this.turn = turn.next(); } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 05eaca3400..4a746f5c24 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -29,7 +29,7 @@ public JanggiFormation readFormationByTeam(Team team) { .orElseThrow(() -> new IllegalArgumentException("올바른 상차림을 선택해주세요.")); } - public Position readSource(Team turn) { + public Position readPiecePositionForMove(Team turn) { System.out.println(); System.out.printf("[%s] 이동할 기물을 선택해주세요. (쉼표 기준으로 분리)%n", turn.getName()); System.out.print("기물: "); @@ -43,7 +43,7 @@ private Position extractPosition() { return new Position(row, col); } - public Position readDestination(Team turn, Piece piece) { + public Position readPiecePositionForArrange(Team turn, Piece piece) { System.out.println(); System.out.printf("[%s] 기물 %s의 다음 위치를 선택해주세요. (쉼표 기준으로 분리)%n", turn.getName(), formatSymbol(piece)); System.out.print("기물: "); From b743a356473e4c8ea0d7d8b2dbbef43b99b9aa96 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Fri, 3 Apr 2026 01:28:12 +0900 Subject: [PATCH 24/26] =?UTF-8?q?refactor:=20=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9E=A5=EA=B8=B0=EA=B2=8C=EC=9E=84=20=EC=B6=94?= =?UTF-8?q?=EC=83=81=ED=99=94=20=EC=88=98=EC=A4=80=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/model/JanggiGame.java | 19 +-- src/main/java/model/Team.java | 12 +- src/main/java/model/board/Board.java | 76 ++--------- src/main/java/model/piece/Cannon.java | 22 ++++ src/main/java/model/piece/Piece.java | 28 +++- src/test/java/model/JanggiGameTest.java | 32 ++--- src/test/java/model/TeamTest.java | 46 +++++++ src/test/java/model/board/BoardTest.java | 122 +++++------------- src/test/java/model/piece/CannonTest.java | 50 +++++++ src/test/java/model/piece/ChariotTest.java | 14 ++ src/test/java/model/piece/ElephantTest.java | 14 ++ src/test/java/model/piece/HorseTest.java | 14 ++ src/test/java/model/piece/SoldierTest.java | 14 ++ src/test/java/model/testdouble/FakePiece.java | 14 +- 14 files changed, 276 insertions(+), 201 deletions(-) create mode 100644 src/test/java/model/TeamTest.java diff --git a/src/main/java/model/JanggiGame.java b/src/main/java/model/JanggiGame.java index 27f925fb01..39b1468759 100644 --- a/src/main/java/model/JanggiGame.java +++ b/src/main/java/model/JanggiGame.java @@ -1,11 +1,14 @@ package model; +import java.util.List; import model.board.Board; import model.board.Position; import model.piece.Piece; public class JanggiGame { + private static final int CANNON_HURDLE_COUNT = 1; + private final Board board; private Team turn; @@ -15,23 +18,23 @@ public JanggiGame(Board board) { } public void movePiece(Position current, Position next) { - selectPiece(current); + Piece piece = selectPiece(current); + piece.validateMove(current, next); + + List path = piece.extractPath(current, next); + List pieces = board.extractPiecesByPath(path); + piece.validatePathCondition(pieces); + board.move(current, next); this.turn = turn.next(); } public Piece selectPiece(Position position) { Piece piece = board.pickPiece(position); - validateCurrentTurn(piece); + turn.validateAlly(piece); return piece; } - private void validateCurrentTurn(Piece piece) { - if (piece.isOtherTeam(turn)) { - throw new IllegalArgumentException(turn.getName() + "의 기물이 아닙니다."); - } - } - public Team getTurn() { return turn; } diff --git a/src/main/java/model/Team.java b/src/main/java/model/Team.java index edf67d92cf..68f566eade 100644 --- a/src/main/java/model/Team.java +++ b/src/main/java/model/Team.java @@ -1,5 +1,7 @@ package model; +import model.piece.Piece; + public enum Team { HAN("한나라"), CHO("초나라"); @@ -9,8 +11,10 @@ public enum Team { this.name = name; } - public String getName() { - return name; + public void validateAlly(Piece piece) { + if (piece.isOtherTeam(this)) { + throw new IllegalArgumentException(this.name + "의 기물이 아닙니다."); + } } public boolean isHan() { @@ -23,4 +27,8 @@ public Team next() { } return HAN; } + + public String getName() { + return name; + } } diff --git a/src/main/java/model/board/Board.java b/src/main/java/model/board/Board.java index ff4ad70ebd..8abf94644a 100644 --- a/src/main/java/model/board/Board.java +++ b/src/main/java/model/board/Board.java @@ -10,7 +10,6 @@ public class Board { public static final int BOARD_ROW = 10; public static final int BOARD_COL = 9; - private static final int CANNON_HURDLE_COUNT = 1; private final Map board; @@ -20,90 +19,37 @@ public Board(Map board) { public void move(Position current, Position next) { Piece piece = pickPiece(current); - validateMove(current, next, piece); + findByPosition(next).ifPresent(piece::validateTarget); board.remove(current); board.put(next, piece); } - private void validateMove(Position current, Position next, Piece piece) { - validateCanMoveByPiece(current, next, piece); - - List path = piece.extractPath(current, next); - if (piece.isCannon()) { - validateContainOnlyOnePiece(path); - validateNotContainCannon(path); - return; - } - - validateNotContainPiece(path); - } - - private void validateNotContainPiece(List path) { - if (hasPieceAt(path)) { - throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); - } - } - - private void validateNotContainCannon(List path) { - if (hasCannon(path)) { - throw new IllegalArgumentException("포는 포를 건너뛸 수 없습니다."); - } - } - - private void validateContainOnlyOnePiece(List path) { - if (countPiecesAt(path) != CANNON_HURDLE_COUNT) { - throw new IllegalArgumentException("포는" + CANNON_HURDLE_COUNT + "개의 기물만 건너 뛰어야 합니다."); - } - } - - private void validateCanMoveByPiece(Position current, Position next, Piece piece) { - if (!piece.canMove(current, next)) { - throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); - } - - findByPosition(next).ifPresent(otherPiece -> { - if (otherPiece.isSameTeam(piece)) { - throw new IllegalArgumentException("아군이 있는 위치로 이동할 수 없습니다."); - } - }); - } - public Piece pickPiece(Position position) { return findByPosition(position) .orElseThrow(() -> new IllegalArgumentException("해당 위치에 존재하는 장기말이 없습니다.")); } - public boolean hasPieceAt(Position position) { - return board.containsKey(position); - } - public void arrangePieces(Map pieces) { board.putAll(pieces); } - private Optional findByPosition(Position position) { - return Optional.ofNullable(board.get(position)); - } - - private int countPiecesAt(List path) { - return (int) path.stream() - .filter(this::hasPieceAt) - .count(); - } - - private boolean hasPieceAt(List path) { + public List extractPiecesByPath(List path) { return path.stream() - .anyMatch(this::hasPieceAt); + .filter(this::hasPieceAt) + .map(this::pickPiece) + .toList(); } - private boolean hasCannon(List path) { - return path.stream() - .filter(this::hasPieceAt) - .anyMatch(position -> pickPiece(position).isCannon()); + private Optional findByPosition(Position position) { + return Optional.ofNullable(board.get(position)); } public Map board() { return Map.copyOf(board); } + + private boolean hasPieceAt(Position position) { + return board.containsKey(position); + } } diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index 1a331493ea..bf0b4f4b3b 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -8,6 +8,8 @@ public class Cannon extends Piece { + private static final int CANNON_HURDLE_COUNT = 1; + public Cannon(Team team) { super(team, PieceType.CANNON); } @@ -24,6 +26,26 @@ public List extractPath(Position current, Position next) { return path; } + @Override + public void validatePathCondition(List pieces) { + if (pieces.size() != CANNON_HURDLE_COUNT) { + throw new IllegalArgumentException("포는 정확히 하나의 기물을 뛰어넘어야 합니다."); + } + + boolean hasCannonAsHurdle = pieces.stream().anyMatch(Piece::isCannon); + if (hasCannonAsHurdle) { + throw new IllegalArgumentException("포는 포를 다리로 쓸 수 없습니다."); + } + } + + @Override + public void validateTarget(Piece otherPiece) { + super.validateTarget(otherPiece); + if (otherPiece.isCannon()) { + throw new IllegalArgumentException("포는 포를 잡을 수 없습니다."); + } + } + @Override protected boolean comparePosition(int rowDiff, int colDiff) { return (colDiff >= 1 && rowDiff == 0) || (colDiff == 0 && rowDiff >= 1); diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 1c76954c27..24395a4483 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -16,10 +16,6 @@ protected Piece(Team team, PieceType type) { public abstract List extractPath(Position current, Position next); - public boolean isSameTeam(Piece other) { - return !isOtherTeam(other.team); - } - public boolean isOtherTeam(Team team) { return this.team != team; } @@ -30,13 +26,31 @@ public boolean canMove(Position current, Position next) { return comparePosition(Math.abs(rowDiff), Math.abs(colDiff)); } + public void validatePathCondition(List pieces) { + if (!pieces.isEmpty()) { + throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); + } + } + + public void validateTarget(Piece otherPiece) { + if (getTeam() == otherPiece.team) { + throw new IllegalArgumentException("아군이 있는 위치로 이동할 수 없습니다."); + } + } + + public void validateMove(Position current, Position next) { + if (!canMove(current, next)) { + throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); + } + } + protected abstract boolean comparePosition(int rowDiff, int colDiff); - public boolean isCho() { - return getTeam() == Team.CHO; + protected boolean isCho() { + return !team.isHan(); } - public boolean isCannon() { + protected boolean isCannon() { return getType() == PieceType.CANNON; } diff --git a/src/test/java/model/JanggiGameTest.java b/src/test/java/model/JanggiGameTest.java index 311f424156..3dab3216e1 100644 --- a/src/test/java/model/JanggiGameTest.java +++ b/src/test/java/model/JanggiGameTest.java @@ -6,39 +6,33 @@ import model.board.Position; import model.piece.Horse; import model.piece.Piece; +import model.testdouble.FakePiece; import model.testdouble.SpyBoard; import org.junit.jupiter.api.Test; class JanggiGameTest { @Test - void 장기에서_장기말을_옮기면_보드에서_장기말이_이동하고_다음_차례가_된다() { - // given - SpyBoard board = SpyBoard.cho(); + void 장기말을_정상적으로_옮기면_보드에서_이동하고_다음_차례가_된다() { + // given: (1,1)에 CHO의 이동 가능한 기물이 있고, 경로가 비어있는 상황 시뮬레이션 + Position source = new Position(1, 1); + Position destination = new Position(2, 2); + FakePiece piece = FakePiece.createFake(Team.CHO); + + SpyBoard board = new SpyBoard(piece); JanggiGame janggiGame = new JanggiGame(board); Team prevTurn = janggiGame.getTurn(); // when - janggiGame.movePiece(new Position(1, 1), new Position(2, 2)); + janggiGame.movePiece(source, destination); // then - assertThat(board.isMoved).isTrue(); + assertThat(board.pickPiece(destination)).isEqualTo(piece); assertThat(janggiGame.getTurn()).isEqualTo(prevTurn.next()); } @Test - void 장기에서_장기말을_옮길_때_다른_팀의_장기말을_옮기면_예외가_발생한다() { - // given - SpyBoard board = SpyBoard.han(); - JanggiGame janggiGame = new JanggiGame(board); - - // when & then - assertThatThrownBy(() -> janggiGame.movePiece(new Position(1, 1), new Position(2, 2))) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 장기에서_현재_턴에_대한_장기물을_선택할_수_있다() { + void 현재_턴의_팀에_맞는_기물을_선택할_수_있다() { // given Piece piece = new Horse(Team.CHO); SpyBoard board = new SpyBoard(piece); @@ -52,8 +46,8 @@ class JanggiGameTest { } @Test - void 장기에서_다른_턴에_대한_장기물을_선택하면_예외가_발생한다() { - // given + void 다른_팀의_기물을_선택하면_예외가_발생한다() { + // given: CHO 턴인데 HAN 기물 배치 SpyBoard board = new SpyBoard(new Horse(Team.HAN)); JanggiGame janggiGame = new JanggiGame(board); diff --git a/src/test/java/model/TeamTest.java b/src/test/java/model/TeamTest.java new file mode 100644 index 0000000000..5f925e4cc0 --- /dev/null +++ b/src/test/java/model/TeamTest.java @@ -0,0 +1,46 @@ +package model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import model.piece.Piece; +import model.testdouble.FakePiece; +import org.junit.jupiter.api.Test; + +class TeamTest { + + @Test + void 한나라_다음_차례는_초나라다() { + // given + Team han = Team.HAN; + + // when + Team next = han.next(); + + // then + assertThat(next).isEqualTo(Team.CHO); + } + + @Test + void 초나라_다음_차례는_한나라다() { + // given + Team cho = Team.CHO; + + // when + Team next = cho.next(); + + // then + assertThat(next).isEqualTo(Team.HAN); + } + + @Test + void 자신의_팀이_아닌_기물을_검증하면_예외가_발생한다() { + // given + Team cho = Team.CHO; + Piece hanPiece = FakePiece.createFake(Team.HAN); + + // when & then + assertThatThrownBy(() -> cho.validateAlly(hanPiece)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/model/board/BoardTest.java b/src/test/java/model/board/BoardTest.java index f7f550298f..f3dc059f55 100644 --- a/src/test/java/model/board/BoardTest.java +++ b/src/test/java/model/board/BoardTest.java @@ -9,8 +9,6 @@ import model.piece.Piece; import model.testdouble.FakePiece; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; class BoardTest { @@ -20,7 +18,7 @@ class BoardTest { @Test void 해당_위치에_기물이_있으면_반환한다() { // given - FakePiece piece = FakePiece.이동_가능(Team.CHO); + FakePiece piece = FakePiece.createFake(Team.CHO); Board board = new Board(Map.of(source, piece)); // when @@ -41,9 +39,10 @@ class BoardTest { } @Test - void 이동_가능한_기물은_정상적으로_이동한다() { + void 보드에서_도착_위치에_기물이_없으면_이동한다() { // given - FakePiece piece = FakePiece.이동_가능(Team.CHO); + FakePiece piece = FakePiece.createFake(Team.CHO); + Board board = new Board(Map.of(source, piece)); // when @@ -54,34 +53,21 @@ class BoardTest { } @Test - void 이동_불가능한_기물을_이동하면_예외가_발생한다() { - // given - FakePiece piece = FakePiece.이동_불가(Team.CHO); - Board board = new Board(Map.of(source, piece)); - - // when & then - assertThatThrownBy(() -> board.move(source, destination)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 도착_위치에_아군이_있으면_예외가_발생한다() { + void 보드에서_도착_위치에_아군이_있으면_이동할_수_없다() { // given - FakePiece piece = FakePiece.이동_가능(Team.CHO); - FakePiece ally = FakePiece.이동_가능(Team.CHO); - - Board board = new Board(Map.of(source, piece, destination, ally)); + FakePiece piece = FakePiece.createFake(Team.CHO); + FakePiece otherPiece = FakePiece.createFake(Team.CHO); + Board board = new Board(Map.of(source, piece, destination, otherPiece)); // when & then - assertThatThrownBy(() -> board.move(source, destination)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> board.move(source, destination)); } @Test - void 도착_위치에_적군이_있으면_이동한다() { + void 보드에서_도착_위치에_적군이_있으면_이동한다() { // given - FakePiece piece = FakePiece.이동_가능(Team.CHO); - FakePiece enemy = FakePiece.이동_가능(Team.HAN); + FakePiece piece = FakePiece.createFake(Team.CHO); + FakePiece enemy = FakePiece.createFake(Team.HAN); Board board = new Board(Map.of(source, piece, destination, enemy)); @@ -93,83 +79,45 @@ class BoardTest { } @Test - void 이동_경로에_기물이_있으면_예외가_발생한다() { - // given - Position vertex = new Position(5, 2); - FakePiece piece = FakePiece.이동_가능하지만_경로_있는_기물(Team.CHO, List.of(vertex)); - FakePiece barricade = FakePiece.이동_가능(Team.HAN); - - Board board = new Board(Map.of(source, piece, vertex, barricade)); - - // when & then - assertThatThrownBy(() -> board.move(source, destination)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 포는_정확히_1개의_기물을_넘어야_한다() { + void 여러_기물을_한번에_배치한다() { // given - Position vertex = new Position(5, 2); - FakePiece cannon = FakePiece.이동_가능한_포(Team.CHO, List.of(vertex)); - FakePiece hurdle = FakePiece.이동_가능(Team.HAN); - - Board board = new Board(Map.of(source, cannon, vertex, hurdle)); + Board board = new Board(Map.of()); + Position pos1 = new Position(0, 0); + Position pos2 = new Position(0, 1); + FakePiece piece1 = FakePiece.createFake(Team.CHO); + FakePiece piece2 = FakePiece.createFake(Team.HAN); // when - board.move(source, destination); + board.arrangePieces(Map.of(pos1, piece1, pos2, piece2)); // then - assertSuccessMoved(board, destination, cannon, source); + assertThat(board.pickPiece(pos1)).isEqualTo(piece1); + assertThat(board.pickPiece(pos2)).isEqualTo(piece2); } @Test - void 포는_넘을_기물이_없으면_예외가_발생한다() { + void 경로상에_존재하는_기물들을_추출한다() { // given - Position vertex = new Position(5, 2); - FakePiece cannon = FakePiece.이동_가능한_포(Team.CHO, List.of(vertex)); - - Board board = new Board(Map.of(source, cannon)); - - // when & then - assertThatThrownBy(() -> board.move(source, destination)) - .isInstanceOf(IllegalArgumentException.class); - } + Position path1 = new Position(5, 1); + Position path2 = new Position(5, 2); // 경로에 기물이 없는 경우 + Position path3 = new Position(5, 3); - @ParameterizedTest - @EnumSource(Team.class) - void 포는_포를_넘을_수_없다(Team team) { - // given - Position vertex = new Position(5, 2); - FakePiece cannon = FakePiece.이동_가능한_포(Team.CHO, List.of(vertex)); - FakePiece otherCannon = FakePiece.이동_가능한_포(team, List.of()); + FakePiece hurdle1 = FakePiece.createFake(Team.HAN); + FakePiece hurdle2 = FakePiece.createFake(Team.CHO); - Board board = new Board(Map.of(source, cannon, vertex, otherCannon)); + Board board = new Board(Map.of(path1, hurdle1, path3, hurdle2)); + List path = List.of(path1, path2, path3); - // when & then - assertThatThrownBy(() -> board.move(source, destination)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void 포는_2개_이상의_기물을_넘으면_예외가_발생한다() { - // given - Position firstVertex = new Position(5, 1); - Position secondVertex = new Position(5, 2); - FakePiece cannon = FakePiece.이동_가능한_포(Team.CHO, List.of(firstVertex, secondVertex)); - - Board board = new Board(Map.of( - source, cannon, - firstVertex, FakePiece.이동_가능(Team.HAN), - secondVertex, FakePiece.이동_가능(Team.HAN) - )); + // when + List extracted = board.extractPiecesByPath(path); - // when & then - assertThatThrownBy(() -> board.move(source, destination)) - .isInstanceOf(IllegalArgumentException.class); + // then + assertThat(extracted).hasSize(2) + .containsExactly(hurdle1, hurdle2); } private void assertSuccessMoved(Board board, Position source, FakePiece piece, Position destination) { assertThat(board.pickPiece(source)).isEqualTo(piece); - assertThat(board.hasPieceAt(destination)).isFalse(); + assertThat(board.board()).doesNotContainKey(destination); } } \ No newline at end of file diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index bb472955ba..2fcc9362fc 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -1,10 +1,12 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; import model.Team; import model.board.Position; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -49,4 +51,52 @@ public class CannonTest { // then assertThat(path).isEqualTo(expectedPath); } + + @Test + void 포가_넘어가는_다리에_포가_있으면_예외가_발생한다() { + // given + Piece cannon = new Cannon(Team.HAN); + Piece hurdleCannon = new Cannon(Team.CHO); // 다리가 포인 경우 + List piecesOnPath = List.of(hurdleCannon); + + // when & then + assertThatThrownBy(() -> cannon.validatePathCondition(piecesOnPath)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_넘어가는_다리가_없으면_예외가_발생한다() { + // given + Piece cannon = new Cannon(Team.HAN); + List emptyPath = List.of(); // 다리가 없는 경우 + + // when & then + assertThatThrownBy(() -> cannon.validatePathCondition(emptyPath)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_넘어가는_다리가_두_개_이상이면_예외가_발생한다() { + // given + Piece cannon = new Cannon(Team.HAN); + List manyHurdles = List.of( + new Cannon(Team.CHO), // 사실 포가 아니어도 상관없지만 규칙상 테스트 + new Cannon(Team.CHO) + ); + + // when & then + assertThatThrownBy(() -> cannon.validatePathCondition(manyHurdles)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 포가_상대_포를_잡으려_하면_예외가_발생한다() { + // given + Piece cannon = new Cannon(Team.HAN); + Piece targetCannon = new Cannon(Team.CHO); + + // when & then + assertThatThrownBy(() -> cannon.validateTarget(targetCannon)) + .isInstanceOf(IllegalArgumentException.class); + } } \ No newline at end of file diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index b9d0540b2c..20f22339d4 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -1,10 +1,13 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; import model.Team; import model.board.Position; +import model.testdouble.FakePiece; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -45,4 +48,15 @@ public class ChariotTest { // then assertThat(path).isEqualTo(expectedPath); } + + @Test + void 차는_이동_경로에_기물이_있으면_예외가_발생한다() { + // given + Piece chariot = new Chariot(Team.HAN); + List obstacles = List.of(FakePiece.createFake(Team.CHO)); + + // when & then + assertThatThrownBy(() -> chariot.validatePathCondition(obstacles)) + .isInstanceOf(IllegalArgumentException.class); + } } \ No newline at end of file diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index 00b114aeb4..b959aecd39 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -1,10 +1,13 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; import model.Team; import model.board.Position; +import model.testdouble.FakePiece; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -48,4 +51,15 @@ public class ElephantTest { // then assertThat(path).isEqualTo(expectedPath); } + + @Test + void 상은_이동_경로에_기물이_있으면_예외가_발생한다() { + // given + Piece elephant = new Elephant(Team.HAN); + List obstacles = List.of(FakePiece.createFake(Team.CHO)); + + // when & then + assertThatThrownBy(() -> elephant.validatePathCondition(obstacles)) + .isInstanceOf(IllegalArgumentException.class); + } } \ No newline at end of file diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index affe087e2b..e39a527b5d 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -1,10 +1,13 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; import model.Team; import model.board.Position; +import model.testdouble.FakePiece; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -48,4 +51,15 @@ public class HorseTest { // then assertThat(path).isEqualTo(expectedPath); } + + @Test + void 마는_이동_경로에_기물이_있으면_예외가_발생한다() { + // given + Piece horse = new Horse(Team.HAN); + List obstacles = List.of(FakePiece.createFake(Team.CHO)); + + // when & then + assertThatThrownBy(() -> horse.validatePathCondition(obstacles)) + .isInstanceOf(IllegalArgumentException.class); + } } \ No newline at end of file diff --git a/src/test/java/model/piece/SoldierTest.java b/src/test/java/model/piece/SoldierTest.java index 24f9ba78a0..889441ec5e 100644 --- a/src/test/java/model/piece/SoldierTest.java +++ b/src/test/java/model/piece/SoldierTest.java @@ -1,10 +1,13 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; import model.Team; import model.board.Position; +import model.testdouble.FakePiece; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -42,4 +45,15 @@ public class SoldierTest { // then assertThat(path).isEmpty(); } + + @Test + void 졸은_이동_경로에_기물이_있으면_예외가_발생한다() { + // given + Piece soldier = new Soldier(Team.HAN); + List obstacles = List.of(FakePiece.createFake(Team.CHO)); + + // when & then + assertThatThrownBy(() -> soldier.validatePathCondition(obstacles)) + .isInstanceOf(IllegalArgumentException.class); + } } \ No newline at end of file diff --git a/src/test/java/model/testdouble/FakePiece.java b/src/test/java/model/testdouble/FakePiece.java index 445f8f99a7..72cd028211 100644 --- a/src/test/java/model/testdouble/FakePiece.java +++ b/src/test/java/model/testdouble/FakePiece.java @@ -17,22 +17,10 @@ public class FakePiece extends Piece { this.path = path; } - public static FakePiece 이동_가능(Team team) { + public static FakePiece createFake(Team team) { return new FakePiece(team, PieceType.SOLDIER, true, List.of()); } - public static FakePiece 이동_불가(Team team) { - return new FakePiece(team, PieceType.SOLDIER, false, List.of()); - } - - public static FakePiece 이동_가능한_포(Team team, List path) { - return new FakePiece(team, PieceType.CANNON, true, path); - } - - public static FakePiece 이동_가능하지만_경로_있는_기물(Team team, List path) { - return new FakePiece(team, PieceType.SOLDIER, true, path); - } - @Override protected boolean comparePosition(int rowDiff, int colDiff) { return movable; From a53122411fa21809a82a085231ff54fec561c599 Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Fri, 3 Apr 2026 17:27:46 +0900 Subject: [PATCH 25/26] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EB=B3=84=20=EC=9D=B4=EB=8F=99=20=EC=A4=91=EB=B3=B5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/JanggiGame.java | 6 +- src/main/java/model/board/Direction.java | 78 ---------------- src/main/java/model/board/Displacement.java | 4 - src/main/java/model/board/Position.java | 16 +--- src/main/java/model/movement/Direction.java | 46 ++++++++++ .../java/model/movement/Displacement.java | 15 ++++ .../java/model/movement/LinearStrategy.java | 26 ++++++ .../java/model/movement/MoveStrategy.java | 9 ++ .../java/model/movement/OneStepStrategy.java | 16 ++++ .../java/model/movement/SteppingStrategy.java | 18 ++++ src/main/java/model/piece/Cannon.java | 15 ---- src/main/java/model/piece/Chariot.java | 16 ---- src/main/java/model/piece/Elephant.java | 16 ---- src/main/java/model/piece/General.java | 7 -- src/main/java/model/piece/Guard.java | 7 -- src/main/java/model/piece/Horse.java | 16 ---- src/main/java/model/piece/Piece.java | 5 +- src/main/java/model/piece/PieceType.java | 30 ++++++- src/main/java/model/piece/Soldier.java | 6 -- src/test/java/model/board/DirectionTest.java | 89 +++++++++++++------ src/test/java/model/board/PositionTest.java | 28 ++++++ .../model/fixture/PieceMovePathFixture.java | 8 +- .../fixture/PieceMovePositionFixture.java | 48 +++++----- src/test/java/model/piece/CannonTest.java | 2 +- 24 files changed, 287 insertions(+), 240 deletions(-) delete mode 100644 src/main/java/model/board/Direction.java delete mode 100644 src/main/java/model/board/Displacement.java create mode 100644 src/main/java/model/movement/Direction.java create mode 100644 src/main/java/model/movement/Displacement.java create mode 100644 src/main/java/model/movement/LinearStrategy.java create mode 100644 src/main/java/model/movement/MoveStrategy.java create mode 100644 src/main/java/model/movement/OneStepStrategy.java create mode 100644 src/main/java/model/movement/SteppingStrategy.java diff --git a/src/main/java/model/JanggiGame.java b/src/main/java/model/JanggiGame.java index 39b1468759..f68211fdb6 100644 --- a/src/main/java/model/JanggiGame.java +++ b/src/main/java/model/JanggiGame.java @@ -7,8 +7,6 @@ public class JanggiGame { - private static final int CANNON_HURDLE_COUNT = 1; - private final Board board; private Team turn; @@ -19,13 +17,13 @@ public JanggiGame(Board board) { public void movePiece(Position current, Position next) { Piece piece = selectPiece(current); - piece.validateMove(current, next); List path = piece.extractPath(current, next); List pieces = board.extractPiecesByPath(path); - piece.validatePathCondition(pieces); + piece.validatePathCondition(pieces); board.move(current, next); + this.turn = turn.next(); } diff --git a/src/main/java/model/board/Direction.java b/src/main/java/model/board/Direction.java deleted file mode 100644 index 37ac02d91b..0000000000 --- a/src/main/java/model/board/Direction.java +++ /dev/null @@ -1,78 +0,0 @@ -package model.board; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -public enum Direction { - EAST(0, 1), - WEST(0, -1), - NORTH(-1, 0), - SOUTH(1, 0), - NORTH_EAST(-1, 1), - SOUTH_EAST(1, 1), - NORTH_WEST(-1, -1), - SOUTH_WEST(1, -1); - - private final int row; - private final int col; - - Direction(int row, int col) { - this.row = row; - this.col = col; - } - - public static Direction from(Position start, Position end) { - Displacement displacement = end.minus(start); - return Stream.of(values()) - .filter(direction -> hasSameDirection(direction, displacement)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("직선 방향이 아닙니다.")); - } - - private static boolean hasSameDirection(Direction direction, Displacement displacement) { - return direction.row * displacement.col() == direction.col * displacement.row() - && direction.row * displacement.row() >= 0 - && direction.col * displacement.col() >= 0; - } - - public static List decomposeToCardinalAndDiagonal(Position start, Position end) { - Displacement displacement = end.minus(start); - Direction cardinal = resolveCardinal(displacement); - Direction diagonal = resolveDiagonal(displacement); - int diagonalCount = Math.min(Math.abs(displacement.row()), Math.abs(displacement.col())); - - List directions = new ArrayList<>(); - directions.add(cardinal); - for (int i = 0; i < diagonalCount; i++) { - directions.add(diagonal); - } - return directions; - } - - private static Direction resolveCardinal(Displacement displacement) { - if (Math.abs(displacement.row()) > Math.abs(displacement.col())) { - return findByRowCol(Integer.signum(displacement.row()), 0); - } - return findByRowCol(0, Integer.signum(displacement.col())); - } - - private static Direction resolveDiagonal(Displacement displacement) { - return findByRowCol(Integer.signum(displacement.row()), Integer.signum(displacement.col())); - } - - private static Direction findByRowCol(int row, int col) { - return Stream.of(values()) - .filter(d -> d.row == row && d.col == col) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("이동할 수 없는 방향입니다.")); - } - - public int row() { - return row; - } - - public int col() { - return col; - } -} diff --git a/src/main/java/model/board/Displacement.java b/src/main/java/model/board/Displacement.java deleted file mode 100644 index c40e7417dc..0000000000 --- a/src/main/java/model/board/Displacement.java +++ /dev/null @@ -1,4 +0,0 @@ -package model.board; - -public record Displacement(int row, int col) { -} diff --git a/src/main/java/model/board/Position.java b/src/main/java/model/board/Position.java index 9db8971776..b2f1da040f 100644 --- a/src/main/java/model/board/Position.java +++ b/src/main/java/model/board/Position.java @@ -3,17 +3,9 @@ import static model.board.Board.BOARD_COL; import static model.board.Board.BOARD_ROW; -public record Position(int row, int col) { - - public static final Position HAN_LEFT_OUTER = new Position(0, 1); - public static final Position HAN_LEFT_INNER = new Position(0, 2); - public static final Position HAN_RIGHT_INNER = new Position(0, 6); - public static final Position HAN_RIGHT_OUTER = new Position(0, 7); +import model.movement.Displacement; - public static final Position CHO_LEFT_OUTER = new Position(9, 1); - public static final Position CHO_LEFT_INNER = new Position(9, 2); - public static final Position CHO_RIGHT_INNER = new Position(9, 6); - public static final Position CHO_RIGHT_OUTER = new Position(9, 7); +public record Position(int row, int col) { public Position { if (row < 0 || row >= BOARD_ROW) { @@ -37,7 +29,7 @@ public int calculateColDiff(Position other) { return this.col - other.col(); } - public Position move(Direction direction) { - return new Position(row + direction.row(), col + direction.col()); + public Position resolveNext(int rowDistance, int colDistance) { + return new Position(this.row + rowDistance, this.col + colDistance); } } diff --git a/src/main/java/model/movement/Direction.java b/src/main/java/model/movement/Direction.java new file mode 100644 index 0000000000..524c07c3f6 --- /dev/null +++ b/src/main/java/model/movement/Direction.java @@ -0,0 +1,46 @@ +package model.movement; + +import java.util.stream.Stream; +import model.board.Position; + +public enum Direction { + EAST(0, 1), + WEST(0, -1), + NORTH(-1, 0), + SOUTH(1, 0), + NORTH_EAST(-1, 1), + SOUTH_EAST(1, 1), + NORTH_WEST(-1, -1), + SOUTH_WEST(1, -1); + + private final int row; + private final int col; + + Direction(int row, int col) { + this.row = row; + this.col = col; + } + + public static Direction of(int rowDiff, int colDiff) { + return Stream.of(values()) + .filter(direction -> direction.isSameDirection(rowDiff, colDiff)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("제자리는 이동할 수 없는 방향입니다.")); + } + + private boolean isSameDirection(int rowDiff, int colDiff) { + return row == Integer.signum(rowDiff) && col == Integer.signum(colDiff); + } + + public Position move(Position target) { + return target.resolveNext(row, col); + } + + public int row() { + return row; + } + + public int col() { + return col; + } +} diff --git a/src/main/java/model/movement/Displacement.java b/src/main/java/model/movement/Displacement.java new file mode 100644 index 0000000000..ca75c3e591 --- /dev/null +++ b/src/main/java/model/movement/Displacement.java @@ -0,0 +1,15 @@ +package model.movement; + +public record Displacement(int rowDiff, int colDiff) { + + public Direction extractCardinal() { + if (Math.abs(rowDiff) > Math.abs(colDiff)) { + return Direction.of(rowDiff, 0); + } + return Direction.of(0, colDiff); + } + + public Direction extractDiagonal() { + return Direction.of(rowDiff, colDiff); + } +} diff --git a/src/main/java/model/movement/LinearStrategy.java b/src/main/java/model/movement/LinearStrategy.java new file mode 100644 index 0000000000..26668460e3 --- /dev/null +++ b/src/main/java/model/movement/LinearStrategy.java @@ -0,0 +1,26 @@ +package model.movement; + +import java.util.ArrayList; +import java.util.List; +import model.board.Position; + +public class LinearStrategy implements MoveStrategy { + + private static Direction resolveCardinal(Position start, Position end) { + Displacement displacement = end.minus(start); + return displacement.extractCardinal(); + } + + @Override + public List extractPath(Position start, Position end) { + Direction direction = resolveCardinal(start, end); + + List path = new ArrayList<>(); + Position step = direction.move(start); + while (!step.equals(end)) { + path.add(step); + step = direction.move(step); + } + return path; + } +} \ No newline at end of file diff --git a/src/main/java/model/movement/MoveStrategy.java b/src/main/java/model/movement/MoveStrategy.java new file mode 100644 index 0000000000..ea10f61c19 --- /dev/null +++ b/src/main/java/model/movement/MoveStrategy.java @@ -0,0 +1,9 @@ +package model.movement; + +import java.util.List; +import model.board.Position; + +public interface MoveStrategy { + + List extractPath(Position start, Position end); +} diff --git a/src/main/java/model/movement/OneStepStrategy.java b/src/main/java/model/movement/OneStepStrategy.java new file mode 100644 index 0000000000..5e2648cac3 --- /dev/null +++ b/src/main/java/model/movement/OneStepStrategy.java @@ -0,0 +1,16 @@ +package model.movement; + +import java.util.List; +import model.board.Position; + +public class OneStepStrategy implements MoveStrategy { + + @Override + public List extractPath(Position start, Position end) { + Displacement displacement = end.minus(start); + Direction cardinal = displacement.extractCardinal(); + + Position step = cardinal.move(start); + return List.of(step); + } +} \ No newline at end of file diff --git a/src/main/java/model/movement/SteppingStrategy.java b/src/main/java/model/movement/SteppingStrategy.java new file mode 100644 index 0000000000..6448585dff --- /dev/null +++ b/src/main/java/model/movement/SteppingStrategy.java @@ -0,0 +1,18 @@ +package model.movement; + +import java.util.List; +import model.board.Position; + +public class SteppingStrategy implements MoveStrategy { + + @Override + public List extractPath(Position start, Position end) { + Displacement displacement = end.minus(start); + Direction cardinal = displacement.extractCardinal(); + Direction diagonal = displacement.extractDiagonal(); + + Position oneStep = cardinal.move(start); + Position stepping = diagonal.move(oneStep); + return List.of(oneStep, stepping); + } +} \ No newline at end of file diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index bf0b4f4b3b..f14fd87acf 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -1,10 +1,7 @@ package model.piece; -import java.util.ArrayList; import java.util.List; import model.Team; -import model.board.Direction; -import model.board.Position; public class Cannon extends Piece { @@ -14,18 +11,6 @@ public Cannon(Team team) { super(team, PieceType.CANNON); } - @Override - public List extractPath(Position current, Position next) { - Direction direction = Direction.from(current, next); - List path = new ArrayList<>(); - Position step = current.move(direction); - while (!step.equals(next)) { - path.add(step); - step = step.move(direction); - } - return path; - } - @Override public void validatePathCondition(List pieces) { if (pieces.size() != CANNON_HURDLE_COUNT) { diff --git a/src/main/java/model/piece/Chariot.java b/src/main/java/model/piece/Chariot.java index 65415b97e0..2c4d131365 100644 --- a/src/main/java/model/piece/Chariot.java +++ b/src/main/java/model/piece/Chariot.java @@ -1,10 +1,6 @@ package model.piece; -import java.util.ArrayList; -import java.util.List; import model.Team; -import model.board.Direction; -import model.board.Position; public class Chariot extends Piece { @@ -12,18 +8,6 @@ public Chariot(Team team) { super(team, PieceType.CHARIOT); } - @Override - public List extractPath(Position current, Position next) { - Direction direction = Direction.from(current, next); - List path = new ArrayList<>(); - Position step = current.move(direction); - while (!step.equals(next)) { - path.add(step); - step = step.move(direction); - } - return path; - } - @Override protected boolean comparePosition(int rowDiff, int colDiff) { return (colDiff >= 1 && rowDiff == 0) || (colDiff == 0 && rowDiff >= 1); diff --git a/src/main/java/model/piece/Elephant.java b/src/main/java/model/piece/Elephant.java index 61e7a5ca20..6dfb1fafe8 100644 --- a/src/main/java/model/piece/Elephant.java +++ b/src/main/java/model/piece/Elephant.java @@ -1,10 +1,6 @@ package model.piece; -import java.util.ArrayList; -import java.util.List; import model.Team; -import model.board.Direction; -import model.board.Position; public class Elephant extends Piece { @@ -12,18 +8,6 @@ public Elephant(Team team) { super(team, PieceType.ELEPHANT); } - @Override - public List extractPath(Position current, Position next) { - List directions = Direction.decomposeToCardinalAndDiagonal(current, next); - List path = new ArrayList<>(); - Position step = current; - for (int i = 0; i < directions.size() - 1; i++) { - step = step.move(directions.get(i)); - path.add(step); - } - return path; - } - @Override protected boolean comparePosition(int rowDiff, int colDiff) { return (colDiff == 3 && rowDiff == 2) || (colDiff == 2 && rowDiff == 3); diff --git a/src/main/java/model/piece/General.java b/src/main/java/model/piece/General.java index 85c25688db..e012368d42 100644 --- a/src/main/java/model/piece/General.java +++ b/src/main/java/model/piece/General.java @@ -1,8 +1,6 @@ package model.piece; -import java.util.List; import model.Team; -import model.board.Position; public class General extends Piece { @@ -10,11 +8,6 @@ public General(Team team) { super(team, PieceType.GENERAL); } - @Override - public List extractPath(Position current, Position next) { - throw new IllegalArgumentException("1단계 궁성 영역 미구현"); - } - @Override protected boolean comparePosition(int rowDiff, int colDiff) { throw new IllegalArgumentException("1단계 궁성 영역 미구현"); diff --git a/src/main/java/model/piece/Guard.java b/src/main/java/model/piece/Guard.java index 4e88347cc8..059d9d0036 100644 --- a/src/main/java/model/piece/Guard.java +++ b/src/main/java/model/piece/Guard.java @@ -1,8 +1,6 @@ package model.piece; -import java.util.List; import model.Team; -import model.board.Position; public class Guard extends Piece { @@ -10,11 +8,6 @@ public Guard(Team team) { super(team, PieceType.GUARD); } - @Override - public List extractPath(Position current, Position next) { - throw new IllegalArgumentException("1단계 궁성 영역 미구현"); - } - @Override protected boolean comparePosition(int rowDiff, int colDiff) { throw new IllegalArgumentException("1단계 궁성 영역 미구현"); diff --git a/src/main/java/model/piece/Horse.java b/src/main/java/model/piece/Horse.java index 90d5bd5d70..a00c0ac82e 100644 --- a/src/main/java/model/piece/Horse.java +++ b/src/main/java/model/piece/Horse.java @@ -1,10 +1,6 @@ package model.piece; -import java.util.ArrayList; -import java.util.List; import model.Team; -import model.board.Direction; -import model.board.Position; public class Horse extends Piece { @@ -12,18 +8,6 @@ public Horse(Team team) { super(team, PieceType.HORSE); } - @Override - public List extractPath(Position current, Position next) { - List directions = Direction.decomposeToCardinalAndDiagonal(current, next); - List path = new ArrayList<>(); - Position step = current; - for (int i = 0; i < directions.size() - 1; i++) { - step = step.move(directions.get(i)); - path.add(step); - } - return path; - } - @Override protected boolean comparePosition(int rowDiff, int colDiff) { return (colDiff == 1 && rowDiff == 2) || (colDiff == 2 && rowDiff == 1); diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index 24395a4483..fb81837530 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -14,7 +14,10 @@ protected Piece(Team team, PieceType type) { this.type = type; } - public abstract List extractPath(Position current, Position next); + public List extractPath(Position current, Position next) { + validateMove(current, next); + return type.extractPath(current, next); + } public boolean isOtherTeam(Team team) { return this.team != team; diff --git a/src/main/java/model/piece/PieceType.java b/src/main/java/model/piece/PieceType.java index 8c38624bf1..c2afe70b14 100644 --- a/src/main/java/model/piece/PieceType.java +++ b/src/main/java/model/piece/PieceType.java @@ -1,6 +1,34 @@ package model.piece; +import java.util.List; +import model.board.Position; +import model.movement.LinearStrategy; +import model.movement.MoveStrategy; +import model.movement.OneStepStrategy; +import model.movement.SteppingStrategy; + public enum PieceType { - CANNON, CHARIOT, ELEPHANT, GENERAL, GUARD, HORSE, SOLDIER + CANNON(new LinearStrategy()), + CHARIOT(new LinearStrategy()), + ELEPHANT(new SteppingStrategy()), + GENERAL((start, end) -> { + throw new IllegalArgumentException("1단계 궁성 영역 미구현"); + }), + GUARD((start, end) -> { + throw new IllegalArgumentException("1단계 궁성 영역 미구현"); + }), + HORSE(new OneStepStrategy()), + SOLDIER((start, end) -> List.of()), + ; + + private final MoveStrategy moveStrategy; + + PieceType(MoveStrategy moveStrategy) { + this.moveStrategy = moveStrategy; + } + + public List extractPath(Position current, Position next) { + return moveStrategy.extractPath(current, next); + } } diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java index 5905f4cdf4..e90ae8337b 100644 --- a/src/main/java/model/piece/Soldier.java +++ b/src/main/java/model/piece/Soldier.java @@ -1,6 +1,5 @@ package model.piece; -import java.util.List; import model.Team; import model.board.Position; @@ -10,11 +9,6 @@ public Soldier(Team team) { super(team, PieceType.SOLDIER); } - @Override - public List extractPath(Position current, Position next) { - return List.of(); - } - @Override public boolean canMove(Position current, Position next) { int rowDiff = next.calculateRowDiff(current); diff --git a/src/test/java/model/board/DirectionTest.java b/src/test/java/model/board/DirectionTest.java index 129357526b..2d229e5bee 100644 --- a/src/test/java/model/board/DirectionTest.java +++ b/src/test/java/model/board/DirectionTest.java @@ -3,40 +3,73 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.stream.Stream; +import model.movement.Direction; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class DirectionTest { - @ParameterizedTest - @CsvSource({ - "3, 4, 5, 4, SOUTH", // 아래로 직선 - "5, 4, 3, 4, NORTH", // 위로 직선 - "3, 4, 3, 6, EAST", // 오른쪽 직선 - "3, 6, 3, 4, WEST", // 왼쪽 직선 - "3, 4, 5, 6, SOUTH_EAST", // 오른쪽 아래 대각선 - "5, 6, 3, 4, NORTH_WEST", // 왼쪽 위 대각선 - "3, 6, 5, 4, SOUTH_WEST", // 왼쪽 아래 대각선 - "5, 4, 3, 6, NORTH_EAST", // 오른쪽 위 대각선 - }) - void 두_위치로부터_방향을_구할_수_있다(int startRow, int startCol, int endRow, int endCol, Direction expected) { - Position start = new Position(startRow, startCol); - Position end = new Position(endRow, endCol); - - Direction result = Direction.from(start, end); - - assertThat(result).isEqualTo(expected); + private static Stream 좌표_차이에_대한_방향_도출_정보() { + return Stream.of( + // 사방위 (Cardinal) - 거리가 1보다 커도 부호만 맞으면 성공해야 함 + Arguments.of(-3, 0, Direction.NORTH), + Arguments.of(5, 0, Direction.SOUTH), + Arguments.of(0, -1, Direction.WEST), + Arguments.of(0, 2, Direction.EAST), + + // 사간방 (Diagonal) - 마/상/궁성 이동 관련 + Arguments.of(-1, 1, Direction.NORTH_EAST), + Arguments.of(2, 2, Direction.SOUTH_EAST), + Arguments.of(-10, -10, Direction.NORTH_WEST), + Arguments.of(3, -2, Direction.SOUTH_WEST) + ); } - @ParameterizedTest - @CsvSource({ - "0, 0, 1, 2", // 비례하지 않는 벡터 - }) - void 유효하지_않은_방향이면_예외가_발생한다(int startRow, int startCol, int endRow, int endCol) { - Position start = new Position(startRow, startCol); - Position end = new Position(endRow, endCol); + private static Stream 방향에_따라_5comma5에서_다음_위치로_이동하는_정보() { + return Stream.of( + // 사방위 (Cardinal) + Arguments.of(Direction.NORTH, 4, 5), + Arguments.of(Direction.SOUTH, 6, 5), + Arguments.of(Direction.WEST, 5, 4), + Arguments.of(Direction.EAST, 5, 6), + + // 사간방 (Diagonal) + Arguments.of(Direction.NORTH_WEST, 4, 4), + Arguments.of(Direction.NORTH_EAST, 4, 6), + Arguments.of(Direction.SOUTH_WEST, 6, 4), + Arguments.of(Direction.SOUTH_EAST, 6, 6) + ); + } + + @ParameterizedTest(name = "rowDiff: {0}, colDiff: {1} => {2}") + @MethodSource("좌표_차이에_대한_방향_도출_정보") + void 좌표_차이를_기반으로_올바른_방향을_구할_수_있다(int rowDiff, int colDiff, Direction expected) { + // when + Direction actual = Direction.of(rowDiff, colDiff); + + // then + assertThat(actual).isEqualTo(expected); + } - assertThatThrownBy(() -> Direction.from(start, end)) + @Test + void 제자리_이동의_경우_방향이_아니다() { + assertThatThrownBy(() -> Direction.of(0, 0)) .isInstanceOf(IllegalArgumentException.class); } -} + + @ParameterizedTest(name = "{0} 방향으로 이동: (5,5) -> ({1}, {2})") + @MethodSource("방향에_따라_5comma5에서_다음_위치로_이동하는_정보") + void 위치_정보에서_방향을_통해_이동한_위치를_구할_수_있다(Direction direction, int expectedRow, int expectedCol) { + // given + Position start = new Position(5, 5); + + // when + Position next = direction.move(start); + + // then + assertThat(next).isEqualTo(new Position(expectedRow, expectedCol)); + } +} \ No newline at end of file diff --git a/src/test/java/model/board/PositionTest.java b/src/test/java/model/board/PositionTest.java index 639fbf63b2..9148ae7a52 100644 --- a/src/test/java/model/board/PositionTest.java +++ b/src/test/java/model/board/PositionTest.java @@ -3,6 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import model.movement.Displacement; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -33,4 +35,30 @@ class PositionTest { assertThatThrownBy(() -> new Position(row, col)) .isInstanceOf(IllegalArgumentException.class); } + + @Test + void 두_위치가_주어진다면_두_위치에_대한_변위를_구할_수_있다() { + // given + Position start = new Position(5, 5); + Position end = new Position(3, 7); + + // when + Displacement result = end.minus(start); + + // then + assertThat(result.rowDiff()).isEqualTo(-2); + assertThat(result.colDiff()).isEqualTo(2); + } + + @Test + void 기존_위치에서_추가적인_거리를_통해_다음_위치를_구할_수_있다() { + // given + Position current = new Position(5, 5); + + // when + Position next = current.resolveNext(-1, 2); + + // then + assertThat(next).isEqualTo(new Position(4, 7)); + } } \ No newline at end of file diff --git a/src/test/java/model/fixture/PieceMovePathFixture.java b/src/test/java/model/fixture/PieceMovePathFixture.java index a1aea7fc6f..6548acb4cb 100644 --- a/src/test/java/model/fixture/PieceMovePathFixture.java +++ b/src/test/java/model/fixture/PieceMovePathFixture.java @@ -34,13 +34,13 @@ public class PieceMovePathFixture { // === 마 이동 데이터 === static Stream 마_이동_경로_테스트_데이터() { return Stream.of( - // 1. 세로(row)로 먼저 움직이는 경우 (rowDiff=2, colDiff=1) + // 1. 세로(rowDiff)로 먼저 움직이는 경우 (rowDiff=2, colDiff=1) Arguments.of(new Position(5, 4), new Position(7, 3), List.of(new Position(6, 4))), // 북서 Arguments.of(new Position(5, 4), new Position(7, 5), List.of(new Position(6, 4))), // 북동 Arguments.of(new Position(5, 4), new Position(3, 3), List.of(new Position(4, 4))), // 남서 Arguments.of(new Position(5, 4), new Position(3, 5), List.of(new Position(4, 4))), // 남동 - // 2. 가로(col)로 먼저 움직이는 경우 (rowDiff=1, colDiff=2) + // 2. 가로(colDiff)로 먼저 움직이는 경우 (rowDiff=1, colDiff=2) Arguments.of(new Position(5, 4), new Position(6, 6), List.of(new Position(5, 5))), // 동북 Arguments.of(new Position(5, 4), new Position(4, 6), List.of(new Position(5, 5))), // 동남 Arguments.of(new Position(5, 4), new Position(6, 2), List.of(new Position(5, 3))), // 서북 @@ -51,14 +51,14 @@ public class PieceMovePathFixture { // === 상 이동 데이터 === static Stream 상_이동_경로_테스트_데이터() { return Stream.of( - // 1. 세로(row)로 먼저 움직이는 경우 (rowDiff=3, colDiff=2) + // 1. 세로(rowDiff)로 먼저 움직이는 경우 (rowDiff=3, colDiff=2) // 북서, 북동, 남서, 남동 순서 Arguments.of(new Position(5, 4), new Position(8, 2), List.of(new Position(6, 4), new Position(7, 3))), Arguments.of(new Position(5, 4), new Position(8, 6), List.of(new Position(6, 4), new Position(7, 5))), Arguments.of(new Position(5, 4), new Position(2, 2), List.of(new Position(4, 4), new Position(3, 3))), Arguments.of(new Position(5, 4), new Position(2, 6), List.of(new Position(4, 4), new Position(3, 5))), - // 2. 가로(col)로 먼저 움직이는 경우 (rowDiff=2, colDiff=3) + // 2. 가로(colDiff)로 먼저 움직이는 경우 (rowDiff=2, colDiff=3) // 동북, 동남, 서북, 서남 순서 Arguments.of(new Position(5, 4), new Position(7, 7), List.of(new Position(5, 5), new Position(6, 6))), Arguments.of(new Position(5, 4), new Position(3, 7), List.of(new Position(5, 5), new Position(4, 6))), diff --git a/src/test/java/model/fixture/PieceMovePositionFixture.java b/src/test/java/model/fixture/PieceMovePositionFixture.java index acbd358e41..75c0c05cca 100644 --- a/src/test/java/model/fixture/PieceMovePositionFixture.java +++ b/src/test/java/model/fixture/PieceMovePositionFixture.java @@ -27,10 +27,10 @@ public class PieceMovePositionFixture { public static Stream 사간방_대각선_이동_방향_케이스() { return Stream.of( - Arguments.of(new Position(2, 2), new Position(4, 4)), // 1. 북동 (NE): row 증가, col 증가 - Arguments.of(new Position(5, 5), new Position(3, 7)), // 2. 남동 (SE): row 감소, col 증가 - Arguments.of(new Position(5, 5), new Position(2, 2)), // 3. 남서 (SW): row 감소, col 감소 - Arguments.of(new Position(2, 5), new Position(4, 3)) // 4. 북서 (NW): row 증가, col 감소 + Arguments.of(new Position(2, 2), new Position(4, 4)), // 1. 북동 (NE): rowDiff 증가, colDiff 증가 + Arguments.of(new Position(5, 5), new Position(3, 7)), // 2. 남동 (SE): rowDiff 감소, colDiff 증가 + Arguments.of(new Position(5, 5), new Position(2, 2)), // 3. 남서 (SW): rowDiff 감소, colDiff 감소 + Arguments.of(new Position(2, 5), new Position(4, 3)) // 4. 북서 (NW): rowDiff 증가, colDiff 감소 ); } @@ -40,14 +40,14 @@ public class PieceMovePositionFixture { public static Stream 마_이동_가능한_위치() { return Stream.of( - Arguments.of(new Position(5, 5), new Position(3, 4)), // 상+좌 (row-2, col-1) - Arguments.of(new Position(5, 5), new Position(3, 6)), // 상+우 (row-2, col+1) - Arguments.of(new Position(5, 5), new Position(7, 4)), // 하+좌 (row+2, col-1) - Arguments.of(new Position(5, 5), new Position(7, 6)), // 하+우 (row+2, col+1) - Arguments.of(new Position(5, 5), new Position(4, 3)), // 좌+상 (row-1, col-2) - Arguments.of(new Position(5, 5), new Position(6, 3)), // 좌+하 (row+1, col-2) - Arguments.of(new Position(5, 5), new Position(4, 7)), // 우+상 (row-1, col+2) - Arguments.of(new Position(5, 5), new Position(6, 7)) // 우+하 (row+1, col+2) + Arguments.of(new Position(5, 5), new Position(3, 4)), // 상+좌 (rowDiff-2, colDiff-1) + Arguments.of(new Position(5, 5), new Position(3, 6)), // 상+우 (rowDiff-2, colDiff+1) + Arguments.of(new Position(5, 5), new Position(7, 4)), // 하+좌 (rowDiff+2, colDiff-1) + Arguments.of(new Position(5, 5), new Position(7, 6)), // 하+우 (rowDiff+2, colDiff+1) + Arguments.of(new Position(5, 5), new Position(4, 3)), // 좌+상 (rowDiff-1, colDiff-2) + Arguments.of(new Position(5, 5), new Position(6, 3)), // 좌+하 (rowDiff+1, colDiff-2) + Arguments.of(new Position(5, 5), new Position(4, 7)), // 우+상 (rowDiff-1, colDiff+2) + Arguments.of(new Position(5, 5), new Position(6, 7)) // 우+하 (rowDiff+1, colDiff+2) ); } @@ -56,7 +56,7 @@ public class PieceMovePositionFixture { Arguments.of(new Position(5, 5), new Position(5, 7)), // 직선 이동 Arguments.of(new Position(5, 5), new Position(7, 5)), // 직선 이동 Arguments.of(new Position(5, 5), new Position(7, 7)), // 정대각선 - Arguments.of(new Position(5, 5), new Position(8, 7)), // 상 이동 (row+3, col+2) + Arguments.of(new Position(5, 5), new Position(8, 7)), // 상 이동 (rowDiff+3, colDiff+2) Arguments.of(new Position(5, 5), new Position(5, 5)) // 제자리 ); } @@ -67,20 +67,20 @@ public class PieceMovePositionFixture { public static Stream 상_이동_가능한_위치() { return Stream.of( - Arguments.of(new Position(5, 5), new Position(2, 3)), // 상+좌 (row-3, col-2) - Arguments.of(new Position(5, 5), new Position(2, 7)), // 상+우 (row-3, col+2) - Arguments.of(new Position(5, 5), new Position(8, 3)), // 하+좌 (row+3, col-2) - Arguments.of(new Position(5, 5), new Position(8, 7)), // 하+우 (row+3, col+2) - Arguments.of(new Position(5, 5), new Position(3, 2)), // 좌+상 (row-2, col-3) - Arguments.of(new Position(5, 5), new Position(7, 2)), // 좌+하 (row+2, col-3) - Arguments.of(new Position(5, 5), new Position(3, 8)), // 우+상 (row-2, col+3) - Arguments.of(new Position(5, 5), new Position(7, 8)) // 우+하 (row+2, col+3) + Arguments.of(new Position(5, 5), new Position(2, 3)), // 상+좌 (rowDiff-3, colDiff-2) + Arguments.of(new Position(5, 5), new Position(2, 7)), // 상+우 (rowDiff-3, colDiff+2) + Arguments.of(new Position(5, 5), new Position(8, 3)), // 하+좌 (rowDiff+3, colDiff-2) + Arguments.of(new Position(5, 5), new Position(8, 7)), // 하+우 (rowDiff+3, colDiff+2) + Arguments.of(new Position(5, 5), new Position(3, 2)), // 좌+상 (rowDiff-2, colDiff-3) + Arguments.of(new Position(5, 5), new Position(7, 2)), // 좌+하 (rowDiff+2, colDiff-3) + Arguments.of(new Position(5, 5), new Position(3, 8)), // 우+상 (rowDiff-2, colDiff+3) + Arguments.of(new Position(5, 5), new Position(7, 8)) // 우+하 (rowDiff+2, colDiff+3) ); } public static Stream 상_이동_불가능한_위치() { return Stream.of( - Arguments.of(new Position(5, 5), new Position(3, 4)), // 마 이동 (row-2, col-1) + Arguments.of(new Position(5, 5), new Position(3, 4)), // 마 이동 (rowDiff-2, colDiff-1) Arguments.of(new Position(5, 5), new Position(5, 8)), // 직선 이동 Arguments.of(new Position(5, 5), new Position(8, 8)), // 정대각선 Arguments.of(new Position(5, 5), new Position(4, 4)), // 1칸 대각선 @@ -93,13 +93,13 @@ public class PieceMovePositionFixture { // ============================ public static Stream 졸_병_이동_가능한_위치() { return Stream.concat( - // 한나라 (HAN): 전진(row+1), 좌우 + // 한나라 (HAN): 전진(rowDiff+1), 좌우 Stream.of( Arguments.of(Team.HAN, new Position(3, 2), new Position(4, 2)), // 전진 Arguments.of(Team.HAN, new Position(3, 2), new Position(3, 1)), // 좌 Arguments.of(Team.HAN, new Position(3, 2), new Position(3, 3)) // 우 ), - // 초나라 (CHO): 전진(row-1), 좌우 + // 초나라 (CHO): 전진(rowDiff-1), 좌우 Stream.of( Arguments.of(Team.CHO, new Position(6, 2), new Position(5, 2)), // 전진 Arguments.of(Team.CHO, new Position(6, 2), new Position(6, 1)), // 좌 diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index 2fcc9362fc..d2fc8f842a 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -80,7 +80,7 @@ public class CannonTest { // given Piece cannon = new Cannon(Team.HAN); List manyHurdles = List.of( - new Cannon(Team.CHO), // 사실 포가 아니어도 상관없지만 규칙상 테스트 + new Cannon(Team.CHO), new Cannon(Team.CHO) ); From f20b496e5e017dd192b808597ab57f212375c77e Mon Sep 17 00:00:00 2001 From: jihwankim128 Date: Fri, 3 Apr 2026 18:13:02 +0900 Subject: [PATCH 26/26] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/model/movement/Displacement.java | 25 +++++++++++++++++++ src/main/java/model/piece/Cannon.java | 9 +++++-- src/main/java/model/piece/Chariot.java | 9 +++++-- src/main/java/model/piece/Elephant.java | 12 +++++++-- src/main/java/model/piece/General.java | 3 ++- src/main/java/model/piece/Guard.java | 3 ++- src/main/java/model/piece/Horse.java | 12 +++++++-- src/main/java/model/piece/Piece.java | 14 +---------- src/main/java/model/piece/Soldier.java | 22 ++++++++++------ src/test/java/model/piece/CannonTest.java | 18 ++++++------- src/test/java/model/piece/ChariotTest.java | 18 ++++++------- src/test/java/model/piece/ElephantTest.java | 16 +++++------- src/test/java/model/piece/HorseTest.java | 17 ++++++------- src/test/java/model/piece/SoldierTest.java | 7 ++++-- src/test/java/model/testdouble/FakePiece.java | 8 +++--- 15 files changed, 116 insertions(+), 77 deletions(-) diff --git a/src/main/java/model/movement/Displacement.java b/src/main/java/model/movement/Displacement.java index ca75c3e591..cf8d3ae4f1 100644 --- a/src/main/java/model/movement/Displacement.java +++ b/src/main/java/model/movement/Displacement.java @@ -12,4 +12,29 @@ public Direction extractCardinal() { public Direction extractDiagonal() { return Direction.of(rowDiff, colDiff); } + + public int absRowDiff() { + return Math.abs(rowDiff); + } + + public int absColDiff() { + return Math.abs(colDiff); + } + + public boolean isNotStraight() { + return colDiff != 0 && rowDiff != 0; + } + + public boolean isNotStepCombination(int longStep, int shortStep) { + return (absRowDiff() != longStep || absColDiff() != shortStep) && + (absRowDiff() != shortStep || absColDiff() != longStep); + } + + public boolean isForwardBy(int forwardCount) { + return rowDiff == forwardCount && colDiff == 0; + } + + public boolean isSideOneStep() { + return rowDiff == 0 && absColDiff() == 1; + } } diff --git a/src/main/java/model/piece/Cannon.java b/src/main/java/model/piece/Cannon.java index f14fd87acf..47f2b26260 100644 --- a/src/main/java/model/piece/Cannon.java +++ b/src/main/java/model/piece/Cannon.java @@ -2,6 +2,8 @@ import java.util.List; import model.Team; +import model.board.Position; +import model.movement.Displacement; public class Cannon extends Piece { @@ -32,7 +34,10 @@ public void validateTarget(Piece otherPiece) { } @Override - protected boolean comparePosition(int rowDiff, int colDiff) { - return (colDiff >= 1 && rowDiff == 0) || (colDiff == 0 && rowDiff >= 1); + protected void validateMove(Position current, Position next) { + Displacement displacement = next.minus(current); + if (displacement.isNotStraight()) { + throw new IllegalArgumentException("포가 이동할 수 없는 위치입니다."); + } } } diff --git a/src/main/java/model/piece/Chariot.java b/src/main/java/model/piece/Chariot.java index 2c4d131365..619f83ae32 100644 --- a/src/main/java/model/piece/Chariot.java +++ b/src/main/java/model/piece/Chariot.java @@ -1,6 +1,8 @@ package model.piece; import model.Team; +import model.board.Position; +import model.movement.Displacement; public class Chariot extends Piece { @@ -9,7 +11,10 @@ public Chariot(Team team) { } @Override - protected boolean comparePosition(int rowDiff, int colDiff) { - return (colDiff >= 1 && rowDiff == 0) || (colDiff == 0 && rowDiff >= 1); + protected void validateMove(Position current, Position next) { + Displacement displacement = next.minus(current); + if (displacement.isNotStraight()) { + throw new IllegalArgumentException("차가 이동할 수 없는 위치입니다."); + } } } diff --git a/src/main/java/model/piece/Elephant.java b/src/main/java/model/piece/Elephant.java index 6dfb1fafe8..15da27c1e3 100644 --- a/src/main/java/model/piece/Elephant.java +++ b/src/main/java/model/piece/Elephant.java @@ -1,15 +1,23 @@ package model.piece; import model.Team; +import model.board.Position; +import model.movement.Displacement; public class Elephant extends Piece { + private static final int ELEPHANT_LONG_STEP = 3; + private static final int ELEPHANT_SHORT_STEP = 2; + public Elephant(Team team) { super(team, PieceType.ELEPHANT); } @Override - protected boolean comparePosition(int rowDiff, int colDiff) { - return (colDiff == 3 && rowDiff == 2) || (colDiff == 2 && rowDiff == 3); + protected void validateMove(Position current, Position next) { + Displacement displacement = next.minus(current); + if (displacement.isNotStepCombination(ELEPHANT_LONG_STEP, ELEPHANT_SHORT_STEP)) { + throw new IllegalArgumentException("상이 이동할 수 없는 위치입니다."); + } } } diff --git a/src/main/java/model/piece/General.java b/src/main/java/model/piece/General.java index e012368d42..c0db5579e8 100644 --- a/src/main/java/model/piece/General.java +++ b/src/main/java/model/piece/General.java @@ -1,6 +1,7 @@ package model.piece; import model.Team; +import model.board.Position; public class General extends Piece { @@ -9,7 +10,7 @@ public General(Team team) { } @Override - protected boolean comparePosition(int rowDiff, int colDiff) { + protected void validateMove(Position current, Position next) { throw new IllegalArgumentException("1단계 궁성 영역 미구현"); } } diff --git a/src/main/java/model/piece/Guard.java b/src/main/java/model/piece/Guard.java index 059d9d0036..9c635738f1 100644 --- a/src/main/java/model/piece/Guard.java +++ b/src/main/java/model/piece/Guard.java @@ -1,6 +1,7 @@ package model.piece; import model.Team; +import model.board.Position; public class Guard extends Piece { @@ -9,7 +10,7 @@ public Guard(Team team) { } @Override - protected boolean comparePosition(int rowDiff, int colDiff) { + protected void validateMove(Position current, Position next) { throw new IllegalArgumentException("1단계 궁성 영역 미구현"); } } diff --git a/src/main/java/model/piece/Horse.java b/src/main/java/model/piece/Horse.java index a00c0ac82e..cd34d14434 100644 --- a/src/main/java/model/piece/Horse.java +++ b/src/main/java/model/piece/Horse.java @@ -1,15 +1,23 @@ package model.piece; import model.Team; +import model.board.Position; +import model.movement.Displacement; public class Horse extends Piece { + private static final int HORSE_LONG_STEP = 2; + private static final int HORSE_SHORT_STEP = 1; + public Horse(Team team) { super(team, PieceType.HORSE); } @Override - protected boolean comparePosition(int rowDiff, int colDiff) { - return (colDiff == 1 && rowDiff == 2) || (colDiff == 2 && rowDiff == 1); + protected void validateMove(Position current, Position next) { + Displacement displacement = next.minus(current); + if (displacement.isNotStepCombination(HORSE_LONG_STEP, HORSE_SHORT_STEP)) { + throw new IllegalArgumentException("마가 이동할 수 없는 위치입니다."); + } } } diff --git a/src/main/java/model/piece/Piece.java b/src/main/java/model/piece/Piece.java index fb81837530..f6cea5ff83 100644 --- a/src/main/java/model/piece/Piece.java +++ b/src/main/java/model/piece/Piece.java @@ -23,12 +23,6 @@ public boolean isOtherTeam(Team team) { return this.team != team; } - public boolean canMove(Position current, Position next) { - int rowDiff = next.calculateRowDiff(current); - int colDiff = next.calculateColDiff(current); - return comparePosition(Math.abs(rowDiff), Math.abs(colDiff)); - } - public void validatePathCondition(List pieces) { if (!pieces.isEmpty()) { throw new IllegalArgumentException("이동 경로에 기물이 있어 이동할 수 없는 위치입니다."); @@ -41,13 +35,7 @@ public void validateTarget(Piece otherPiece) { } } - public void validateMove(Position current, Position next) { - if (!canMove(current, next)) { - throw new IllegalArgumentException("해당 기물이 이동할 수 없는 위치입니다."); - } - } - - protected abstract boolean comparePosition(int rowDiff, int colDiff); + protected abstract void validateMove(Position current, Position next); protected boolean isCho() { return !team.isHan(); diff --git a/src/main/java/model/piece/Soldier.java b/src/main/java/model/piece/Soldier.java index e90ae8337b..f5e21fc7c7 100644 --- a/src/main/java/model/piece/Soldier.java +++ b/src/main/java/model/piece/Soldier.java @@ -2,25 +2,31 @@ import model.Team; import model.board.Position; +import model.movement.Displacement; public class Soldier extends Piece { + private static final int SOLDIER_FORWARD_STEP = 1; + public Soldier(Team team) { super(team, PieceType.SOLDIER); } @Override - public boolean canMove(Position current, Position next) { - int rowDiff = next.calculateRowDiff(current); - int colDiff = Math.abs(next.calculateColDiff(current)); - return comparePosition(rowDiff, colDiff); + protected void validateMove(Position current, Position next) { + Displacement displacement = next.minus(current); + int forwardCount = resolveForwardCount(); + + if (!(displacement.isForwardBy(forwardCount) || displacement.isSideOneStep())) { + throw new IllegalArgumentException("졸이 이동할 수 없는 위치입니다."); + } } - @Override - protected boolean comparePosition(int rowDiff, int colDiff) { + private int resolveForwardCount() { if (isCho()) { - return (rowDiff == -1 && colDiff == 0) || (rowDiff == 0 && colDiff == 1); + return -SOLDIER_FORWARD_STEP; } - return (rowDiff == 1 && colDiff == 0) || (rowDiff == 0 && colDiff == 1); + return SOLDIER_FORWARD_STEP; } + } diff --git a/src/test/java/model/piece/CannonTest.java b/src/test/java/model/piece/CannonTest.java index d2fc8f842a..796291c9f3 100644 --- a/src/test/java/model/piece/CannonTest.java +++ b/src/test/java/model/piece/CannonTest.java @@ -1,6 +1,7 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; @@ -18,25 +19,20 @@ public class CannonTest { // given Piece cannon = new Cannon(Team.HAN); - // when - boolean canMove = cannon.canMove(current, next); - - // then - assertThat(canMove).isTrue(); + // when & then + assertThatCode(() -> cannon.validateMove(current, next)) + .doesNotThrowAnyException(); } @ParameterizedTest @MethodSource("model.fixture.PieceMovePositionFixture#사간방_대각선_이동_방향_케이스") - @MethodSource("model.fixture.PieceMovePositionFixture#제자리_이동_케이스") void 포는_대각선이나_제자리로_이동할_수_없다(Position current, Position next) { // given Piece cannon = new Cannon(Team.HAN); - // when - boolean canMove = cannon.canMove(current, next); - - // then - assertThat(canMove).isFalse(); + // when & then + assertThatThrownBy(() -> cannon.validateMove(current, next)) + .isInstanceOf(IllegalArgumentException.class); } @ParameterizedTest diff --git a/src/test/java/model/piece/ChariotTest.java b/src/test/java/model/piece/ChariotTest.java index 20f22339d4..c0c228b0e3 100644 --- a/src/test/java/model/piece/ChariotTest.java +++ b/src/test/java/model/piece/ChariotTest.java @@ -1,6 +1,7 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; @@ -18,22 +19,21 @@ public class ChariotTest { void 차는_직선으로_이동할_수_있다(Position current, Position next) { // given Piece chariot = new Chariot(Team.HAN); - // when - boolean canMove = chariot.canMove(current, next); - // then - assertThat(canMove).isTrue(); + + // when & then + assertThatCode(() -> chariot.validateMove(current, next)) + .doesNotThrowAnyException(); } @ParameterizedTest @MethodSource("model.fixture.PieceMovePositionFixture#사간방_대각선_이동_방향_케이스") - @MethodSource("model.fixture.PieceMovePositionFixture#제자리_이동_케이스") void 차는_대각선_또는_제자리로_이동할_수_없다(Position current, Position next) { // given Piece chariot = new Chariot(Team.HAN); - // when - boolean canMove = chariot.canMove(current, next); - // then - assertThat(canMove).isFalse(); + + // when & then + assertThatThrownBy(() -> chariot.validateMove(current, next)) + .isInstanceOf(IllegalArgumentException.class); } @ParameterizedTest diff --git a/src/test/java/model/piece/ElephantTest.java b/src/test/java/model/piece/ElephantTest.java index b959aecd39..7d14631c14 100644 --- a/src/test/java/model/piece/ElephantTest.java +++ b/src/test/java/model/piece/ElephantTest.java @@ -1,6 +1,7 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; @@ -19,11 +20,9 @@ public class ElephantTest { // given Piece elephant = new Elephant(Team.HAN); - // when - boolean canMove = elephant.canMove(current, next); - - // then - assertThat(canMove).isTrue(); + // when & then + assertThatCode(() -> elephant.validateMove(current, next)) + .doesNotThrowAnyException(); } @ParameterizedTest @@ -32,11 +31,8 @@ public class ElephantTest { // given Piece elephant = new Elephant(Team.HAN); - // when - boolean canMove = elephant.canMove(current, next); - - // then - assertThat(canMove).isFalse(); + // when & then + assertThatThrownBy(() -> elephant.validateMove(current, next)); } @ParameterizedTest diff --git a/src/test/java/model/piece/HorseTest.java b/src/test/java/model/piece/HorseTest.java index e39a527b5d..8dbe1c958f 100644 --- a/src/test/java/model/piece/HorseTest.java +++ b/src/test/java/model/piece/HorseTest.java @@ -1,6 +1,7 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; @@ -19,11 +20,9 @@ public class HorseTest { // given Piece horse = new Horse(Team.HAN); - // when - boolean canMove = horse.canMove(current, next); - - // then - assertThat(canMove).isTrue(); + // when & then + assertThatCode(() -> horse.validateMove(current, next)) + .doesNotThrowAnyException(); } @ParameterizedTest @@ -32,11 +31,9 @@ public class HorseTest { // given Piece horse = new Horse(Team.HAN); - // when - boolean canMove = horse.canMove(current, next); - - // then - assertThat(canMove).isFalse(); + // when & then + assertThatThrownBy(() -> horse.validateMove(current, next)) + .isInstanceOf(IllegalArgumentException.class); } @ParameterizedTest diff --git a/src/test/java/model/piece/SoldierTest.java b/src/test/java/model/piece/SoldierTest.java index 889441ec5e..f229aff1e3 100644 --- a/src/test/java/model/piece/SoldierTest.java +++ b/src/test/java/model/piece/SoldierTest.java @@ -1,6 +1,7 @@ package model.piece; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; @@ -20,7 +21,8 @@ public class SoldierTest { Piece soldier = new Soldier(team); // when & then - assertThat(soldier.canMove(current, next)).isTrue(); + assertThatCode(() -> soldier.validateMove(current, next)) + .doesNotThrowAnyException(); } @ParameterizedTest(name = "{0}나라 졸 일 때, {1}에서 {2}로 이동할 수 없다.") @@ -30,7 +32,8 @@ public class SoldierTest { Piece soldier = new Soldier(team); // when & then - assertThat(soldier.canMove(current, next)).isFalse(); + assertThatThrownBy(() -> soldier.validateMove(current, next)) + .isInstanceOf(IllegalArgumentException.class); } @ParameterizedTest(name = "{0}나라 졸 일 때, {1}에서 {2}로 이동 시 무조건 경로가 비어있다.") diff --git a/src/test/java/model/testdouble/FakePiece.java b/src/test/java/model/testdouble/FakePiece.java index 72cd028211..c8b05d087e 100644 --- a/src/test/java/model/testdouble/FakePiece.java +++ b/src/test/java/model/testdouble/FakePiece.java @@ -22,12 +22,12 @@ public static FakePiece createFake(Team team) { } @Override - protected boolean comparePosition(int rowDiff, int colDiff) { - return movable; + public List extractPath(Position current, Position next) { + return path; } @Override - public List extractPath(Position current, Position next) { - return path; + protected void validateMove(Position current, Position next) { + } }