From 0ceec3ce41775a8c97c5e4b2044f1f62fa5ffbc4 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 25 Mar 2026 13:52:30 +0900 Subject: [PATCH 01/45] =?UTF-8?q?docs(README):=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=ED=81=B4=201:=201=EB=8B=A8=EA=B3=84=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 9775dda0ae..3613fa1c12 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ # java-janggi 장기 미션 저장소 + +--- + +## **🚀 기능 요구 사항** + +### 1.1 **보드 초기화** + +게임 시작 시 장기판과 전체 기물을 올바른 위치에 초기화한다. + +- [ ] 장기판 초기화 전략을 번호로 입력받는다. + - 상마마상, 상마상마, 마상마상, 마상상마 + - [ ] 사용자 입력값 유효성 검사 + - [ ] 공백인 경우 예외 처리 + - [ ] 숫자가 아닌 문자일 경우 예외 처리 + - [ ] 정수 범위를 벗어난 경우 예외 처리 + - [ ] 도메인 규칙 검증 + - [ ] 입력값이 선택지 범위에 포함되지 않는 경우 예외 처리 +- [ ] 전략이 반영된 장기판 상태를 출력한다. From 7f73e77d18023a708a64abb93b277425e0430fee Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 25 Mar 2026 16:03:46 +0900 Subject: [PATCH 02/45] =?UTF-8?q?feat:=20=EC=9E=A5=EA=B8=B0=ED=8C=90=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=A0=84=EB=9E=B5=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=85=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력 검증 및 예외 처리 - 입력이 정수가 아닌 경우 - 입력값에 매핑되는 전략 패턴이 존재하지 않는 경우 - 전략 패턴 식별 라벨 추가 --- README.md | 14 +++---- src/main/java/.gitkeep | 0 src/main/java/janggi/Application.java | 16 +++++++ .../java/janggi/controller/JanggiFlow.java | 29 +++++++++++++ .../janggi/strategy/ArrangementStrategy.java | 22 ++++++++++ .../java/janggi/strategy/SangMaSangMa.java | 9 ++++ .../java/janggi/strategy/StrategyLabel.java | 25 +++++++++++ .../java/janggi/view/ApplicationView.java | 37 ++++++++++++++++ src/main/java/janggi/view/ConsoleReader.java | 42 +++++++++++++++++++ src/main/java/janggi/view/ConsoleWriter.java | 13 ++++++ src/main/java/janggi/view/Input.java | 5 +++ src/main/java/janggi/view/Output.java | 7 ++++ 12 files changed, 212 insertions(+), 7 deletions(-) delete mode 100644 src/main/java/.gitkeep create mode 100644 src/main/java/janggi/Application.java create mode 100644 src/main/java/janggi/controller/JanggiFlow.java create mode 100644 src/main/java/janggi/strategy/ArrangementStrategy.java create mode 100644 src/main/java/janggi/strategy/SangMaSangMa.java create mode 100644 src/main/java/janggi/strategy/StrategyLabel.java create mode 100644 src/main/java/janggi/view/ApplicationView.java create mode 100644 src/main/java/janggi/view/ConsoleReader.java create mode 100644 src/main/java/janggi/view/ConsoleWriter.java create mode 100644 src/main/java/janggi/view/Input.java create mode 100644 src/main/java/janggi/view/Output.java diff --git a/README.md b/README.md index 3613fa1c12..aa732cd8bd 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ 게임 시작 시 장기판과 전체 기물을 올바른 위치에 초기화한다. -- [ ] 장기판 초기화 전략을 번호로 입력받는다. +- [x] 장기판 초기화 전략을 번호로 입력받는다. - 상마마상, 상마상마, 마상마상, 마상상마 - - [ ] 사용자 입력값 유효성 검사 - - [ ] 공백인 경우 예외 처리 - - [ ] 숫자가 아닌 문자일 경우 예외 처리 - - [ ] 정수 범위를 벗어난 경우 예외 처리 - - [ ] 도메인 규칙 검증 - - [ ] 입력값이 선택지 범위에 포함되지 않는 경우 예외 처리 + - [x] 사용자 입력값 유효성 검사 + - [x] 공백인 경우 예외 처리 + - [x] 숫자가 아닌 문자일 경우 예외 처리 + - [x] 정수 범위를 벗어난 경우 예외 처리 + - [x] 도메인 규칙 검증 + - [x] 입력값이 선택지 범위에 포함되지 않는 경우 예외 처리 - [ ] 전략이 반영된 장기판 상태를 출력한다. diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java new file mode 100644 index 0000000000..45c52d9c1e --- /dev/null +++ b/src/main/java/janggi/Application.java @@ -0,0 +1,16 @@ +package janggi; + +import janggi.controller.JanggiFlow; +import janggi.view.ApplicationView; +import janggi.view.ConsoleReader; +import janggi.view.ConsoleWriter; + +public class Application { + + public static void main(String[] args) { + ApplicationView view = new ApplicationView(new ConsoleWriter(), new ConsoleReader()); + JanggiFlow janggi = new JanggiFlow(view); + + janggi.process(); + } +} diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java new file mode 100644 index 0000000000..62bcac83a7 --- /dev/null +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -0,0 +1,29 @@ +package janggi.controller; + +import janggi.strategy.ArrangementStrategy; +import janggi.strategy.SangMaSangMa; +import janggi.view.ApplicationView; +import java.util.List; + +public class JanggiFlow { + private final ApplicationView view; + + private final List strategies; + + public JanggiFlow(ApplicationView view) { + this.view = view; + this.strategies = List.of(new SangMaSangMa()); + } + + public void process() { + int decisionNumber = view.requestArrangementStrategyDecision(strategies); + ArrangementStrategy strategy = findStrategyWithCorrespondingDecisionNumber(decisionNumber); + } + + private ArrangementStrategy findStrategyWithCorrespondingDecisionNumber(int decisionNumber) { + return strategies.stream() + .filter(strategy -> strategy.isDecisionNumberMatching(decisionNumber)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("일치하는 전략 번호가 없습니다: " + decisionNumber)); + } +} diff --git a/src/main/java/janggi/strategy/ArrangementStrategy.java b/src/main/java/janggi/strategy/ArrangementStrategy.java new file mode 100644 index 0000000000..b170ff12f7 --- /dev/null +++ b/src/main/java/janggi/strategy/ArrangementStrategy.java @@ -0,0 +1,22 @@ +package janggi.strategy; + +public abstract class ArrangementStrategy { + + protected final StrategyLabel label; + + protected ArrangementStrategy(StrategyLabel label) { + this.label = label; + } + + public boolean isDecisionNumberMatching(int decisionNumber) { + return label.getDecisionNumber() == decisionNumber; + } + + public String name() { + return label.getName(); + } + + public int decisionNumber() { + return label.getDecisionNumber(); + } +} diff --git a/src/main/java/janggi/strategy/SangMaSangMa.java b/src/main/java/janggi/strategy/SangMaSangMa.java new file mode 100644 index 0000000000..bf188c919e --- /dev/null +++ b/src/main/java/janggi/strategy/SangMaSangMa.java @@ -0,0 +1,9 @@ +package janggi.strategy; + +public class SangMaSangMa extends ArrangementStrategy { + + public SangMaSangMa() { + super(StrategyLabel.EHEH); + } + +} diff --git a/src/main/java/janggi/strategy/StrategyLabel.java b/src/main/java/janggi/strategy/StrategyLabel.java new file mode 100644 index 0000000000..81e42c351c --- /dev/null +++ b/src/main/java/janggi/strategy/StrategyLabel.java @@ -0,0 +1,25 @@ +package janggi.strategy; + +public enum StrategyLabel { + + HEHE("마상마상", 1), + HEEH("마상상마", 2), + EHHE("상마마상", 3), + EHEH("상마상마", 4); + + private final String name; + private final int decisionNumber; + + StrategyLabel(String name, int decisionNumber) { + this.name = name; + this.decisionNumber = decisionNumber; + } + + public String getName() { + return name; + } + + public int getDecisionNumber() { + return decisionNumber; + } +} diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java new file mode 100644 index 0000000000..ddd1a0fed2 --- /dev/null +++ b/src/main/java/janggi/view/ApplicationView.java @@ -0,0 +1,37 @@ +package janggi.view; + +import janggi.strategy.ArrangementStrategy; +import java.util.List; +import java.util.function.Supplier; + +public class ApplicationView { + + private final Output outputWriter; + private final Input inputReader; + + public ApplicationView(Output outputWriter, Input inputReader) { + this.outputWriter = outputWriter; + this.inputReader = inputReader; + } + + public int requestArrangementStrategyDecision(List strategies) { + outputWriter.printPromptMessage("초기화 전략 번호를 입력해주세요."); + + for (ArrangementStrategy strategy : strategies) { + String strategyDecisionOption = String.format("%d. %s", strategy.decisionNumber(), strategy.name()); + outputWriter.printPromptMessage(strategyDecisionOption); + } + + return retry(inputReader::readInteger); + } + + private T retry(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + outputWriter.printErrorMessage(e); + } + } + } +} diff --git a/src/main/java/janggi/view/ConsoleReader.java b/src/main/java/janggi/view/ConsoleReader.java new file mode 100644 index 0000000000..2ed40908f9 --- /dev/null +++ b/src/main/java/janggi/view/ConsoleReader.java @@ -0,0 +1,42 @@ +package janggi.view; + +import java.util.Scanner; + +public class ConsoleReader implements Input { + private static final String NUMERIC_FORMAT_REGEX = "-?\\d+"; + + private final Scanner scanner; + + public ConsoleReader() { + this.scanner = new Scanner(System.in); + } + + @Override + public int readInteger() { + String input = scanner.nextLine().trim(); + validateIsBlank(input); + validateIsNumeric(input); + return parseToInt(input); + } + + private void validateIsBlank(String input) { + if (input.isBlank()) { + throw new IllegalArgumentException("공백은 허용되지 않습니다."); + } + } + + private void validateIsNumeric(String input) { + if (input.matches(NUMERIC_FORMAT_REGEX)) { + return; + } + throw new IllegalArgumentException("숫자가 아닌 문자를 입력할 수 없습니다."); + } + + private int parseToInt(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("정수 범위를 초과할 수 없습니다."); + } + } +} diff --git a/src/main/java/janggi/view/ConsoleWriter.java b/src/main/java/janggi/view/ConsoleWriter.java new file mode 100644 index 0000000000..137fe6f73b --- /dev/null +++ b/src/main/java/janggi/view/ConsoleWriter.java @@ -0,0 +1,13 @@ +package janggi.view; + +public class ConsoleWriter implements Output { + @Override + public void printPromptMessage(String promptMessage) { + System.out.println(promptMessage); + } + + @Override + public void printErrorMessage(RuntimeException e) { + System.out.println("[ERROR] " + e.getMessage()); + } +} diff --git a/src/main/java/janggi/view/Input.java b/src/main/java/janggi/view/Input.java new file mode 100644 index 0000000000..82faff628e --- /dev/null +++ b/src/main/java/janggi/view/Input.java @@ -0,0 +1,5 @@ +package janggi.view; + +public interface Input { + int readInteger(); +} diff --git a/src/main/java/janggi/view/Output.java b/src/main/java/janggi/view/Output.java new file mode 100644 index 0000000000..aabef6cce7 --- /dev/null +++ b/src/main/java/janggi/view/Output.java @@ -0,0 +1,7 @@ +package janggi.view; + +public interface Output { + void printPromptMessage(String promptMessage); + + void printErrorMessage(RuntimeException e); +} From 0be33b8dd21d4b3e3d44c6bf4a705758a7da429c Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 25 Mar 2026 22:13:38 +0900 Subject: [PATCH 03/45] =?UTF-8?q?feat:=20=EC=9E=A5=EA=B8=B0=ED=8C=90=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EB=B0=B0=EC=B9=98=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 - 도메인 패키지 분리 --- .../java/janggi/controller/JanggiFlow.java | 5 ++ src/main/java/janggi/domain/Side.java | 8 ++ src/main/java/janggi/domain/piece/Cha.java | 14 ++++ .../java/janggi/domain/piece/EmptyPiece.java | 16 ++++ src/main/java/janggi/domain/piece/Gung.java | 14 ++++ src/main/java/janggi/domain/piece/Jol.java | 14 ++++ src/main/java/janggi/domain/piece/Ma.java | 15 ++++ src/main/java/janggi/domain/piece/Piece.java | 17 ++++ src/main/java/janggi/domain/piece/Po.java | 14 ++++ src/main/java/janggi/domain/piece/Sa.java | 14 ++++ src/main/java/janggi/domain/piece/Sang.java | 14 ++++ .../janggi/strategy/ArrangementStrategy.java | 13 ++- .../java/janggi/strategy/BoardAssembler.java | 83 +++++++++++++++++++ .../java/janggi/strategy/MaSangMaSang.java | 23 +++++ .../java/janggi/strategy/MaSangSangMa.java | 24 ++++++ .../java/janggi/strategy/SangMaMaSang.java | 23 +++++ .../java/janggi/strategy/SangMaSangMa.java | 14 ++++ src/test/java/.gitkeep | 0 .../janggi/strategy/BoardAssemblerTest.java | 40 +++++++++ .../janggi/strategy/MaSangMaSangTest.java | 67 +++++++++++++++ .../janggi/strategy/MaSangSangMaTest.java | 67 +++++++++++++++ .../janggi/strategy/SangMaMaSangTest.java | 67 +++++++++++++++ .../janggi/strategy/SangMaSangMaTest.java | 67 +++++++++++++++ 23 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 src/main/java/janggi/domain/Side.java create mode 100644 src/main/java/janggi/domain/piece/Cha.java create mode 100644 src/main/java/janggi/domain/piece/EmptyPiece.java create mode 100644 src/main/java/janggi/domain/piece/Gung.java create mode 100644 src/main/java/janggi/domain/piece/Jol.java create mode 100644 src/main/java/janggi/domain/piece/Ma.java create mode 100644 src/main/java/janggi/domain/piece/Piece.java create mode 100644 src/main/java/janggi/domain/piece/Po.java create mode 100644 src/main/java/janggi/domain/piece/Sa.java create mode 100644 src/main/java/janggi/domain/piece/Sang.java create mode 100644 src/main/java/janggi/strategy/BoardAssembler.java create mode 100644 src/main/java/janggi/strategy/MaSangMaSang.java create mode 100644 src/main/java/janggi/strategy/MaSangSangMa.java create mode 100644 src/main/java/janggi/strategy/SangMaMaSang.java delete mode 100644 src/test/java/.gitkeep create mode 100644 src/test/java/janggi/strategy/BoardAssemblerTest.java create mode 100644 src/test/java/janggi/strategy/MaSangMaSangTest.java create mode 100644 src/test/java/janggi/strategy/MaSangSangMaTest.java create mode 100644 src/test/java/janggi/strategy/SangMaMaSangTest.java create mode 100644 src/test/java/janggi/strategy/SangMaSangMaTest.java diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 62bcac83a7..e8e2d32fd4 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -1,6 +1,8 @@ package janggi.controller; +import janggi.domain.piece.Piece; import janggi.strategy.ArrangementStrategy; +import janggi.strategy.BoardAssembler; import janggi.strategy.SangMaSangMa; import janggi.view.ApplicationView; import java.util.List; @@ -18,6 +20,9 @@ public JanggiFlow(ApplicationView view) { public void process() { int decisionNumber = view.requestArrangementStrategyDecision(strategies); ArrangementStrategy strategy = findStrategyWithCorrespondingDecisionNumber(decisionNumber); + BoardAssembler assembler = new BoardAssembler(); + Piece[][] pieces = assembler.assemble(strategy, strategy); + System.out.println(pieces); } private ArrangementStrategy findStrategyWithCorrespondingDecisionNumber(int decisionNumber) { diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java new file mode 100644 index 0000000000..7e6b24c9d2 --- /dev/null +++ b/src/main/java/janggi/domain/Side.java @@ -0,0 +1,8 @@ +package janggi.domain; + +public enum Side { + + CHO, + HAN, + NONE +} diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java new file mode 100644 index 0000000000..314a1193ca --- /dev/null +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -0,0 +1,14 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class Cha extends Piece { + public Cha(Side side) { + super(side); + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/src/main/java/janggi/domain/piece/EmptyPiece.java b/src/main/java/janggi/domain/piece/EmptyPiece.java new file mode 100644 index 0000000000..880b427dd7 --- /dev/null +++ b/src/main/java/janggi/domain/piece/EmptyPiece.java @@ -0,0 +1,16 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class EmptyPiece extends Piece { + + public EmptyPiece() { + super(Side.NONE); + } + + @Override + public boolean isEmpty() { + return true; + } + +} diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java new file mode 100644 index 0000000000..fbecb37abf --- /dev/null +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -0,0 +1,14 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class Gung extends Piece { + public Gung(Side side) { + super(side); + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/src/main/java/janggi/domain/piece/Jol.java b/src/main/java/janggi/domain/piece/Jol.java new file mode 100644 index 0000000000..af834c0447 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Jol.java @@ -0,0 +1,14 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class Jol extends Piece { + public Jol(Side side) { + super(side); + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java new file mode 100644 index 0000000000..7df9f73de6 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -0,0 +1,15 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class Ma extends Piece { + public Ma(Side side) { + super(side); + } + + @Override + public boolean isEmpty() { + return false; + } + +} diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java new file mode 100644 index 0000000000..019f1c19bd --- /dev/null +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -0,0 +1,17 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public abstract class Piece { + protected final Side side; + + protected Piece(Side side) { + this.side = side; + } + + public abstract boolean isEmpty(); + + public boolean isSameSide(Side side) { + return this.side.equals(side); + } +} diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java new file mode 100644 index 0000000000..2f49e8f2ed --- /dev/null +++ b/src/main/java/janggi/domain/piece/Po.java @@ -0,0 +1,14 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class Po extends Piece { + public Po(Side side) { + super(side); + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java new file mode 100644 index 0000000000..90533cb278 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -0,0 +1,14 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class Sa extends Piece { + public Sa(Side side) { + super(side); + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java new file mode 100644 index 0000000000..a8dc8bf2d6 --- /dev/null +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -0,0 +1,14 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class Sang extends Piece { + public Sang(Side side) { + super(side); + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/src/main/java/janggi/strategy/ArrangementStrategy.java b/src/main/java/janggi/strategy/ArrangementStrategy.java index b170ff12f7..cb92f07412 100644 --- a/src/main/java/janggi/strategy/ArrangementStrategy.java +++ b/src/main/java/janggi/strategy/ArrangementStrategy.java @@ -1,7 +1,9 @@ package janggi.strategy; -public abstract class ArrangementStrategy { +import janggi.domain.piece.Piece; +import janggi.domain.Side; +public abstract class ArrangementStrategy { protected final StrategyLabel label; protected ArrangementStrategy(StrategyLabel label) { @@ -19,4 +21,13 @@ public String name() { public int decisionNumber() { return label.getDecisionNumber(); } + + protected int calculateRow(int boardMaxLength, Side side) { + if (side.equals(Side.CHO)) { + return boardMaxLength - 1; + } + return 0; + } + + public abstract void place(Piece[][] arrangement, Side side); } diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java new file mode 100644 index 0000000000..192e091768 --- /dev/null +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -0,0 +1,83 @@ +package janggi.strategy; + +import janggi.domain.piece.Cha; +import janggi.domain.piece.Gung; +import janggi.domain.piece.Jol; +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Po; +import janggi.domain.piece.Sa; +import janggi.domain.piece.Sang; +import janggi.domain.Side; +import java.util.List; +import java.util.function.Function; + +public class BoardAssembler { + + private static final int DEFAULT_ROWS = 10; + private static final int DEFAULT_COLS = 9; + + public Piece[][] assemble(ArrangementStrategy cho, ArrangementStrategy han) { + Piece[][] arrangement = new Piece[DEFAULT_ROWS][DEFAULT_COLS]; + + setupCommonPieces(arrangement); + + cho.place(arrangement, Side.CHO); + han.place(arrangement, Side.HAN); + + return arrangement; + } + + private void setupCommonPieces(Piece[][] grid) { + setUpOneSide(grid, Side.HAN); + setUpOneSide(grid, Side.CHO); + } + + private void setUpOneSide(Piece[][] grid, Side side) { + for (DefaultPieceFactory factory : DefaultPieceFactory.values()) { + setUpPiece(grid, side, factory); + } + } + + private void setUpPiece(Piece[][] grid, Side side, DefaultPieceFactory factory) { + for (int col : factory.getCols()) { + int row = factory.getRow(side); + grid[row][col] = factory.createPiece(side); + } + } + + private enum DefaultPieceFactory { + CHA(0, List.of(0, 8), Cha::new), + MA(0, List.of(1, 7), Ma::new), + SANG(0, List.of(2, 6), Sang::new), + SA(0, List.of(3, 5), Sa::new), + GUNG(1, List.of(4), Gung::new), + PO(2, List.of(1, 7), Po::new), + JOL(3, List.of(0, 2, 4, 6, 8), Jol::new); + + private final int row; + private final List cols; + private final Function pieceClass; + + DefaultPieceFactory(int row, List cols, Function pieceClass) { + this.row = row; + this.cols = cols; + this.pieceClass = pieceClass; + } + + public Piece createPiece(Side side) { + return pieceClass.apply(side); + } + + public int getRow(Side side) { + if (side.equals(Side.CHO)) { + return DEFAULT_ROWS - row - 1; + } + return row; + } + + public List getCols() { + return cols; + } + } +} diff --git a/src/main/java/janggi/strategy/MaSangMaSang.java b/src/main/java/janggi/strategy/MaSangMaSang.java new file mode 100644 index 0000000000..2e473813d3 --- /dev/null +++ b/src/main/java/janggi/strategy/MaSangMaSang.java @@ -0,0 +1,23 @@ +package janggi.strategy; + +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import janggi.domain.Side; + +public class MaSangMaSang extends ArrangementStrategy { + + public MaSangMaSang() { + super(StrategyLabel.HEHE); + } + + @Override + public void place(Piece[][] board, Side side) { + int boardMaxLength = board.length; + int row = calculateRow(boardMaxLength, side); + board[row][1] = new Ma(side); + board[row][2] = new Sang(side); + board[row][6] = new Ma(side); + board[row][7] = new Sang(side); + } +} diff --git a/src/main/java/janggi/strategy/MaSangSangMa.java b/src/main/java/janggi/strategy/MaSangSangMa.java new file mode 100644 index 0000000000..af3405193d --- /dev/null +++ b/src/main/java/janggi/strategy/MaSangSangMa.java @@ -0,0 +1,24 @@ +package janggi.strategy; + +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import janggi.domain.Side; + +public class MaSangSangMa extends ArrangementStrategy { + + public MaSangSangMa() { + super(StrategyLabel.HEEH); + } + + @Override + public void place(Piece[][] board, Side side) { + int boardMaxLength = board.length; + int row = calculateRow(boardMaxLength, side); + board[row][1] = new Ma(side); + board[row][2] = new Sang(side); + board[row][6] = new Sang(side); + board[row][7] = new Ma(side); + } + +} diff --git a/src/main/java/janggi/strategy/SangMaMaSang.java b/src/main/java/janggi/strategy/SangMaMaSang.java new file mode 100644 index 0000000000..2ca2446e34 --- /dev/null +++ b/src/main/java/janggi/strategy/SangMaMaSang.java @@ -0,0 +1,23 @@ +package janggi.strategy; + +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import janggi.domain.Side; + +public class SangMaMaSang extends ArrangementStrategy { + + public SangMaMaSang() { + super(StrategyLabel.EHHE); + } + + @Override + public void place(Piece[][] board, Side side) { + int boardMaxLength = board.length; + int row = calculateRow(boardMaxLength, side); + board[row][1] = new Sang(side); + board[row][2] = new Ma(side); + board[row][6] = new Ma(side); + board[row][7] = new Sang(side); + } +} diff --git a/src/main/java/janggi/strategy/SangMaSangMa.java b/src/main/java/janggi/strategy/SangMaSangMa.java index bf188c919e..97b5c6a82e 100644 --- a/src/main/java/janggi/strategy/SangMaSangMa.java +++ b/src/main/java/janggi/strategy/SangMaSangMa.java @@ -1,9 +1,23 @@ package janggi.strategy; +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import janggi.domain.Side; + public class SangMaSangMa extends ArrangementStrategy { public SangMaSangMa() { super(StrategyLabel.EHEH); } + @Override + public void place(Piece[][] board, Side side) { + int boardMaxLength = board.length; + int row = calculateRow(boardMaxLength, side); + board[row][1] = new Sang(side); + board[row][2] = new Ma(side); + board[row][6] = new Sang(side); + board[row][7] = new Ma(side); + } } diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/test/java/janggi/strategy/BoardAssemblerTest.java b/src/test/java/janggi/strategy/BoardAssemblerTest.java new file mode 100644 index 0000000000..2b92216636 --- /dev/null +++ b/src/test/java/janggi/strategy/BoardAssemblerTest.java @@ -0,0 +1,40 @@ +package janggi.strategy; + +import janggi.domain.piece.Cha; +import janggi.domain.piece.Jol; +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BoardAssemblerTest { + + @Test + @DisplayName("공통 기물과 각 팀의 전략이 합쳐져 전체 보드를 생성한다.") + void shouldAssembleFullBoard() { + // given + BoardAssembler assembler = new BoardAssembler(); + ArrangementStrategy choStrategy = new MaSangMaSang(); // 마-상-마-상 (1,2,6,7) + ArrangementStrategy hanStrategy = new SangMaMaSang(); // 상-마-마-상 (1,2,6,7 다름) + + // when + Piece[][] board = assembler.assemble(choStrategy, hanStrategy); + + // then + // 1. 공통 기물 검증 (샘플) + Assertions.assertThat(board[0][0]).isInstanceOf(Cha.class); // 한 차 + Assertions.assertThat(board[9][8]).isInstanceOf(Cha.class); // 초 차 + Assertions.assertThat(board[3][0]).isInstanceOf(Jol.class); // 한 졸 + + // 2. 전략 기물 검증 (전략이 실제로 동작했는지 확인) + // 초(Side.CHO)는 9번 행에 배치됨 + Assertions.assertThat(board[9][1]).isInstanceOf(Ma.class); // MaSangMaSang의 첫 번째 마 + Assertions.assertThat(board[9][2]).isInstanceOf(Sang.class); // MaSangMaSang의 첫 번째 상 + + // 3. 빈 칸 검증 (기물이 없어야 할 곳) + Assertions.assertThat(board[4][0]).isNull(); // 장기판 중간은 비어있어야 함 + } + +} diff --git a/src/test/java/janggi/strategy/MaSangMaSangTest.java b/src/test/java/janggi/strategy/MaSangMaSangTest.java new file mode 100644 index 0000000000..e1017e3d08 --- /dev/null +++ b/src/test/java/janggi/strategy/MaSangMaSangTest.java @@ -0,0 +1,67 @@ +package janggi.strategy; + + +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import janggi.domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MaSangMaSangTest { + + @Test + @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangMaSang의 위치에 생성해 넣어준다.") + void shouldPlaceMaSangMaSangWhenSideHan() { + // given + Piece[][] grid = new Piece[10][9]; + ArrangementStrategy strategy = new MaSangMaSang(); + Side side = Side.HAN; + + // when + strategy.place(grid, side); + Piece leftMa = grid[0][1]; + Piece leftSang = grid[0][2]; + Piece rightMa = grid[0][6]; + Piece rightSang = grid[0][7]; + + // then + Assertions.assertThat(leftMa).isInstanceOf(Ma.class); + Assertions.assertThat(leftSang).isInstanceOf(Sang.class); + Assertions.assertThat(rightMa).isInstanceOf(Ma.class); + Assertions.assertThat(rightSang).isInstanceOf(Sang.class); + + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + } + + @Test + @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 MaSangMaSang의 위치에 생성해 넣어준다.") + void shouldPlaceMaSangMaSangWhenSideCho() { + // given + Piece[][] grid = new Piece[10][9]; + ArrangementStrategy strategy = new MaSangMaSang(); + Side side = Side.CHO; + + // when + strategy.place(grid, side); + Piece leftMa = grid[9][1]; + Piece leftSang = grid[9][2]; + Piece rightMa = grid[9][6]; + Piece rightSang = grid[9][7]; + + // then + Assertions.assertThat(leftMa).isInstanceOf(Ma.class); + Assertions.assertThat(leftSang).isInstanceOf(Sang.class); + Assertions.assertThat(rightMa).isInstanceOf(Ma.class); + Assertions.assertThat(rightSang).isInstanceOf(Sang.class); + + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + } +} diff --git a/src/test/java/janggi/strategy/MaSangSangMaTest.java b/src/test/java/janggi/strategy/MaSangSangMaTest.java new file mode 100644 index 0000000000..f1e6aa8905 --- /dev/null +++ b/src/test/java/janggi/strategy/MaSangSangMaTest.java @@ -0,0 +1,67 @@ +package janggi.strategy; + + +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import janggi.domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MaSangSangMaTest { + + @Test + @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangSangMa의 위치에 생성해 넣어준다.") + void shouldPlaceMaSangSangMaWhenSideHan() { + // given + Piece[][] grid = new Piece[10][9]; + ArrangementStrategy strategy = new MaSangSangMa(); + Side side = Side.HAN; + + // when + strategy.place(grid, side); + Piece leftMa = grid[0][1]; + Piece leftSang = grid[0][2]; + Piece rightSang = grid[0][6]; + Piece rightMa = grid[0][7]; + + // then + Assertions.assertThat(leftMa).isInstanceOf(Ma.class); + Assertions.assertThat(leftSang).isInstanceOf(Sang.class); + Assertions.assertThat(rightSang).isInstanceOf(Sang.class); + Assertions.assertThat(rightMa).isInstanceOf(Ma.class); + + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + } + + @Test + @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 MaSangSangMa의 위치에 생성해 넣어준다.") + void shouldPlaceMaSangSangMaWhenSideCho() { + // given + Piece[][] grid = new Piece[10][9]; + ArrangementStrategy strategy = new MaSangSangMa(); + Side side = Side.CHO; + + // when + strategy.place(grid, side); + Piece leftMa = grid[9][1]; + Piece leftSang = grid[9][2]; + Piece rightSang = grid[9][6]; + Piece rightMa = grid[9][7]; + + // then + Assertions.assertThat(leftMa).isInstanceOf(Ma.class); + Assertions.assertThat(leftSang).isInstanceOf(Sang.class); + Assertions.assertThat(rightSang).isInstanceOf(Sang.class); + Assertions.assertThat(rightMa).isInstanceOf(Ma.class); + + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + } +} diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java new file mode 100644 index 0000000000..5bea0a6b19 --- /dev/null +++ b/src/test/java/janggi/strategy/SangMaMaSangTest.java @@ -0,0 +1,67 @@ +package janggi.strategy; + + +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import janggi.domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SangMaMaSangTest { + + @Test + @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaMaSang의 위치에 생성해 넣어준다.") + void shouldPlaceSangMaMaSangWhenSideHan() { + // given + Piece[][] grid = new Piece[10][9]; + ArrangementStrategy strategy = new SangMaMaSang(); + Side side = Side.HAN; + + // when + strategy.place(grid, side); + Piece leftSang = grid[0][1]; + Piece leftMa = grid[0][2]; + Piece rightMa = grid[0][6]; + Piece rightSang = grid[0][7]; + + // then + Assertions.assertThat(leftSang).isInstanceOf(Sang.class); + Assertions.assertThat(leftMa).isInstanceOf(Ma.class); + Assertions.assertThat(rightMa).isInstanceOf(Ma.class); + Assertions.assertThat(rightSang).isInstanceOf(Sang.class); + + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + } + + @Test + @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 SangMaMaSang의 위치에 생성해 넣어준다.") + void shouldPlaceSangMaMaSangWhenSideCho() { + // given + Piece[][] grid = new Piece[10][9]; + ArrangementStrategy strategy = new SangMaMaSang(); + Side side = Side.CHO; + + // when + strategy.place(grid, side); + Piece leftSang = grid[9][1]; + Piece leftMa = grid[9][2]; + Piece rightMa = grid[9][6]; + Piece rightSang = grid[9][7]; + + // then + Assertions.assertThat(leftSang).isInstanceOf(Sang.class); + Assertions.assertThat(leftMa).isInstanceOf(Ma.class); + Assertions.assertThat(rightMa).isInstanceOf(Ma.class); + Assertions.assertThat(rightSang).isInstanceOf(Sang.class); + + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + } +} diff --git a/src/test/java/janggi/strategy/SangMaSangMaTest.java b/src/test/java/janggi/strategy/SangMaSangMaTest.java new file mode 100644 index 0000000000..2ad9e3eec7 --- /dev/null +++ b/src/test/java/janggi/strategy/SangMaSangMaTest.java @@ -0,0 +1,67 @@ +package janggi.strategy; + + +import janggi.domain.piece.Ma; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Sang; +import janggi.domain.Side; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SangMaSangMaTest { + + @Test + @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaSangMa의 위치에 생성해 넣어준다.") + void shouldPlaceSangMaSangMaWhenSideHan() { + // given + Piece[][] grid = new Piece[10][9]; + ArrangementStrategy strategy = new SangMaSangMa(); + Side side = Side.HAN; + + // when + strategy.place(grid, side); + Piece leftSang = grid[0][1]; + Piece leftMa = grid[0][2]; + Piece rightSang = grid[0][6]; + Piece rightMa = grid[0][7]; + + // then + Assertions.assertThat(leftSang).isInstanceOf(Sang.class); + Assertions.assertThat(leftMa).isInstanceOf(Ma.class); + Assertions.assertThat(rightSang).isInstanceOf(Sang.class); + Assertions.assertThat(rightMa).isInstanceOf(Ma.class); + + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + } + + @Test + @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 SangMaSangMa의 위치에 생성해 넣어준다.") + void shouldPlaceSangMaSangMaWhenSideCho() { + // given + Piece[][] grid = new Piece[10][9]; + ArrangementStrategy strategy = new SangMaSangMa(); + Side side = Side.CHO; + + // when + strategy.place(grid, side); + Piece leftSang = grid[9][1]; + Piece leftMa = grid[9][2]; + Piece rightSang = grid[9][6]; + Piece rightMa = grid[9][7]; + + // then + Assertions.assertThat(leftSang).isInstanceOf(Sang.class); + Assertions.assertThat(leftMa).isInstanceOf(Ma.class); + Assertions.assertThat(rightSang).isInstanceOf(Sang.class); + Assertions.assertThat(rightMa).isInstanceOf(Ma.class); + + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + } +} From e8d6b4c939271e4587086f586dca160d1557a4fa Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 11:18:24 +0900 Subject: [PATCH 04/45] =?UTF-8?q?refactor(BoardAssembler):=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=EC=97=90=20=EC=A0=84=EB=9E=B5=EC=9D=84=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/janggi/controller/JanggiFlow.java | 4 ++-- .../java/janggi/strategy/BoardAssembler.java | 18 ++++++++++++++--- .../janggi/strategy/BoardAssemblerTest.java | 20 +++++++++---------- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index e8e2d32fd4..0e4d3711fa 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -20,8 +20,8 @@ public JanggiFlow(ApplicationView view) { public void process() { int decisionNumber = view.requestArrangementStrategyDecision(strategies); ArrangementStrategy strategy = findStrategyWithCorrespondingDecisionNumber(decisionNumber); - BoardAssembler assembler = new BoardAssembler(); - Piece[][] pieces = assembler.assemble(strategy, strategy); + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + Piece[][] pieces = assembler.assemble(); System.out.println(pieces); } diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java index 192e091768..c50b93919d 100644 --- a/src/main/java/janggi/strategy/BoardAssembler.java +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -17,13 +17,25 @@ public class BoardAssembler { private static final int DEFAULT_ROWS = 10; private static final int DEFAULT_COLS = 9; - public Piece[][] assemble(ArrangementStrategy cho, ArrangementStrategy han) { + private final ArrangementStrategy hanStrategy; + private final ArrangementStrategy choStrategy; + + private BoardAssembler(ArrangementStrategy hanStrategy, ArrangementStrategy choStrategy) { + this.hanStrategy = hanStrategy; + this.choStrategy = choStrategy; + } + + public static BoardAssembler of(ArrangementStrategy hanStrategy, ArrangementStrategy choStrategy) { + return new BoardAssembler(hanStrategy, choStrategy); + } + + public Piece[][] assemble() { Piece[][] arrangement = new Piece[DEFAULT_ROWS][DEFAULT_COLS]; setupCommonPieces(arrangement); - cho.place(arrangement, Side.CHO); - han.place(arrangement, Side.HAN); + hanStrategy.place(arrangement, Side.HAN); + choStrategy.place(arrangement, Side.CHO); return arrangement; } diff --git a/src/test/java/janggi/strategy/BoardAssemblerTest.java b/src/test/java/janggi/strategy/BoardAssemblerTest.java index 2b92216636..85c0b0e6ca 100644 --- a/src/test/java/janggi/strategy/BoardAssemblerTest.java +++ b/src/test/java/janggi/strategy/BoardAssemblerTest.java @@ -15,26 +15,24 @@ class BoardAssemblerTest { @DisplayName("공통 기물과 각 팀의 전략이 합쳐져 전체 보드를 생성한다.") void shouldAssembleFullBoard() { // given - BoardAssembler assembler = new BoardAssembler(); - ArrangementStrategy choStrategy = new MaSangMaSang(); // 마-상-마-상 (1,2,6,7) - ArrangementStrategy hanStrategy = new SangMaMaSang(); // 상-마-마-상 (1,2,6,7 다름) + ArrangementStrategy hanStrategy = new SangMaMaSang(); + ArrangementStrategy choStrategy = new MaSangMaSang(); + BoardAssembler assembler = BoardAssembler.of(hanStrategy, choStrategy); // when - Piece[][] board = assembler.assemble(choStrategy, hanStrategy); + Piece[][] board = assembler.assemble(); // then - // 1. 공통 기물 검증 (샘플) - Assertions.assertThat(board[0][0]).isInstanceOf(Cha.class); // 한 차 - Assertions.assertThat(board[9][8]).isInstanceOf(Cha.class); // 초 차 - Assertions.assertThat(board[3][0]).isInstanceOf(Jol.class); // 한 졸 + // 1. 공통 기물 검증 + Assertions.assertThat(board[0][0]).isInstanceOf(Cha.class); + Assertions.assertThat(board[9][8]).isInstanceOf(Cha.class); + Assertions.assertThat(board[3][0]).isInstanceOf(Jol.class); // 2. 전략 기물 검증 (전략이 실제로 동작했는지 확인) - // 초(Side.CHO)는 9번 행에 배치됨 Assertions.assertThat(board[9][1]).isInstanceOf(Ma.class); // MaSangMaSang의 첫 번째 마 Assertions.assertThat(board[9][2]).isInstanceOf(Sang.class); // MaSangMaSang의 첫 번째 상 - // 3. 빈 칸 검증 (기물이 없어야 할 곳) - Assertions.assertThat(board[4][0]).isNull(); // 장기판 중간은 비어있어야 함 + Assertions.assertThat(board[4][0]).isNull(); } } From 9c9605d0a9f2a55545535c2bcd0f20bbac751974 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 11:27:41 +0900 Subject: [PATCH 05/45] =?UTF-8?q?feat(BoardAssembler):=20=EB=B9=84?= =?UTF-8?q?=EC=96=B4=EC=9E=88=EB=8A=94=20=EC=9C=84=EC=B9=98=EB=A5=BC=20Emp?= =?UTF-8?q?tyPiece=20=EA=B0=9D=EC=B2=B4=EB=A1=9C=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/strategy/BoardAssembler.java | 15 ++++++++++++++- .../java/janggi/strategy/BoardAssemblerTest.java | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java index c50b93919d..7b14f923f7 100644 --- a/src/main/java/janggi/strategy/BoardAssembler.java +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -1,6 +1,8 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Cha; +import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Gung; import janggi.domain.piece.Jol; import janggi.domain.piece.Ma; @@ -8,7 +10,6 @@ import janggi.domain.piece.Po; import janggi.domain.piece.Sa; import janggi.domain.piece.Sang; -import janggi.domain.Side; import java.util.List; import java.util.function.Function; @@ -37,6 +38,8 @@ public Piece[][] assemble() { hanStrategy.place(arrangement, Side.HAN); choStrategy.place(arrangement, Side.CHO); + setupEmptyPieces(arrangement); + return arrangement; } @@ -45,6 +48,16 @@ private void setupCommonPieces(Piece[][] grid) { setUpOneSide(grid, Side.CHO); } + private void setupEmptyPieces(Piece[][] arrangement) { + for (int i = 0; i < DEFAULT_ROWS; i++) { + for (int j = 0; j < DEFAULT_COLS; j++) { + if (arrangement[i][j] == null) { + arrangement[i][j] = new EmptyPiece(); + } + } + } + } + private void setUpOneSide(Piece[][] grid, Side side) { for (DefaultPieceFactory factory : DefaultPieceFactory.values()) { setUpPiece(grid, side, factory); diff --git a/src/test/java/janggi/strategy/BoardAssemblerTest.java b/src/test/java/janggi/strategy/BoardAssemblerTest.java index 85c0b0e6ca..695133ecdd 100644 --- a/src/test/java/janggi/strategy/BoardAssemblerTest.java +++ b/src/test/java/janggi/strategy/BoardAssemblerTest.java @@ -1,6 +1,7 @@ package janggi.strategy; import janggi.domain.piece.Cha; +import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Jol; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; @@ -32,7 +33,7 @@ void shouldAssembleFullBoard() { Assertions.assertThat(board[9][1]).isInstanceOf(Ma.class); // MaSangMaSang의 첫 번째 마 Assertions.assertThat(board[9][2]).isInstanceOf(Sang.class); // MaSangMaSang의 첫 번째 상 - Assertions.assertThat(board[4][0]).isNull(); + Assertions.assertThat(board[4][0]).isInstanceOf(EmptyPiece.class); } } From 3f0af3a3c6745170debca26ec134ced877cdfdcb Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 13:39:11 +0900 Subject: [PATCH 06/45] =?UTF-8?q?refactor(Piece):=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=ED=8E=B8=EC=9D=84=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EC=9D=98=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isSameSide 메서드의 파라미터 타입을 Side 에서 Piece로 수정 --- src/main/java/janggi/domain/piece/Piece.java | 4 ++-- .../java/janggi/strategy/BoardAssembler.java | 8 ++++---- src/test/java/janggi/domain/TeamPiece.java | 15 +++++++++++++++ .../janggi/strategy/MaSangMaSangTest.java | 19 +++++++++++-------- .../janggi/strategy/MaSangSangMaTest.java | 19 +++++++++++-------- .../janggi/strategy/SangMaMaSangTest.java | 19 +++++++++++-------- .../janggi/strategy/SangMaSangMaTest.java | 19 +++++++++++-------- 7 files changed, 65 insertions(+), 38 deletions(-) create mode 100644 src/test/java/janggi/domain/TeamPiece.java diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 019f1c19bd..2562c9676e 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -11,7 +11,7 @@ protected Piece(Side side) { public abstract boolean isEmpty(); - public boolean isSameSide(Side side) { - return this.side.equals(side); + public boolean isSameSide(Piece piece) { + return this.side.equals(piece.side); } } diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java index 7b14f923f7..4e067103e7 100644 --- a/src/main/java/janggi/strategy/BoardAssembler.java +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -49,10 +49,10 @@ private void setupCommonPieces(Piece[][] grid) { } private void setupEmptyPieces(Piece[][] arrangement) { - for (int i = 0; i < DEFAULT_ROWS; i++) { - for (int j = 0; j < DEFAULT_COLS; j++) { - if (arrangement[i][j] == null) { - arrangement[i][j] = new EmptyPiece(); + for (int row = 0; row < DEFAULT_ROWS; row++) { + for (int col = 0; col < DEFAULT_COLS; col++) { + if (arrangement[row][col] == null) { + arrangement[row][col] = new EmptyPiece(); } } } diff --git a/src/test/java/janggi/domain/TeamPiece.java b/src/test/java/janggi/domain/TeamPiece.java new file mode 100644 index 0000000000..220005f1bd --- /dev/null +++ b/src/test/java/janggi/domain/TeamPiece.java @@ -0,0 +1,15 @@ +package janggi.domain; + +import janggi.domain.piece.Piece; + +public class TeamPiece extends Piece { + + public TeamPiece(Side side) { + super(side); + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/src/test/java/janggi/strategy/MaSangMaSangTest.java b/src/test/java/janggi/strategy/MaSangMaSangTest.java index e1017e3d08..1b377dbe4c 100644 --- a/src/test/java/janggi/strategy/MaSangMaSangTest.java +++ b/src/test/java/janggi/strategy/MaSangMaSangTest.java @@ -1,6 +1,7 @@ package janggi.strategy; +import janggi.domain.TeamPiece; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; @@ -18,6 +19,7 @@ void shouldPlaceMaSangMaSangWhenSideHan() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new MaSangMaSang(); Side side = Side.HAN; + Piece expectedSidePiece = new TeamPiece(side); // when strategy.place(grid, side); @@ -32,10 +34,10 @@ void shouldPlaceMaSangMaSangWhenSideHan() { Assertions.assertThat(rightMa).isInstanceOf(Ma.class); Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); } @Test @@ -45,6 +47,7 @@ void shouldPlaceMaSangMaSangWhenSideCho() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new MaSangMaSang(); Side side = Side.CHO; + Piece expectedSidePiece = new TeamPiece(side); // when strategy.place(grid, side); @@ -59,9 +62,9 @@ void shouldPlaceMaSangMaSangWhenSideCho() { Assertions.assertThat(rightMa).isInstanceOf(Ma.class); Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); } } diff --git a/src/test/java/janggi/strategy/MaSangSangMaTest.java b/src/test/java/janggi/strategy/MaSangSangMaTest.java index f1e6aa8905..12fee056de 100644 --- a/src/test/java/janggi/strategy/MaSangSangMaTest.java +++ b/src/test/java/janggi/strategy/MaSangSangMaTest.java @@ -1,6 +1,7 @@ package janggi.strategy; +import janggi.domain.TeamPiece; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; @@ -18,6 +19,7 @@ void shouldPlaceMaSangSangMaWhenSideHan() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new MaSangSangMa(); Side side = Side.HAN; + Piece expectedSidePiece = new TeamPiece(side); // when strategy.place(grid, side); @@ -32,10 +34,10 @@ void shouldPlaceMaSangSangMaWhenSideHan() { Assertions.assertThat(rightSang).isInstanceOf(Sang.class); Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); } @Test @@ -45,6 +47,7 @@ void shouldPlaceMaSangSangMaWhenSideCho() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new MaSangSangMa(); Side side = Side.CHO; + Piece expectedSidePiece = new TeamPiece(side); // when strategy.place(grid, side); @@ -59,9 +62,9 @@ void shouldPlaceMaSangSangMaWhenSideCho() { Assertions.assertThat(rightSang).isInstanceOf(Sang.class); Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); } } diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java index 5bea0a6b19..3a7ee38b51 100644 --- a/src/test/java/janggi/strategy/SangMaMaSangTest.java +++ b/src/test/java/janggi/strategy/SangMaMaSangTest.java @@ -1,6 +1,7 @@ package janggi.strategy; +import janggi.domain.TeamPiece; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; @@ -18,6 +19,7 @@ void shouldPlaceSangMaMaSangWhenSideHan() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new SangMaMaSang(); Side side = Side.HAN; + Piece expectedSidePiece = new TeamPiece(side); // when strategy.place(grid, side); @@ -32,10 +34,10 @@ void shouldPlaceSangMaMaSangWhenSideHan() { Assertions.assertThat(rightMa).isInstanceOf(Ma.class); Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); } @Test @@ -45,6 +47,7 @@ void shouldPlaceSangMaMaSangWhenSideCho() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new SangMaMaSang(); Side side = Side.CHO; + Piece expectedSidePiece = new TeamPiece(side); // when strategy.place(grid, side); @@ -59,9 +62,9 @@ void shouldPlaceSangMaMaSangWhenSideCho() { Assertions.assertThat(rightMa).isInstanceOf(Ma.class); Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); } } diff --git a/src/test/java/janggi/strategy/SangMaSangMaTest.java b/src/test/java/janggi/strategy/SangMaSangMaTest.java index 2ad9e3eec7..36b771646f 100644 --- a/src/test/java/janggi/strategy/SangMaSangMaTest.java +++ b/src/test/java/janggi/strategy/SangMaSangMaTest.java @@ -1,6 +1,7 @@ package janggi.strategy; +import janggi.domain.TeamPiece; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; @@ -18,6 +19,7 @@ void shouldPlaceSangMaSangMaWhenSideHan() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new SangMaSangMa(); Side side = Side.HAN; + Piece expectedSidePiece = new TeamPiece(side); // when strategy.place(grid, side); @@ -32,10 +34,10 @@ void shouldPlaceSangMaSangMaWhenSideHan() { Assertions.assertThat(rightSang).isInstanceOf(Sang.class); Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); } @Test @@ -45,6 +47,7 @@ void shouldPlaceSangMaSangMaWhenSideCho() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new SangMaSangMa(); Side side = Side.CHO; + Piece expectedSidePiece = new TeamPiece(side); // when strategy.place(grid, side); @@ -59,9 +62,9 @@ void shouldPlaceSangMaSangMaWhenSideCho() { Assertions.assertThat(rightSang).isInstanceOf(Sang.class); Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); } } From 381957cdbd08b928a9a0c187bb9fc6997000c698 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 13:42:27 +0900 Subject: [PATCH 07/45] =?UTF-8?q?feat(Board):=20=EC=9E=A5=EA=B8=B0?= =?UTF-8?q?=ED=8C=90=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=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 - BoardAssembler에게 전달받은 2차원 배열 정보를 조합해 Board 객체를 생성함 - Board의 Map 내용을 2차원 List 형태로 반환함 --- src/main/java/janggi/domain/board/Board.java | 52 ++++++++++++++++++++ src/test/java/janggi/domain/BoardTest.java | 33 +++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/main/java/janggi/domain/board/Board.java create mode 100644 src/test/java/janggi/domain/BoardTest.java diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java new file mode 100644 index 0000000000..25a30cdaa9 --- /dev/null +++ b/src/main/java/janggi/domain/board/Board.java @@ -0,0 +1,52 @@ +package janggi.domain.board; + +import janggi.domain.piece.Piece; +import janggi.strategy.BoardAssembler; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Board { + private final Map boardState; + private final int height; + private final int width; + + private Board(Map boardState, int height, int width) { + this.boardState = boardState; + this.height = height; + this.width = width; + } + + public static Board create(BoardAssembler assembler) { + Piece[][] pieces = assembler.assemble(); + Map boardState = new HashMap<>(); + + int height = pieces.length; + int width = pieces[0].length; + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + boardState.put(new Location(row, col), pieces[row][col]); + } + } + + return new Board(boardState, height, width); + } + + public List> to2DArray() { + List> pieces = new ArrayList<>(); + for (int row = 0; row < height; row++) { + List line = new ArrayList<>(); + for (int col = 0; col < width; col++) { + Piece piece = boardState.get(new Location(row, col)); + line.add(piece); + } + pieces.add(List.copyOf(line)); + } + return List.copyOf(pieces); + } + + private record Location(int row, int col) { + } +} diff --git a/src/test/java/janggi/domain/BoardTest.java b/src/test/java/janggi/domain/BoardTest.java new file mode 100644 index 0000000000..1a25288352 --- /dev/null +++ b/src/test/java/janggi/domain/BoardTest.java @@ -0,0 +1,33 @@ +package janggi.domain; + +import janggi.domain.board.Board; +import janggi.domain.piece.Piece; +import janggi.strategy.BoardAssembler; +import janggi.strategy.MaSangMaSang; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BoardTest { + + @Test + @DisplayName("보드가 전략에 맞춰 정상적으로 생성된다.") + void shouldReturnBoardWithSelectedArrangementStrategy() { + BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Board board = Board.create(assembler); + List> pieces = board.to2DArray(); + + Piece[][] assemble = assembler.assemble(); + + for (int row = 0; row < assemble.length; row++) { + for (int col = 0; col < assemble[row].length; col++) { + Piece result = assemble[row][col]; + Piece expectedPiece = pieces.get(row).get(col); + Assertions.assertThat(result).isInstanceOf(expectedPiece.getClass()); + Assertions.assertThat(result.isSameSide(expectedPiece)).isTrue(); + + } + } + } +} From c1c423e55441eda63cd20e19af357eef017a44ac Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 14:36:13 +0900 Subject: [PATCH 08/45] =?UTF-8?q?feat:=20=EC=9E=A5=EA=B8=B0=ED=8C=90=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=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 - 장기 기물에 기물명 필드를 추가함 - 팀(Side)의 팀명 필드를 추가함 - 각 팀의 장기 초기화 전략을 선택할 수 있도록 흐름을 수정함 --- README.md | 2 +- .../java/janggi/controller/JanggiFlow.java | 29 ++++++++++++++----- src/main/java/janggi/domain/Side.java | 15 ++++++++-- src/main/java/janggi/domain/piece/Cha.java | 5 +++- .../java/janggi/domain/piece/EmptyPiece.java | 4 ++- src/main/java/janggi/domain/piece/Gung.java | 5 +++- src/main/java/janggi/domain/piece/Jol.java | 5 +++- src/main/java/janggi/domain/piece/Ma.java | 5 +++- src/main/java/janggi/domain/piece/Piece.java | 8 ++++- src/main/java/janggi/domain/piece/Po.java | 5 +++- src/main/java/janggi/domain/piece/Sa.java | 5 +++- src/main/java/janggi/domain/piece/Sang.java | 5 +++- .../janggi/strategy/ArrangementStrategy.java | 2 +- .../java/janggi/strategy/MaSangMaSang.java | 2 +- .../java/janggi/strategy/MaSangSangMa.java | 2 +- .../java/janggi/strategy/SangMaMaSang.java | 2 +- .../java/janggi/strategy/SangMaSangMa.java | 2 +- .../java/janggi/view/ApplicationView.java | 17 +++++++++-- src/main/java/janggi/view/ConsoleWriter.java | 15 ++++++++++ src/main/java/janggi/view/Output.java | 4 +++ src/test/java/janggi/domain/TeamPiece.java | 4 ++- 21 files changed, 114 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index aa732cd8bd..50a7ec70ab 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,4 @@ - [x] 정수 범위를 벗어난 경우 예외 처리 - [x] 도메인 규칙 검증 - [x] 입력값이 선택지 범위에 포함되지 않는 경우 예외 처리 -- [ ] 전략이 반영된 장기판 상태를 출력한다. +- [x] 전략이 반영된 장기판 상태를 출력한다. diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 0e4d3711fa..a832a682ce 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -1,28 +1,41 @@ package janggi.controller; -import janggi.domain.piece.Piece; +import janggi.domain.Side; +import janggi.domain.board.Board; import janggi.strategy.ArrangementStrategy; import janggi.strategy.BoardAssembler; +import janggi.strategy.MaSangMaSang; +import janggi.strategy.MaSangSangMa; +import janggi.strategy.SangMaMaSang; import janggi.strategy.SangMaSangMa; import janggi.view.ApplicationView; import java.util.List; public class JanggiFlow { private final ApplicationView view; - private final List strategies; public JanggiFlow(ApplicationView view) { this.view = view; - this.strategies = List.of(new SangMaSangMa()); + this.strategies = List.of( + new MaSangMaSang(), + new MaSangSangMa(), + new SangMaMaSang(), + new SangMaSangMa() + ); } public void process() { - int decisionNumber = view.requestArrangementStrategyDecision(strategies); - ArrangementStrategy strategy = findStrategyWithCorrespondingDecisionNumber(decisionNumber); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); - Piece[][] pieces = assembler.assemble(); - System.out.println(pieces); + ArrangementStrategy hanStrategy = askStrategy(Side.HAN); + ArrangementStrategy choStrategy = askStrategy(Side.CHO); + Board board = Board.create(BoardAssembler.of(hanStrategy, choStrategy)); + + view.responseBoardArray(board.to2DArray()); + } + + public ArrangementStrategy askStrategy(Side side) { + int decisionNumber = view.requestArrangementStrategyDecision(side, strategies); + return findStrategyWithCorrespondingDecisionNumber(decisionNumber); } private ArrangementStrategy findStrategyWithCorrespondingDecisionNumber(int decisionNumber) { diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java index 7e6b24c9d2..b9fe5eecfe 100644 --- a/src/main/java/janggi/domain/Side.java +++ b/src/main/java/janggi/domain/Side.java @@ -1,8 +1,17 @@ package janggi.domain; public enum Side { + HAN("한"), + CHO("초"), + NONE("없음"); - CHO, - HAN, - NONE + private final String name; + + Side(String name) { + this.name = name; + } + + public String getName() { + return name; + } } diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index 314a1193ca..7b7d5ccc77 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -3,8 +3,11 @@ import janggi.domain.Side; public class Cha extends Piece { + + private static final String PIECE_NAME = "차"; + public Cha(Side side) { - super(side); + super(PIECE_NAME, side); } @Override diff --git a/src/main/java/janggi/domain/piece/EmptyPiece.java b/src/main/java/janggi/domain/piece/EmptyPiece.java index 880b427dd7..6a58372443 100644 --- a/src/main/java/janggi/domain/piece/EmptyPiece.java +++ b/src/main/java/janggi/domain/piece/EmptyPiece.java @@ -4,8 +4,10 @@ public class EmptyPiece extends Piece { + private static final String PIECE_NAME = "ㆍ"; + public EmptyPiece() { - super(Side.NONE); + super(PIECE_NAME, Side.NONE); } @Override diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index fbecb37abf..1a7cd7d8c9 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -3,8 +3,11 @@ import janggi.domain.Side; public class Gung extends Piece { + + private static final String PIECE_NAME = "궁"; + public Gung(Side side) { - super(side); + super(PIECE_NAME, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Jol.java b/src/main/java/janggi/domain/piece/Jol.java index af834c0447..62e960f779 100644 --- a/src/main/java/janggi/domain/piece/Jol.java +++ b/src/main/java/janggi/domain/piece/Jol.java @@ -3,8 +3,11 @@ import janggi.domain.Side; public class Jol extends Piece { + + private static final String PIECE_NAME = "졸"; + public Jol(Side side) { - super(side); + super(PIECE_NAME, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index 7df9f73de6..bea623e7e6 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -3,8 +3,11 @@ import janggi.domain.Side; public class Ma extends Piece { + + private static final String PIECE_NAME = "마"; + public Ma(Side side) { - super(side); + super(PIECE_NAME, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 2562c9676e..5b36123ae4 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -3,9 +3,11 @@ import janggi.domain.Side; public abstract class Piece { + protected final String name; protected final Side side; - protected Piece(Side side) { + protected Piece(String name, Side side) { + this.name = name; this.side = side; } @@ -14,4 +16,8 @@ protected Piece(Side side) { public boolean isSameSide(Piece piece) { return this.side.equals(piece.side); } + + public String getName() { + return name; + } } diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 2f49e8f2ed..9d94a9f804 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -3,8 +3,11 @@ import janggi.domain.Side; public class Po extends Piece { + + private static final String PIECE_NAME = "포"; + public Po(Side side) { - super(side); + super(PIECE_NAME, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index 90533cb278..e5ea0b979c 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -3,8 +3,11 @@ import janggi.domain.Side; public class Sa extends Piece { + + private static final String PIECE_NAME = "사"; + public Sa(Side side) { - super(side); + super(PIECE_NAME, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index a8dc8bf2d6..e282a70095 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -3,8 +3,11 @@ import janggi.domain.Side; public class Sang extends Piece { + + private static final String PIECE_NAME = "상"; + public Sang(Side side) { - super(side); + super(PIECE_NAME, side); } @Override diff --git a/src/main/java/janggi/strategy/ArrangementStrategy.java b/src/main/java/janggi/strategy/ArrangementStrategy.java index cb92f07412..1fcc4d5147 100644 --- a/src/main/java/janggi/strategy/ArrangementStrategy.java +++ b/src/main/java/janggi/strategy/ArrangementStrategy.java @@ -1,7 +1,7 @@ package janggi.strategy; -import janggi.domain.piece.Piece; import janggi.domain.Side; +import janggi.domain.piece.Piece; public abstract class ArrangementStrategy { protected final StrategyLabel label; diff --git a/src/main/java/janggi/strategy/MaSangMaSang.java b/src/main/java/janggi/strategy/MaSangMaSang.java index 2e473813d3..cb8c54fae4 100644 --- a/src/main/java/janggi/strategy/MaSangMaSang.java +++ b/src/main/java/janggi/strategy/MaSangMaSang.java @@ -1,9 +1,9 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; -import janggi.domain.Side; public class MaSangMaSang extends ArrangementStrategy { diff --git a/src/main/java/janggi/strategy/MaSangSangMa.java b/src/main/java/janggi/strategy/MaSangSangMa.java index af3405193d..143fded17b 100644 --- a/src/main/java/janggi/strategy/MaSangSangMa.java +++ b/src/main/java/janggi/strategy/MaSangSangMa.java @@ -1,9 +1,9 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; -import janggi.domain.Side; public class MaSangSangMa extends ArrangementStrategy { diff --git a/src/main/java/janggi/strategy/SangMaMaSang.java b/src/main/java/janggi/strategy/SangMaMaSang.java index 2ca2446e34..b9ce0fa8a0 100644 --- a/src/main/java/janggi/strategy/SangMaMaSang.java +++ b/src/main/java/janggi/strategy/SangMaMaSang.java @@ -1,9 +1,9 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; -import janggi.domain.Side; public class SangMaMaSang extends ArrangementStrategy { diff --git a/src/main/java/janggi/strategy/SangMaSangMa.java b/src/main/java/janggi/strategy/SangMaSangMa.java index 97b5c6a82e..1789520ac5 100644 --- a/src/main/java/janggi/strategy/SangMaSangMa.java +++ b/src/main/java/janggi/strategy/SangMaSangMa.java @@ -1,9 +1,9 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; -import janggi.domain.Side; public class SangMaSangMa extends ArrangementStrategy { diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java index ddd1a0fed2..307b8d121a 100644 --- a/src/main/java/janggi/view/ApplicationView.java +++ b/src/main/java/janggi/view/ApplicationView.java @@ -1,5 +1,7 @@ package janggi.view; +import janggi.domain.Side; +import janggi.domain.piece.Piece; import janggi.strategy.ArrangementStrategy; import java.util.List; import java.util.function.Supplier; @@ -14,8 +16,9 @@ public ApplicationView(Output outputWriter, Input inputReader) { this.inputReader = inputReader; } - public int requestArrangementStrategyDecision(List strategies) { - outputWriter.printPromptMessage("초기화 전략 번호를 입력해주세요."); + public int + requestArrangementStrategyDecision(Side side, List strategies) { + outputWriter.printPromptMessage(side.getName() + "팀의 초기화 전략 번호를 입력해주세요."); for (ArrangementStrategy strategy : strategies) { String strategyDecisionOption = String.format("%d. %s", strategy.decisionNumber(), strategy.name()); @@ -25,6 +28,16 @@ public int requestArrangementStrategyDecision(List strategi return retry(inputReader::readInteger); } + public void responseBoardArray(List> board2DArray) { + List> stringMatrix = board2DArray.stream() + .map(row -> row.stream() + .map(Piece::getName) + .toList() + ).toList(); + + outputWriter.printStringMatrix(stringMatrix); + } + private T retry(Supplier supplier) { while (true) { try { diff --git a/src/main/java/janggi/view/ConsoleWriter.java b/src/main/java/janggi/view/ConsoleWriter.java index 137fe6f73b..b0fe95e85d 100644 --- a/src/main/java/janggi/view/ConsoleWriter.java +++ b/src/main/java/janggi/view/ConsoleWriter.java @@ -1,5 +1,7 @@ package janggi.view; +import java.util.List; + public class ConsoleWriter implements Output { @Override public void printPromptMessage(String promptMessage) { @@ -10,4 +12,17 @@ public void printPromptMessage(String promptMessage) { public void printErrorMessage(RuntimeException e) { System.out.println("[ERROR] " + e.getMessage()); } + + @Override + public void printStringMatrix(List> matrix) { + StringBuilder matrixSnapshot = new StringBuilder(); + for (List strings : matrix) { + for (String string : strings) { + matrixSnapshot.append(String.format("%-2s", string)); + } + matrixSnapshot.append("\n"); + } + + System.out.println(matrixSnapshot); + } } diff --git a/src/main/java/janggi/view/Output.java b/src/main/java/janggi/view/Output.java index aabef6cce7..beca7eca61 100644 --- a/src/main/java/janggi/view/Output.java +++ b/src/main/java/janggi/view/Output.java @@ -1,7 +1,11 @@ package janggi.view; +import java.util.List; + public interface Output { void printPromptMessage(String promptMessage); void printErrorMessage(RuntimeException e); + + void printStringMatrix(List> matrix); } diff --git a/src/test/java/janggi/domain/TeamPiece.java b/src/test/java/janggi/domain/TeamPiece.java index 220005f1bd..94464dcc59 100644 --- a/src/test/java/janggi/domain/TeamPiece.java +++ b/src/test/java/janggi/domain/TeamPiece.java @@ -4,8 +4,10 @@ public class TeamPiece extends Piece { + private static final String PIECE_NAME = "test"; + public TeamPiece(Side side) { - super(side); + super(PIECE_NAME, side); } @Override From b750b23c3adf1c3fef9e79427ca2cf2f153f9f82 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 14:56:37 +0900 Subject: [PATCH 09/45] =?UTF-8?q?refactor(JolByeong):=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=AA=85=EC=9D=84=20=ED=8F=AC=EA=B4=84=EC=A0=81=20?= =?UTF-8?q?=EC=9D=98=EB=AF=B8=EB=A5=BC=20=EA=B0=96=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 양 팀의 기물명을 모두 표현할 수 있도록 클래스명을 Jol 에서 JolByeong으로 변경함 --- src/main/java/janggi/domain/piece/Jol.java | 17 ------------ .../java/janggi/domain/piece/Jolbyeong.java | 27 +++++++++++++++++++ .../java/janggi/strategy/BoardAssembler.java | 4 +-- .../janggi/strategy/BoardAssemblerTest.java | 4 +-- 4 files changed, 31 insertions(+), 21 deletions(-) delete mode 100644 src/main/java/janggi/domain/piece/Jol.java create mode 100644 src/main/java/janggi/domain/piece/Jolbyeong.java diff --git a/src/main/java/janggi/domain/piece/Jol.java b/src/main/java/janggi/domain/piece/Jol.java deleted file mode 100644 index 62e960f779..0000000000 --- a/src/main/java/janggi/domain/piece/Jol.java +++ /dev/null @@ -1,17 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Side; - -public class Jol extends Piece { - - private static final String PIECE_NAME = "졸"; - - public Jol(Side side) { - super(PIECE_NAME, side); - } - - @Override - public boolean isEmpty() { - return false; - } -} diff --git a/src/main/java/janggi/domain/piece/Jolbyeong.java b/src/main/java/janggi/domain/piece/Jolbyeong.java new file mode 100644 index 0000000000..1240e698ae --- /dev/null +++ b/src/main/java/janggi/domain/piece/Jolbyeong.java @@ -0,0 +1,27 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public class Jolbyeong extends Piece { + + private static final String PIECE_NAME = "졸병"; + private static final String CHO_PIECE_NAME = "졸"; + private static final String HAN_PIECE_NAME = "병"; + + public Jolbyeong(Side side) { + super(PIECE_NAME, side); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String getName() { + if (side.equals(Side.HAN)) { + return HAN_PIECE_NAME; + } + return CHO_PIECE_NAME; + } +} diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java index 4e067103e7..33f88f7ef0 100644 --- a/src/main/java/janggi/strategy/BoardAssembler.java +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -4,7 +4,7 @@ import janggi.domain.piece.Cha; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Gung; -import janggi.domain.piece.Jol; +import janggi.domain.piece.Jolbyeong; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Po; @@ -78,7 +78,7 @@ private enum DefaultPieceFactory { SA(0, List.of(3, 5), Sa::new), GUNG(1, List.of(4), Gung::new), PO(2, List.of(1, 7), Po::new), - JOL(3, List.of(0, 2, 4, 6, 8), Jol::new); + JOL(3, List.of(0, 2, 4, 6, 8), Jolbyeong::new); private final int row; private final List cols; diff --git a/src/test/java/janggi/strategy/BoardAssemblerTest.java b/src/test/java/janggi/strategy/BoardAssemblerTest.java index 695133ecdd..7666709460 100644 --- a/src/test/java/janggi/strategy/BoardAssemblerTest.java +++ b/src/test/java/janggi/strategy/BoardAssemblerTest.java @@ -2,7 +2,7 @@ import janggi.domain.piece.Cha; import janggi.domain.piece.EmptyPiece; -import janggi.domain.piece.Jol; +import janggi.domain.piece.Jolbyeong; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; @@ -27,7 +27,7 @@ void shouldAssembleFullBoard() { // 1. 공통 기물 검증 Assertions.assertThat(board[0][0]).isInstanceOf(Cha.class); Assertions.assertThat(board[9][8]).isInstanceOf(Cha.class); - Assertions.assertThat(board[3][0]).isInstanceOf(Jol.class); + Assertions.assertThat(board[3][0]).isInstanceOf(Jolbyeong.class); // 2. 전략 기물 검증 (전략이 실제로 동작했는지 확인) Assertions.assertThat(board[9][1]).isInstanceOf(Ma.class); // MaSangMaSang의 첫 번째 마 From d699f786680311a9e5433a2c30b696e07f845831 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 14:57:32 +0900 Subject: [PATCH 10/45] =?UTF-8?q?style:=20=EA=B0=9C=ED=96=89=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/controller/JanggiFlow.java | 1 + src/main/java/janggi/domain/Side.java | 1 + src/main/java/janggi/domain/board/Board.java | 1 + src/main/java/janggi/domain/piece/EmptyPiece.java | 1 - src/main/java/janggi/domain/piece/Ma.java | 1 - src/main/java/janggi/domain/piece/Piece.java | 1 + src/main/java/janggi/strategy/ArrangementStrategy.java | 1 + src/main/java/janggi/strategy/MaSangSangMa.java | 1 - src/main/java/janggi/view/ConsoleReader.java | 1 + src/main/java/janggi/view/ConsoleWriter.java | 1 + src/main/java/janggi/view/Input.java | 1 + src/main/java/janggi/view/Output.java | 1 + 12 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index a832a682ce..43c79bc86a 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -12,6 +12,7 @@ import java.util.List; public class JanggiFlow { + private final ApplicationView view; private final List strategies; diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java index b9fe5eecfe..4205190997 100644 --- a/src/main/java/janggi/domain/Side.java +++ b/src/main/java/janggi/domain/Side.java @@ -1,6 +1,7 @@ package janggi.domain; public enum Side { + HAN("한"), CHO("초"), NONE("없음"); diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 25a30cdaa9..b4b9d60fd5 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -8,6 +8,7 @@ import java.util.Map; public class Board { + private final Map boardState; private final int height; private final int width; diff --git a/src/main/java/janggi/domain/piece/EmptyPiece.java b/src/main/java/janggi/domain/piece/EmptyPiece.java index 6a58372443..646c2213f7 100644 --- a/src/main/java/janggi/domain/piece/EmptyPiece.java +++ b/src/main/java/janggi/domain/piece/EmptyPiece.java @@ -14,5 +14,4 @@ public EmptyPiece() { public boolean isEmpty() { return true; } - } diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index bea623e7e6..a3cc2db830 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -14,5 +14,4 @@ public Ma(Side side) { public boolean isEmpty() { return false; } - } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 5b36123ae4..533d287b44 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -3,6 +3,7 @@ import janggi.domain.Side; public abstract class Piece { + protected final String name; protected final Side side; diff --git a/src/main/java/janggi/strategy/ArrangementStrategy.java b/src/main/java/janggi/strategy/ArrangementStrategy.java index 1fcc4d5147..a4dd8ab1db 100644 --- a/src/main/java/janggi/strategy/ArrangementStrategy.java +++ b/src/main/java/janggi/strategy/ArrangementStrategy.java @@ -4,6 +4,7 @@ import janggi.domain.piece.Piece; public abstract class ArrangementStrategy { + protected final StrategyLabel label; protected ArrangementStrategy(StrategyLabel label) { diff --git a/src/main/java/janggi/strategy/MaSangSangMa.java b/src/main/java/janggi/strategy/MaSangSangMa.java index 143fded17b..d4537a76ed 100644 --- a/src/main/java/janggi/strategy/MaSangSangMa.java +++ b/src/main/java/janggi/strategy/MaSangSangMa.java @@ -20,5 +20,4 @@ public void place(Piece[][] board, Side side) { board[row][6] = new Sang(side); board[row][7] = new Ma(side); } - } diff --git a/src/main/java/janggi/view/ConsoleReader.java b/src/main/java/janggi/view/ConsoleReader.java index 2ed40908f9..bdfac06d61 100644 --- a/src/main/java/janggi/view/ConsoleReader.java +++ b/src/main/java/janggi/view/ConsoleReader.java @@ -3,6 +3,7 @@ import java.util.Scanner; public class ConsoleReader implements Input { + private static final String NUMERIC_FORMAT_REGEX = "-?\\d+"; private final Scanner scanner; diff --git a/src/main/java/janggi/view/ConsoleWriter.java b/src/main/java/janggi/view/ConsoleWriter.java index b0fe95e85d..4682cfe4be 100644 --- a/src/main/java/janggi/view/ConsoleWriter.java +++ b/src/main/java/janggi/view/ConsoleWriter.java @@ -3,6 +3,7 @@ import java.util.List; public class ConsoleWriter implements Output { + @Override public void printPromptMessage(String promptMessage) { System.out.println(promptMessage); diff --git a/src/main/java/janggi/view/Input.java b/src/main/java/janggi/view/Input.java index 82faff628e..337a8db1a6 100644 --- a/src/main/java/janggi/view/Input.java +++ b/src/main/java/janggi/view/Input.java @@ -1,5 +1,6 @@ package janggi.view; public interface Input { + int readInteger(); } diff --git a/src/main/java/janggi/view/Output.java b/src/main/java/janggi/view/Output.java index beca7eca61..a467a70693 100644 --- a/src/main/java/janggi/view/Output.java +++ b/src/main/java/janggi/view/Output.java @@ -3,6 +3,7 @@ import java.util.List; public interface Output { + void printPromptMessage(String promptMessage); void printErrorMessage(RuntimeException e); From 8f0e51755c2000d467f319b5c8f8cb72f94b0529 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 15:13:49 +0900 Subject: [PATCH 11/45] =?UTF-8?q?docs(README):=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=ED=81=B4=201:=202=EB=8B=A8=EA=B3=84=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기물 이동 규칙을 주요 게임 규칙으로 추가 --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 50a7ec70ab..da0fd4d1fc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,36 @@ --- -## **🚀 기능 요구 사항** +## 💡 주요 게임 규칙 + +### 기물 이동 규칙 + +- **궁, 사** + - 궁궐 안에서만 방향 상관없이 1칸씩 이동 + - 뛰어넘기 불가 + - 시작 위치 고정 +- **상** + - 앞/옆/뒤 1칸 + 대각선 2칸 + - 뛰어넘기 불가 + - 마와 위치 변경 가능 +- **마** + - 앞/옆/뒤 1칸 + 대각선 1칸 + - 뛰어넘기 불가 + - 상과 위치 변경 가능 +- **차** + - 직선으로 원하는 만큼 이동 + - 뛰어넘기 불가 + - 시작 위치 고정 +- **포** + - 앞/옆/뒤 원하는 만큼 이동 + - 포를 제외한 기물을 1개 뛰어넘어야만 이동 + - 시작 위치 고정 +- **졸/병** + - 앞/옆으로만 1칸 전진 + - 뛰어넘기 불가 + - 시작 위치 고정 + +## 🚀 기능 요구 사항 ### 1.1 **보드 초기화** @@ -19,3 +48,20 @@ - [x] 도메인 규칙 검증 - [x] 입력값이 선택지 범위에 포함되지 않는 경우 예외 처리 - [x] 전략이 반영된 장기판 상태를 출력한다. + +### 1.2 기물 이동 + +- [ ] 이동할 기물의 위치와 이동할 위치를 사용자로부터 입력받는다. + - [ ] 사용자 입력값 유효성 검사 + - [ ] 공백인 경우 예외 처리 + - [ ] `,` 로 구분된 좌표 형식이 아닌 경우 예외 처리 + - [ ] 좌표값이 숫자가 아닌 경우 예외 처리 + - [ ] 좌표값이 정수 범위를 벗어난 경우 예외 처리 + - [ ] 도메인 규칙 검증 + - [ ] 좌표값이 보드판의 범위를 벗어난 경우 예외 처리 + - [ ] 해당 좌표에 이동시키고자 하는 기물이 존재하지 않는 경우 예외 처리 +- [ ] 기물을 이동시킨다. + - [ ] 도메인 규칙 검증 + - [ ] 기물 규칙에 따라 이동할 위치에 도달할 수 없는 경우 예외 처리 + - [ ] 이동할 위치의 기존 기물은 제거된다. +- [ ] 이동이 반영된 장기판 상태를 출력한다. From 748f71419fe43172ed2aff790cf03f8b396cb259 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 20:21:32 +0900 Subject: [PATCH 12/45] =?UTF-8?q?refactor:=20=EC=A0=95=EC=88=98=EB=A5=BC?= =?UTF-8?q?=20=EC=9D=BD=EC=96=B4=EC=98=A4=EB=8A=94=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - readInteger를 readInt로 변경: Integer는 참조 타입 클래스명으로, input 값으로 null이 들어올 수 있다는 의미를 내포할 수 있으므로 메서드의 실제 반환 타입인 int로 수정함 --- src/main/java/janggi/view/ApplicationView.java | 5 ++--- src/main/java/janggi/view/ConsoleReader.java | 2 +- src/main/java/janggi/view/Input.java | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java index 307b8d121a..465eeb6b2f 100644 --- a/src/main/java/janggi/view/ApplicationView.java +++ b/src/main/java/janggi/view/ApplicationView.java @@ -16,8 +16,7 @@ public ApplicationView(Output outputWriter, Input inputReader) { this.inputReader = inputReader; } - public int - requestArrangementStrategyDecision(Side side, List strategies) { + public int requestArrangementStrategyDecision(Side side, List strategies) { outputWriter.printPromptMessage(side.getName() + "팀의 초기화 전략 번호를 입력해주세요."); for (ArrangementStrategy strategy : strategies) { @@ -25,7 +24,7 @@ public ApplicationView(Output outputWriter, Input inputReader) { outputWriter.printPromptMessage(strategyDecisionOption); } - return retry(inputReader::readInteger); + return retry(inputReader::readInt); } public void responseBoardArray(List> board2DArray) { diff --git a/src/main/java/janggi/view/ConsoleReader.java b/src/main/java/janggi/view/ConsoleReader.java index bdfac06d61..226cae0b51 100644 --- a/src/main/java/janggi/view/ConsoleReader.java +++ b/src/main/java/janggi/view/ConsoleReader.java @@ -13,7 +13,7 @@ public ConsoleReader() { } @Override - public int readInteger() { + public int readInt() { String input = scanner.nextLine().trim(); validateIsBlank(input); validateIsNumeric(input); diff --git a/src/main/java/janggi/view/Input.java b/src/main/java/janggi/view/Input.java index 337a8db1a6..167897b759 100644 --- a/src/main/java/janggi/view/Input.java +++ b/src/main/java/janggi/view/Input.java @@ -2,5 +2,5 @@ public interface Input { - int readInteger(); + int readInt(); } From 4060f819174acfab99627f1a431c84825be6dbcf Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 26 Mar 2026 20:23:48 +0900 Subject: [PATCH 13/45] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=A2=8C=ED=91=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=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 | 18 +++--- .../java/janggi/controller/JanggiFlow.java | 40 ++++++++++++- src/main/java/janggi/domain/Location.java | 19 ++++++ src/main/java/janggi/domain/Side.java | 7 +++ src/main/java/janggi/domain/board/Board.java | 12 +++- .../java/janggi/view/ApplicationView.java | 20 +++++++ src/main/java/janggi/view/ConsoleReader.java | 16 +++++ src/main/java/janggi/view/Input.java | 4 ++ src/test/java/janggi/domain/BoardTest.java | 60 ++++++++++++++++++- src/test/java/janggi/domain/TeamPiece.java | 2 +- 10 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 src/main/java/janggi/domain/Location.java diff --git a/README.md b/README.md index da0fd4d1fc..ef276dd3bf 100644 --- a/README.md +++ b/README.md @@ -51,15 +51,15 @@ ### 1.2 기물 이동 -- [ ] 이동할 기물의 위치와 이동할 위치를 사용자로부터 입력받는다. - - [ ] 사용자 입력값 유효성 검사 - - [ ] 공백인 경우 예외 처리 - - [ ] `,` 로 구분된 좌표 형식이 아닌 경우 예외 처리 - - [ ] 좌표값이 숫자가 아닌 경우 예외 처리 - - [ ] 좌표값이 정수 범위를 벗어난 경우 예외 처리 - - [ ] 도메인 규칙 검증 - - [ ] 좌표값이 보드판의 범위를 벗어난 경우 예외 처리 - - [ ] 해당 좌표에 이동시키고자 하는 기물이 존재하지 않는 경우 예외 처리 +- [x] 이동할 기물의 위치와 이동할 위치를 사용자로부터 입력받는다. + - [x] 사용자 입력값 유효성 검사 + - [x] 공백인 경우 예외 처리 + - [x] `,` 로 구분된 좌표 형식이 아닌 경우 예외 처리 + - [x] 좌표값이 숫자가 아닌 경우 예외 처리 + - [x] 좌표값이 정수 범위를 벗어난 경우 예외 처리 + - [x] 도메인 규칙 검증 + - [x] 좌표값이 보드판의 범위를 벗어난 경우 예외 처리 + - [x] 해당 좌표에 이동시키고자 하는 기물이 존재하지 않는 경우 예외 처리 - [ ] 기물을 이동시킨다. - [ ] 도메인 규칙 검증 - [ ] 기물 규칙에 따라 이동할 위치에 도달할 수 없는 경우 예외 처리 diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 43c79bc86a..e33fa3cbf8 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -1,5 +1,6 @@ package janggi.controller; +import janggi.domain.Location; import janggi.domain.Side; import janggi.domain.board.Board; import janggi.strategy.ArrangementStrategy; @@ -10,6 +11,7 @@ import janggi.strategy.SangMaSangMa; import janggi.view.ApplicationView; import java.util.List; +import java.util.function.Supplier; public class JanggiFlow { @@ -32,9 +34,33 @@ public void process() { Board board = Board.create(BoardAssembler.of(hanStrategy, choStrategy)); view.responseBoardArray(board.to2DArray()); + + Side current = Side.HAN; + for (int i = 0; i < 2; i++) { + view.responseCurrentSide(current); + Location locationOfPiece = retry(() -> askLocationOfPiece(board)); + Location locationToMove = retry(() -> askLocationToMove(board)); + + current = current.switchTurn(); + } + } + + private Location askLocationOfPiece(Board board) { + List locationOfPiece = view.requestLocationOfPiece(); + Location verifiedLocation = Location.from(locationOfPiece); + board.validateLocation(verifiedLocation); + board.validatePieceExist(verifiedLocation); + return verifiedLocation; + } + + private Location askLocationToMove(Board board) { + List locationToMove = view.requestLocationToMove(); + Location verifiedLocation = Location.from(locationToMove); + board.validateLocation(verifiedLocation); + return verifiedLocation; } - public ArrangementStrategy askStrategy(Side side) { + private ArrangementStrategy askStrategy(Side side) { int decisionNumber = view.requestArrangementStrategyDecision(side, strategies); return findStrategyWithCorrespondingDecisionNumber(decisionNumber); } @@ -45,4 +71,16 @@ private ArrangementStrategy findStrategyWithCorrespondingDecisionNumber(int deci .findFirst() .orElseThrow(() -> new IllegalArgumentException("일치하는 전략 번호가 없습니다: " + decisionNumber)); } + + private T retry(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + view.responseErrorMessage(e); + } + } + } + + } diff --git a/src/main/java/janggi/domain/Location.java b/src/main/java/janggi/domain/Location.java new file mode 100644 index 0000000000..a8a3232831 --- /dev/null +++ b/src/main/java/janggi/domain/Location.java @@ -0,0 +1,19 @@ +package janggi.domain; + +import java.util.List; + +public record Location(int x, int y) { + + private static final int COORDINATE_COUNT = 2; + + public static Location from(List position) { + validateCount(position); + return new Location(position.get(0), position.get(1)); + } + + private static void validateCount(List position) { + if (position.size() != COORDINATE_COUNT) { + throw new IllegalArgumentException("좌표의 개수는 " + COORDINATE_COUNT + "개 입니다."); + } + } +} diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java index 4205190997..02fdf05167 100644 --- a/src/main/java/janggi/domain/Side.java +++ b/src/main/java/janggi/domain/Side.java @@ -15,4 +15,11 @@ public enum Side { public String getName() { return name; } + + public Side switchTurn() { + if (this == HAN) { + return CHO; + } + return HAN; + } } diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index b4b9d60fd5..c0b0675926 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -1,5 +1,6 @@ package janggi.domain.board; +import janggi.domain.Location; import janggi.domain.piece.Piece; import janggi.strategy.BoardAssembler; import java.util.ArrayList; @@ -48,6 +49,15 @@ public List> to2DArray() { return List.copyOf(pieces); } - private record Location(int row, int col) { + public void validateLocation(Location location) { + if (!boardState.containsKey(location)) { + throw new IllegalArgumentException("해당 좌표는 보드판에 존재하지 않습니다."); + } + } + + public void validatePieceExist(Location location) { + if (boardState.get(location).isEmpty()) { + throw new IllegalArgumentException("해당 좌표에 기물이 존재하지 않습니다."); + } } } diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java index 465eeb6b2f..6e15e2ffd7 100644 --- a/src/main/java/janggi/view/ApplicationView.java +++ b/src/main/java/janggi/view/ApplicationView.java @@ -37,6 +37,26 @@ public void responseBoardArray(List> board2DArray) { outputWriter.printStringMatrix(stringMatrix); } + public void responseCurrentSide(Side currentSide) { + outputWriter.printPromptMessage(currentSide.getName() + "팀의 차례입니다."); + } + + public List requestLocationOfPiece() { + outputWriter.printPromptMessage("이동 시킬 기물의 좌표를 입력해주세요. (,로 구분)"); + + return retry(inputReader::readIntegers); + } + + public List requestLocationToMove() { + outputWriter.printPromptMessage("해당 기물이 이동할 좌표를 입력해주세요. (,로 구분)"); + + return retry(inputReader::readIntegers); + } + + public void responseErrorMessage(RuntimeException e) { + outputWriter.printErrorMessage(e); + } + private T retry(Supplier supplier) { while (true) { try { diff --git a/src/main/java/janggi/view/ConsoleReader.java b/src/main/java/janggi/view/ConsoleReader.java index 226cae0b51..525570c56b 100644 --- a/src/main/java/janggi/view/ConsoleReader.java +++ b/src/main/java/janggi/view/ConsoleReader.java @@ -1,5 +1,7 @@ package janggi.view; +import java.util.Arrays; +import java.util.List; import java.util.Scanner; public class ConsoleReader implements Input { @@ -20,6 +22,20 @@ public int readInt() { return parseToInt(input); } + @Override + public List readIntegers() { + String input = scanner.nextLine().trim(); + validateIsBlank(input); + String[] strings = input.split("\\s*,\\s*"); + List integers = Arrays.stream(strings) + .map(s -> { + validateIsBlank(s); + validateIsNumeric(s); + return parseToInt(s); + }).toList(); + return List.copyOf(integers); + } + private void validateIsBlank(String input) { if (input.isBlank()) { throw new IllegalArgumentException("공백은 허용되지 않습니다."); diff --git a/src/main/java/janggi/view/Input.java b/src/main/java/janggi/view/Input.java index 167897b759..f2783c9914 100644 --- a/src/main/java/janggi/view/Input.java +++ b/src/main/java/janggi/view/Input.java @@ -1,6 +1,10 @@ package janggi.view; +import java.util.List; + public interface Input { int readInt(); + + List readIntegers(); } diff --git a/src/test/java/janggi/domain/BoardTest.java b/src/test/java/janggi/domain/BoardTest.java index 1a25288352..5cd29f9265 100644 --- a/src/test/java/janggi/domain/BoardTest.java +++ b/src/test/java/janggi/domain/BoardTest.java @@ -1,5 +1,7 @@ package janggi.domain; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import janggi.domain.board.Board; import janggi.domain.piece.Piece; import janggi.strategy.BoardAssembler; @@ -7,6 +9,7 @@ import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class BoardTest { @@ -26,8 +29,63 @@ void shouldReturnBoardWithSelectedArrangementStrategy() { Piece expectedPiece = pieces.get(row).get(col); Assertions.assertThat(result).isInstanceOf(expectedPiece.getClass()); Assertions.assertThat(result.isSameSide(expectedPiece)).isTrue(); - } } } + + @Nested + class ValidateLocationTest { + @Test + @DisplayName("보드에 입력받은 좌표가 존재하면 예외를 발생시키지 않는다.") + void shouldNotThrowExceptionWhenLocationExists() { + // given + BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Board board = Board.create(assembler); + Location location = Location.from(List.of(1,1)); + + // when & then + assertDoesNotThrow(() -> board.validateLocation(location)); + } + + @Test + @DisplayName("보드에 입력받은 좌표가 존재하지 않으면 예외를 발생시킨다.") + void shouldThrowExceptionWhenLocationDoesNotExist() { + // given + BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Board board = Board.create(assembler); + Location location = Location.from(List.of(11,11)); + + // when & then + Assertions.assertThatThrownBy(() -> board.validateLocation(location)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + class ValidatePieceExistTest { + @Test + @DisplayName("입력받은 좌표에 기물이 존재하면 예외를 발생시키지 않는다.") + void shouldNotThrowExceptionWhenPieceExistsAtLocation() { + // given + BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Board board = Board.create(assembler); + Location location = Location.from(List.of(0,1)); + + // when & then + assertDoesNotThrow(() -> board.validatePieceExist(location)); + } + + @Test + @DisplayName("입력받은 좌표에 기물이 존재하지 않으면 예외를 발생시킨다.") + void shouldThrowExceptionWhenPieceDoesNotExistsAtLocation() { + // given + BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Board board = Board.create(assembler); + Location location = Location.from(List.of(5,5)); + + // when & then + Assertions.assertThatThrownBy(() -> board.validatePieceExist(location)) + .isInstanceOf(IllegalArgumentException.class); + } + } } diff --git a/src/test/java/janggi/domain/TeamPiece.java b/src/test/java/janggi/domain/TeamPiece.java index 94464dcc59..5738ae5730 100644 --- a/src/test/java/janggi/domain/TeamPiece.java +++ b/src/test/java/janggi/domain/TeamPiece.java @@ -12,6 +12,6 @@ public TeamPiece(Side side) { @Override public boolean isEmpty() { - return false; + return true; } } From 584b04359e37d065a33c5b3d86e226b30a31f57f Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 27 Mar 2026 20:57:08 +0900 Subject: [PATCH 14/45] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 프로덕션 클래스 패키지 구조와 동일하도록 테스트 클래스의 패키지 경로 수정 --- src/test/java/janggi/domain/{ => board}/BoardTest.java | 4 ++-- src/test/java/janggi/strategy/MaSangMaSangTest.java | 1 - src/test/java/janggi/strategy/MaSangSangMaTest.java | 1 - src/test/java/janggi/strategy/SangMaMaSangTest.java | 1 - src/test/java/janggi/strategy/SangMaSangMaTest.java | 1 - src/test/java/janggi/{domain => strategy}/TeamPiece.java | 2 +- 6 files changed, 3 insertions(+), 7 deletions(-) rename src/test/java/janggi/domain/{ => board}/BoardTest.java (98%) rename src/test/java/janggi/{domain => strategy}/TeamPiece.java (91%) diff --git a/src/test/java/janggi/domain/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java similarity index 98% rename from src/test/java/janggi/domain/BoardTest.java rename to src/test/java/janggi/domain/board/BoardTest.java index 5cd29f9265..7aef0afffa 100644 --- a/src/test/java/janggi/domain/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -1,8 +1,8 @@ -package janggi.domain; +package janggi.domain.board; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import janggi.domain.board.Board; +import janggi.domain.Location; import janggi.domain.piece.Piece; import janggi.strategy.BoardAssembler; import janggi.strategy.MaSangMaSang; diff --git a/src/test/java/janggi/strategy/MaSangMaSangTest.java b/src/test/java/janggi/strategy/MaSangMaSangTest.java index 1b377dbe4c..92a327f7fd 100644 --- a/src/test/java/janggi/strategy/MaSangMaSangTest.java +++ b/src/test/java/janggi/strategy/MaSangMaSangTest.java @@ -1,7 +1,6 @@ package janggi.strategy; -import janggi.domain.TeamPiece; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; diff --git a/src/test/java/janggi/strategy/MaSangSangMaTest.java b/src/test/java/janggi/strategy/MaSangSangMaTest.java index 12fee056de..a05ea1087e 100644 --- a/src/test/java/janggi/strategy/MaSangSangMaTest.java +++ b/src/test/java/janggi/strategy/MaSangSangMaTest.java @@ -1,7 +1,6 @@ package janggi.strategy; -import janggi.domain.TeamPiece; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java index 3a7ee38b51..2eed3ee77f 100644 --- a/src/test/java/janggi/strategy/SangMaMaSangTest.java +++ b/src/test/java/janggi/strategy/SangMaMaSangTest.java @@ -1,7 +1,6 @@ package janggi.strategy; -import janggi.domain.TeamPiece; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; diff --git a/src/test/java/janggi/strategy/SangMaSangMaTest.java b/src/test/java/janggi/strategy/SangMaSangMaTest.java index 36b771646f..dca4ac8687 100644 --- a/src/test/java/janggi/strategy/SangMaSangMaTest.java +++ b/src/test/java/janggi/strategy/SangMaSangMaTest.java @@ -1,7 +1,6 @@ package janggi.strategy; -import janggi.domain.TeamPiece; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; diff --git a/src/test/java/janggi/domain/TeamPiece.java b/src/test/java/janggi/strategy/TeamPiece.java similarity index 91% rename from src/test/java/janggi/domain/TeamPiece.java rename to src/test/java/janggi/strategy/TeamPiece.java index 5738ae5730..a6e4c50163 100644 --- a/src/test/java/janggi/domain/TeamPiece.java +++ b/src/test/java/janggi/strategy/TeamPiece.java @@ -1,4 +1,4 @@ -package janggi.domain; +package janggi.strategy; import janggi.domain.piece.Piece; From 92c55ab186ff539487d98f7d7ba9120613db5768 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 27 Mar 2026 21:01:45 +0900 Subject: [PATCH 15/45] =?UTF-8?q?feat:=20=EC=A1=B8=EB=B3=91=EC=9D=98=20?= =?UTF-8?q?=EA=B8=B0=EB=AC=BC=20=EC=9D=B4=EB=8F=99=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=A2=8C=ED=91=9C=20=EB=AA=A9=EB=A1=9D=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=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 - 기물의 이동 방향에 따라 좌표에 더해줄 값을 알려주는 Direction 클래스 추가 - 기물이 특정 위치로 이동하기 위해 지나쳐야 하는 경로의 Location 목록을 알려주는 Route 클래스 추가 - Piece 추상 클래스에 calculateRoute 추상 메서드 추가 - 졸병 기물의 이동 규칙을 적용한 기물 이동 기능 추가 --- src/main/java/janggi/domain/Location.java | 4 + .../janggi/domain/direction/Direction.java | 27 +++++++ .../java/janggi/domain/direction/Route.java | 28 +++++++ src/main/java/janggi/domain/piece/Cha.java | 7 ++ .../java/janggi/domain/piece/EmptyPiece.java | 7 ++ src/main/java/janggi/domain/piece/Gung.java | 7 ++ .../java/janggi/domain/piece/Jolbyeong.java | 34 ++++++++ src/main/java/janggi/domain/piece/Ma.java | 7 ++ src/main/java/janggi/domain/piece/Piece.java | 4 + src/main/java/janggi/domain/piece/Po.java | 8 ++ src/main/java/janggi/domain/piece/Sa.java | 7 ++ src/main/java/janggi/domain/piece/Sang.java | 7 ++ .../janggi/domain/direction/RouteTest.java | 37 +++++++++ .../janggi/domain/piece/JolbyeongTest.java | 79 +++++++++++++++++++ src/test/java/janggi/strategy/TeamPiece.java | 8 ++ 15 files changed, 271 insertions(+) create mode 100644 src/main/java/janggi/domain/direction/Direction.java create mode 100644 src/main/java/janggi/domain/direction/Route.java create mode 100644 src/test/java/janggi/domain/direction/RouteTest.java create mode 100644 src/test/java/janggi/domain/piece/JolbyeongTest.java diff --git a/src/main/java/janggi/domain/Location.java b/src/main/java/janggi/domain/Location.java index a8a3232831..9b1aa375be 100644 --- a/src/main/java/janggi/domain/Location.java +++ b/src/main/java/janggi/domain/Location.java @@ -16,4 +16,8 @@ private static void validateCount(List position) { throw new IllegalArgumentException("좌표의 개수는 " + COORDINATE_COUNT + "개 입니다."); } } + + public Location add(int dx, int dy) { + return new Location(x + dx, y + dy); + } } diff --git a/src/main/java/janggi/domain/direction/Direction.java b/src/main/java/janggi/domain/direction/Direction.java new file mode 100644 index 0000000000..b4373e125f --- /dev/null +++ b/src/main/java/janggi/domain/direction/Direction.java @@ -0,0 +1,27 @@ +package janggi.domain.direction; + +import janggi.domain.Location; + +public enum Direction { + + FRONT(0,1), + LEFT(-1,0), + RIGHT(1, 0), + BACK(0, -1), + FRONT_LEFT(-1,1), + FRONT_RIGHT(1,1), + BACK_LEFT(-1,-1), + BACK_RIGHT(1, -1); + + private final int dx; + private final int dy; + + Direction(int dx, int dy) { + this.dx = dx; + this.dy = dy; + } + + public Location apply(Location location) { + return location.add(dx, dy); + } +} diff --git a/src/main/java/janggi/domain/direction/Route.java b/src/main/java/janggi/domain/direction/Route.java new file mode 100644 index 0000000000..64fff892cd --- /dev/null +++ b/src/main/java/janggi/domain/direction/Route.java @@ -0,0 +1,28 @@ +package janggi.domain.direction; + +import janggi.domain.Location; +import java.util.ArrayList; +import java.util.List; + +public class Route { + + private final List directions; + + private Route(List directions) { + this.directions = directions; + } + + public static Route of(List directions) { + return new Route(directions); + } + + public List apply(Location current) { + List result = new ArrayList<>(); + for(Direction direction : directions) { + current = direction.apply(current); + result.add(current); + } + + return result; + } +} diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index 7b7d5ccc77..90da8fcfb0 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Location; import janggi.domain.Side; +import java.util.List; public class Cha extends Piece { @@ -14,4 +16,9 @@ public Cha(Side side) { public boolean isEmpty() { return false; } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } } diff --git a/src/main/java/janggi/domain/piece/EmptyPiece.java b/src/main/java/janggi/domain/piece/EmptyPiece.java index 646c2213f7..230a301035 100644 --- a/src/main/java/janggi/domain/piece/EmptyPiece.java +++ b/src/main/java/janggi/domain/piece/EmptyPiece.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Location; import janggi.domain.Side; +import java.util.List; public class EmptyPiece extends Piece { @@ -14,4 +16,9 @@ public EmptyPiece() { public boolean isEmpty() { return true; } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } } diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 1a7cd7d8c9..34804f7c61 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Location; import janggi.domain.Side; +import java.util.List; public class Gung extends Piece { @@ -14,4 +16,9 @@ public Gung(Side side) { public boolean isEmpty() { return false; } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } } diff --git a/src/main/java/janggi/domain/piece/Jolbyeong.java b/src/main/java/janggi/domain/piece/Jolbyeong.java index 1240e698ae..5efcc31a00 100644 --- a/src/main/java/janggi/domain/piece/Jolbyeong.java +++ b/src/main/java/janggi/domain/piece/Jolbyeong.java @@ -1,6 +1,16 @@ package janggi.domain.piece; +import static janggi.domain.direction.Direction.BACK; +import static janggi.domain.direction.Direction.FRONT; +import static janggi.domain.direction.Direction.LEFT; +import static janggi.domain.direction.Direction.RIGHT; + +import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.direction.Direction; +import janggi.domain.direction.Route; + +import java.util.List; public class Jolbyeong extends Piece { @@ -17,6 +27,30 @@ public boolean isEmpty() { return false; } + @Override + public List calculateRoute(Location from, Location to) { + List directions = List.of( + Route.of(List.of(getRealFront(side))), + Route.of(List.of(LEFT)), + Route.of(List.of(RIGHT)) + ); + + for(Route route : directions) { + List locations = route.apply(from); + if(locations.getLast().equals(to)) { + return locations; + } + } + throw new IllegalArgumentException(getName() + "은 해당 위치에 도달할 수 없습니다."); + } + + private Direction getRealFront(Side side) { + if (side.equals(Side.HAN)) { + return FRONT; + } + return BACK; + } + @Override public String getName() { if (side.equals(Side.HAN)) { diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index a3cc2db830..92e0b283d7 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Location; import janggi.domain.Side; +import java.util.List; public class Ma extends Piece { @@ -14,4 +16,9 @@ public Ma(Side side) { public boolean isEmpty() { return false; } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 533d287b44..7aca9d7b14 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Location; import janggi.domain.Side; +import java.util.List; public abstract class Piece { @@ -14,6 +16,8 @@ protected Piece(String name, Side side) { public abstract boolean isEmpty(); + public abstract List calculateRoute(Location from, Location to); + public boolean isSameSide(Piece piece) { return this.side.equals(piece.side); } diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 9d94a9f804..6b04b73e89 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Location; import janggi.domain.Side; +import java.util.List; public class Po extends Piece { @@ -14,4 +16,10 @@ public Po(Side side) { public boolean isEmpty() { return false; } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } } + diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index e5ea0b979c..316d8befce 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Location; import janggi.domain.Side; +import java.util.List; public class Sa extends Piece { @@ -14,4 +16,9 @@ public Sa(Side side) { public boolean isEmpty() { return false; } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } } diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index e282a70095..3e4d5a15e0 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Location; import janggi.domain.Side; +import java.util.List; public class Sang extends Piece { @@ -14,4 +16,9 @@ public Sang(Side side) { public boolean isEmpty() { return false; } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } } diff --git a/src/test/java/janggi/domain/direction/RouteTest.java b/src/test/java/janggi/domain/direction/RouteTest.java new file mode 100644 index 0000000000..832f595f00 --- /dev/null +++ b/src/test/java/janggi/domain/direction/RouteTest.java @@ -0,0 +1,37 @@ +package janggi.domain.direction; + +import janggi.domain.Location; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RouteTest { + + @Test + @DisplayName("현재 위치에서 주어진 Direction 들을 적용했을 때 지나는 경로를 반환한다.") + void shouldReturnRouteLocationsCalculatedByCurrentLocation() { + // given + Location current = Location.from(List.of(0,0)); + Route route = Route.of( + List.of( + Direction.FRONT, // 0,1 + Direction.FRONT_LEFT, // -1, 2 + Direction.FRONT_RIGHT, // 0, 3 + Direction.BACK // 0, 2 + ) + ); + List expected = List.of( + Location.from(List.of(0,1)), + Location.from(List.of(-1,2)), + Location.from(List.of(0,3)), + Location.from(List.of(0,2)) + ); + + // when + List routeLocations = route.apply(current); + + // then + Assertions.assertThat(routeLocations).isEqualTo(expected); + } +} diff --git a/src/test/java/janggi/domain/piece/JolbyeongTest.java b/src/test/java/janggi/domain/piece/JolbyeongTest.java new file mode 100644 index 0000000000..a9d00bbd9c --- /dev/null +++ b/src/test/java/janggi/domain/piece/JolbyeongTest.java @@ -0,0 +1,79 @@ +package janggi.domain.piece; + +import janggi.domain.Location; +import janggi.domain.Side; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class JolbyeongTest { + @Nested + class CalculateRouteTest { + @Test + @DisplayName("한팀 졸병이 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") + void shouldReturnRouteForReachableLocationWhenTeamHan() { + // given + Location from = Location.from(List.of(0, 0)); + Location to = Location.from(List.of(0, 1)); + Piece piece = new Jolbyeong(Side.HAN); + + + // when & then + Assertions.assertThat(piece.calculateRoute(from, to)) + .isEqualTo(List.of(to)); + } + + @Test + @DisplayName("초팀 졸병이 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") + void shouldReturnRouteForReachableLocationWhenTeamCho() { + // given + Location from = Location.from(List.of(0, 1)); + Location to = Location.from(List.of(0, 0)); + Piece piece = new Jolbyeong(Side.CHO); + + + // when & then + Assertions.assertThat(piece.calculateRoute(from, to)) + .isEqualTo(List.of(to)); + } + + @Test + @DisplayName("한팀 졸병이 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") + void shouldThrowExceptionForUnReachableLocationWhenTeamHan() { + // given + Location from = Location.from(List.of(0, 1)); + Location to = Location.from(List.of(0, 0)); + Piece piece = new Jolbyeong(Side.HAN); + + // when & then + Assertions.assertThatThrownBy(() -> piece.calculateRoute(from, to)) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @DisplayName("초팀 졸병이 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") + @MethodSource("provideUnreachableCoordination") + void shouldThrowExceptionForUnReachableLocationWhenTeamCho(List coordination) { + // given + Location from = Location.from(List.of(0, 0)); + Location to = Location.from(coordination); + Piece piece = new Jolbyeong(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> piece.calculateRoute(from, to)) + .isInstanceOf(IllegalArgumentException.class); + } + + static List> provideUnreachableCoordination() { + return List.of( + List.of(0,2), // 거리가 멀어서 도달할 수 없는 경우 + List.of(1,1), // 대각선으로 이동하는 경우 + List.of(0,1) // 뒤로 이동하는 경우 + ); + } + } +} diff --git a/src/test/java/janggi/strategy/TeamPiece.java b/src/test/java/janggi/strategy/TeamPiece.java index a6e4c50163..cb08ac08ef 100644 --- a/src/test/java/janggi/strategy/TeamPiece.java +++ b/src/test/java/janggi/strategy/TeamPiece.java @@ -1,6 +1,9 @@ package janggi.strategy; +import janggi.domain.Location; +import janggi.domain.Side; import janggi.domain.piece.Piece; +import java.util.List; public class TeamPiece extends Piece { @@ -14,4 +17,9 @@ public TeamPiece(Side side) { public boolean isEmpty() { return true; } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } } From a9e05d2efe3903f326b292e9ffd8cce20eec950d Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 27 Mar 2026 21:10:46 +0900 Subject: [PATCH 16/45] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=98=20=EA=B8=B0?= =?UTF-8?q?=EB=AC=BC=20=EC=9D=B4=EB=8F=99=20=EA=B2=BD=EB=A1=9C=20=EC=A2=8C?= =?UTF-8?q?=ED=91=9C=20=EB=AA=A9=EB=A1=9D=20=EA=B3=84=EC=82=B0=20=EA=B8=B0?= =?UTF-8?q?=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 --- src/main/java/janggi/domain/piece/Ma.java | 23 +++++++- src/test/java/janggi/domain/piece/MaTest.java | 57 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/test/java/janggi/domain/piece/MaTest.java diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index 92e0b283d7..d4785d5dab 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -1,7 +1,10 @@ package janggi.domain.piece; +import static janggi.domain.direction.Direction.*; + import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.direction.Route; import java.util.List; public class Ma extends Piece { @@ -19,6 +22,24 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { - return List.of(); + List directions = List.of( + Route.of(List.of(FRONT, FRONT_LEFT)), + Route.of(List.of(FRONT, FRONT_RIGHT)), + Route.of(List.of(RIGHT, FRONT_RIGHT)), + Route.of(List.of(RIGHT, BACK_RIGHT)), + Route.of(List.of(LEFT, FRONT_LEFT)), + Route.of(List.of(LEFT, BACK_LEFT)), + Route.of(List.of(BACK, BACK_LEFT)), + Route.of(List.of(BACK, BACK_RIGHT)) + ); + + for(Route route : directions) { + List locations = route.apply(from); + if(locations.getLast().equals(to)) { + return locations; + } + } + + throw new IllegalArgumentException("마는 해당 위치에 도달할 수 없습니다."); } } diff --git a/src/test/java/janggi/domain/piece/MaTest.java b/src/test/java/janggi/domain/piece/MaTest.java new file mode 100644 index 0000000000..7c14882626 --- /dev/null +++ b/src/test/java/janggi/domain/piece/MaTest.java @@ -0,0 +1,57 @@ +package janggi.domain.piece; + +import janggi.domain.Location; +import janggi.domain.Side; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class MaTest { + + @Nested + class CalculateRouteTest { + + @Test + @DisplayName("마가 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") + void shouldReturnRouteForReachableLocationWhenTeamHan() { + // given + Location from = Location.from(List.of(0, 0)); + Location to = Location.from(List.of(1,2)); + Piece piece = new Ma(Side.HAN); + + List expected = List.of( + Location.from(List.of(0,1)), + Location.from(List.of(1,2)) + ); + + // when & then + Assertions.assertThat(piece.calculateRoute(from, to)) + .isEqualTo(expected); + } + + @ParameterizedTest + @DisplayName("마가 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") + @MethodSource("provideUnreachableCoordination") + void shouldThrowExceptionForUnReachableLocationWhenTeamCho(List destination) { + // given + Location from = Location.from(List.of(0, 0)); + Location to = Location.from(destination); + Piece piece = new Ma(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> piece.calculateRoute(from, to)) + .isInstanceOf(IllegalArgumentException.class); + } + static List> provideUnreachableCoordination() { + return List.of( + List.of(0,3), + List.of(-1,3), + List.of(0,1) + ); + } + } +} From ac68f60ce5fb42ec09fc251160de382c7699df6e Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 27 Mar 2026 21:24:14 +0900 Subject: [PATCH 17/45] =?UTF-8?q?feat:=20=EC=83=81=EC=9D=98=20=EA=B8=B0?= =?UTF-8?q?=EB=AC=BC=20=EC=9D=B4=EB=8F=99=20=EA=B2=BD=EB=A1=9C=20=EC=A2=8C?= =?UTF-8?q?=ED=91=9C=20=EB=AA=A9=EB=A1=9D=20=EA=B3=84=EC=82=B0=20=EA=B8=B0?= =?UTF-8?q?=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 --- src/main/java/janggi/domain/piece/Sang.java | 30 +++++++- .../java/janggi/domain/piece/SangTest.java | 68 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/test/java/janggi/domain/piece/SangTest.java diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index 3e4d5a15e0..8262b469e2 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -1,7 +1,17 @@ package janggi.domain.piece; +import static janggi.domain.direction.Direction.BACK; +import static janggi.domain.direction.Direction.BACK_LEFT; +import static janggi.domain.direction.Direction.BACK_RIGHT; +import static janggi.domain.direction.Direction.FRONT; +import static janggi.domain.direction.Direction.FRONT_LEFT; +import static janggi.domain.direction.Direction.FRONT_RIGHT; +import static janggi.domain.direction.Direction.LEFT; +import static janggi.domain.direction.Direction.RIGHT; + import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.direction.Route; import java.util.List; public class Sang extends Piece { @@ -19,6 +29,24 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { - return List.of(); + List directions = List.of( + Route.of(List.of(FRONT, FRONT_LEFT, FRONT_LEFT)), + Route.of(List.of(FRONT, FRONT_RIGHT, FRONT_RIGHT)), + Route.of(List.of(RIGHT, FRONT_RIGHT, FRONT_RIGHT)), + Route.of(List.of(RIGHT, BACK_RIGHT, BACK_RIGHT)), + Route.of(List.of(LEFT, FRONT_LEFT, FRONT_LEFT)), + Route.of(List.of(LEFT, BACK_LEFT, BACK_LEFT)), + Route.of(List.of(BACK, BACK_LEFT, BACK_LEFT)), + Route.of(List.of(BACK, BACK_RIGHT, BACK_RIGHT)) + ); + + for(Route route : directions) { + List locations = route.apply(from); + if(locations.getLast().equals(to)) { + return locations; + } + } + + throw new IllegalArgumentException("상은 해당 위치에 도달할 수 없습니다."); } } diff --git a/src/test/java/janggi/domain/piece/SangTest.java b/src/test/java/janggi/domain/piece/SangTest.java new file mode 100644 index 0000000000..75f0cade67 --- /dev/null +++ b/src/test/java/janggi/domain/piece/SangTest.java @@ -0,0 +1,68 @@ +package janggi.domain.piece; + +import janggi.domain.Location; +import janggi.domain.Side; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class SangTest { + + @Nested + class CalculateRouteTest { + + @ParameterizedTest + @DisplayName("상이 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") + @MethodSource("provideListsForTesting") + void shouldReturnRouteForReachableLocationWhenTeamHan(List destination, List expected) { + // given + Location from = Location.from(List.of(0, 0)); + Location to = Location.from(destination); + Piece piece = new Sang(Side.HAN); + + // when & then + Assertions.assertThat(piece.calculateRoute(from, to)) + .isEqualTo(expected); + } + + static Stream provideListsForTesting() { + return Stream.of( + Arguments.of( + List.of(2,3), + List.of(new Location(0, 1), new Location(1, 2), new Location(2,3)) + ), + Arguments.of( + List.of(3,2), + List.of(new Location(1, 0), new Location(2, 1), new Location(3, 2)) + ) + ); + } + + @ParameterizedTest + @DisplayName("상이 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") + @MethodSource("provideUnreachableCoordination") + void shouldThrowExceptionForUnReachableLocationWhenTeamCho(List destination) { + // given + Location from = Location.from(List.of(0, 0)); + Location to = Location.from(destination); + Piece piece = new Sang(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> piece.calculateRoute(from, to)) + .isInstanceOf(IllegalArgumentException.class); + } + + static List> provideUnreachableCoordination() { + return List.of( + List.of(0,4), + List.of(-1,3), + List.of(0,1) + ); + } + } +} From 6322b8c4a2558f7ab2fd33204923b64225d92b4e Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 27 Mar 2026 21:34:39 +0900 Subject: [PATCH 18/45] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EB=A7=88=EC=99=80=20=EC=83=81=EC=9D=98=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=AA=85=EC=9D=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 마와 상은 팀에 관계 없이 경로 계산이 가능하므로 테스트명에서 WhenTeamHan, WhenTeamCho 를 제거함 --- src/test/java/janggi/domain/piece/MaTest.java | 4 ++-- src/test/java/janggi/domain/piece/SangTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/janggi/domain/piece/MaTest.java b/src/test/java/janggi/domain/piece/MaTest.java index 7c14882626..60ac17494c 100644 --- a/src/test/java/janggi/domain/piece/MaTest.java +++ b/src/test/java/janggi/domain/piece/MaTest.java @@ -17,7 +17,7 @@ class CalculateRouteTest { @Test @DisplayName("마가 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") - void shouldReturnRouteForReachableLocationWhenTeamHan() { + void shouldReturnRouteForReachableLocation() { // given Location from = Location.from(List.of(0, 0)); Location to = Location.from(List.of(1,2)); @@ -36,7 +36,7 @@ void shouldReturnRouteForReachableLocationWhenTeamHan() { @ParameterizedTest @DisplayName("마가 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") @MethodSource("provideUnreachableCoordination") - void shouldThrowExceptionForUnReachableLocationWhenTeamCho(List destination) { + void shouldThrowExceptionForUnReachableLocation(List destination) { // given Location from = Location.from(List.of(0, 0)); Location to = Location.from(destination); diff --git a/src/test/java/janggi/domain/piece/SangTest.java b/src/test/java/janggi/domain/piece/SangTest.java index 75f0cade67..0d3824b859 100644 --- a/src/test/java/janggi/domain/piece/SangTest.java +++ b/src/test/java/janggi/domain/piece/SangTest.java @@ -19,7 +19,7 @@ class CalculateRouteTest { @ParameterizedTest @DisplayName("상이 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") @MethodSource("provideListsForTesting") - void shouldReturnRouteForReachableLocationWhenTeamHan(List destination, List expected) { + void shouldReturnRouteForReachableLocation(List destination, List expected) { // given Location from = Location.from(List.of(0, 0)); Location to = Location.from(destination); @@ -46,7 +46,7 @@ static Stream provideListsForTesting() { @ParameterizedTest @DisplayName("상이 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") @MethodSource("provideUnreachableCoordination") - void shouldThrowExceptionForUnReachableLocationWhenTeamCho(List destination) { + void shouldThrowExceptionForUnReachableLocation(List destination) { // given Location from = Location.from(List.of(0, 0)); Location to = Location.from(destination); From f5f2291e76667f218260d5fae69576d0d78134f4 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 27 Mar 2026 21:53:56 +0900 Subject: [PATCH 19/45] =?UTF-8?q?feat:=20=EA=B6=81,=20=EC=82=AC=EC=9D=98?= =?UTF-8?q?=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4=EB=8F=99=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=20=EC=A2=8C=ED=91=9C=20=EB=AA=A9=EB=A1=9D=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=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 - 이동 경로 계산 규칙이 중복되는 기물의 계산 로직을 RouteRule 인터페이스로 추상화함 - 궁과 사는 궁성이라는 영역 제한 규칙을 동일하게 가지므로 GungSeongRouteRule 이라는 구현 클래스를 사용하도록 함 --- .../domain/direction/GungSeongRouteRule.java | 39 +++++++++++++ .../janggi/domain/direction/RouteRule.java | 9 +++ src/main/java/janggi/domain/piece/Gung.java | 9 ++- src/main/java/janggi/domain/piece/Sa.java | 9 ++- .../direction/GungSeongRouteRuleTest.java | 58 +++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/main/java/janggi/domain/direction/GungSeongRouteRule.java create mode 100644 src/main/java/janggi/domain/direction/RouteRule.java create mode 100644 src/test/java/janggi/domain/direction/GungSeongRouteRuleTest.java diff --git a/src/main/java/janggi/domain/direction/GungSeongRouteRule.java b/src/main/java/janggi/domain/direction/GungSeongRouteRule.java new file mode 100644 index 0000000000..243bf69145 --- /dev/null +++ b/src/main/java/janggi/domain/direction/GungSeongRouteRule.java @@ -0,0 +1,39 @@ +package janggi.domain.direction; + +import static janggi.domain.direction.Direction.BACK; +import static janggi.domain.direction.Direction.BACK_LEFT; +import static janggi.domain.direction.Direction.BACK_RIGHT; +import static janggi.domain.direction.Direction.FRONT; +import static janggi.domain.direction.Direction.FRONT_LEFT; +import static janggi.domain.direction.Direction.FRONT_RIGHT; +import static janggi.domain.direction.Direction.LEFT; +import static janggi.domain.direction.Direction.RIGHT; + +import janggi.domain.Location; +import java.util.List; + +public class GungSeongRouteRule implements RouteRule { + + @Override + public List calculateRoute(Location from, Location to) { + List directions = List.of( + Route.of(List.of(FRONT)), + Route.of(List.of(LEFT)), + Route.of(List.of(RIGHT)), + Route.of(List.of(BACK)), + Route.of(List.of(FRONT_LEFT)), + Route.of(List.of(FRONT_RIGHT)), + Route.of(List.of(BACK_LEFT)), + Route.of(List.of(BACK_RIGHT)) + ); + + for(Route route : directions) { + List locations = route.apply(from); + if(locations.getLast().equals(to)) { + return locations; + } + } + + throw new IllegalArgumentException("해당 위치에 도달할 수 없습니다."); + } +} diff --git a/src/main/java/janggi/domain/direction/RouteRule.java b/src/main/java/janggi/domain/direction/RouteRule.java new file mode 100644 index 0000000000..af138d0e6a --- /dev/null +++ b/src/main/java/janggi/domain/direction/RouteRule.java @@ -0,0 +1,9 @@ +package janggi.domain.direction; + +import janggi.domain.Location; +import java.util.List; + +public interface RouteRule { + + List calculateRoute(Location from, Location to); +} diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 34804f7c61..889a83d0cc 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -2,11 +2,14 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.direction.RouteRule; +import janggi.domain.direction.GungSeongRouteRule; import java.util.List; public class Gung extends Piece { private static final String PIECE_NAME = "궁"; + private static final RouteRule ROUTE_RULE = new GungSeongRouteRule(); public Gung(Side side) { super(PIECE_NAME, side); @@ -19,6 +22,10 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { - return List.of(); + try { + return ROUTE_RULE.calculateRoute(from, to); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("궁은 해당 위치에 도달할 수 없습니다."); + } } } diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index 316d8befce..d54db7c0e8 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -2,11 +2,14 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.direction.RouteRule; +import janggi.domain.direction.GungSeongRouteRule; import java.util.List; public class Sa extends Piece { private static final String PIECE_NAME = "사"; + private static final RouteRule ROUTE_RULE = new GungSeongRouteRule(); public Sa(Side side) { super(PIECE_NAME, side); @@ -19,6 +22,10 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { - return List.of(); + try { + return ROUTE_RULE.calculateRoute(from, to); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("사는 해당 위치에 도달할 수 없습니다."); + } } } diff --git a/src/test/java/janggi/domain/direction/GungSeongRouteRuleTest.java b/src/test/java/janggi/domain/direction/GungSeongRouteRuleTest.java new file mode 100644 index 0000000000..973a0a90ba --- /dev/null +++ b/src/test/java/janggi/domain/direction/GungSeongRouteRuleTest.java @@ -0,0 +1,58 @@ +package janggi.domain.direction; + +import janggi.domain.Location; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class GungSeongRouteRuleTest { + + @ParameterizedTest + @DisplayName("궁성 안에서 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") + @MethodSource("provideReachableCoordination") + void shouldReturnRouteForReachableLocation(List destination) { + // given + Location from = Location.from(List.of(4, 1)); + Location to = Location.from(destination); + RouteRule routeRule = new GungSeongRouteRule(); + + // when & then + Assertions.assertThat(routeRule.calculateRoute(from, to)) + .isEqualTo(List.of(to)); + } + + static List> provideReachableCoordination() { + return List.of( + List.of(4, 2), //상 + List.of(4, 0), //하 + List.of(3, 1), //좌 + List.of(5, 1), //우 + List.of(5, 2), //우대각 + List.of(3, 2) //좌대각 + ); + } + + @ParameterizedTest + @DisplayName("궁성을 벗어난 위치를 파라미터로 받으면 예외가 발생한다.") + @MethodSource("provideUnreachableCoordination") + void shouldThrowExceptionForUnReachableLocation(List coordination) { + // given + Location from = Location.from(List.of(3, 2)); + Location to = Location.from(coordination); + RouteRule routeRule = new GungSeongRouteRule(); + + // when & then + Assertions.assertThatThrownBy(() -> routeRule.calculateRoute(from, to)) + .isInstanceOf(IllegalArgumentException.class); + } + + static List> provideUnreachableCoordination() { + return List.of( + List.of(3, 4), + List.of(5, 2), + List.of(3, 5) + ); + } +} From 356b5788fa5a8b8a7bcfe29983019ae9a8379d84 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 11:11:40 +0900 Subject: [PATCH 20/45] =?UTF-8?q?feat:=20=EC=B0=A8,=20=EC=83=81=EC=9D=98?= =?UTF-8?q?=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4=EB=8F=99=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=20=EC=A2=8C=ED=91=9C=20=EB=AA=A9=EB=A1=9D=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=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 --- src/main/java/janggi/domain/Location.java | 8 +++ .../java/janggi/domain/direction/Route.java | 10 ++- .../domain/direction/StraightRouteRule.java | 39 +++++++++++ src/main/java/janggi/domain/piece/Cha.java | 9 ++- src/main/java/janggi/domain/piece/Po.java | 9 ++- .../direction/StraightRouteRuleTest.java | 69 +++++++++++++++++++ .../java/janggi/domain/piece/SangTest.java | 17 +++-- 7 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 src/main/java/janggi/domain/direction/StraightRouteRule.java create mode 100644 src/test/java/janggi/domain/direction/StraightRouteRuleTest.java diff --git a/src/main/java/janggi/domain/Location.java b/src/main/java/janggi/domain/Location.java index 9b1aa375be..cff3ee502e 100644 --- a/src/main/java/janggi/domain/Location.java +++ b/src/main/java/janggi/domain/Location.java @@ -20,4 +20,12 @@ private static void validateCount(List position) { public Location add(int dx, int dy) { return new Location(x + dx, y + dy); } + + public int calculateHorizontalDiff(Location to) { + return to.y - this.y; + } + + public int calculateVerticalDiff(Location to) { + return to.x - this.x; + } } diff --git a/src/main/java/janggi/domain/direction/Route.java b/src/main/java/janggi/domain/direction/Route.java index 64fff892cd..b65a44ace4 100644 --- a/src/main/java/janggi/domain/direction/Route.java +++ b/src/main/java/janggi/domain/direction/Route.java @@ -18,11 +18,19 @@ public static Route of(List directions) { public List apply(Location current) { List result = new ArrayList<>(); - for(Direction direction : directions) { + for (Direction direction : directions) { current = direction.apply(current); result.add(current); } return result; } + + public static Route create(Direction direction, int count) { + List result = new ArrayList<>(); + for (int i = 0; i < count; i++) { + result.add(direction); + } + return new Route(result); + } } diff --git a/src/main/java/janggi/domain/direction/StraightRouteRule.java b/src/main/java/janggi/domain/direction/StraightRouteRule.java new file mode 100644 index 0000000000..2133b8059b --- /dev/null +++ b/src/main/java/janggi/domain/direction/StraightRouteRule.java @@ -0,0 +1,39 @@ +package janggi.domain.direction; + +import static janggi.domain.direction.Direction.BACK; +import static janggi.domain.direction.Direction.FRONT; +import static janggi.domain.direction.Direction.LEFT; +import static janggi.domain.direction.Direction.RIGHT; + +import janggi.domain.Location; +import java.util.List; + +public class StraightRouteRule implements RouteRule { + + @Override + public List calculateRoute(Location from, Location to) { + int maxDistance = calculateMaxDistance(from, to); + + List directions = List.of( + Route.create(FRONT, maxDistance), + Route.create(BACK, maxDistance), + Route.create(LEFT, maxDistance), + Route.create(RIGHT, maxDistance) + ); + + for(Route route : directions) { + List locations = route.apply(from); + if(locations.contains(to)) { + return locations; + } + } + + throw new IllegalArgumentException("해당 위치에 도달할 수 없습니다."); + } + + private int calculateMaxDistance(Location from, Location to) { + int horizontalDiff = Math.abs(from.calculateHorizontalDiff(to)); + int verticalDiff = Math.abs(from.calculateVerticalDiff(to)); + return Math.max(horizontalDiff, verticalDiff); + } +} diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index 90da8fcfb0..4aad45fea9 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -2,11 +2,14 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.direction.RouteRule; +import janggi.domain.direction.StraightRouteRule; import java.util.List; public class Cha extends Piece { private static final String PIECE_NAME = "차"; + private static final RouteRule ROUTE_RULE = new StraightRouteRule(); public Cha(Side side) { super(PIECE_NAME, side); @@ -19,6 +22,10 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { - return List.of(); + try { + return ROUTE_RULE.calculateRoute(from, to); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("차는 해당 위치에 도달할 수 없습니다."); + } } } diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 6b04b73e89..241706efb9 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -2,11 +2,14 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.direction.GungSeongRouteRule; +import janggi.domain.direction.RouteRule; import java.util.List; public class Po extends Piece { private static final String PIECE_NAME = "포"; + private static final RouteRule ROUTE_RULE = new GungSeongRouteRule(); public Po(Side side) { super(PIECE_NAME, side); @@ -19,7 +22,11 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { - return List.of(); + try { + return ROUTE_RULE.calculateRoute(from, to); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("포는 해당 위치에 도달할 수 없습니다."); + } } } diff --git a/src/test/java/janggi/domain/direction/StraightRouteRuleTest.java b/src/test/java/janggi/domain/direction/StraightRouteRuleTest.java new file mode 100644 index 0000000000..3abd1436ab --- /dev/null +++ b/src/test/java/janggi/domain/direction/StraightRouteRuleTest.java @@ -0,0 +1,69 @@ +package janggi.domain.direction; + +import janggi.domain.Location; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class StraightRouteRuleTest { + + @ParameterizedTest + @DisplayName("직선으로 이동할 위치를 파라미터로 받으면 이동 경로를 반환한다.") + @MethodSource("provideReachableCoordination") + void shouldReturnRouteForReachableLocation(Location destination, List route) { + // given + Location from = Location.from(List.of(2, 2)); + RouteRule routeRule = new StraightRouteRule(); + + // when & then + Assertions.assertThat(routeRule.calculateRoute(from, destination)) + .isEqualTo(route); + } + + static Stream provideReachableCoordination() { + return Stream.of( + Arguments.of( + new Location(0, 2), // 상 + List.of(new Location(1, 2), new Location(0, 2)) + ), + Arguments.of( + new Location(5, 2), // 하 + List.of(new Location(3, 2), new Location(4, 2), new Location(5, 2)) + ), + Arguments.of( + new Location(2, 0), // 좌 + List.of(new Location(2, 1), new Location(2, 0)) + ), + Arguments.of( + new Location(2, 5), // 우 + List.of(new Location(2, 3), new Location(2, 4), new Location(2, 5)) + ) + ); + } + + @ParameterizedTest + @DisplayName("직선으로 이동이 불가능한 위치를 파라미터로 받으면 예외가 발생한다.") + @MethodSource("provideUnreachableCoordination") + void shouldThrowExceptionForUnReachableLocation(List coordination) { + // given + Location from = Location.from(List.of(1, 1)); + Location to = Location.from(coordination); + RouteRule routeRule = new StraightRouteRule(); + + // when & then + Assertions.assertThatThrownBy(() -> routeRule.calculateRoute(from, to)) + .isInstanceOf(IllegalArgumentException.class); + } + + static List> provideUnreachableCoordination() { + return List.of( + List.of(3, 3), + List.of(3, 2), + List.of(2, 2) + ); + } +} diff --git a/src/test/java/janggi/domain/piece/SangTest.java b/src/test/java/janggi/domain/piece/SangTest.java index 0d3824b859..b4c6e5bd24 100644 --- a/src/test/java/janggi/domain/piece/SangTest.java +++ b/src/test/java/janggi/domain/piece/SangTest.java @@ -19,25 +19,24 @@ class CalculateRouteTest { @ParameterizedTest @DisplayName("상이 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") @MethodSource("provideListsForTesting") - void shouldReturnRouteForReachableLocation(List destination, List expected) { + void shouldReturnRouteForReachableLocation(Location destination, List expected) { // given Location from = Location.from(List.of(0, 0)); - Location to = Location.from(destination); Piece piece = new Sang(Side.HAN); // when & then - Assertions.assertThat(piece.calculateRoute(from, to)) + Assertions.assertThat(piece.calculateRoute(from, destination)) .isEqualTo(expected); } static Stream provideListsForTesting() { return Stream.of( Arguments.of( - List.of(2,3), - List.of(new Location(0, 1), new Location(1, 2), new Location(2,3)) + new Location(2, 3), + List.of(new Location(0, 1), new Location(1, 2), new Location(2, 3)) ), Arguments.of( - List.of(3,2), + new Location(3, 2), List.of(new Location(1, 0), new Location(2, 1), new Location(3, 2)) ) ); @@ -59,9 +58,9 @@ void shouldThrowExceptionForUnReachableLocation(List destination) { static List> provideUnreachableCoordination() { return List.of( - List.of(0,4), - List.of(-1,3), - List.of(0,1) + List.of(0, 4), + List.of(-1, 3), + List.of(0, 1) ); } } From c605c7b82bed40be5504b9f74a5cdf607df20d31 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 11:25:04 +0900 Subject: [PATCH 21/45] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EA=B2=BD=EB=A1=9C=20=EB=B0=98=ED=99=98=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EC=9D=98=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4,=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기물 이동 규칙을 검증하는 방법은 두가지임: 이동 가능한 경로, 경로 도중에 장애물이 있는지 여부 - 각각 검증을 위한 인터페이스를 개별적으로 만들면서 '규칙'의 의미가 여러 클래스로 흩어지게 되었므로, 각 클래스가 검증하는 대상에 집중한 이름으로 네이밍 변경 --- ...SeongRouteRule.java => GungSeongRouteProvider.java} | 2 +- .../direction/{RouteRule.java => RouteProvider.java} | 2 +- ...raightRouteRule.java => StraightRouteProvider.java} | 2 +- src/main/java/janggi/domain/piece/Cha.java | 8 ++++---- src/main/java/janggi/domain/piece/Gung.java | 8 ++++---- src/main/java/janggi/domain/piece/Po.java | 8 ++++---- src/main/java/janggi/domain/piece/Sa.java | 8 ++++---- ...teRuleTest.java => GungSeongRouteProviderTest.java} | 10 +++++----- ...uteRuleTest.java => StraightRouteProviderTest.java} | 10 +++++----- 9 files changed, 29 insertions(+), 29 deletions(-) rename src/main/java/janggi/domain/direction/{GungSeongRouteRule.java => GungSeongRouteProvider.java} (95%) rename src/main/java/janggi/domain/direction/{RouteRule.java => RouteProvider.java} (82%) rename src/main/java/janggi/domain/direction/{StraightRouteRule.java => StraightRouteProvider.java} (95%) rename src/test/java/janggi/domain/direction/{GungSeongRouteRuleTest.java => GungSeongRouteProviderTest.java} (84%) rename src/test/java/janggi/domain/direction/{StraightRouteRuleTest.java => StraightRouteProviderTest.java} (87%) diff --git a/src/main/java/janggi/domain/direction/GungSeongRouteRule.java b/src/main/java/janggi/domain/direction/GungSeongRouteProvider.java similarity index 95% rename from src/main/java/janggi/domain/direction/GungSeongRouteRule.java rename to src/main/java/janggi/domain/direction/GungSeongRouteProvider.java index 243bf69145..36958ab5c9 100644 --- a/src/main/java/janggi/domain/direction/GungSeongRouteRule.java +++ b/src/main/java/janggi/domain/direction/GungSeongRouteProvider.java @@ -12,7 +12,7 @@ import janggi.domain.Location; import java.util.List; -public class GungSeongRouteRule implements RouteRule { +public class GungSeongRouteProvider implements RouteProvider { @Override public List calculateRoute(Location from, Location to) { diff --git a/src/main/java/janggi/domain/direction/RouteRule.java b/src/main/java/janggi/domain/direction/RouteProvider.java similarity index 82% rename from src/main/java/janggi/domain/direction/RouteRule.java rename to src/main/java/janggi/domain/direction/RouteProvider.java index af138d0e6a..cc674f739c 100644 --- a/src/main/java/janggi/domain/direction/RouteRule.java +++ b/src/main/java/janggi/domain/direction/RouteProvider.java @@ -3,7 +3,7 @@ import janggi.domain.Location; import java.util.List; -public interface RouteRule { +public interface RouteProvider { List calculateRoute(Location from, Location to); } diff --git a/src/main/java/janggi/domain/direction/StraightRouteRule.java b/src/main/java/janggi/domain/direction/StraightRouteProvider.java similarity index 95% rename from src/main/java/janggi/domain/direction/StraightRouteRule.java rename to src/main/java/janggi/domain/direction/StraightRouteProvider.java index 2133b8059b..98fe58ce7b 100644 --- a/src/main/java/janggi/domain/direction/StraightRouteRule.java +++ b/src/main/java/janggi/domain/direction/StraightRouteProvider.java @@ -8,7 +8,7 @@ import janggi.domain.Location; import java.util.List; -public class StraightRouteRule implements RouteRule { +public class StraightRouteProvider implements RouteProvider { @Override public List calculateRoute(Location from, Location to) { diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index 4aad45fea9..96cdfb51b6 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -2,14 +2,14 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.RouteRule; -import janggi.domain.direction.StraightRouteRule; +import janggi.domain.direction.RouteProvider; +import janggi.domain.direction.StraightRouteProvider; import java.util.List; public class Cha extends Piece { private static final String PIECE_NAME = "차"; - private static final RouteRule ROUTE_RULE = new StraightRouteRule(); + private static final RouteProvider ROUTE_PROVIDER = new StraightRouteProvider(); public Cha(Side side) { super(PIECE_NAME, side); @@ -23,7 +23,7 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { try { - return ROUTE_RULE.calculateRoute(from, to); + return ROUTE_PROVIDER.calculateRoute(from, to); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("차는 해당 위치에 도달할 수 없습니다."); } diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 889a83d0cc..1deba5c388 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -2,14 +2,14 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.RouteRule; -import janggi.domain.direction.GungSeongRouteRule; +import janggi.domain.direction.RouteProvider; +import janggi.domain.direction.GungSeongRouteProvider; import java.util.List; public class Gung extends Piece { private static final String PIECE_NAME = "궁"; - private static final RouteRule ROUTE_RULE = new GungSeongRouteRule(); + private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); public Gung(Side side) { super(PIECE_NAME, side); @@ -23,7 +23,7 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { try { - return ROUTE_RULE.calculateRoute(from, to); + return ROUTE_PROVIDER.calculateRoute(from, to); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("궁은 해당 위치에 도달할 수 없습니다."); } diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 241706efb9..579828dd57 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -2,14 +2,14 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.GungSeongRouteRule; -import janggi.domain.direction.RouteRule; +import janggi.domain.direction.GungSeongRouteProvider; +import janggi.domain.direction.RouteProvider; import java.util.List; public class Po extends Piece { private static final String PIECE_NAME = "포"; - private static final RouteRule ROUTE_RULE = new GungSeongRouteRule(); + private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); public Po(Side side) { super(PIECE_NAME, side); @@ -23,7 +23,7 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { try { - return ROUTE_RULE.calculateRoute(from, to); + return ROUTE_PROVIDER.calculateRoute(from, to); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("포는 해당 위치에 도달할 수 없습니다."); } diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index d54db7c0e8..a1153efe6d 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -2,14 +2,14 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.RouteRule; -import janggi.domain.direction.GungSeongRouteRule; +import janggi.domain.direction.RouteProvider; +import janggi.domain.direction.GungSeongRouteProvider; import java.util.List; public class Sa extends Piece { private static final String PIECE_NAME = "사"; - private static final RouteRule ROUTE_RULE = new GungSeongRouteRule(); + private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); public Sa(Side side) { super(PIECE_NAME, side); @@ -23,7 +23,7 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { try { - return ROUTE_RULE.calculateRoute(from, to); + return ROUTE_PROVIDER.calculateRoute(from, to); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("사는 해당 위치에 도달할 수 없습니다."); } diff --git a/src/test/java/janggi/domain/direction/GungSeongRouteRuleTest.java b/src/test/java/janggi/domain/direction/GungSeongRouteProviderTest.java similarity index 84% rename from src/test/java/janggi/domain/direction/GungSeongRouteRuleTest.java rename to src/test/java/janggi/domain/direction/GungSeongRouteProviderTest.java index 973a0a90ba..68b2385100 100644 --- a/src/test/java/janggi/domain/direction/GungSeongRouteRuleTest.java +++ b/src/test/java/janggi/domain/direction/GungSeongRouteProviderTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -class GungSeongRouteRuleTest { +class GungSeongRouteProviderTest { @ParameterizedTest @DisplayName("궁성 안에서 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") @@ -16,10 +16,10 @@ void shouldReturnRouteForReachableLocation(List destination) { // given Location from = Location.from(List.of(4, 1)); Location to = Location.from(destination); - RouteRule routeRule = new GungSeongRouteRule(); + RouteProvider routeProvider = new GungSeongRouteProvider(); // when & then - Assertions.assertThat(routeRule.calculateRoute(from, to)) + Assertions.assertThat(routeProvider.calculateRoute(from, to)) .isEqualTo(List.of(to)); } @@ -41,10 +41,10 @@ void shouldThrowExceptionForUnReachableLocation(List coordination) { // given Location from = Location.from(List.of(3, 2)); Location to = Location.from(coordination); - RouteRule routeRule = new GungSeongRouteRule(); + RouteProvider routeProvider = new GungSeongRouteProvider(); // when & then - Assertions.assertThatThrownBy(() -> routeRule.calculateRoute(from, to)) + Assertions.assertThatThrownBy(() -> routeProvider.calculateRoute(from, to)) .isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/janggi/domain/direction/StraightRouteRuleTest.java b/src/test/java/janggi/domain/direction/StraightRouteProviderTest.java similarity index 87% rename from src/test/java/janggi/domain/direction/StraightRouteRuleTest.java rename to src/test/java/janggi/domain/direction/StraightRouteProviderTest.java index 3abd1436ab..2f0c771fd4 100644 --- a/src/test/java/janggi/domain/direction/StraightRouteRuleTest.java +++ b/src/test/java/janggi/domain/direction/StraightRouteProviderTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class StraightRouteRuleTest { +class StraightRouteProviderTest { @ParameterizedTest @DisplayName("직선으로 이동할 위치를 파라미터로 받으면 이동 경로를 반환한다.") @@ -17,10 +17,10 @@ class StraightRouteRuleTest { void shouldReturnRouteForReachableLocation(Location destination, List route) { // given Location from = Location.from(List.of(2, 2)); - RouteRule routeRule = new StraightRouteRule(); + RouteProvider routeProvider = new StraightRouteProvider(); // when & then - Assertions.assertThat(routeRule.calculateRoute(from, destination)) + Assertions.assertThat(routeProvider.calculateRoute(from, destination)) .isEqualTo(route); } @@ -52,10 +52,10 @@ void shouldThrowExceptionForUnReachableLocation(List coordination) { // given Location from = Location.from(List.of(1, 1)); Location to = Location.from(coordination); - RouteRule routeRule = new StraightRouteRule(); + RouteProvider routeProvider = new StraightRouteProvider(); // when & then - Assertions.assertThatThrownBy(() -> routeRule.calculateRoute(from, to)) + Assertions.assertThatThrownBy(() -> routeProvider.calculateRoute(from, to)) .isInstanceOf(IllegalArgumentException.class); } From 0441baa4a5e52794944ab79ecb74726d07ce97b6 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 11:34:42 +0900 Subject: [PATCH 22/45] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - direction으로 되어있던 규칙 패키지명을 rule로 변경 - 경로에 관한 규칙을 지닌 인터페이스 및 클래스들을 하위 패키지인 route 패키지로 이동 --- src/main/java/janggi/domain/piece/Cha.java | 4 ++-- src/main/java/janggi/domain/piece/Gung.java | 4 ++-- .../java/janggi/domain/piece/Jolbyeong.java | 12 ++++++------ src/main/java/janggi/domain/piece/Ma.java | 4 ++-- src/main/java/janggi/domain/piece/Po.java | 4 ++-- src/main/java/janggi/domain/piece/Sa.java | 4 ++-- src/main/java/janggi/domain/piece/Sang.java | 18 +++++++++--------- .../{direction => rule/route}/Direction.java | 2 +- .../route}/GungSeongRouteProvider.java | 18 +++++++++--------- .../{direction => rule/route}/Route.java | 2 +- .../route}/RouteProvider.java | 2 +- .../route}/StraightRouteProvider.java | 10 +++++----- .../route}/GungSeongRouteProviderTest.java | 2 +- .../{direction => rule/route}/RouteTest.java | 2 +- .../route}/StraightRouteProviderTest.java | 2 +- 15 files changed, 45 insertions(+), 45 deletions(-) rename src/main/java/janggi/domain/{direction => rule/route}/Direction.java (92%) rename src/main/java/janggi/domain/{direction => rule/route}/GungSeongRouteProvider.java (64%) rename src/main/java/janggi/domain/{direction => rule/route}/Route.java (96%) rename src/main/java/janggi/domain/{direction => rule/route}/RouteProvider.java (82%) rename src/main/java/janggi/domain/{direction => rule/route}/StraightRouteProvider.java (80%) rename src/test/java/janggi/domain/{direction => rule/route}/GungSeongRouteProviderTest.java (98%) rename src/test/java/janggi/domain/{direction => rule/route}/RouteTest.java (97%) rename src/test/java/janggi/domain/{direction => rule/route}/StraightRouteProviderTest.java (98%) diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index 96cdfb51b6..671336ad9a 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -2,8 +2,8 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.RouteProvider; -import janggi.domain.direction.StraightRouteProvider; +import janggi.domain.rule.route.RouteProvider; +import janggi.domain.rule.route.StraightRouteProvider; import java.util.List; public class Cha extends Piece { diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 1deba5c388..85f94f4f56 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -2,8 +2,8 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.RouteProvider; -import janggi.domain.direction.GungSeongRouteProvider; +import janggi.domain.rule.route.RouteProvider; +import janggi.domain.rule.route.GungSeongRouteProvider; import java.util.List; public class Gung extends Piece { diff --git a/src/main/java/janggi/domain/piece/Jolbyeong.java b/src/main/java/janggi/domain/piece/Jolbyeong.java index 5efcc31a00..681d869b28 100644 --- a/src/main/java/janggi/domain/piece/Jolbyeong.java +++ b/src/main/java/janggi/domain/piece/Jolbyeong.java @@ -1,14 +1,14 @@ package janggi.domain.piece; -import static janggi.domain.direction.Direction.BACK; -import static janggi.domain.direction.Direction.FRONT; -import static janggi.domain.direction.Direction.LEFT; -import static janggi.domain.direction.Direction.RIGHT; +import static janggi.domain.rule.route.Direction.BACK; +import static janggi.domain.rule.route.Direction.FRONT; +import static janggi.domain.rule.route.Direction.LEFT; +import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.Direction; -import janggi.domain.direction.Route; +import janggi.domain.rule.route.Direction; +import janggi.domain.rule.route.Route; import java.util.List; diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index d4785d5dab..949e9a6ca3 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -1,10 +1,10 @@ package janggi.domain.piece; -import static janggi.domain.direction.Direction.*; +import static janggi.domain.rule.route.Direction.*; import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.Route; +import janggi.domain.rule.route.Route; import java.util.List; public class Ma extends Piece { diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 579828dd57..2580a0f5b9 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -2,8 +2,8 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.GungSeongRouteProvider; -import janggi.domain.direction.RouteProvider; +import janggi.domain.rule.route.GungSeongRouteProvider; +import janggi.domain.rule.route.RouteProvider; import java.util.List; public class Po extends Piece { diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index a1153efe6d..452f3f6f54 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -2,8 +2,8 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.RouteProvider; -import janggi.domain.direction.GungSeongRouteProvider; +import janggi.domain.rule.route.RouteProvider; +import janggi.domain.rule.route.GungSeongRouteProvider; import java.util.List; public class Sa extends Piece { diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index 8262b469e2..990cfd3ff0 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -1,17 +1,17 @@ package janggi.domain.piece; -import static janggi.domain.direction.Direction.BACK; -import static janggi.domain.direction.Direction.BACK_LEFT; -import static janggi.domain.direction.Direction.BACK_RIGHT; -import static janggi.domain.direction.Direction.FRONT; -import static janggi.domain.direction.Direction.FRONT_LEFT; -import static janggi.domain.direction.Direction.FRONT_RIGHT; -import static janggi.domain.direction.Direction.LEFT; -import static janggi.domain.direction.Direction.RIGHT; +import static janggi.domain.rule.route.Direction.BACK; +import static janggi.domain.rule.route.Direction.BACK_LEFT; +import static janggi.domain.rule.route.Direction.BACK_RIGHT; +import static janggi.domain.rule.route.Direction.FRONT; +import static janggi.domain.rule.route.Direction.FRONT_LEFT; +import static janggi.domain.rule.route.Direction.FRONT_RIGHT; +import static janggi.domain.rule.route.Direction.LEFT; +import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.direction.Route; +import janggi.domain.rule.route.Route; import java.util.List; public class Sang extends Piece { diff --git a/src/main/java/janggi/domain/direction/Direction.java b/src/main/java/janggi/domain/rule/route/Direction.java similarity index 92% rename from src/main/java/janggi/domain/direction/Direction.java rename to src/main/java/janggi/domain/rule/route/Direction.java index b4373e125f..0d18a3f85d 100644 --- a/src/main/java/janggi/domain/direction/Direction.java +++ b/src/main/java/janggi/domain/rule/route/Direction.java @@ -1,4 +1,4 @@ -package janggi.domain.direction; +package janggi.domain.rule.route; import janggi.domain.Location; diff --git a/src/main/java/janggi/domain/direction/GungSeongRouteProvider.java b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java similarity index 64% rename from src/main/java/janggi/domain/direction/GungSeongRouteProvider.java rename to src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java index 36958ab5c9..26ba5e29bc 100644 --- a/src/main/java/janggi/domain/direction/GungSeongRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java @@ -1,13 +1,13 @@ -package janggi.domain.direction; +package janggi.domain.rule.route; -import static janggi.domain.direction.Direction.BACK; -import static janggi.domain.direction.Direction.BACK_LEFT; -import static janggi.domain.direction.Direction.BACK_RIGHT; -import static janggi.domain.direction.Direction.FRONT; -import static janggi.domain.direction.Direction.FRONT_LEFT; -import static janggi.domain.direction.Direction.FRONT_RIGHT; -import static janggi.domain.direction.Direction.LEFT; -import static janggi.domain.direction.Direction.RIGHT; +import static janggi.domain.rule.route.Direction.BACK; +import static janggi.domain.rule.route.Direction.BACK_LEFT; +import static janggi.domain.rule.route.Direction.BACK_RIGHT; +import static janggi.domain.rule.route.Direction.FRONT; +import static janggi.domain.rule.route.Direction.FRONT_LEFT; +import static janggi.domain.rule.route.Direction.FRONT_RIGHT; +import static janggi.domain.rule.route.Direction.LEFT; +import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; import java.util.List; diff --git a/src/main/java/janggi/domain/direction/Route.java b/src/main/java/janggi/domain/rule/route/Route.java similarity index 96% rename from src/main/java/janggi/domain/direction/Route.java rename to src/main/java/janggi/domain/rule/route/Route.java index b65a44ace4..0be8725635 100644 --- a/src/main/java/janggi/domain/direction/Route.java +++ b/src/main/java/janggi/domain/rule/route/Route.java @@ -1,4 +1,4 @@ -package janggi.domain.direction; +package janggi.domain.rule.route; import janggi.domain.Location; import java.util.ArrayList; diff --git a/src/main/java/janggi/domain/direction/RouteProvider.java b/src/main/java/janggi/domain/rule/route/RouteProvider.java similarity index 82% rename from src/main/java/janggi/domain/direction/RouteProvider.java rename to src/main/java/janggi/domain/rule/route/RouteProvider.java index cc674f739c..32fd405a69 100644 --- a/src/main/java/janggi/domain/direction/RouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/RouteProvider.java @@ -1,4 +1,4 @@ -package janggi.domain.direction; +package janggi.domain.rule.route; import janggi.domain.Location; import java.util.List; diff --git a/src/main/java/janggi/domain/direction/StraightRouteProvider.java b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java similarity index 80% rename from src/main/java/janggi/domain/direction/StraightRouteProvider.java rename to src/main/java/janggi/domain/rule/route/StraightRouteProvider.java index 98fe58ce7b..43f5c80eeb 100644 --- a/src/main/java/janggi/domain/direction/StraightRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java @@ -1,9 +1,9 @@ -package janggi.domain.direction; +package janggi.domain.rule.route; -import static janggi.domain.direction.Direction.BACK; -import static janggi.domain.direction.Direction.FRONT; -import static janggi.domain.direction.Direction.LEFT; -import static janggi.domain.direction.Direction.RIGHT; +import static janggi.domain.rule.route.Direction.BACK; +import static janggi.domain.rule.route.Direction.FRONT; +import static janggi.domain.rule.route.Direction.LEFT; +import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; import java.util.List; diff --git a/src/test/java/janggi/domain/direction/GungSeongRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java similarity index 98% rename from src/test/java/janggi/domain/direction/GungSeongRouteProviderTest.java rename to src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java index 68b2385100..bd572fc277 100644 --- a/src/test/java/janggi/domain/direction/GungSeongRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java @@ -1,4 +1,4 @@ -package janggi.domain.direction; +package janggi.domain.rule.route; import janggi.domain.Location; import java.util.List; diff --git a/src/test/java/janggi/domain/direction/RouteTest.java b/src/test/java/janggi/domain/rule/route/RouteTest.java similarity index 97% rename from src/test/java/janggi/domain/direction/RouteTest.java rename to src/test/java/janggi/domain/rule/route/RouteTest.java index 832f595f00..afea2a27ca 100644 --- a/src/test/java/janggi/domain/direction/RouteTest.java +++ b/src/test/java/janggi/domain/rule/route/RouteTest.java @@ -1,4 +1,4 @@ -package janggi.domain.direction; +package janggi.domain.rule.route; import janggi.domain.Location; import java.util.List; diff --git a/src/test/java/janggi/domain/direction/StraightRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java similarity index 98% rename from src/test/java/janggi/domain/direction/StraightRouteProviderTest.java rename to src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java index 2f0c771fd4..47765a9e50 100644 --- a/src/test/java/janggi/domain/direction/StraightRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java @@ -1,4 +1,4 @@ -package janggi.domain.direction; +package janggi.domain.rule.route; import janggi.domain.Location; import java.util.List; From 2ed703c9cd53d56e1e500cd62a004553c05d3973 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 12:22:56 +0900 Subject: [PATCH 23/45] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EC=8B=9C=20=EC=9E=A5=EC=95=A0=EB=AC=BC=20=EC=B6=A9?= =?UTF-8?q?=EB=8F=8C=20=EA=B0=90=EC=A7=80=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 포를 제외한 기물의 이동 경로에 다른 기물이 존재하는지 확인하고 예외를 발생시키도록 함 - 도착지에 같은 팀의 기물이 존재할 경우 예외를 발생시키도록 함 - 전체 코드 포맷팅을 정렬함 --- src/main/java/janggi/domain/piece/Cha.java | 8 +++ .../java/janggi/domain/piece/EmptyPiece.java | 7 +- src/main/java/janggi/domain/piece/Gung.java | 10 ++- .../java/janggi/domain/piece/Jolbyeong.java | 13 +++- src/main/java/janggi/domain/piece/Ma.java | 21 +++++- src/main/java/janggi/domain/piece/Piece.java | 2 + src/main/java/janggi/domain/piece/Po.java | 5 ++ src/main/java/janggi/domain/piece/Sa.java | 10 ++- src/main/java/janggi/domain/piece/Sang.java | 12 +++- .../rule/collision/CollisionDetector.java | 9 +++ .../collision/DefaultCollisionDetector.java | 29 ++++++++ .../janggi/domain/rule/route/Direction.java | 10 +-- .../rule/route/GungSeongRouteProvider.java | 4 +- .../java/janggi/domain/rule/route/Route.java | 16 ++--- .../rule/route/StraightRouteProvider.java | 4 +- .../java/janggi/domain/piece/TeamPiece.java | 37 +++++++++++ .../DefaultCollisionDetectorTest.java | 66 +++++++++++++++++++ .../rule/collision/TestCollisionDetector.java | 11 ++++ .../janggi/strategy/MaSangMaSangTest.java | 1 + .../janggi/strategy/MaSangSangMaTest.java | 1 + .../janggi/strategy/SangMaMaSangTest.java | 1 + .../janggi/strategy/SangMaSangMaTest.java | 1 + src/test/java/janggi/strategy/TeamPiece.java | 25 ------- 23 files changed, 250 insertions(+), 53 deletions(-) create mode 100644 src/main/java/janggi/domain/rule/collision/CollisionDetector.java create mode 100644 src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java create mode 100644 src/test/java/janggi/domain/piece/TeamPiece.java create mode 100644 src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java create mode 100644 src/test/java/janggi/domain/rule/collision/TestCollisionDetector.java delete mode 100644 src/test/java/janggi/strategy/TeamPiece.java diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index 671336ad9a..f12f5554e3 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -2,6 +2,8 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.RouteProvider; import janggi.domain.rule.route.StraightRouteProvider; import java.util.List; @@ -10,6 +12,7 @@ public class Cha extends Piece { private static final String PIECE_NAME = "차"; private static final RouteProvider ROUTE_PROVIDER = new StraightRouteProvider(); + private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); public Cha(Side side) { super(PIECE_NAME, side); @@ -28,4 +31,9 @@ public List calculateRoute(Location from, Location to) { throw new IllegalArgumentException("차는 해당 위치에 도달할 수 없습니다."); } } + + @Override + public void detectCollision(List piecesOnPath) { + COLLISION_DETECTOR.check(this, piecesOnPath); + } } diff --git a/src/main/java/janggi/domain/piece/EmptyPiece.java b/src/main/java/janggi/domain/piece/EmptyPiece.java index 230a301035..cb30750703 100644 --- a/src/main/java/janggi/domain/piece/EmptyPiece.java +++ b/src/main/java/janggi/domain/piece/EmptyPiece.java @@ -19,6 +19,11 @@ public boolean isEmpty() { @Override public List calculateRoute(Location from, Location to) { - return List.of(); + throw new IllegalArgumentException("빈 객체는 이동할 수 없습니다."); + } + + @Override + public void detectCollision(List piecesOnPath) { + throw new IllegalArgumentException("빈 객체는 이동할 수 없습니다."); } } diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 85f94f4f56..0ebd5d0f13 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -2,14 +2,17 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.rule.route.RouteProvider; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.GungSeongRouteProvider; +import janggi.domain.rule.route.RouteProvider; import java.util.List; public class Gung extends Piece { private static final String PIECE_NAME = "궁"; private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); + private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); public Gung(Side side) { super(PIECE_NAME, side); @@ -28,4 +31,9 @@ public List calculateRoute(Location from, Location to) { throw new IllegalArgumentException("궁은 해당 위치에 도달할 수 없습니다."); } } + + @Override + public void detectCollision(List piecesOnPath) { + COLLISION_DETECTOR.check(this, piecesOnPath); + } } diff --git a/src/main/java/janggi/domain/piece/Jolbyeong.java b/src/main/java/janggi/domain/piece/Jolbyeong.java index 681d869b28..6e935ccbeb 100644 --- a/src/main/java/janggi/domain/piece/Jolbyeong.java +++ b/src/main/java/janggi/domain/piece/Jolbyeong.java @@ -7,9 +7,10 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.Direction; import janggi.domain.rule.route.Route; - import java.util.List; public class Jolbyeong extends Piece { @@ -17,6 +18,7 @@ public class Jolbyeong extends Piece { private static final String PIECE_NAME = "졸병"; private static final String CHO_PIECE_NAME = "졸"; private static final String HAN_PIECE_NAME = "병"; + private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); public Jolbyeong(Side side) { super(PIECE_NAME, side); @@ -35,15 +37,20 @@ public List calculateRoute(Location from, Location to) { Route.of(List.of(RIGHT)) ); - for(Route route : directions) { + for (Route route : directions) { List locations = route.apply(from); - if(locations.getLast().equals(to)) { + if (locations.getLast().equals(to)) { return locations; } } throw new IllegalArgumentException(getName() + "은 해당 위치에 도달할 수 없습니다."); } + @Override + public void detectCollision(List piecesOnPath) { + COLLISION_DETECTOR.check(this, piecesOnPath); + } + private Direction getRealFront(Side side) { if (side.equals(Side.HAN)) { return FRONT; diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index 949e9a6ca3..45b33420a0 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -1,15 +1,25 @@ package janggi.domain.piece; -import static janggi.domain.rule.route.Direction.*; +import static janggi.domain.rule.route.Direction.BACK; +import static janggi.domain.rule.route.Direction.BACK_LEFT; +import static janggi.domain.rule.route.Direction.BACK_RIGHT; +import static janggi.domain.rule.route.Direction.FRONT; +import static janggi.domain.rule.route.Direction.FRONT_LEFT; +import static janggi.domain.rule.route.Direction.FRONT_RIGHT; +import static janggi.domain.rule.route.Direction.LEFT; +import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.Route; import java.util.List; public class Ma extends Piece { private static final String PIECE_NAME = "마"; + private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); public Ma(Side side) { super(PIECE_NAME, side); @@ -33,13 +43,18 @@ public List calculateRoute(Location from, Location to) { Route.of(List.of(BACK, BACK_RIGHT)) ); - for(Route route : directions) { + for (Route route : directions) { List locations = route.apply(from); - if(locations.getLast().equals(to)) { + if (locations.getLast().equals(to)) { return locations; } } throw new IllegalArgumentException("마는 해당 위치에 도달할 수 없습니다."); } + + @Override + public void detectCollision(List piecesOnPath) { + COLLISION_DETECTOR.check(this, piecesOnPath); + } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 7aca9d7b14..09e1db04c4 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -18,6 +18,8 @@ protected Piece(String name, Side side) { public abstract List calculateRoute(Location from, Location to); + public abstract void detectCollision(List piecesOnPath); + public boolean isSameSide(Piece piece) { return this.side.equals(piece.side); } diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 2580a0f5b9..22d04facd3 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -28,5 +28,10 @@ public List calculateRoute(Location from, Location to) { throw new IllegalArgumentException("포는 해당 위치에 도달할 수 없습니다."); } } + + @Override + public void detectCollision(List piecesOnPath) { + + } } diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index 452f3f6f54..34efdb8f3a 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -2,14 +2,17 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.rule.route.RouteProvider; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.GungSeongRouteProvider; +import janggi.domain.rule.route.RouteProvider; import java.util.List; public class Sa extends Piece { private static final String PIECE_NAME = "사"; private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); + private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); public Sa(Side side) { super(PIECE_NAME, side); @@ -28,4 +31,9 @@ public List calculateRoute(Location from, Location to) { throw new IllegalArgumentException("사는 해당 위치에 도달할 수 없습니다."); } } + + @Override + public void detectCollision(List piecesOnPath) { + COLLISION_DETECTOR.check(this, piecesOnPath); + } } diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index 990cfd3ff0..6f057e26fe 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -11,12 +11,15 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.Route; import java.util.List; public class Sang extends Piece { private static final String PIECE_NAME = "상"; + private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); public Sang(Side side) { super(PIECE_NAME, side); @@ -40,13 +43,18 @@ public List calculateRoute(Location from, Location to) { Route.of(List.of(BACK, BACK_RIGHT, BACK_RIGHT)) ); - for(Route route : directions) { + for (Route route : directions) { List locations = route.apply(from); - if(locations.getLast().equals(to)) { + if (locations.getLast().equals(to)) { return locations; } } throw new IllegalArgumentException("상은 해당 위치에 도달할 수 없습니다."); } + + @Override + public void detectCollision(List piecesOnPath) { + COLLISION_DETECTOR.check(this, piecesOnPath); + } } diff --git a/src/main/java/janggi/domain/rule/collision/CollisionDetector.java b/src/main/java/janggi/domain/rule/collision/CollisionDetector.java new file mode 100644 index 0000000000..9d94fe4f73 --- /dev/null +++ b/src/main/java/janggi/domain/rule/collision/CollisionDetector.java @@ -0,0 +1,9 @@ +package janggi.domain.rule.collision; + +import janggi.domain.piece.Piece; +import java.util.List; + +public interface CollisionDetector { + + void check(Piece piece, List piecesOnPath); +} diff --git a/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java b/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java new file mode 100644 index 0000000000..c656bcfaa2 --- /dev/null +++ b/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java @@ -0,0 +1,29 @@ +package janggi.domain.rule.collision; + +import janggi.domain.piece.Piece; +import java.util.List; + +public class DefaultCollisionDetector implements CollisionDetector { + + @Override + public void check(Piece piece, List piecesOnPath) { + validateMiddlePath(piecesOnPath); + validateDestination(piece, piecesOnPath); + } + + private void validateMiddlePath(List piecesOnPath) { + List middlePath = piecesOnPath.subList(0, piecesOnPath.size() - 1); + for (Piece pathPiece : middlePath) { + if (!pathPiece.isEmpty()) { + throw new IllegalArgumentException("이동 경로에 기물이 존재합니다"); + } + } + } + + private void validateDestination(Piece piece, List piecesOnPath) { + Piece destinationPiece = piecesOnPath.getLast(); + if (destinationPiece.isSameSide(piece)) { + throw new IllegalArgumentException("도착 위치에 같은 팀이 존재합니다"); + } + } +} diff --git a/src/main/java/janggi/domain/rule/route/Direction.java b/src/main/java/janggi/domain/rule/route/Direction.java index 0d18a3f85d..d89612fd38 100644 --- a/src/main/java/janggi/domain/rule/route/Direction.java +++ b/src/main/java/janggi/domain/rule/route/Direction.java @@ -4,13 +4,13 @@ public enum Direction { - FRONT(0,1), - LEFT(-1,0), + FRONT(0, 1), + LEFT(-1, 0), RIGHT(1, 0), BACK(0, -1), - FRONT_LEFT(-1,1), - FRONT_RIGHT(1,1), - BACK_LEFT(-1,-1), + FRONT_LEFT(-1, 1), + FRONT_RIGHT(1, 1), + BACK_LEFT(-1, -1), BACK_RIGHT(1, -1); private final int dx; diff --git a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java index 26ba5e29bc..2bdf6ea937 100644 --- a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java @@ -27,9 +27,9 @@ public List calculateRoute(Location from, Location to) { Route.of(List.of(BACK_RIGHT)) ); - for(Route route : directions) { + for (Route route : directions) { List locations = route.apply(from); - if(locations.getLast().equals(to)) { + if (locations.getLast().equals(to)) { return locations; } } diff --git a/src/main/java/janggi/domain/rule/route/Route.java b/src/main/java/janggi/domain/rule/route/Route.java index 0be8725635..66cd885396 100644 --- a/src/main/java/janggi/domain/rule/route/Route.java +++ b/src/main/java/janggi/domain/rule/route/Route.java @@ -16,6 +16,14 @@ public static Route of(List directions) { return new Route(directions); } + public static Route create(Direction direction, int count) { + List result = new ArrayList<>(); + for (int i = 0; i < count; i++) { + result.add(direction); + } + return new Route(result); + } + public List apply(Location current) { List result = new ArrayList<>(); for (Direction direction : directions) { @@ -25,12 +33,4 @@ public List apply(Location current) { return result; } - - public static Route create(Direction direction, int count) { - List result = new ArrayList<>(); - for (int i = 0; i < count; i++) { - result.add(direction); - } - return new Route(result); - } } diff --git a/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java index 43f5c80eeb..69b0df611e 100644 --- a/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java @@ -21,9 +21,9 @@ public List calculateRoute(Location from, Location to) { Route.create(RIGHT, maxDistance) ); - for(Route route : directions) { + for (Route route : directions) { List locations = route.apply(from); - if(locations.contains(to)) { + if (locations.contains(to)) { return locations; } } diff --git a/src/test/java/janggi/domain/piece/TeamPiece.java b/src/test/java/janggi/domain/piece/TeamPiece.java new file mode 100644 index 0000000000..e7a4d8c01c --- /dev/null +++ b/src/test/java/janggi/domain/piece/TeamPiece.java @@ -0,0 +1,37 @@ +package janggi.domain.piece; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.TestCollisionDetector; +import java.util.List; + +public class TeamPiece extends Piece { + + private static final String PIECE_NAME = "test"; + private final CollisionDetector detector; + + public TeamPiece(Side side) { + this(side, new TestCollisionDetector()); + } + + public TeamPiece(Side side, CollisionDetector detector) { + super(PIECE_NAME, side); + this.detector = detector; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public List calculateRoute(Location from, Location to) { + return List.of(); + } + + @Override + public void detectCollision(List piecesOnPath) { + detector.check(this, piecesOnPath); + } +} diff --git a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java new file mode 100644 index 0000000000..47a28cc3e8 --- /dev/null +++ b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java @@ -0,0 +1,66 @@ +package janggi.domain.rule.collision; + +import janggi.domain.Side; +import janggi.domain.piece.EmptyPiece; +import janggi.domain.piece.Piece; +import janggi.domain.piece.TeamPiece; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DefaultCollisionDetectorTest { + + @Test + @DisplayName("이동 경로에 장애물이 존재하지 않고, 마지막 경로에 위치한 기물이 존재하지 않으면 예외를 반환하지 않는다.") + void shouldNotThrowExceptionWhenNoPieceOnPathAndNoPieceOnDestination() { + // given + CollisionDetector collisionDetector = new DefaultCollisionDetector(); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece()); + Piece piece = new TeamPiece(Side.CHO); + + // when & then + Assertions.assertThatNoException() + .isThrownBy(() -> collisionDetector.check(piece, piecesOnPath)); + } + + @Test + @DisplayName("이동 경로에 장애물이 존재하지 않고, 마지막 경로에 위치한 기물이 상대팀이면 예외를 반환하지 않는다.") + void shouldNotThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsOtherSide() { + // given + CollisionDetector collisionDetector = new DefaultCollisionDetector(); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TeamPiece(Side.HAN)); + Piece piece = new TeamPiece(Side.CHO); + + // when & then + Assertions.assertThatNoException() + .isThrownBy(() -> collisionDetector.check(piece, piecesOnPath)); + } + + @Test + @DisplayName("이동 경로에 장애물이 존재하는 경우 예외를 발생시킨다.") + void shouldThrowExceptionWhenPieceOnPathExist() { + // given + CollisionDetector collisionDetector = new DefaultCollisionDetector(); + List piecesOnPath = List.of(new TeamPiece(Side.HAN), new EmptyPiece(), new EmptyPiece()); + Piece piece = new TeamPiece(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이동 경로에 장애물이 존재하지 않고, 마지막 경로에 위치한 기물이 우리팀이면 예외를 발생시킨다.") + void shouldThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsMySide() { + // given + CollisionDetector collisionDetector = new DefaultCollisionDetector(); + Side side = Side.HAN; + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TeamPiece(side)); + Piece piece = new TeamPiece(side); + + // when & then + Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/janggi/domain/rule/collision/TestCollisionDetector.java b/src/test/java/janggi/domain/rule/collision/TestCollisionDetector.java new file mode 100644 index 0000000000..5f778ef4fa --- /dev/null +++ b/src/test/java/janggi/domain/rule/collision/TestCollisionDetector.java @@ -0,0 +1,11 @@ +package janggi.domain.rule.collision; + +import janggi.domain.piece.Piece; +import java.util.List; + +public class TestCollisionDetector implements CollisionDetector{ + @Override + public void check(Piece piece, List piecesOnPath) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/test/java/janggi/strategy/MaSangMaSangTest.java b/src/test/java/janggi/strategy/MaSangMaSangTest.java index 92a327f7fd..9d51ebe4e1 100644 --- a/src/test/java/janggi/strategy/MaSangMaSangTest.java +++ b/src/test/java/janggi/strategy/MaSangMaSangTest.java @@ -5,6 +5,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; import janggi.domain.Side; +import janggi.domain.piece.TeamPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/janggi/strategy/MaSangSangMaTest.java b/src/test/java/janggi/strategy/MaSangSangMaTest.java index a05ea1087e..83d4586322 100644 --- a/src/test/java/janggi/strategy/MaSangSangMaTest.java +++ b/src/test/java/janggi/strategy/MaSangSangMaTest.java @@ -5,6 +5,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; import janggi.domain.Side; +import janggi.domain.piece.TeamPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java index 2eed3ee77f..33e964d8ed 100644 --- a/src/test/java/janggi/strategy/SangMaMaSangTest.java +++ b/src/test/java/janggi/strategy/SangMaMaSangTest.java @@ -5,6 +5,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; import janggi.domain.Side; +import janggi.domain.piece.TeamPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/janggi/strategy/SangMaSangMaTest.java b/src/test/java/janggi/strategy/SangMaSangMaTest.java index dca4ac8687..035b05be1c 100644 --- a/src/test/java/janggi/strategy/SangMaSangMaTest.java +++ b/src/test/java/janggi/strategy/SangMaSangMaTest.java @@ -5,6 +5,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; import janggi.domain.Side; +import janggi.domain.piece.TeamPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/janggi/strategy/TeamPiece.java b/src/test/java/janggi/strategy/TeamPiece.java deleted file mode 100644 index cb08ac08ef..0000000000 --- a/src/test/java/janggi/strategy/TeamPiece.java +++ /dev/null @@ -1,25 +0,0 @@ -package janggi.strategy; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.piece.Piece; -import java.util.List; - -public class TeamPiece extends Piece { - - private static final String PIECE_NAME = "test"; - - public TeamPiece(Side side) { - super(PIECE_NAME, side); - } - - @Override - public boolean isEmpty() { - return true; - } - - @Override - public List calculateRoute(Location from, Location to) { - return List.of(); - } -} From 8e916fd5760446b8a0330742e1f45003b7a08a0e Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 12:54:19 +0900 Subject: [PATCH 24/45] =?UTF-8?q?feat:=20=ED=8F=AC=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9E=A5=EC=95=A0=EB=AC=BC=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=EA=B0=90=EC=A7=80=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 --- src/main/java/janggi/domain/piece/Po.java | 5 +- .../rule/collision/PoCollisionDetector.java | 43 +++++++ .../collision/PoCollisionDetectorTest.java | 105 ++++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java create mode 100644 src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 22d04facd3..603d55bf63 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -2,6 +2,8 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.PoCollisionDetector; import janggi.domain.rule.route.GungSeongRouteProvider; import janggi.domain.rule.route.RouteProvider; import java.util.List; @@ -10,6 +12,7 @@ public class Po extends Piece { private static final String PIECE_NAME = "포"; private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); + private static final CollisionDetector COLLISION_DETECTOR = new PoCollisionDetector(); public Po(Side side) { super(PIECE_NAME, side); @@ -31,7 +34,7 @@ public List calculateRoute(Location from, Location to) { @Override public void detectCollision(List piecesOnPath) { - + COLLISION_DETECTOR.check(this, piecesOnPath); } } diff --git a/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java b/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java new file mode 100644 index 0000000000..f4dd255487 --- /dev/null +++ b/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java @@ -0,0 +1,43 @@ +package janggi.domain.rule.collision; + +import janggi.domain.piece.Piece; +import java.util.List; + +public class PoCollisionDetector implements CollisionDetector { + + private static final int REQUIRED_SCREEN_COUNT = 1; + + @Override + public void check(Piece piece, List piecesOnPath) { + validateOneObstacle(piecesOnPath); + validatePoExistence(piece, piecesOnPath); + validateDestination(piece, piecesOnPath); + } + + private void validateOneObstacle(List piecesOnPath) { + List middlePath = piecesOnPath.subList(0, piecesOnPath.size() - 1); + + long obstacleCount = middlePath.stream() + .filter(piece -> !piece.isEmpty()) + .count(); + + if (obstacleCount != REQUIRED_SCREEN_COUNT) { + throw new IllegalArgumentException("포는 반드시 하나의 기물을 넘어야 합니다."); + } + } + + private void validatePoExistence(Piece self, List piecesOnPath) { + for (Piece piece : piecesOnPath) { + if (self.getClass() == piece.getClass()) { + throw new IllegalArgumentException("이동 경로 또는 도착지에 포가 존재할 수 없습니다."); + } + } + } + + private void validateDestination(Piece piece, List piecesOnPath) { + Piece destinationPiece = piecesOnPath.getLast(); + if (destinationPiece.isSameSide(piece)) { + throw new IllegalArgumentException("도착 위치에 같은 팀이 존재합니다"); + } + } +} diff --git a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java new file mode 100644 index 0000000000..6afd7b8f0a --- /dev/null +++ b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java @@ -0,0 +1,105 @@ +package janggi.domain.rule.collision; + +import janggi.domain.Side; +import janggi.domain.piece.EmptyPiece; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Po; +import janggi.domain.piece.TeamPiece; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PoCollisionDetectorTest { + + @Test + @DisplayName("이동 경로에 포가 아닌 장애물이 1개 존재하고, 마지막 경로 좌표에 기물이 존재하지 않으면 예외를 반환하지 않는다.") + void shouldNotThrowExceptionWhenOneNonPoObstacleAndEmptyDestination() { + // given + CollisionDetector collisionDetector = new PoCollisionDetector(); + List piecesOnPath = List.of(new EmptyPiece(), new TeamPiece(Side.HAN), new EmptyPiece()); + Piece piece = new Po(Side.CHO); + + // when & then + Assertions.assertThatNoException() + .isThrownBy(() -> collisionDetector.check(piece, piecesOnPath)); + } + + @Test + @DisplayName("이동 경로에 포가 아닌 장애물이 1개 존재하고, 마지막 경로에 위치한 기물이 상대팀이면 예외를 반환하지 않는다.") + void shouldNotThrowExceptionWhenOneNonPoObstacleAndEnemyAtDestination() { + // given + CollisionDetector collisionDetector = new PoCollisionDetector(); + List piecesOnPath = List.of(new EmptyPiece(), new TeamPiece(Side.HAN), new TeamPiece(Side.HAN)); + Piece piece = new Po(Side.CHO); + + // when & then + Assertions.assertThatNoException() + .isThrownBy(() -> collisionDetector.check(piece, piecesOnPath)); + } + + @Test + @DisplayName("이동 경로에 장애물이 2개 이상 존재하는 경우 예외를 발생시킨다.") + void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { + // given + CollisionDetector collisionDetector = new PoCollisionDetector(); + List piecesOnPath = List.of(new TeamPiece(Side.HAN), new TeamPiece(Side.HAN), new EmptyPiece()); + Piece piece = new Po(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이동 경로에 장애물이 존재하지 않는 경우 예외를 발생시킨다.") + void shouldThrowExceptionWhenNoObstacleOnPath() { + // given + CollisionDetector collisionDetector = new PoCollisionDetector(); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TeamPiece(Side.HAN)); + Piece piece = new Po(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이동 경로에 포가 존재하는 경우 예외를 발생시킨다.") + void shouldThrowExceptionWhenPoOnPath() { + // given + CollisionDetector collisionDetector = new DefaultCollisionDetector(); + List piecesOnPath = List.of(new EmptyPiece(), new Po(Side.HAN), new TeamPiece(Side.HAN)); + Piece piece = new Po(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("도착지에 상대팀의 포가 존재하는 경우 예외를 발생시킨다.") + void shouldThrowExceptionWhenPoOfOtherSideOnDestination() { + // given + CollisionDetector collisionDetector = new PoCollisionDetector(); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new Po(Side.HAN)); + Piece piece = new Po(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("도착지에 우리팀 기물이 존재하는 경우 예외를 발생시킨다.") + void shouldThrowExceptionWhenSameSidePieceOnDestination() { + // given + CollisionDetector collisionDetector = new PoCollisionDetector(); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TeamPiece(Side.CHO)); + Piece piece = new Po(Side.CHO); + + // when & then + Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + .isInstanceOf(IllegalArgumentException.class); + } +} From 6aadc0b6b17fcecf63ab1b394ffed7ac0f4de290 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 14:19:13 +0900 Subject: [PATCH 25/45] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=EA=B5=AC=ED=98=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트용 구현 클래스들을 support 패키지에 모으고 클래스명을 Test로 시작하도록 네이밍 컨벤션 통일 --- .../collision/DefaultCollisionDetectorTest.java | 16 ++++++++-------- .../rule/collision/PoCollisionDetectorTest.java | 14 +++++++------- .../java/janggi/strategy/MaSangMaSangTest.java | 6 +++--- .../java/janggi/strategy/MaSangSangMaTest.java | 6 +++--- .../java/janggi/strategy/SangMaMaSangTest.java | 6 +++--- .../java/janggi/strategy/SangMaSangMaTest.java | 6 +++--- .../TestCollisionDetector.java | 5 +++-- .../TeamPiece.java => support/TestPiece.java} | 10 +++++----- 8 files changed, 35 insertions(+), 34 deletions(-) rename src/test/java/janggi/{domain/rule/collision => support}/TestCollisionDetector.java (57%) rename src/test/java/janggi/{domain/piece/TeamPiece.java => support/TestPiece.java} (75%) diff --git a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java index 47a28cc3e8..b757373ee0 100644 --- a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java +++ b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java @@ -3,7 +3,7 @@ import janggi.domain.Side; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; -import janggi.domain.piece.TeamPiece; +import janggi.support.TestPiece; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -17,7 +17,7 @@ void shouldNotThrowExceptionWhenNoPieceOnPathAndNoPieceOnDestination() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece()); - Piece piece = new TeamPiece(Side.CHO); + Piece piece = new TestPiece(Side.CHO); // when & then Assertions.assertThatNoException() @@ -29,8 +29,8 @@ void shouldNotThrowExceptionWhenNoPieceOnPathAndNoPieceOnDestination() { void shouldNotThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsOtherSide() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TeamPiece(Side.HAN)); - Piece piece = new TeamPiece(Side.CHO); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TestPiece(Side.HAN)); + Piece piece = new TestPiece(Side.CHO); // when & then Assertions.assertThatNoException() @@ -42,8 +42,8 @@ void shouldNotThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsOtherSide() void shouldThrowExceptionWhenPieceOnPathExist() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); - List piecesOnPath = List.of(new TeamPiece(Side.HAN), new EmptyPiece(), new EmptyPiece()); - Piece piece = new TeamPiece(Side.CHO); + List piecesOnPath = List.of(new TestPiece(Side.HAN), new EmptyPiece(), new EmptyPiece()); + Piece piece = new TestPiece(Side.CHO); // when & then Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) @@ -56,8 +56,8 @@ void shouldThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsMySide() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); Side side = Side.HAN; - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TeamPiece(side)); - Piece piece = new TeamPiece(side); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TestPiece(side)); + Piece piece = new TestPiece(side); // when & then Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) diff --git a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java index 6afd7b8f0a..976f5a1844 100644 --- a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java +++ b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java @@ -4,7 +4,7 @@ import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; import janggi.domain.piece.Po; -import janggi.domain.piece.TeamPiece; +import janggi.support.TestPiece; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -17,7 +17,7 @@ class PoCollisionDetectorTest { void shouldNotThrowExceptionWhenOneNonPoObstacleAndEmptyDestination() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new TeamPiece(Side.HAN), new EmptyPiece()); + List piecesOnPath = List.of(new EmptyPiece(), new TestPiece(Side.HAN), new EmptyPiece()); Piece piece = new Po(Side.CHO); // when & then @@ -30,7 +30,7 @@ void shouldNotThrowExceptionWhenOneNonPoObstacleAndEmptyDestination() { void shouldNotThrowExceptionWhenOneNonPoObstacleAndEnemyAtDestination() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new TeamPiece(Side.HAN), new TeamPiece(Side.HAN)); + List piecesOnPath = List.of(new EmptyPiece(), new TestPiece(Side.HAN), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then @@ -43,7 +43,7 @@ void shouldNotThrowExceptionWhenOneNonPoObstacleAndEnemyAtDestination() { void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new TeamPiece(Side.HAN), new TeamPiece(Side.HAN), new EmptyPiece()); + List piecesOnPath = List.of(new TestPiece(Side.HAN), new TestPiece(Side.HAN), new EmptyPiece()); Piece piece = new Po(Side.CHO); // when & then @@ -56,7 +56,7 @@ void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { void shouldThrowExceptionWhenNoObstacleOnPath() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TeamPiece(Side.HAN)); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then @@ -69,7 +69,7 @@ void shouldThrowExceptionWhenNoObstacleOnPath() { void shouldThrowExceptionWhenPoOnPath() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new Po(Side.HAN), new TeamPiece(Side.HAN)); + List piecesOnPath = List.of(new EmptyPiece(), new Po(Side.HAN), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then @@ -95,7 +95,7 @@ void shouldThrowExceptionWhenPoOfOtherSideOnDestination() { void shouldThrowExceptionWhenSameSidePieceOnDestination() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TeamPiece(Side.CHO)); + List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TestPiece(Side.CHO)); Piece piece = new Po(Side.CHO); // when & then diff --git a/src/test/java/janggi/strategy/MaSangMaSangTest.java b/src/test/java/janggi/strategy/MaSangMaSangTest.java index 9d51ebe4e1..2a38b56bb1 100644 --- a/src/test/java/janggi/strategy/MaSangMaSangTest.java +++ b/src/test/java/janggi/strategy/MaSangMaSangTest.java @@ -5,7 +5,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; import janggi.domain.Side; -import janggi.domain.piece.TeamPiece; +import janggi.support.TestPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +19,7 @@ void shouldPlaceMaSangMaSangWhenSideHan() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new MaSangMaSang(); Side side = Side.HAN; - Piece expectedSidePiece = new TeamPiece(side); + Piece expectedSidePiece = new TestPiece(side); // when strategy.place(grid, side); @@ -47,7 +47,7 @@ void shouldPlaceMaSangMaSangWhenSideCho() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new MaSangMaSang(); Side side = Side.CHO; - Piece expectedSidePiece = new TeamPiece(side); + Piece expectedSidePiece = new TestPiece(side); // when strategy.place(grid, side); diff --git a/src/test/java/janggi/strategy/MaSangSangMaTest.java b/src/test/java/janggi/strategy/MaSangSangMaTest.java index 83d4586322..0fc395a718 100644 --- a/src/test/java/janggi/strategy/MaSangSangMaTest.java +++ b/src/test/java/janggi/strategy/MaSangSangMaTest.java @@ -5,7 +5,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; import janggi.domain.Side; -import janggi.domain.piece.TeamPiece; +import janggi.support.TestPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +19,7 @@ void shouldPlaceMaSangSangMaWhenSideHan() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new MaSangSangMa(); Side side = Side.HAN; - Piece expectedSidePiece = new TeamPiece(side); + Piece expectedSidePiece = new TestPiece(side); // when strategy.place(grid, side); @@ -47,7 +47,7 @@ void shouldPlaceMaSangSangMaWhenSideCho() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new MaSangSangMa(); Side side = Side.CHO; - Piece expectedSidePiece = new TeamPiece(side); + Piece expectedSidePiece = new TestPiece(side); // when strategy.place(grid, side); diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java index 33e964d8ed..1ba46d1a0f 100644 --- a/src/test/java/janggi/strategy/SangMaMaSangTest.java +++ b/src/test/java/janggi/strategy/SangMaMaSangTest.java @@ -5,7 +5,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; import janggi.domain.Side; -import janggi.domain.piece.TeamPiece; +import janggi.support.TestPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +19,7 @@ void shouldPlaceSangMaMaSangWhenSideHan() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new SangMaMaSang(); Side side = Side.HAN; - Piece expectedSidePiece = new TeamPiece(side); + Piece expectedSidePiece = new TestPiece(side); // when strategy.place(grid, side); @@ -47,7 +47,7 @@ void shouldPlaceSangMaMaSangWhenSideCho() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new SangMaMaSang(); Side side = Side.CHO; - Piece expectedSidePiece = new TeamPiece(side); + Piece expectedSidePiece = new TestPiece(side); // when strategy.place(grid, side); diff --git a/src/test/java/janggi/strategy/SangMaSangMaTest.java b/src/test/java/janggi/strategy/SangMaSangMaTest.java index 035b05be1c..b131b46a4e 100644 --- a/src/test/java/janggi/strategy/SangMaSangMaTest.java +++ b/src/test/java/janggi/strategy/SangMaSangMaTest.java @@ -5,7 +5,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; import janggi.domain.Side; -import janggi.domain.piece.TeamPiece; +import janggi.support.TestPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +19,7 @@ void shouldPlaceSangMaSangMaWhenSideHan() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new SangMaSangMa(); Side side = Side.HAN; - Piece expectedSidePiece = new TeamPiece(side); + Piece expectedSidePiece = new TestPiece(side); // when strategy.place(grid, side); @@ -47,7 +47,7 @@ void shouldPlaceSangMaSangMaWhenSideCho() { Piece[][] grid = new Piece[10][9]; ArrangementStrategy strategy = new SangMaSangMa(); Side side = Side.CHO; - Piece expectedSidePiece = new TeamPiece(side); + Piece expectedSidePiece = new TestPiece(side); // when strategy.place(grid, side); diff --git a/src/test/java/janggi/domain/rule/collision/TestCollisionDetector.java b/src/test/java/janggi/support/TestCollisionDetector.java similarity index 57% rename from src/test/java/janggi/domain/rule/collision/TestCollisionDetector.java rename to src/test/java/janggi/support/TestCollisionDetector.java index 5f778ef4fa..31688a6dcc 100644 --- a/src/test/java/janggi/domain/rule/collision/TestCollisionDetector.java +++ b/src/test/java/janggi/support/TestCollisionDetector.java @@ -1,9 +1,10 @@ -package janggi.domain.rule.collision; +package janggi.support; import janggi.domain.piece.Piece; +import janggi.domain.rule.collision.CollisionDetector; import java.util.List; -public class TestCollisionDetector implements CollisionDetector{ +public class TestCollisionDetector implements CollisionDetector { @Override public void check(Piece piece, List piecesOnPath) { throw new UnsupportedOperationException(); diff --git a/src/test/java/janggi/domain/piece/TeamPiece.java b/src/test/java/janggi/support/TestPiece.java similarity index 75% rename from src/test/java/janggi/domain/piece/TeamPiece.java rename to src/test/java/janggi/support/TestPiece.java index e7a4d8c01c..03181b539e 100644 --- a/src/test/java/janggi/domain/piece/TeamPiece.java +++ b/src/test/java/janggi/support/TestPiece.java @@ -1,21 +1,21 @@ -package janggi.domain.piece; +package janggi.support; import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.piece.Piece; import janggi.domain.rule.collision.CollisionDetector; -import janggi.domain.rule.collision.TestCollisionDetector; import java.util.List; -public class TeamPiece extends Piece { +public class TestPiece extends Piece { private static final String PIECE_NAME = "test"; private final CollisionDetector detector; - public TeamPiece(Side side) { + public TestPiece(Side side) { this(side, new TestCollisionDetector()); } - public TeamPiece(Side side, CollisionDetector detector) { + public TestPiece(Side side, CollisionDetector detector) { super(PIECE_NAME, side); this.detector = detector; } From 580025263b67871a276d0029c149f052c4c4be1e Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 15:01:36 +0900 Subject: [PATCH 26/45] =?UTF-8?q?feat(Board):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=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 | 8 ++--- src/main/java/janggi/domain/board/Board.java | 29 ++++++++++++++++ .../java/janggi/domain/board/BoardTest.java | 29 ++++++++++++++++ .../support/TestArrangementStrategy.java | 33 +++++++++++++++++++ .../janggi/support/TestCollisionDetector.java | 12 ------- src/test/java/janggi/support/TestPiece.java | 9 +---- 6 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 src/test/java/janggi/support/TestArrangementStrategy.java delete mode 100644 src/test/java/janggi/support/TestCollisionDetector.java diff --git a/README.md b/README.md index ef276dd3bf..4159131215 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,8 @@ - [x] 도메인 규칙 검증 - [x] 좌표값이 보드판의 범위를 벗어난 경우 예외 처리 - [x] 해당 좌표에 이동시키고자 하는 기물이 존재하지 않는 경우 예외 처리 -- [ ] 기물을 이동시킨다. - - [ ] 도메인 규칙 검증 - - [ ] 기물 규칙에 따라 이동할 위치에 도달할 수 없는 경우 예외 처리 - - [ ] 이동할 위치의 기존 기물은 제거된다. +- [x] 기물을 이동시킨다. + - [x] 도메인 규칙 검증 + - [x] 기물 규칙에 따라 이동할 위치에 도달할 수 없는 경우 예외 처리 + - [x] 이동할 위치의 기존 기물은 제거된다. - [ ] 이동이 반영된 장기판 상태를 출력한다. diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index c0b0675926..1297152748 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -1,6 +1,7 @@ package janggi.domain.board; import janggi.domain.Location; +import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; import janggi.strategy.BoardAssembler; import java.util.ArrayList; @@ -36,6 +37,34 @@ public static Board create(BoardAssembler assembler) { return new Board(boardState, height, width); } + public void move(Location from, Location to) { + validateMove(from, to); + + Piece piece = boardState.get(from); + List piecesOnPath = getPiecesOnRoute(piece, from, to); + + piece.detectCollision(piecesOnPath); + + executeMove(from, to, piece); + } + + private void validateMove(Location from, Location to) { + validateLocation(from); + validateLocation(to); + validatePieceExist(from); + } + + private List getPiecesOnRoute(Piece piece, Location from, Location to) { + return piece.calculateRoute(from, to).stream() + .map(boardState::get) + .toList(); + } + + private void executeMove(Location from, Location to, Piece piece) { + boardState.put(to, piece); + boardState.put(from, new EmptyPiece()); // 싱글톤 혹은 정적 상수 권장 + } + public List> to2DArray() { List> pieces = new ArrayList<>(); for (int row = 0; row < height; row++) { diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 7aef0afffa..5f056658c4 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -3,10 +3,15 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import janggi.domain.Location; +import janggi.domain.Side; import janggi.domain.piece.Piece; +import janggi.strategy.ArrangementStrategy; import janggi.strategy.BoardAssembler; import janggi.strategy.MaSangMaSang; +import janggi.support.TestArrangementStrategy; +import janggi.support.TestPiece; import java.util.List; +import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -88,4 +93,28 @@ void shouldThrowExceptionWhenPieceDoesNotExistsAtLocation() { .isInstanceOf(IllegalArgumentException.class); } } + + @Test + @DisplayName("이동할 기물의 위치와 도착지 좌표를 받아 기물을 이동시킨다.") + void shouldMovePieceToDestination() { + // given + Piece testPiece = new TestPiece(Side.HAN); + Map initialPieces = Map.of( + new Location(1, 1), testPiece + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + Board board = Board.create(assembler); + Location from = new Location(1, 1); + Location to = new Location(0,0); + + // when + board.move(from, to); + List> board2DArray = board.to2DArray(); + + // then + Assertions.assertThat(board2DArray.get(from.y()).get(from.x()).isEmpty()).isTrue(); + Assertions.assertThat(board2DArray.get(to.y()).get(to.x())).isEqualTo(testPiece); + } } diff --git a/src/test/java/janggi/support/TestArrangementStrategy.java b/src/test/java/janggi/support/TestArrangementStrategy.java new file mode 100644 index 0000000000..32cac0f8f8 --- /dev/null +++ b/src/test/java/janggi/support/TestArrangementStrategy.java @@ -0,0 +1,33 @@ +package janggi.support; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.EmptyPiece; +import janggi.domain.piece.Piece; +import janggi.strategy.ArrangementStrategy; +import janggi.strategy.StrategyLabel; +import java.util.Map; + +public class TestArrangementStrategy extends ArrangementStrategy { + private final Map customPieces; + + public TestArrangementStrategy(Map customPieces) { + super(StrategyLabel.HEHE); + this.customPieces = customPieces; + } + + @Override + public void place(Piece[][] arrangement, Side side) { + for (int row = 0; row < arrangement.length; row++) { + for (int col = 0; col < arrangement[row].length; col++) { + arrangement[row][col] = new EmptyPiece(); + } + } + + customPieces.forEach((loc, piece) -> { + arrangement[loc.x()][loc.y()] = piece; + }); + } + + +} diff --git a/src/test/java/janggi/support/TestCollisionDetector.java b/src/test/java/janggi/support/TestCollisionDetector.java deleted file mode 100644 index 31688a6dcc..0000000000 --- a/src/test/java/janggi/support/TestCollisionDetector.java +++ /dev/null @@ -1,12 +0,0 @@ -package janggi.support; - -import janggi.domain.piece.Piece; -import janggi.domain.rule.collision.CollisionDetector; -import java.util.List; - -public class TestCollisionDetector implements CollisionDetector { - @Override - public void check(Piece piece, List piecesOnPath) { - throw new UnsupportedOperationException(); - } -} diff --git a/src/test/java/janggi/support/TestPiece.java b/src/test/java/janggi/support/TestPiece.java index 03181b539e..8eff69ac12 100644 --- a/src/test/java/janggi/support/TestPiece.java +++ b/src/test/java/janggi/support/TestPiece.java @@ -3,21 +3,14 @@ import janggi.domain.Location; import janggi.domain.Side; import janggi.domain.piece.Piece; -import janggi.domain.rule.collision.CollisionDetector; import java.util.List; public class TestPiece extends Piece { private static final String PIECE_NAME = "test"; - private final CollisionDetector detector; public TestPiece(Side side) { - this(side, new TestCollisionDetector()); - } - - public TestPiece(Side side, CollisionDetector detector) { super(PIECE_NAME, side); - this.detector = detector; } @Override @@ -32,6 +25,6 @@ public List calculateRoute(Location from, Location to) { @Override public void detectCollision(List piecesOnPath) { - detector.check(this, piecesOnPath); + return; } } From b55d7fa6ccda684de94ede4198defc2e2753ed1e Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 15:58:49 +0900 Subject: [PATCH 27/45] =?UTF-8?q?feat:=20=EC=9D=B4=EB=8F=99=EC=9D=B4=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=EB=90=9C=20=EC=9E=A5=EA=B8=B0=ED=8C=90=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기물 이동의 출발 좌표와 도착 좌표를 검증하는 validate 메서드들의 구조를 변경 --- README.md | 2 +- .../java/janggi/controller/JanggiFlow.java | 30 +-- src/main/java/janggi/domain/board/Board.java | 35 ++- src/main/java/janggi/domain/piece/Piece.java | 4 + .../java/janggi/domain/board/BoardTest.java | 201 +++++++++++++++--- 5 files changed, 223 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 4159131215..18aef1d75e 100644 --- a/README.md +++ b/README.md @@ -64,4 +64,4 @@ - [x] 도메인 규칙 검증 - [x] 기물 규칙에 따라 이동할 위치에 도달할 수 없는 경우 예외 처리 - [x] 이동할 위치의 기존 기물은 제거된다. -- [ ] 이동이 반영된 장기판 상태를 출력한다. +- [x] 이동이 반영된 장기판 상태를 출력한다. diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index e33fa3cbf8..29cbe5da4e 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -11,7 +11,6 @@ import janggi.strategy.SangMaSangMa; import janggi.view.ApplicationView; import java.util.List; -import java.util.function.Supplier; public class JanggiFlow { @@ -33,30 +32,32 @@ public void process() { ArrangementStrategy choStrategy = askStrategy(Side.CHO); Board board = Board.create(BoardAssembler.of(hanStrategy, choStrategy)); - view.responseBoardArray(board.to2DArray()); - Side current = Side.HAN; - for (int i = 0; i < 2; i++) { + while (board.isNotEmpty()) { + view.responseBoardArray(board.to2DArray()); view.responseCurrentSide(current); - Location locationOfPiece = retry(() -> askLocationOfPiece(board)); - Location locationToMove = retry(() -> askLocationToMove(board)); + final Side turnSide = current; + retryAction(() -> { + Location from = askLocationOfPiece(turnSide, board); + Location to = askLocationToMove(turnSide, board); + board.move(from, to); + }); current = current.switchTurn(); } } - private Location askLocationOfPiece(Board board) { + private Location askLocationOfPiece(Side current, Board board) { List locationOfPiece = view.requestLocationOfPiece(); Location verifiedLocation = Location.from(locationOfPiece); - board.validateLocation(verifiedLocation); - board.validatePieceExist(verifiedLocation); + board.validateLocationOfPiece(current, verifiedLocation); return verifiedLocation; } - private Location askLocationToMove(Board board) { + private Location askLocationToMove(Side current, Board board) { List locationToMove = view.requestLocationToMove(); Location verifiedLocation = Location.from(locationToMove); - board.validateLocation(verifiedLocation); + board.validateLocationToMove(current, verifiedLocation); return verifiedLocation; } @@ -72,15 +73,14 @@ private ArrangementStrategy findStrategyWithCorrespondingDecisionNumber(int deci .orElseThrow(() -> new IllegalArgumentException("일치하는 전략 번호가 없습니다: " + decisionNumber)); } - private T retry(Supplier supplier) { + private void retryAction(Runnable runnable) { while (true) { try { - return supplier.get(); + runnable.run(); + return; } catch (IllegalArgumentException e) { view.responseErrorMessage(e); } } } - - } diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 1297152748..b19887d58d 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -1,6 +1,7 @@ package janggi.domain.board; import janggi.domain.Location; +import janggi.domain.Side; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; import janggi.strategy.BoardAssembler; @@ -48,6 +49,28 @@ public void move(Location from, Location to) { executeMove(from, to, piece); } + public void validateLocationOfPiece(Side currentSide, Location locationOfPiece) { + validateLocation(locationOfPiece); + validatePieceExist(locationOfPiece); + Piece piece = boardState.get(locationOfPiece); + if (isNotSameSide(piece, currentSide)) { + throw new IllegalArgumentException("본인 팀의 기물만 선택할 수 있습니다."); + } + } + + public boolean isNotEmpty() { + return !boardState.values().stream() + .allMatch(Piece::isEmpty); + } + + public void validateLocationToMove(Side currentSide, Location locationToMove) { + validateLocation(locationToMove); + Piece target = boardState.get(locationToMove); + if (isOccupiedBySameSide(target, currentSide)) { + throw new IllegalArgumentException("같은 편의 기물이 있는 위치로 이동할 수 없습니다."); + } + } + private void validateMove(Location from, Location to) { validateLocation(from); validateLocation(to); @@ -78,15 +101,23 @@ public List> to2DArray() { return List.copyOf(pieces); } - public void validateLocation(Location location) { + private void validateLocation(Location location) { if (!boardState.containsKey(location)) { throw new IllegalArgumentException("해당 좌표는 보드판에 존재하지 않습니다."); } } - public void validatePieceExist(Location location) { + private void validatePieceExist(Location location) { if (boardState.get(location).isEmpty()) { throw new IllegalArgumentException("해당 좌표에 기물이 존재하지 않습니다."); } } + + private boolean isNotSameSide(Piece piece, Side side) { + return !piece.isSameSide(side); + } + + private boolean isOccupiedBySameSide(Piece piece, Side side) { + return !piece.isEmpty() && piece.isSameSide(side); + } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 09e1db04c4..e259b328a5 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -24,6 +24,10 @@ public boolean isSameSide(Piece piece) { return this.side.equals(piece.side); } + public boolean isSameSide(Side side) { + return this.side.equals(side); + } + public String getName() { return name; } diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 5f056658c4..d49a3a0b68 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -4,10 +4,10 @@ import janggi.domain.Location; import janggi.domain.Side; +import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; import janggi.strategy.ArrangementStrategy; import janggi.strategy.BoardAssembler; -import janggi.strategy.MaSangMaSang; import janggi.support.TestArrangementStrategy; import janggi.support.TestPiece; import java.util.List; @@ -22,74 +22,181 @@ class BoardTest { @Test @DisplayName("보드가 전략에 맞춰 정상적으로 생성된다.") void shouldReturnBoardWithSelectedArrangementStrategy() { - BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + // given + Side currentSide = Side.HAN; + Piece testPiece = new TestPiece(currentSide); + Piece emptyPiece = new EmptyPiece(); + Map initialPieces = Map.of( + new Location(1, 1), testPiece, + new Location(2, 2), emptyPiece + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + // when + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); List> pieces = board.to2DArray(); - Piece[][] assemble = assembler.assemble(); - - for (int row = 0; row < assemble.length; row++) { - for (int col = 0; col < assemble[row].length; col++) { - Piece result = assemble[row][col]; - Piece expectedPiece = pieces.get(row).get(col); - Assertions.assertThat(result).isInstanceOf(expectedPiece.getClass()); - Assertions.assertThat(result.isSameSide(expectedPiece)).isTrue(); - } - } + // then + Assertions.assertThat(pieces.get(1).get(1)).isEqualTo(testPiece); + Assertions.assertThat(pieces.get(2).get(2)).isEqualTo(emptyPiece); } @Nested - class ValidateLocationTest { + class ValidateLocationExistenceTest { @Test @DisplayName("보드에 입력받은 좌표가 존재하면 예외를 발생시키지 않는다.") void shouldNotThrowExceptionWhenLocationExists() { // given - BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Side currentSide = Side.HAN; + Map initialPieces = Map.of( + new Location(1, 1), new TestPiece(currentSide) + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); - Location location = Location.from(List.of(1,1)); + Location location = Location.from(List.of(1, 1)); // when & then - assertDoesNotThrow(() -> board.validateLocation(location)); + assertDoesNotThrow(() -> board.validateLocationOfPiece(currentSide, location)); } @Test @DisplayName("보드에 입력받은 좌표가 존재하지 않으면 예외를 발생시킨다.") void shouldThrowExceptionWhenLocationDoesNotExist() { // given - BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Side currentSide = Side.HAN; + Map initialPieces = Map.of( + new Location(1, 1), new TestPiece(currentSide) + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); - Location location = Location.from(List.of(11,11)); + Location location = Location.from(List.of(11, 11)); // when & then - Assertions.assertThatThrownBy(() -> board.validateLocation(location)) - .isInstanceOf(IllegalArgumentException.class); + Assertions.assertThatThrownBy(() -> board.validateLocationOfPiece(currentSide, location)) + .isInstanceOf(IllegalArgumentException.class); } } @Nested - class ValidatePieceExistTest { + class ValidateLocationOfPieceTest { + @Test + @DisplayName("출발 좌표에 같은 팀 기물이 존재하면 예외를 발생시키지 않는다.") + void shouldNotThrowExceptionWhenPieceOnSameSideExistsAtLocation() { + // given + Side currentSide = Side.HAN; + Map initialPieces = Map.of( + new Location(1, 1), new TestPiece(currentSide) + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + Board board = Board.create(assembler); + Location location = Location.from(List.of(1, 1)); + + // when & then + assertDoesNotThrow(() -> board.validateLocationOfPiece(currentSide, location)); + } + @Test - @DisplayName("입력받은 좌표에 기물이 존재하면 예외를 발생시키지 않는다.") - void shouldNotThrowExceptionWhenPieceExistsAtLocation() { + @DisplayName("출발 좌표에 상대 팀 기물이 존재하면 예외를 발생시킨다.") + void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { // given - BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Side currentSide = Side.HAN; + Side otherSide = Side.CHO; + Map initialPieces = Map.of( + new Location(1, 1), new TestPiece(currentSide) + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); - Location location = Location.from(List.of(0,1)); + Location location = Location.from(List.of(1, 1)); // when & then - assertDoesNotThrow(() -> board.validatePieceExist(location)); + Assertions.assertThatThrownBy(() -> board.validateLocationOfPiece(otherSide, location)) + .isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("입력받은 좌표에 기물이 존재하지 않으면 예외를 발생시킨다.") + @DisplayName("출발 좌표에 기물이 존재하지 않으면 예외를 발생시킨다.") void shouldThrowExceptionWhenPieceDoesNotExistsAtLocation() { // given - BoardAssembler assembler = BoardAssembler.of(new MaSangMaSang(), new MaSangMaSang()); + Map initialPieces = Map.of( + new Location(5, 5), new EmptyPiece() + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); - Location location = Location.from(List.of(5,5)); + Location location = Location.from(List.of(5, 5)); // when & then - Assertions.assertThatThrownBy(() -> board.validatePieceExist(location)) + Assertions.assertThatThrownBy(() -> board.validateLocationOfPiece(Side.HAN, location)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + class ValidateLocationToMoveTest { + @Test + @DisplayName("도착 좌표에 기물이 존재하지 않으면 예외를 발생시키지 않는다.") + void shouldNotThrowExceptionWhenPieceDoesNotExistsAtLocation() { + // given + Map initialPieces = Map.of( + new Location(1, 1), new EmptyPiece() + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + Board board = Board.create(assembler); + Location location = Location.from(List.of(1, 1)); + + // when & then + Assertions.assertThatNoException() + .isThrownBy(() -> board.validateLocationToMove(Side.HAN, location)); + } + + @Test + @DisplayName("도착 좌표에 상대 팀 기물이 존재하면 예외를 발생시키지 않는다.") + void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { + // given + Side currentSide = Side.HAN; + Side otherSide = Side.CHO; + Map initialPieces = Map.of( + new Location(1, 1), new TestPiece(otherSide) + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + Board board = Board.create(assembler); + Location location = Location.from(List.of(1, 1)); + + // when & then + Assertions.assertThatNoException() + .isThrownBy(() -> board.validateLocationToMove(currentSide, location)); + } + + @Test + @DisplayName("도착 좌표에 같은 팀 기물이 존재하면 예외를 발생시킨다.") + void shouldNotThrowExceptionWhenPieceOnSameSideExistsAtLocation() { + // given + Side currentSide = Side.HAN; + Map initialPieces = Map.of( + new Location(1, 1), new TestPiece(currentSide) + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + Board board = Board.create(assembler); + Location location = Location.from(List.of(1, 1)); + + // when & then + Assertions.assertThatThrownBy(() -> board.validateLocationToMove(currentSide, location)) .isInstanceOf(IllegalArgumentException.class); } } @@ -107,7 +214,7 @@ void shouldMovePieceToDestination() { BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); Location from = new Location(1, 1); - Location to = new Location(0,0); + Location to = new Location(0, 0); // when board.move(from, to); @@ -117,4 +224,36 @@ void shouldMovePieceToDestination() { Assertions.assertThat(board2DArray.get(from.y()).get(from.x()).isEmpty()).isTrue(); Assertions.assertThat(board2DArray.get(to.y()).get(to.x())).isEqualTo(testPiece); } + + @Test + @DisplayName("보드에 기물이 하나라도 있으면 true를 반환한다") + void shouldReturnTrueForNoneEmptyBoard() { + // given + Map initialPieces = Map.of( + new Location(1, 1), new TestPiece(Side.HAN), + new Location(1, 2), new EmptyPiece() + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + Board board = Board.create(assembler); + + // when & then + Assertions.assertThat(board.isNotEmpty()).isTrue(); + } + + @Test + @DisplayName("보드에 기물이 존재하지 않으면 false를 반환한다.") + void shouldReturnTrueForEmptyBoard() { + // given + Map initialPieces = Map.of( + new Location(1, 1), new EmptyPiece(), + new Location(1, 2), new EmptyPiece() + ); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + Board board = Board.create(assembler); + + // when & then + Assertions.assertThat(board.isNotEmpty()).isFalse(); + } } From 770eef2698482d99efca6928a1e5f7c6bf9cc847 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 17:08:52 +0900 Subject: [PATCH 28/45] =?UTF-8?q?refactor(EmptyPiece):=20=EB=B9=88=20?= =?UTF-8?q?=EA=B8=B0=EB=AC=BC=EC=97=90=20=EC=8B=B1=EA=B8=80=ED=86=A4=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상태를 가지지 않는 불변 객체이므로, 불필요한 객체 생성을 막기 위해 싱글톤으로 변경 --- src/main/java/janggi/domain/board/Board.java | 2 +- src/main/java/janggi/domain/piece/EmptyPiece.java | 8 +++++++- src/main/java/janggi/strategy/BoardAssembler.java | 2 +- src/test/java/janggi/domain/board/BoardTest.java | 12 ++++++------ .../collision/DefaultCollisionDetectorTest.java | 8 ++++---- .../rule/collision/PoCollisionDetectorTest.java | 14 +++++++------- .../janggi/support/TestArrangementStrategy.java | 2 +- 7 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index b19887d58d..ae53b93b01 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -85,7 +85,7 @@ private List getPiecesOnRoute(Piece piece, Location from, Location to) { private void executeMove(Location from, Location to, Piece piece) { boardState.put(to, piece); - boardState.put(from, new EmptyPiece()); // 싱글톤 혹은 정적 상수 권장 + boardState.put(from, EmptyPiece.getInstance()); } public List> to2DArray() { diff --git a/src/main/java/janggi/domain/piece/EmptyPiece.java b/src/main/java/janggi/domain/piece/EmptyPiece.java index cb30750703..b0247baf08 100644 --- a/src/main/java/janggi/domain/piece/EmptyPiece.java +++ b/src/main/java/janggi/domain/piece/EmptyPiece.java @@ -4,14 +4,20 @@ import janggi.domain.Side; import java.util.List; +@SuppressWarnings("java:S6548") public class EmptyPiece extends Piece { + private static final EmptyPiece INSTANCE = new EmptyPiece(); private static final String PIECE_NAME = "ㆍ"; - public EmptyPiece() { + private EmptyPiece() { super(PIECE_NAME, Side.NONE); } + public static EmptyPiece getInstance() { + return INSTANCE; + } + @Override public boolean isEmpty() { return true; diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java index 33f88f7ef0..65e0022085 100644 --- a/src/main/java/janggi/strategy/BoardAssembler.java +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -52,7 +52,7 @@ private void setupEmptyPieces(Piece[][] arrangement) { for (int row = 0; row < DEFAULT_ROWS; row++) { for (int col = 0; col < DEFAULT_COLS; col++) { if (arrangement[row][col] == null) { - arrangement[row][col] = new EmptyPiece(); + arrangement[row][col] = EmptyPiece.getInstance(); } } } diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index d49a3a0b68..59832e711c 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -25,7 +25,7 @@ void shouldReturnBoardWithSelectedArrangementStrategy() { // given Side currentSide = Side.HAN; Piece testPiece = new TestPiece(currentSide); - Piece emptyPiece = new EmptyPiece(); + Piece emptyPiece = EmptyPiece.getInstance(); Map initialPieces = Map.of( new Location(1, 1), testPiece, new Location(2, 2), emptyPiece @@ -127,7 +127,7 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { void shouldThrowExceptionWhenPieceDoesNotExistsAtLocation() { // given Map initialPieces = Map.of( - new Location(5, 5), new EmptyPiece() + new Location(5, 5), EmptyPiece.getInstance() ); ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); @@ -148,7 +148,7 @@ class ValidateLocationToMoveTest { void shouldNotThrowExceptionWhenPieceDoesNotExistsAtLocation() { // given Map initialPieces = Map.of( - new Location(1, 1), new EmptyPiece() + new Location(1, 1), EmptyPiece.getInstance() ); ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); @@ -231,7 +231,7 @@ void shouldReturnTrueForNoneEmptyBoard() { // given Map initialPieces = Map.of( new Location(1, 1), new TestPiece(Side.HAN), - new Location(1, 2), new EmptyPiece() + new Location(1, 2), EmptyPiece.getInstance() ); ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); @@ -246,8 +246,8 @@ void shouldReturnTrueForNoneEmptyBoard() { void shouldReturnTrueForEmptyBoard() { // given Map initialPieces = Map.of( - new Location(1, 1), new EmptyPiece(), - new Location(1, 2), new EmptyPiece() + new Location(1, 1), EmptyPiece.getInstance(), + new Location(1, 2), EmptyPiece.getInstance() ); ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); diff --git a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java index b757373ee0..5467872403 100644 --- a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java +++ b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java @@ -16,7 +16,7 @@ class DefaultCollisionDetectorTest { void shouldNotThrowExceptionWhenNoPieceOnPathAndNoPieceOnDestination() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece()); + List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance()); Piece piece = new TestPiece(Side.CHO); // when & then @@ -29,7 +29,7 @@ void shouldNotThrowExceptionWhenNoPieceOnPathAndNoPieceOnDestination() { void shouldNotThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsOtherSide() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TestPiece(Side.HAN)); + List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.HAN)); Piece piece = new TestPiece(Side.CHO); // when & then @@ -42,7 +42,7 @@ void shouldNotThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsOtherSide() void shouldThrowExceptionWhenPieceOnPathExist() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); - List piecesOnPath = List.of(new TestPiece(Side.HAN), new EmptyPiece(), new EmptyPiece()); + List piecesOnPath = List.of(new TestPiece(Side.HAN), EmptyPiece.getInstance(), EmptyPiece.getInstance()); Piece piece = new TestPiece(Side.CHO); // when & then @@ -56,7 +56,7 @@ void shouldThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsMySide() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); Side side = Side.HAN; - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TestPiece(side)); + List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(side)); Piece piece = new TestPiece(side); // when & then diff --git a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java index 976f5a1844..71452b6ab0 100644 --- a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java +++ b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java @@ -17,7 +17,7 @@ class PoCollisionDetectorTest { void shouldNotThrowExceptionWhenOneNonPoObstacleAndEmptyDestination() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new TestPiece(Side.HAN), new EmptyPiece()); + List piecesOnPath = List.of(EmptyPiece.getInstance(), new TestPiece(Side.HAN), EmptyPiece.getInstance()); Piece piece = new Po(Side.CHO); // when & then @@ -30,7 +30,7 @@ void shouldNotThrowExceptionWhenOneNonPoObstacleAndEmptyDestination() { void shouldNotThrowExceptionWhenOneNonPoObstacleAndEnemyAtDestination() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new TestPiece(Side.HAN), new TestPiece(Side.HAN)); + List piecesOnPath = List.of(EmptyPiece.getInstance(), new TestPiece(Side.HAN), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then @@ -43,7 +43,7 @@ void shouldNotThrowExceptionWhenOneNonPoObstacleAndEnemyAtDestination() { void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new TestPiece(Side.HAN), new TestPiece(Side.HAN), new EmptyPiece()); + List piecesOnPath = List.of(new TestPiece(Side.HAN), new TestPiece(Side.HAN), EmptyPiece.getInstance()); Piece piece = new Po(Side.CHO); // when & then @@ -56,7 +56,7 @@ void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { void shouldThrowExceptionWhenNoObstacleOnPath() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TestPiece(Side.HAN)); + List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then @@ -69,7 +69,7 @@ void shouldThrowExceptionWhenNoObstacleOnPath() { void shouldThrowExceptionWhenPoOnPath() { // given CollisionDetector collisionDetector = new DefaultCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new Po(Side.HAN), new TestPiece(Side.HAN)); + List piecesOnPath = List.of(EmptyPiece.getInstance(), new Po(Side.HAN), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then @@ -82,7 +82,7 @@ void shouldThrowExceptionWhenPoOnPath() { void shouldThrowExceptionWhenPoOfOtherSideOnDestination() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new Po(Side.HAN)); + List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new Po(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then @@ -95,7 +95,7 @@ void shouldThrowExceptionWhenPoOfOtherSideOnDestination() { void shouldThrowExceptionWhenSameSidePieceOnDestination() { // given CollisionDetector collisionDetector = new PoCollisionDetector(); - List piecesOnPath = List.of(new EmptyPiece(), new EmptyPiece(), new TestPiece(Side.CHO)); + List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.CHO)); Piece piece = new Po(Side.CHO); // when & then diff --git a/src/test/java/janggi/support/TestArrangementStrategy.java b/src/test/java/janggi/support/TestArrangementStrategy.java index 32cac0f8f8..f40698a8e1 100644 --- a/src/test/java/janggi/support/TestArrangementStrategy.java +++ b/src/test/java/janggi/support/TestArrangementStrategy.java @@ -20,7 +20,7 @@ public TestArrangementStrategy(Map customPieces) { public void place(Piece[][] arrangement, Side side) { for (int row = 0; row < arrangement.length; row++) { for (int col = 0; col < arrangement[row].length; col++) { - arrangement[row][col] = new EmptyPiece(); + arrangement[row][col] = EmptyPiece.getInstance(); } } From 1c4338360a937c666b8a4aa1cd644cd9eb253676 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 20:42:28 +0900 Subject: [PATCH 29/45] =?UTF-8?q?refactor(strategy):=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=EC=97=90=20?= =?UTF-8?q?=EC=8B=B1=EA=B8=80=ED=86=A4=20=ED=8C=A8=ED=84=B4=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 - 상태를 가지지 않는 불변 객체이므로, 불필요한 객체 생성을 막기 위해 싱글톤으로 변경 - BoardAssemblerTest가 프로덕션 전략에 의존하지 않도록, 테스트 전용 전략 객체를 주입하여 테스트 결합도를 낮춤 --- .../java/janggi/controller/JanggiFlow.java | 8 ++-- .../java/janggi/strategy/MaSangMaSang.java | 9 +++- .../java/janggi/strategy/MaSangSangMa.java | 9 +++- .../java/janggi/strategy/SangMaMaSang.java | 9 +++- .../java/janggi/strategy/SangMaSangMa.java | 9 +++- .../janggi/strategy/BoardAssemblerTest.java | 47 ++++++++++++++----- .../janggi/strategy/MaSangMaSangTest.java | 8 ++-- .../janggi/strategy/MaSangSangMaTest.java | 8 ++-- .../janggi/strategy/SangMaMaSangTest.java | 8 ++-- .../janggi/strategy/SangMaSangMaTest.java | 8 ++-- .../support/TestArrangementStrategy.java | 20 ++++++-- 11 files changed, 103 insertions(+), 40 deletions(-) diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 29cbe5da4e..79c37285a7 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -20,10 +20,10 @@ public class JanggiFlow { public JanggiFlow(ApplicationView view) { this.view = view; this.strategies = List.of( - new MaSangMaSang(), - new MaSangSangMa(), - new SangMaMaSang(), - new SangMaSangMa() + MaSangMaSang.getInstance(), + MaSangSangMa.getInstance(), + SangMaMaSang.getInstance(), + SangMaSangMa.getInstance() ); } diff --git a/src/main/java/janggi/strategy/MaSangMaSang.java b/src/main/java/janggi/strategy/MaSangMaSang.java index cb8c54fae4..7052a0f68d 100644 --- a/src/main/java/janggi/strategy/MaSangMaSang.java +++ b/src/main/java/janggi/strategy/MaSangMaSang.java @@ -5,12 +5,19 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; +@SuppressWarnings("java:S6548") public class MaSangMaSang extends ArrangementStrategy { - public MaSangMaSang() { + private static final MaSangMaSang INSTANCE = new MaSangMaSang(); + + private MaSangMaSang() { super(StrategyLabel.HEHE); } + public static MaSangMaSang getInstance() { + return INSTANCE; + } + @Override public void place(Piece[][] board, Side side) { int boardMaxLength = board.length; diff --git a/src/main/java/janggi/strategy/MaSangSangMa.java b/src/main/java/janggi/strategy/MaSangSangMa.java index d4537a76ed..700b77cec5 100644 --- a/src/main/java/janggi/strategy/MaSangSangMa.java +++ b/src/main/java/janggi/strategy/MaSangSangMa.java @@ -5,12 +5,19 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; +@SuppressWarnings("java:S6548") public class MaSangSangMa extends ArrangementStrategy { - public MaSangSangMa() { + private static final MaSangSangMa INSTANCE = new MaSangSangMa(); + + private MaSangSangMa() { super(StrategyLabel.HEEH); } + public static MaSangSangMa getInstance() { + return INSTANCE; + } + @Override public void place(Piece[][] board, Side side) { int boardMaxLength = board.length; diff --git a/src/main/java/janggi/strategy/SangMaMaSang.java b/src/main/java/janggi/strategy/SangMaMaSang.java index b9ce0fa8a0..a8df69583e 100644 --- a/src/main/java/janggi/strategy/SangMaMaSang.java +++ b/src/main/java/janggi/strategy/SangMaMaSang.java @@ -5,12 +5,19 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; +@SuppressWarnings("java:S6548") public class SangMaMaSang extends ArrangementStrategy { - public SangMaMaSang() { + private static final SangMaMaSang INSTANCE = new SangMaMaSang(); + + private SangMaMaSang() { super(StrategyLabel.EHHE); } + public static SangMaMaSang getInstance() { + return INSTANCE; + } + @Override public void place(Piece[][] board, Side side) { int boardMaxLength = board.length; diff --git a/src/main/java/janggi/strategy/SangMaSangMa.java b/src/main/java/janggi/strategy/SangMaSangMa.java index 1789520ac5..ec2b16b307 100644 --- a/src/main/java/janggi/strategy/SangMaSangMa.java +++ b/src/main/java/janggi/strategy/SangMaSangMa.java @@ -5,12 +5,19 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; +@SuppressWarnings("java:S6548") public class SangMaSangMa extends ArrangementStrategy { - public SangMaSangMa() { + private static final SangMaSangMa INSTANCE = new SangMaSangMa(); + + private SangMaSangMa() { super(StrategyLabel.EHEH); } + public static SangMaSangMa getInstance() { + return INSTANCE; + } + @Override public void place(Piece[][] board, Side side) { int boardMaxLength = board.length; diff --git a/src/test/java/janggi/strategy/BoardAssemblerTest.java b/src/test/java/janggi/strategy/BoardAssemblerTest.java index 7666709460..0c7073d44a 100644 --- a/src/test/java/janggi/strategy/BoardAssemblerTest.java +++ b/src/test/java/janggi/strategy/BoardAssemblerTest.java @@ -1,11 +1,14 @@ package janggi.strategy; +import janggi.domain.Location; +import janggi.domain.Side; import janggi.domain.piece.Cha; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Jolbyeong; -import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; +import janggi.support.TestArrangementStrategy; +import janggi.support.TestPiece; +import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,8 +19,17 @@ class BoardAssemblerTest { @DisplayName("공통 기물과 각 팀의 전략이 합쳐져 전체 보드를 생성한다.") void shouldAssembleFullBoard() { // given - ArrangementStrategy hanStrategy = new SangMaMaSang(); - ArrangementStrategy choStrategy = new MaSangMaSang(); + Side han = Side.HAN; + Map customPiecesOfHan = Map.of( + new Location(4, 0), new TestPiece(han) + ); + ArrangementStrategy hanStrategy = new TestArrangementStrategy(customPiecesOfHan, false); + + Side cho = Side.CHO; + Map customPiecesOfCho = Map.of( + new Location(5, 0), new TestPiece(cho) + ); + ArrangementStrategy choStrategy = new TestArrangementStrategy(customPiecesOfCho, false); BoardAssembler assembler = BoardAssembler.of(hanStrategy, choStrategy); // when @@ -25,15 +37,28 @@ void shouldAssembleFullBoard() { // then // 1. 공통 기물 검증 - Assertions.assertThat(board[0][0]).isInstanceOf(Cha.class); - Assertions.assertThat(board[9][8]).isInstanceOf(Cha.class); - Assertions.assertThat(board[3][0]).isInstanceOf(Jolbyeong.class); + Piece chaOfHan = board[0][0]; + Assertions.assertThat(chaOfHan).isInstanceOf(Cha.class); + Assertions.assertThat(chaOfHan.isSameSide(han)).isTrue(); + + Piece chaOfCho = board[9][8]; + Assertions.assertThat(chaOfCho).isInstanceOf(Cha.class); + Assertions.assertThat(chaOfCho.isSameSide(cho)).isTrue(); + + Piece jolbyeongOfHan = board[3][0]; + Assertions.assertThat(jolbyeongOfHan).isInstanceOf(Jolbyeong.class); + Assertions.assertThat(jolbyeongOfHan.isSameSide(han)).isTrue(); // 2. 전략 기물 검증 (전략이 실제로 동작했는지 확인) - Assertions.assertThat(board[9][1]).isInstanceOf(Ma.class); // MaSangMaSang의 첫 번째 마 - Assertions.assertThat(board[9][2]).isInstanceOf(Sang.class); // MaSangMaSang의 첫 번째 상 + Piece testPieceOfHan = board[4][0]; + Assertions.assertThat(testPieceOfHan).isInstanceOf(TestPiece.class); + Assertions.assertThat(testPieceOfHan.isSameSide(han)).isTrue(); - Assertions.assertThat(board[4][0]).isInstanceOf(EmptyPiece.class); - } + Piece testPieceOfCho = board[5][0]; + Assertions.assertThat(testPieceOfCho).isInstanceOf(TestPiece.class); + Assertions.assertThat(testPieceOfCho.isSameSide(cho)).isTrue(); + Piece emptyPiece = board[4][5]; + Assertions.assertThat(emptyPiece).isEqualTo(EmptyPiece.getInstance()); + } } diff --git a/src/test/java/janggi/strategy/MaSangMaSangTest.java b/src/test/java/janggi/strategy/MaSangMaSangTest.java index 2a38b56bb1..a6387adced 100644 --- a/src/test/java/janggi/strategy/MaSangMaSangTest.java +++ b/src/test/java/janggi/strategy/MaSangMaSangTest.java @@ -12,17 +12,18 @@ class MaSangMaSangTest { + private static final ArrangementStrategy STRATEGY = MaSangMaSang.getInstance(); + @Test @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangMaSang의 위치에 생성해 넣어준다.") void shouldPlaceMaSangMaSangWhenSideHan() { // given Piece[][] grid = new Piece[10][9]; - ArrangementStrategy strategy = new MaSangMaSang(); Side side = Side.HAN; Piece expectedSidePiece = new TestPiece(side); // when - strategy.place(grid, side); + STRATEGY.place(grid, side); Piece leftMa = grid[0][1]; Piece leftSang = grid[0][2]; Piece rightMa = grid[0][6]; @@ -45,12 +46,11 @@ void shouldPlaceMaSangMaSangWhenSideHan() { void shouldPlaceMaSangMaSangWhenSideCho() { // given Piece[][] grid = new Piece[10][9]; - ArrangementStrategy strategy = new MaSangMaSang(); Side side = Side.CHO; Piece expectedSidePiece = new TestPiece(side); // when - strategy.place(grid, side); + STRATEGY.place(grid, side); Piece leftMa = grid[9][1]; Piece leftSang = grid[9][2]; Piece rightMa = grid[9][6]; diff --git a/src/test/java/janggi/strategy/MaSangSangMaTest.java b/src/test/java/janggi/strategy/MaSangSangMaTest.java index 0fc395a718..eb840e3aab 100644 --- a/src/test/java/janggi/strategy/MaSangSangMaTest.java +++ b/src/test/java/janggi/strategy/MaSangSangMaTest.java @@ -12,17 +12,18 @@ class MaSangSangMaTest { + private static final ArrangementStrategy STRATEGY = MaSangSangMa.getInstance(); + @Test @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangSangMa의 위치에 생성해 넣어준다.") void shouldPlaceMaSangSangMaWhenSideHan() { // given Piece[][] grid = new Piece[10][9]; - ArrangementStrategy strategy = new MaSangSangMa(); Side side = Side.HAN; Piece expectedSidePiece = new TestPiece(side); // when - strategy.place(grid, side); + STRATEGY.place(grid, side); Piece leftMa = grid[0][1]; Piece leftSang = grid[0][2]; Piece rightSang = grid[0][6]; @@ -45,12 +46,11 @@ void shouldPlaceMaSangSangMaWhenSideHan() { void shouldPlaceMaSangSangMaWhenSideCho() { // given Piece[][] grid = new Piece[10][9]; - ArrangementStrategy strategy = new MaSangSangMa(); Side side = Side.CHO; Piece expectedSidePiece = new TestPiece(side); // when - strategy.place(grid, side); + STRATEGY.place(grid, side); Piece leftMa = grid[9][1]; Piece leftSang = grid[9][2]; Piece rightSang = grid[9][6]; diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java index 1ba46d1a0f..369998ee1e 100644 --- a/src/test/java/janggi/strategy/SangMaMaSangTest.java +++ b/src/test/java/janggi/strategy/SangMaMaSangTest.java @@ -12,17 +12,18 @@ class SangMaMaSangTest { + private static final ArrangementStrategy STRATEGY = SangMaMaSang.getInstance(); + @Test @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaMaSang의 위치에 생성해 넣어준다.") void shouldPlaceSangMaMaSangWhenSideHan() { // given Piece[][] grid = new Piece[10][9]; - ArrangementStrategy strategy = new SangMaMaSang(); Side side = Side.HAN; Piece expectedSidePiece = new TestPiece(side); // when - strategy.place(grid, side); + STRATEGY.place(grid, side); Piece leftSang = grid[0][1]; Piece leftMa = grid[0][2]; Piece rightMa = grid[0][6]; @@ -45,12 +46,11 @@ void shouldPlaceSangMaMaSangWhenSideHan() { void shouldPlaceSangMaMaSangWhenSideCho() { // given Piece[][] grid = new Piece[10][9]; - ArrangementStrategy strategy = new SangMaMaSang(); Side side = Side.CHO; Piece expectedSidePiece = new TestPiece(side); // when - strategy.place(grid, side); + STRATEGY.place(grid, side); Piece leftSang = grid[9][1]; Piece leftMa = grid[9][2]; Piece rightMa = grid[9][6]; diff --git a/src/test/java/janggi/strategy/SangMaSangMaTest.java b/src/test/java/janggi/strategy/SangMaSangMaTest.java index b131b46a4e..e8c78b66a8 100644 --- a/src/test/java/janggi/strategy/SangMaSangMaTest.java +++ b/src/test/java/janggi/strategy/SangMaSangMaTest.java @@ -12,17 +12,18 @@ class SangMaSangMaTest { + private static final ArrangementStrategy STRATEGY = SangMaSangMa.getInstance(); + @Test @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaSangMa의 위치에 생성해 넣어준다.") void shouldPlaceSangMaSangMaWhenSideHan() { // given Piece[][] grid = new Piece[10][9]; - ArrangementStrategy strategy = new SangMaSangMa(); Side side = Side.HAN; Piece expectedSidePiece = new TestPiece(side); // when - strategy.place(grid, side); + STRATEGY.place(grid, side); Piece leftSang = grid[0][1]; Piece leftMa = grid[0][2]; Piece rightSang = grid[0][6]; @@ -45,12 +46,11 @@ void shouldPlaceSangMaSangMaWhenSideHan() { void shouldPlaceSangMaSangMaWhenSideCho() { // given Piece[][] grid = new Piece[10][9]; - ArrangementStrategy strategy = new SangMaSangMa(); Side side = Side.CHO; Piece expectedSidePiece = new TestPiece(side); // when - strategy.place(grid, side); + STRATEGY.place(grid, side); Piece leftSang = grid[9][1]; Piece leftMa = grid[9][2]; Piece rightSang = grid[9][6]; diff --git a/src/test/java/janggi/support/TestArrangementStrategy.java b/src/test/java/janggi/support/TestArrangementStrategy.java index f40698a8e1..1c18f34099 100644 --- a/src/test/java/janggi/support/TestArrangementStrategy.java +++ b/src/test/java/janggi/support/TestArrangementStrategy.java @@ -10,18 +10,22 @@ public class TestArrangementStrategy extends ArrangementStrategy { private final Map customPieces; + private final boolean shouldClear; public TestArrangementStrategy(Map customPieces) { + this(customPieces, true); + } + + public TestArrangementStrategy(Map customPieces, boolean shouldClear) { super(StrategyLabel.HEHE); this.customPieces = customPieces; + this.shouldClear = shouldClear; } @Override public void place(Piece[][] arrangement, Side side) { - for (int row = 0; row < arrangement.length; row++) { - for (int col = 0; col < arrangement[row].length; col++) { - arrangement[row][col] = EmptyPiece.getInstance(); - } + if (shouldClear) { + clearBoard(arrangement); } customPieces.forEach((loc, piece) -> { @@ -29,5 +33,11 @@ public void place(Piece[][] arrangement, Side side) { }); } - + private void clearBoard(Piece[][] arrangement) { + for (int row = 0; row < arrangement.length; row++) { + for (int col = 0; col < arrangement[row].length; col++) { + arrangement[row][col] = EmptyPiece.getInstance(); + } + } + } } From 41c328750c6a8316f06e6bd7feece4ab8849f860 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 21:06:15 +0900 Subject: [PATCH 30/45] =?UTF-8?q?refactor(rule):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EC=97=90=20=EC=8B=B1=EA=B8=80?= =?UTF-8?q?=ED=86=A4=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상태를 가지지 않는 불변 객체이므로, 불필요한 객체 생성을 막기 위해 싱글톤으로 변경 - Po 클래스의 RouteProvider 상수가 GungSeongRouteProvider의 인스턴스를 할당받던 것을 Po의 직선 이동 규칙에 맞도록 StraightRouteProvider의 인스턴스를 할당받도록 함으로써 버그 수정 --- src/main/java/janggi/domain/piece/Cha.java | 4 ++-- src/main/java/janggi/domain/piece/Gung.java | 4 ++-- .../java/janggi/domain/piece/Jolbyeong.java | 2 +- src/main/java/janggi/domain/piece/Ma.java | 2 +- src/main/java/janggi/domain/piece/Po.java | 6 ++--- src/main/java/janggi/domain/piece/Sa.java | 4 ++-- src/main/java/janggi/domain/piece/Sang.java | 2 +- .../collision/DefaultCollisionDetector.java | 9 ++++++++ .../rule/collision/PoCollisionDetector.java | 8 +++++++ .../rule/route/GungSeongRouteProvider.java | 9 ++++++++ .../rule/route/StraightRouteProvider.java | 9 ++++++++ .../DefaultCollisionDetectorTest.java | 14 +++++------ .../collision/PoCollisionDetectorTest.java | 23 ++++++++----------- .../route/GungSeongRouteProviderTest.java | 8 +++---- .../rule/route/StraightRouteProviderTest.java | 8 +++---- 15 files changed, 70 insertions(+), 42 deletions(-) diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index f12f5554e3..d8af827f62 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -11,8 +11,8 @@ public class Cha extends Piece { private static final String PIECE_NAME = "차"; - private static final RouteProvider ROUTE_PROVIDER = new StraightRouteProvider(); - private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); + private static final RouteProvider ROUTE_PROVIDER = StraightRouteProvider.getInstance(); + private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Cha(Side side) { super(PIECE_NAME, side); diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 0ebd5d0f13..f9bdc4c56c 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -11,8 +11,8 @@ public class Gung extends Piece { private static final String PIECE_NAME = "궁"; - private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); - private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); + private static final RouteProvider ROUTE_PROVIDER = GungSeongRouteProvider.getInstance(); + private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Gung(Side side) { super(PIECE_NAME, side); diff --git a/src/main/java/janggi/domain/piece/Jolbyeong.java b/src/main/java/janggi/domain/piece/Jolbyeong.java index 6e935ccbeb..c5902d7010 100644 --- a/src/main/java/janggi/domain/piece/Jolbyeong.java +++ b/src/main/java/janggi/domain/piece/Jolbyeong.java @@ -18,7 +18,7 @@ public class Jolbyeong extends Piece { private static final String PIECE_NAME = "졸병"; private static final String CHO_PIECE_NAME = "졸"; private static final String HAN_PIECE_NAME = "병"; - private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); + private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Jolbyeong(Side side) { super(PIECE_NAME, side); diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index 45b33420a0..0c43bb467a 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -19,7 +19,7 @@ public class Ma extends Piece { private static final String PIECE_NAME = "마"; - private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); + private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Ma(Side side) { super(PIECE_NAME, side); diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 603d55bf63..6828ecbc2c 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -4,15 +4,15 @@ import janggi.domain.Side; import janggi.domain.rule.collision.CollisionDetector; import janggi.domain.rule.collision.PoCollisionDetector; -import janggi.domain.rule.route.GungSeongRouteProvider; import janggi.domain.rule.route.RouteProvider; +import janggi.domain.rule.route.StraightRouteProvider; import java.util.List; public class Po extends Piece { private static final String PIECE_NAME = "포"; - private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); - private static final CollisionDetector COLLISION_DETECTOR = new PoCollisionDetector(); + private static final RouteProvider ROUTE_PROVIDER = StraightRouteProvider.getInstance(); + private static final CollisionDetector COLLISION_DETECTOR = PoCollisionDetector.getInstance(); public Po(Side side) { super(PIECE_NAME, side); diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index 34efdb8f3a..6a811f7723 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -11,8 +11,8 @@ public class Sa extends Piece { private static final String PIECE_NAME = "사"; - private static final RouteProvider ROUTE_PROVIDER = new GungSeongRouteProvider(); - private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); + private static final RouteProvider ROUTE_PROVIDER = GungSeongRouteProvider.getInstance(); + private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Sa(Side side) { super(PIECE_NAME, side); diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index 6f057e26fe..0abcfa9f3b 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -19,7 +19,7 @@ public class Sang extends Piece { private static final String PIECE_NAME = "상"; - private static final CollisionDetector COLLISION_DETECTOR = new DefaultCollisionDetector(); + private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Sang(Side side) { super(PIECE_NAME, side); diff --git a/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java b/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java index c656bcfaa2..71be37e0fe 100644 --- a/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java +++ b/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java @@ -3,8 +3,17 @@ import janggi.domain.piece.Piece; import java.util.List; +@SuppressWarnings("java:S6548") public class DefaultCollisionDetector implements CollisionDetector { + private static final DefaultCollisionDetector INSTANCE = new DefaultCollisionDetector(); + + private DefaultCollisionDetector() {} + + public static DefaultCollisionDetector getInstance() { + return INSTANCE; + } + @Override public void check(Piece piece, List piecesOnPath) { validateMiddlePath(piecesOnPath); diff --git a/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java b/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java index f4dd255487..6b2c12d858 100644 --- a/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java +++ b/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java @@ -3,10 +3,18 @@ import janggi.domain.piece.Piece; import java.util.List; +@SuppressWarnings("java:S6548") public class PoCollisionDetector implements CollisionDetector { + private static final PoCollisionDetector INSTANCE = new PoCollisionDetector(); private static final int REQUIRED_SCREEN_COUNT = 1; + private PoCollisionDetector() {} + + public static PoCollisionDetector getInstance() { + return INSTANCE; + } + @Override public void check(Piece piece, List piecesOnPath) { validateOneObstacle(piecesOnPath); diff --git a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java index 2bdf6ea937..97e7af092e 100644 --- a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java @@ -12,8 +12,17 @@ import janggi.domain.Location; import java.util.List; +@SuppressWarnings("java:S6548") public class GungSeongRouteProvider implements RouteProvider { + private static final GungSeongRouteProvider INSTANCE = new GungSeongRouteProvider(); + + private GungSeongRouteProvider() {} + + public static GungSeongRouteProvider getInstance() { + return INSTANCE; + } + @Override public List calculateRoute(Location from, Location to) { List directions = List.of( diff --git a/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java index 69b0df611e..9835c5e848 100644 --- a/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java @@ -8,8 +8,17 @@ import janggi.domain.Location; import java.util.List; +@SuppressWarnings("java:S6548") public class StraightRouteProvider implements RouteProvider { + private static final StraightRouteProvider INSTANCE = new StraightRouteProvider(); + + private StraightRouteProvider() {} + + public static StraightRouteProvider getInstance() { + return INSTANCE; + } + @Override public List calculateRoute(Location from, Location to) { int maxDistance = calculateMaxDistance(from, to); diff --git a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java index 5467872403..7b41c5b0de 100644 --- a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java +++ b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java @@ -11,42 +11,41 @@ class DefaultCollisionDetectorTest { + private static final DefaultCollisionDetector DEFAULT_COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); + @Test @DisplayName("이동 경로에 장애물이 존재하지 않고, 마지막 경로에 위치한 기물이 존재하지 않으면 예외를 반환하지 않는다.") void shouldNotThrowExceptionWhenNoPieceOnPathAndNoPieceOnDestination() { // given - CollisionDetector collisionDetector = new DefaultCollisionDetector(); List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance()); Piece piece = new TestPiece(Side.CHO); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> collisionDetector.check(piece, piecesOnPath)); + .isThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(piece, piecesOnPath)); } @Test @DisplayName("이동 경로에 장애물이 존재하지 않고, 마지막 경로에 위치한 기물이 상대팀이면 예외를 반환하지 않는다.") void shouldNotThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsOtherSide() { // given - CollisionDetector collisionDetector = new DefaultCollisionDetector(); List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.HAN)); Piece piece = new TestPiece(Side.CHO); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> collisionDetector.check(piece, piecesOnPath)); + .isThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(piece, piecesOnPath)); } @Test @DisplayName("이동 경로에 장애물이 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenPieceOnPathExist() { // given - CollisionDetector collisionDetector = new DefaultCollisionDetector(); List piecesOnPath = List.of(new TestPiece(Side.HAN), EmptyPiece.getInstance(), EmptyPiece.getInstance()); Piece piece = new TestPiece(Side.CHO); // when & then - Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(piece, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -54,13 +53,12 @@ void shouldThrowExceptionWhenPieceOnPathExist() { @DisplayName("이동 경로에 장애물이 존재하지 않고, 마지막 경로에 위치한 기물이 우리팀이면 예외를 발생시킨다.") void shouldThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsMySide() { // given - CollisionDetector collisionDetector = new DefaultCollisionDetector(); Side side = Side.HAN; List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(side)); Piece piece = new TestPiece(side); // when & then - Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(piece, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java index 71452b6ab0..928c479d53 100644 --- a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java +++ b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java @@ -12,42 +12,41 @@ class PoCollisionDetectorTest { + private static final PoCollisionDetector PO_COLLISION_DETECTOR = PoCollisionDetector.getInstance(); + @Test @DisplayName("이동 경로에 포가 아닌 장애물이 1개 존재하고, 마지막 경로 좌표에 기물이 존재하지 않으면 예외를 반환하지 않는다.") void shouldNotThrowExceptionWhenOneNonPoObstacleAndEmptyDestination() { // given - CollisionDetector collisionDetector = new PoCollisionDetector(); List piecesOnPath = List.of(EmptyPiece.getInstance(), new TestPiece(Side.HAN), EmptyPiece.getInstance()); Piece piece = new Po(Side.CHO); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> collisionDetector.check(piece, piecesOnPath)); + .isThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)); } @Test @DisplayName("이동 경로에 포가 아닌 장애물이 1개 존재하고, 마지막 경로에 위치한 기물이 상대팀이면 예외를 반환하지 않는다.") void shouldNotThrowExceptionWhenOneNonPoObstacleAndEnemyAtDestination() { // given - CollisionDetector collisionDetector = new PoCollisionDetector(); List piecesOnPath = List.of(EmptyPiece.getInstance(), new TestPiece(Side.HAN), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> collisionDetector.check(piece, piecesOnPath)); + .isThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)); } @Test @DisplayName("이동 경로에 장애물이 2개 이상 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { // given - CollisionDetector collisionDetector = new PoCollisionDetector(); List piecesOnPath = List.of(new TestPiece(Side.HAN), new TestPiece(Side.HAN), EmptyPiece.getInstance()); Piece piece = new Po(Side.CHO); // when & then - Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -55,12 +54,11 @@ void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { @DisplayName("이동 경로에 장애물이 존재하지 않는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenNoObstacleOnPath() { // given - CollisionDetector collisionDetector = new PoCollisionDetector(); List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then - Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -68,12 +66,11 @@ void shouldThrowExceptionWhenNoObstacleOnPath() { @DisplayName("이동 경로에 포가 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenPoOnPath() { // given - CollisionDetector collisionDetector = new DefaultCollisionDetector(); List piecesOnPath = List.of(EmptyPiece.getInstance(), new Po(Side.HAN), new TestPiece(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then - Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -81,12 +78,11 @@ void shouldThrowExceptionWhenPoOnPath() { @DisplayName("도착지에 상대팀의 포가 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenPoOfOtherSideOnDestination() { // given - CollisionDetector collisionDetector = new PoCollisionDetector(); List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new Po(Side.HAN)); Piece piece = new Po(Side.CHO); // when & then - Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -94,12 +90,11 @@ void shouldThrowExceptionWhenPoOfOtherSideOnDestination() { @DisplayName("도착지에 우리팀 기물이 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenSameSidePieceOnDestination() { // given - CollisionDetector collisionDetector = new PoCollisionDetector(); List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.CHO)); Piece piece = new Po(Side.CHO); // when & then - Assertions.assertThatThrownBy(() -> collisionDetector.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java index bd572fc277..5825f8efb3 100644 --- a/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java @@ -9,6 +9,8 @@ class GungSeongRouteProviderTest { + private static final GungSeongRouteProvider GUNG_SEONG_ROUTE_PROVIDER = GungSeongRouteProvider.getInstance(); + @ParameterizedTest @DisplayName("궁성 안에서 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") @MethodSource("provideReachableCoordination") @@ -16,10 +18,9 @@ void shouldReturnRouteForReachableLocation(List destination) { // given Location from = Location.from(List.of(4, 1)); Location to = Location.from(destination); - RouteProvider routeProvider = new GungSeongRouteProvider(); // when & then - Assertions.assertThat(routeProvider.calculateRoute(from, to)) + Assertions.assertThat(GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(from, to)) .isEqualTo(List.of(to)); } @@ -41,10 +42,9 @@ void shouldThrowExceptionForUnReachableLocation(List coordination) { // given Location from = Location.from(List.of(3, 2)); Location to = Location.from(coordination); - RouteProvider routeProvider = new GungSeongRouteProvider(); // when & then - Assertions.assertThatThrownBy(() -> routeProvider.calculateRoute(from, to)) + Assertions.assertThatThrownBy(() -> GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(from, to)) .isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java index 47765a9e50..19c281a968 100644 --- a/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java @@ -11,16 +11,17 @@ class StraightRouteProviderTest { + private static final StraightRouteProvider STRAIGHT_ROUTE_PROVIDER = StraightRouteProvider.getInstance(); + @ParameterizedTest @DisplayName("직선으로 이동할 위치를 파라미터로 받으면 이동 경로를 반환한다.") @MethodSource("provideReachableCoordination") void shouldReturnRouteForReachableLocation(Location destination, List route) { // given Location from = Location.from(List.of(2, 2)); - RouteProvider routeProvider = new StraightRouteProvider(); // when & then - Assertions.assertThat(routeProvider.calculateRoute(from, destination)) + Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, destination)) .isEqualTo(route); } @@ -52,10 +53,9 @@ void shouldThrowExceptionForUnReachableLocation(List coordination) { // given Location from = Location.from(List.of(1, 1)); Location to = Location.from(coordination); - RouteProvider routeProvider = new StraightRouteProvider(); // when & then - Assertions.assertThatThrownBy(() -> routeProvider.calculateRoute(from, to)) + Assertions.assertThatThrownBy(() -> STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, to)) .isInstanceOf(IllegalArgumentException.class); } From dc92d78a52fe1e0718e9c9d865db06d1199d0ad2 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 21:58:28 +0900 Subject: [PATCH 31/45] =?UTF-8?q?refactor(piece):=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9D=84=20=EA=B2=B0=EC=A0=95=ED=95=98?= =?UTF-8?q?=EB=8A=94=20Enum=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기물의 종류를 표현하는 PieceType Enum 클래스 구현 - 각 기물마다 구현하던 Piece의 isEmpty() 추상 메서드를 일반 메서드로 변경 - isEmpty() 호출 시 PieceType이 EMPTY인지 확인하도록 로직을 작성해 응집도를 높임 --- src/main/java/janggi/domain/Side.java | 8 ++-- src/main/java/janggi/domain/piece/Cha.java | 8 +--- .../java/janggi/domain/piece/EmptyPiece.java | 12 ++--- src/main/java/janggi/domain/piece/Gung.java | 8 +--- .../java/janggi/domain/piece/Jolbyeong.java | 45 +++++++++---------- src/main/java/janggi/domain/piece/Ma.java | 8 +--- src/main/java/janggi/domain/piece/Piece.java | 16 ++++--- .../java/janggi/domain/piece/PieceType.java | 24 ++++++++++ src/main/java/janggi/domain/piece/Po.java | 8 +--- src/main/java/janggi/domain/piece/Sa.java | 8 +--- src/main/java/janggi/domain/piece/Sang.java | 8 +--- .../java/janggi/view/ApplicationView.java | 4 +- src/test/java/janggi/support/TestPiece.java | 5 +-- 13 files changed, 71 insertions(+), 91 deletions(-) create mode 100644 src/main/java/janggi/domain/piece/PieceType.java diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java index 02fdf05167..e9904b0527 100644 --- a/src/main/java/janggi/domain/Side.java +++ b/src/main/java/janggi/domain/Side.java @@ -12,14 +12,14 @@ public enum Side { this.name = name; } - public String getName() { - return name; - } - public Side switchTurn() { if (this == HAN) { return CHO; } return HAN; } + + public String getName() { + return name; + } } diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index d8af827f62..5bae8de3d2 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -10,17 +10,11 @@ public class Cha extends Piece { - private static final String PIECE_NAME = "차"; private static final RouteProvider ROUTE_PROVIDER = StraightRouteProvider.getInstance(); private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Cha(Side side) { - super(PIECE_NAME, side); - } - - @Override - public boolean isEmpty() { - return false; + super(PieceType.CHA, side); } @Override diff --git a/src/main/java/janggi/domain/piece/EmptyPiece.java b/src/main/java/janggi/domain/piece/EmptyPiece.java index b0247baf08..80fc7b816c 100644 --- a/src/main/java/janggi/domain/piece/EmptyPiece.java +++ b/src/main/java/janggi/domain/piece/EmptyPiece.java @@ -8,28 +8,22 @@ public class EmptyPiece extends Piece { private static final EmptyPiece INSTANCE = new EmptyPiece(); - private static final String PIECE_NAME = "ㆍ"; private EmptyPiece() { - super(PIECE_NAME, Side.NONE); + super(PieceType.EMPTY, Side.NONE); } public static EmptyPiece getInstance() { return INSTANCE; } - @Override - public boolean isEmpty() { - return true; - } - @Override public List calculateRoute(Location from, Location to) { - throw new IllegalArgumentException("빈 객체는 이동할 수 없습니다."); + throw new UnsupportedOperationException("빈 객체는 이동할 수 없습니다."); } @Override public void detectCollision(List piecesOnPath) { - throw new IllegalArgumentException("빈 객체는 이동할 수 없습니다."); + throw new UnsupportedOperationException("빈 객체는 이동할 수 없습니다."); } } diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index f9bdc4c56c..91c6b90033 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -10,17 +10,11 @@ public class Gung extends Piece { - private static final String PIECE_NAME = "궁"; private static final RouteProvider ROUTE_PROVIDER = GungSeongRouteProvider.getInstance(); private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Gung(Side side) { - super(PIECE_NAME, side); - } - - @Override - public boolean isEmpty() { - return false; + super(PieceType.GUNG, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Jolbyeong.java b/src/main/java/janggi/domain/piece/Jolbyeong.java index c5902d7010..5ecee48d9f 100644 --- a/src/main/java/janggi/domain/piece/Jolbyeong.java +++ b/src/main/java/janggi/domain/piece/Jolbyeong.java @@ -1,7 +1,5 @@ package janggi.domain.piece; -import static janggi.domain.rule.route.Direction.BACK; -import static janggi.domain.rule.route.Direction.FRONT; import static janggi.domain.rule.route.Direction.LEFT; import static janggi.domain.rule.route.Direction.RIGHT; @@ -15,24 +13,26 @@ public class Jolbyeong extends Piece { - private static final String PIECE_NAME = "졸병"; - private static final String CHO_PIECE_NAME = "졸"; - private static final String HAN_PIECE_NAME = "병"; private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Jolbyeong(Side side) { - super(PIECE_NAME, side); + super(determineType(side), side); } - @Override - public boolean isEmpty() { - return false; + private static PieceType determineType(Side side) { + if (side == Side.HAN) { + return PieceType.BYEONG; + } + if (side == Side.CHO) { + return PieceType.JOL; + } + throw new IllegalArgumentException("진영이 존재하지 않아 기물명을 정할 수 없습니다."); } @Override public List calculateRoute(Location from, Location to) { List directions = List.of( - Route.of(List.of(getRealFront(side))), + Route.of(List.of(getFrontDirection())), Route.of(List.of(LEFT)), Route.of(List.of(RIGHT)) ); @@ -43,26 +43,21 @@ public List calculateRoute(Location from, Location to) { return locations; } } - throw new IllegalArgumentException(getName() + "은 해당 위치에 도달할 수 없습니다."); - } - - @Override - public void detectCollision(List piecesOnPath) { - COLLISION_DETECTOR.check(this, piecesOnPath); + throw new IllegalArgumentException(getPieceType().getNameFormat() + "은 해당 위치에 도달할 수 없습니다."); } - private Direction getRealFront(Side side) { - if (side.equals(Side.HAN)) { - return FRONT; + private Direction getFrontDirection() { + if (side == Side.HAN) { + return Direction.FRONT; + } + if (side == Side.CHO) { + return Direction.BACK; } - return BACK; + throw new IllegalStateException("진영이 존재하지 않아 전진 방향을 정할 수 없습니다."); } @Override - public String getName() { - if (side.equals(Side.HAN)) { - return HAN_PIECE_NAME; - } - return CHO_PIECE_NAME; + public void detectCollision(List piecesOnPath) { + COLLISION_DETECTOR.check(this, piecesOnPath); } } diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index 0c43bb467a..62f624fc06 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -18,16 +18,10 @@ public class Ma extends Piece { - private static final String PIECE_NAME = "마"; private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Ma(Side side) { - super(PIECE_NAME, side); - } - - @Override - public boolean isEmpty() { - return false; + super(PieceType.MA, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index e259b328a5..917379c7fb 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -6,20 +6,22 @@ public abstract class Piece { - protected final String name; + private final PieceType pieceType; protected final Side side; - protected Piece(String name, Side side) { - this.name = name; + protected Piece(PieceType pieceType, Side side) { + this.pieceType = pieceType; this.side = side; } - public abstract boolean isEmpty(); - public abstract List calculateRoute(Location from, Location to); public abstract void detectCollision(List piecesOnPath); + public boolean isEmpty() { + return pieceType == PieceType.EMPTY; + } + public boolean isSameSide(Piece piece) { return this.side.equals(piece.side); } @@ -28,7 +30,7 @@ public boolean isSameSide(Side side) { return this.side.equals(side); } - public String getName() { - return name; + public PieceType getPieceType() { + return pieceType; } } diff --git a/src/main/java/janggi/domain/piece/PieceType.java b/src/main/java/janggi/domain/piece/PieceType.java new file mode 100644 index 0000000000..858ff6fa66 --- /dev/null +++ b/src/main/java/janggi/domain/piece/PieceType.java @@ -0,0 +1,24 @@ +package janggi.domain.piece; + +public enum PieceType { + + EMPTY("ㆍ"), + GUNG("궁"), + SA("사"), + MA("마"), + SANG("상"), + CHA("차"), + PO("포"), + JOL("졸"), + BYEONG("병"); + + private final String nameFormat; + + PieceType(String nameFormat) { + this.nameFormat = nameFormat; + } + + public String getNameFormat() { + return nameFormat; + } +} diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index 6828ecbc2c..f3d6266880 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -10,17 +10,11 @@ public class Po extends Piece { - private static final String PIECE_NAME = "포"; private static final RouteProvider ROUTE_PROVIDER = StraightRouteProvider.getInstance(); private static final CollisionDetector COLLISION_DETECTOR = PoCollisionDetector.getInstance(); public Po(Side side) { - super(PIECE_NAME, side); - } - - @Override - public boolean isEmpty() { - return false; + super(PieceType.PO, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index 6a811f7723..c232315afc 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -10,17 +10,11 @@ public class Sa extends Piece { - private static final String PIECE_NAME = "사"; private static final RouteProvider ROUTE_PROVIDER = GungSeongRouteProvider.getInstance(); private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Sa(Side side) { - super(PIECE_NAME, side); - } - - @Override - public boolean isEmpty() { - return false; + super(PieceType.SA, side); } @Override diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index 0abcfa9f3b..12057e6dd8 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -18,16 +18,10 @@ public class Sang extends Piece { - private static final String PIECE_NAME = "상"; private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); public Sang(Side side) { - super(PIECE_NAME, side); - } - - @Override - public boolean isEmpty() { - return false; + super(PieceType.SANG, side); } @Override diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java index 6e15e2ffd7..9fd18d1ae1 100644 --- a/src/main/java/janggi/view/ApplicationView.java +++ b/src/main/java/janggi/view/ApplicationView.java @@ -2,6 +2,7 @@ import janggi.domain.Side; import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceType; import janggi.strategy.ArrangementStrategy; import java.util.List; import java.util.function.Supplier; @@ -30,7 +31,8 @@ public int requestArrangementStrategyDecision(Side side, List> board2DArray) { List> stringMatrix = board2DArray.stream() .map(row -> row.stream() - .map(Piece::getName) + .map(Piece::getPieceType) + .map(PieceType::getNameFormat) .toList() ).toList(); diff --git a/src/test/java/janggi/support/TestPiece.java b/src/test/java/janggi/support/TestPiece.java index 8eff69ac12..2ce258b31e 100644 --- a/src/test/java/janggi/support/TestPiece.java +++ b/src/test/java/janggi/support/TestPiece.java @@ -3,14 +3,13 @@ import janggi.domain.Location; import janggi.domain.Side; import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceType; import java.util.List; public class TestPiece extends Piece { - private static final String PIECE_NAME = "test"; - public TestPiece(Side side) { - super(PIECE_NAME, side); + super(PieceType.CHA, side); } @Override From f7db4f5a96b4667ec826a8b40dc2424e6ede7ee9 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 28 Mar 2026 23:41:26 +0900 Subject: [PATCH 32/45] =?UTF-8?q?refactor:=20=EA=B8=B8=20=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EA=B3=B5=ED=86=B5=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C=20=EB=B0=8F=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D/=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 - RouteProvider: 중복되던 길 찾기 필터링 로직을 static 메서드(findValidPath)로 추출해 일원화하고 다형성 구조 개선 - Piece: 기물 내부의 불필요한 예외 번역을 걷어내고, 에러 발생 시 PieceType을 전달해 정확한 메시지를 반환하도록 수정 - Board: 출발지 정보를 활용해 제자리 이동 방지 로직을 추가하고, 불필요한 래퍼 메서드를 제거해 응집도를 높임 - 의도를 명확히 드러내도록 각종 클래스의 메서드명 및 변수명 변경 --- .../java/janggi/controller/JanggiFlow.java | 8 +-- src/main/java/janggi/domain/Location.java | 14 ++-- src/main/java/janggi/domain/Side.java | 2 +- src/main/java/janggi/domain/board/Board.java | 69 +++++++++---------- src/main/java/janggi/domain/piece/Cha.java | 6 +- src/main/java/janggi/domain/piece/Gung.java | 6 +- .../java/janggi/domain/piece/Jolbyeong.java | 17 ++--- src/main/java/janggi/domain/piece/Ma.java | 28 +++----- src/main/java/janggi/domain/piece/Piece.java | 4 +- src/main/java/janggi/domain/piece/Po.java | 6 +- src/main/java/janggi/domain/piece/Sa.java | 6 +- src/main/java/janggi/domain/piece/Sang.java | 28 +++----- .../collision/DefaultCollisionDetector.java | 13 ++-- .../rule/collision/PoCollisionDetector.java | 13 ++-- .../rule/route/GungSeongRouteProvider.java | 33 ++++----- .../java/janggi/domain/rule/route/Route.java | 18 ++--- .../domain/rule/route/RouteProvider.java | 17 ++++- .../rule/route/StraightRouteProvider.java | 25 +++---- .../java/janggi/strategy/MaSangMaSang.java | 2 +- .../java/janggi/strategy/MaSangSangMa.java | 2 +- .../java/janggi/strategy/SangMaMaSang.java | 2 +- .../java/janggi/strategy/SangMaSangMa.java | 2 +- .../java/janggi/strategy/StrategyLabel.java | 8 +-- .../java/janggi/domain/board/BoardTest.java | 15 ++-- .../route/GungSeongRouteProviderTest.java | 5 +- .../domain/rule/route/RouteProviderTest.java | 44 ++++++++++++ .../janggi/domain/rule/route/RouteTest.java | 4 +- .../rule/route/StraightRouteProviderTest.java | 5 +- .../janggi/strategy/MaSangMaSangTest.java | 21 +++--- .../janggi/strategy/MaSangSangMaTest.java | 21 +++--- .../janggi/strategy/SangMaMaSangTest.java | 21 +++--- .../janggi/strategy/SangMaSangMaTest.java | 21 +++--- .../support/TestArrangementStrategy.java | 2 +- 33 files changed, 253 insertions(+), 235 deletions(-) create mode 100644 src/test/java/janggi/domain/rule/route/RouteProviderTest.java diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 79c37285a7..1e0818fbaf 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -40,10 +40,10 @@ public void process() { final Side turnSide = current; retryAction(() -> { Location from = askLocationOfPiece(turnSide, board); - Location to = askLocationToMove(turnSide, board); + Location to = askLocationToMove(from, turnSide, board); board.move(from, to); }); - current = current.switchTurn(); + current = current.switchSide(); } } @@ -54,10 +54,10 @@ private Location askLocationOfPiece(Side current, Board board) { return verifiedLocation; } - private Location askLocationToMove(Side current, Board board) { + private Location askLocationToMove(Location startingLocation, Side current, Board board) { List locationToMove = view.requestLocationToMove(); Location verifiedLocation = Location.from(locationToMove); - board.validateLocationToMove(current, verifiedLocation); + board.validateLocationToMove(startingLocation, verifiedLocation, current); return verifiedLocation; } diff --git a/src/main/java/janggi/domain/Location.java b/src/main/java/janggi/domain/Location.java index cff3ee502e..cf34d2773f 100644 --- a/src/main/java/janggi/domain/Location.java +++ b/src/main/java/janggi/domain/Location.java @@ -4,16 +4,16 @@ public record Location(int x, int y) { - private static final int COORDINATE_COUNT = 2; + private static final int COORDINATION_COUNT = 2; - public static Location from(List position) { - validateCount(position); - return new Location(position.get(0), position.get(1)); + public static Location from(List coordination) { + validateCount(coordination); + return new Location(coordination.get(0), coordination.get(1)); } - private static void validateCount(List position) { - if (position.size() != COORDINATE_COUNT) { - throw new IllegalArgumentException("좌표의 개수는 " + COORDINATE_COUNT + "개 입니다."); + private static void validateCount(List coordination) { + if (coordination.size() != COORDINATION_COUNT) { + throw new IllegalArgumentException("좌표의 개수는 " + COORDINATION_COUNT + "개 입니다."); } } diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java index e9904b0527..f5e8d2d4ea 100644 --- a/src/main/java/janggi/domain/Side.java +++ b/src/main/java/janggi/domain/Side.java @@ -12,7 +12,7 @@ public enum Side { this.name = name; } - public Side switchTurn() { + public Side switchSide() { if (this == HAN) { return CHO; } diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index ae53b93b01..72361bff38 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -38,17 +38,6 @@ public static Board create(BoardAssembler assembler) { return new Board(boardState, height, width); } - public void move(Location from, Location to) { - validateMove(from, to); - - Piece piece = boardState.get(from); - List piecesOnPath = getPiecesOnRoute(piece, from, to); - - piece.detectCollision(piecesOnPath); - - executeMove(from, to, piece); - } - public void validateLocationOfPiece(Side currentSide, Location locationOfPiece) { validateLocation(locationOfPiece); validatePieceExist(locationOfPiece); @@ -58,23 +47,25 @@ public void validateLocationOfPiece(Side currentSide, Location locationOfPiece) } } - public boolean isNotEmpty() { - return !boardState.values().stream() - .allMatch(Piece::isEmpty); + private void validateLocation(Location location) { + if (!boardState.containsKey(location)) { + throw new IllegalArgumentException("해당 좌표는 보드판에 존재하지 않습니다."); + } } - public void validateLocationToMove(Side currentSide, Location locationToMove) { - validateLocation(locationToMove); - Piece target = boardState.get(locationToMove); - if (isOccupiedBySameSide(target, currentSide)) { - throw new IllegalArgumentException("같은 편의 기물이 있는 위치로 이동할 수 없습니다."); + private void validatePieceExist(Location location) { + if (boardState.get(location).isEmpty()) { + throw new IllegalArgumentException("해당 좌표에 기물이 존재하지 않습니다."); } } - private void validateMove(Location from, Location to) { - validateLocation(from); - validateLocation(to); - validatePieceExist(from); + public void move(Location from, Location to) { + Piece piece = boardState.get(from); + List piecesOnPath = getPiecesOnRoute(piece, from, to); + + piece.detectCollision(piecesOnPath); + + executeMove(from, to, piece); } private List getPiecesOnRoute(Piece piece, Location from, Location to) { @@ -88,6 +79,26 @@ private void executeMove(Location from, Location to, Piece piece) { boardState.put(from, EmptyPiece.getInstance()); } + public void validateLocationToMove(Location startingLocation, Location locationToMove, Side currentSide) { + validateLocation(locationToMove); + validateMovementOccurrence(startingLocation, locationToMove); + Piece target = boardState.get(locationToMove); + if (isOccupiedBySameSide(target, currentSide)) { + throw new IllegalArgumentException("같은 편의 기물이 있는 위치로 이동할 수 없습니다."); + } + } + + private void validateMovementOccurrence(Location from, Location to) { + if (from.equals(to)) { + throw new IllegalArgumentException("기물의 도착 위치는 출발 위치와 일치할 수 없습니다."); + } + } + + public boolean isNotEmpty() { + return !boardState.values().stream() + .allMatch(Piece::isEmpty); + } + public List> to2DArray() { List> pieces = new ArrayList<>(); for (int row = 0; row < height; row++) { @@ -101,18 +112,6 @@ public List> to2DArray() { return List.copyOf(pieces); } - private void validateLocation(Location location) { - if (!boardState.containsKey(location)) { - throw new IllegalArgumentException("해당 좌표는 보드판에 존재하지 않습니다."); - } - } - - private void validatePieceExist(Location location) { - if (boardState.get(location).isEmpty()) { - throw new IllegalArgumentException("해당 좌표에 기물이 존재하지 않습니다."); - } - } - private boolean isNotSameSide(Piece piece, Side side) { return !piece.isSameSide(side); } diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java index 5bae8de3d2..8133bf0603 100644 --- a/src/main/java/janggi/domain/piece/Cha.java +++ b/src/main/java/janggi/domain/piece/Cha.java @@ -19,11 +19,7 @@ public Cha(Side side) { @Override public List calculateRoute(Location from, Location to) { - try { - return ROUTE_PROVIDER.calculateRoute(from, to); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("차는 해당 위치에 도달할 수 없습니다."); - } + return ROUTE_PROVIDER.calculateRoute(pieceType, from, to); } @Override diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 91c6b90033..b0a7ecfbf9 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -19,11 +19,7 @@ public Gung(Side side) { @Override public List calculateRoute(Location from, Location to) { - try { - return ROUTE_PROVIDER.calculateRoute(from, to); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("궁은 해당 위치에 도달할 수 없습니다."); - } + return ROUTE_PROVIDER.calculateRoute(pieceType, from, to); } @Override diff --git a/src/main/java/janggi/domain/piece/Jolbyeong.java b/src/main/java/janggi/domain/piece/Jolbyeong.java index 5ecee48d9f..788dd46b51 100644 --- a/src/main/java/janggi/domain/piece/Jolbyeong.java +++ b/src/main/java/janggi/domain/piece/Jolbyeong.java @@ -9,6 +9,7 @@ import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.Direction; import janggi.domain.rule.route.Route; +import janggi.domain.rule.route.RouteProvider; import java.util.List; public class Jolbyeong extends Piece { @@ -31,19 +32,13 @@ private static PieceType determineType(Side side) { @Override public List calculateRoute(Location from, Location to) { - List directions = List.of( - Route.of(List.of(getFrontDirection())), - Route.of(List.of(LEFT)), - Route.of(List.of(RIGHT)) + List possibleRoutes = List.of( + Route.from(List.of(getFrontDirection())), + Route.from(List.of(LEFT)), + Route.from(List.of(RIGHT)) ); - for (Route route : directions) { - List locations = route.apply(from); - if (locations.getLast().equals(to)) { - return locations; - } - } - throw new IllegalArgumentException(getPieceType().getNameFormat() + "은 해당 위치에 도달할 수 없습니다."); + return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); } private Direction getFrontDirection() { diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index 62f624fc06..de8730b466 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -14,6 +14,7 @@ import janggi.domain.rule.collision.CollisionDetector; import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.Route; +import janggi.domain.rule.route.RouteProvider; import java.util.List; public class Ma extends Piece { @@ -26,25 +27,18 @@ public Ma(Side side) { @Override public List calculateRoute(Location from, Location to) { - List directions = List.of( - Route.of(List.of(FRONT, FRONT_LEFT)), - Route.of(List.of(FRONT, FRONT_RIGHT)), - Route.of(List.of(RIGHT, FRONT_RIGHT)), - Route.of(List.of(RIGHT, BACK_RIGHT)), - Route.of(List.of(LEFT, FRONT_LEFT)), - Route.of(List.of(LEFT, BACK_LEFT)), - Route.of(List.of(BACK, BACK_LEFT)), - Route.of(List.of(BACK, BACK_RIGHT)) + List possibleRoutes = List.of( + Route.from(List.of(FRONT, FRONT_LEFT)), + Route.from(List.of(FRONT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, BACK_RIGHT)), + Route.from(List.of(LEFT, FRONT_LEFT)), + Route.from(List.of(LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_LEFT)), + Route.from(List.of(BACK, BACK_RIGHT)) ); - for (Route route : directions) { - List locations = route.apply(from); - if (locations.getLast().equals(to)) { - return locations; - } - } - - throw new IllegalArgumentException("마는 해당 위치에 도달할 수 없습니다."); + return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); } @Override diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 917379c7fb..6e49be0ceb 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -6,7 +6,7 @@ public abstract class Piece { - private final PieceType pieceType; + protected final PieceType pieceType; protected final Side side; protected Piece(PieceType pieceType, Side side) { @@ -31,6 +31,6 @@ public boolean isSameSide(Side side) { } public PieceType getPieceType() { - return pieceType; + return this.pieceType; } } diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java index f3d6266880..dd0fadd724 100644 --- a/src/main/java/janggi/domain/piece/Po.java +++ b/src/main/java/janggi/domain/piece/Po.java @@ -19,11 +19,7 @@ public Po(Side side) { @Override public List calculateRoute(Location from, Location to) { - try { - return ROUTE_PROVIDER.calculateRoute(from, to); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("포는 해당 위치에 도달할 수 없습니다."); - } + return ROUTE_PROVIDER.calculateRoute(pieceType, from, to); } @Override diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index c232315afc..964853626e 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -19,11 +19,7 @@ public Sa(Side side) { @Override public List calculateRoute(Location from, Location to) { - try { - return ROUTE_PROVIDER.calculateRoute(from, to); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("사는 해당 위치에 도달할 수 없습니다."); - } + return ROUTE_PROVIDER.calculateRoute(pieceType, from, to); } @Override diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index 12057e6dd8..37dd1b0ac0 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -14,6 +14,7 @@ import janggi.domain.rule.collision.CollisionDetector; import janggi.domain.rule.collision.DefaultCollisionDetector; import janggi.domain.rule.route.Route; +import janggi.domain.rule.route.RouteProvider; import java.util.List; public class Sang extends Piece { @@ -26,25 +27,18 @@ public Sang(Side side) { @Override public List calculateRoute(Location from, Location to) { - List directions = List.of( - Route.of(List.of(FRONT, FRONT_LEFT, FRONT_LEFT)), - Route.of(List.of(FRONT, FRONT_RIGHT, FRONT_RIGHT)), - Route.of(List.of(RIGHT, FRONT_RIGHT, FRONT_RIGHT)), - Route.of(List.of(RIGHT, BACK_RIGHT, BACK_RIGHT)), - Route.of(List.of(LEFT, FRONT_LEFT, FRONT_LEFT)), - Route.of(List.of(LEFT, BACK_LEFT, BACK_LEFT)), - Route.of(List.of(BACK, BACK_LEFT, BACK_LEFT)), - Route.of(List.of(BACK, BACK_RIGHT, BACK_RIGHT)) + List possibleRoutes = List.of( + Route.from(List.of(FRONT, FRONT_LEFT, FRONT_LEFT)), + Route.from(List.of(FRONT, FRONT_RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, FRONT_RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, BACK_RIGHT, BACK_RIGHT)), + Route.from(List.of(LEFT, FRONT_LEFT, FRONT_LEFT)), + Route.from(List.of(LEFT, BACK_LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_RIGHT, BACK_RIGHT)) ); - for (Route route : directions) { - List locations = route.apply(from); - if (locations.getLast().equals(to)) { - return locations; - } - } - - throw new IllegalArgumentException("상은 해당 위치에 도달할 수 없습니다."); + return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); } @Override diff --git a/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java b/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java index 71be37e0fe..dda6495c28 100644 --- a/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java +++ b/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java @@ -8,7 +8,8 @@ public class DefaultCollisionDetector implements CollisionDetector { private static final DefaultCollisionDetector INSTANCE = new DefaultCollisionDetector(); - private DefaultCollisionDetector() {} + private DefaultCollisionDetector() { + } public static DefaultCollisionDetector getInstance() { return INSTANCE; @@ -23,9 +24,13 @@ public void check(Piece piece, List piecesOnPath) { private void validateMiddlePath(List piecesOnPath) { List middlePath = piecesOnPath.subList(0, piecesOnPath.size() - 1); for (Piece pathPiece : middlePath) { - if (!pathPiece.isEmpty()) { - throw new IllegalArgumentException("이동 경로에 기물이 존재합니다"); - } + validateNoObstacle(pathPiece); + } + } + + private void validateNoObstacle(Piece pathPiece) { + if (!pathPiece.isEmpty()) { + throw new IllegalArgumentException("이동 경로에 기물이 존재합니다"); } } diff --git a/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java b/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java index 6b2c12d858..b36ddd9274 100644 --- a/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java +++ b/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java @@ -9,7 +9,8 @@ public class PoCollisionDetector implements CollisionDetector { private static final PoCollisionDetector INSTANCE = new PoCollisionDetector(); private static final int REQUIRED_SCREEN_COUNT = 1; - private PoCollisionDetector() {} + private PoCollisionDetector() { + } public static PoCollisionDetector getInstance() { return INSTANCE; @@ -36,9 +37,13 @@ private void validateOneObstacle(List piecesOnPath) { private void validatePoExistence(Piece self, List piecesOnPath) { for (Piece piece : piecesOnPath) { - if (self.getClass() == piece.getClass()) { - throw new IllegalArgumentException("이동 경로 또는 도착지에 포가 존재할 수 없습니다."); - } + validateNonePo(self, piece); + } + } + + private void validateNonePo(Piece self, Piece piece) { + if (self.getClass() == piece.getClass()) { + throw new IllegalArgumentException("이동 경로 또는 도착지에 포가 존재할 수 없습니다."); } } diff --git a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java index 97e7af092e..63a681f563 100644 --- a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java @@ -10,6 +10,7 @@ import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; +import janggi.domain.piece.PieceType; import java.util.List; @SuppressWarnings("java:S6548") @@ -17,32 +18,26 @@ public class GungSeongRouteProvider implements RouteProvider { private static final GungSeongRouteProvider INSTANCE = new GungSeongRouteProvider(); - private GungSeongRouteProvider() {} + private GungSeongRouteProvider() { + } public static GungSeongRouteProvider getInstance() { return INSTANCE; } @Override - public List calculateRoute(Location from, Location to) { - List directions = List.of( - Route.of(List.of(FRONT)), - Route.of(List.of(LEFT)), - Route.of(List.of(RIGHT)), - Route.of(List.of(BACK)), - Route.of(List.of(FRONT_LEFT)), - Route.of(List.of(FRONT_RIGHT)), - Route.of(List.of(BACK_LEFT)), - Route.of(List.of(BACK_RIGHT)) + public List calculateRoute(PieceType pieceType, Location from, Location to) { + List possibleRoutes = List.of( + Route.from(List.of(FRONT)), + Route.from(List.of(LEFT)), + Route.from(List.of(RIGHT)), + Route.from(List.of(BACK)), + Route.from(List.of(FRONT_LEFT)), + Route.from(List.of(FRONT_RIGHT)), + Route.from(List.of(BACK_LEFT)), + Route.from(List.of(BACK_RIGHT)) ); - for (Route route : directions) { - List locations = route.apply(from); - if (locations.getLast().equals(to)) { - return locations; - } - } - - throw new IllegalArgumentException("해당 위치에 도달할 수 없습니다."); + return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); } } diff --git a/src/main/java/janggi/domain/rule/route/Route.java b/src/main/java/janggi/domain/rule/route/Route.java index 66cd885396..e969ebc86a 100644 --- a/src/main/java/janggi/domain/rule/route/Route.java +++ b/src/main/java/janggi/domain/rule/route/Route.java @@ -12,25 +12,25 @@ private Route(List directions) { this.directions = directions; } - public static Route of(List directions) { + public static Route from(List directions) { return new Route(directions); } - public static Route create(Direction direction, int count) { - List result = new ArrayList<>(); + public static Route of(Direction direction, int count) { + List way = new ArrayList<>(); for (int i = 0; i < count; i++) { - result.add(direction); + way.add(direction); } - return new Route(result); + return new Route(way); } - public List apply(Location current) { - List result = new ArrayList<>(); + public List calculateLocationsOnPath(Location current) { + List locationsOnPath = new ArrayList<>(); for (Direction direction : directions) { current = direction.apply(current); - result.add(current); + locationsOnPath.add(current); } - return result; + return locationsOnPath; } } diff --git a/src/main/java/janggi/domain/rule/route/RouteProvider.java b/src/main/java/janggi/domain/rule/route/RouteProvider.java index 32fd405a69..8808817008 100644 --- a/src/main/java/janggi/domain/rule/route/RouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/RouteProvider.java @@ -1,9 +1,24 @@ package janggi.domain.rule.route; import janggi.domain.Location; +import janggi.domain.piece.PieceType; import java.util.List; public interface RouteProvider { - List calculateRoute(Location from, Location to); + static List findValidPath(PieceType pieceType, Location from, Location to, List possibleRoutes) { + for (Route route : possibleRoutes) { + List locationsOnPath = route.calculateLocationsOnPath(from); + Location expectedDestination = locationsOnPath.getLast(); + if (expectedDestination.equals(to)) { + return locationsOnPath; + } + } + + throw new IllegalArgumentException( + String.format("%s은(는) 해당 위치(%s)에 도달할 수 없습니다.", pieceType.getNameFormat(), to) + ); + } + + List calculateRoute(PieceType pieceType, Location from, Location to); } diff --git a/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java index 9835c5e848..f9bbed0a25 100644 --- a/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java @@ -6,6 +6,7 @@ import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; +import janggi.domain.piece.PieceType; import java.util.List; @SuppressWarnings("java:S6548") @@ -13,31 +14,25 @@ public class StraightRouteProvider implements RouteProvider { private static final StraightRouteProvider INSTANCE = new StraightRouteProvider(); - private StraightRouteProvider() {} + private StraightRouteProvider() { + } public static StraightRouteProvider getInstance() { return INSTANCE; } @Override - public List calculateRoute(Location from, Location to) { + public List calculateRoute(PieceType pieceType, Location from, Location to) { int maxDistance = calculateMaxDistance(from, to); - List directions = List.of( - Route.create(FRONT, maxDistance), - Route.create(BACK, maxDistance), - Route.create(LEFT, maxDistance), - Route.create(RIGHT, maxDistance) + List possibleRoutes = List.of( + Route.of(FRONT, maxDistance), + Route.of(BACK, maxDistance), + Route.of(LEFT, maxDistance), + Route.of(RIGHT, maxDistance) ); - for (Route route : directions) { - List locations = route.apply(from); - if (locations.contains(to)) { - return locations; - } - } - - throw new IllegalArgumentException("해당 위치에 도달할 수 없습니다."); + return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); } private int calculateMaxDistance(Location from, Location to) { diff --git a/src/main/java/janggi/strategy/MaSangMaSang.java b/src/main/java/janggi/strategy/MaSangMaSang.java index 7052a0f68d..2b40788a2c 100644 --- a/src/main/java/janggi/strategy/MaSangMaSang.java +++ b/src/main/java/janggi/strategy/MaSangMaSang.java @@ -11,7 +11,7 @@ public class MaSangMaSang extends ArrangementStrategy { private static final MaSangMaSang INSTANCE = new MaSangMaSang(); private MaSangMaSang() { - super(StrategyLabel.HEHE); + super(StrategyLabel.MSMS); } public static MaSangMaSang getInstance() { diff --git a/src/main/java/janggi/strategy/MaSangSangMa.java b/src/main/java/janggi/strategy/MaSangSangMa.java index 700b77cec5..127e862361 100644 --- a/src/main/java/janggi/strategy/MaSangSangMa.java +++ b/src/main/java/janggi/strategy/MaSangSangMa.java @@ -11,7 +11,7 @@ public class MaSangSangMa extends ArrangementStrategy { private static final MaSangSangMa INSTANCE = new MaSangSangMa(); private MaSangSangMa() { - super(StrategyLabel.HEEH); + super(StrategyLabel.MSSM); } public static MaSangSangMa getInstance() { diff --git a/src/main/java/janggi/strategy/SangMaMaSang.java b/src/main/java/janggi/strategy/SangMaMaSang.java index a8df69583e..f5da43b3d7 100644 --- a/src/main/java/janggi/strategy/SangMaMaSang.java +++ b/src/main/java/janggi/strategy/SangMaMaSang.java @@ -11,7 +11,7 @@ public class SangMaMaSang extends ArrangementStrategy { private static final SangMaMaSang INSTANCE = new SangMaMaSang(); private SangMaMaSang() { - super(StrategyLabel.EHHE); + super(StrategyLabel.SMMS); } public static SangMaMaSang getInstance() { diff --git a/src/main/java/janggi/strategy/SangMaSangMa.java b/src/main/java/janggi/strategy/SangMaSangMa.java index ec2b16b307..e2bf645555 100644 --- a/src/main/java/janggi/strategy/SangMaSangMa.java +++ b/src/main/java/janggi/strategy/SangMaSangMa.java @@ -11,7 +11,7 @@ public class SangMaSangMa extends ArrangementStrategy { private static final SangMaSangMa INSTANCE = new SangMaSangMa(); private SangMaSangMa() { - super(StrategyLabel.EHEH); + super(StrategyLabel.SMSM); } public static SangMaSangMa getInstance() { diff --git a/src/main/java/janggi/strategy/StrategyLabel.java b/src/main/java/janggi/strategy/StrategyLabel.java index 81e42c351c..27c14e17a9 100644 --- a/src/main/java/janggi/strategy/StrategyLabel.java +++ b/src/main/java/janggi/strategy/StrategyLabel.java @@ -2,10 +2,10 @@ public enum StrategyLabel { - HEHE("마상마상", 1), - HEEH("마상상마", 2), - EHHE("상마마상", 3), - EHEH("상마상마", 4); + MSMS("마상마상", 1), + MSSM("마상상마", 2), + SMMS("상마마상", 3), + SMSM("상마상마", 4); private final String name; private final int decisionNumber; diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 59832e711c..6cffe560b2 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -154,11 +154,12 @@ void shouldNotThrowExceptionWhenPieceDoesNotExistsAtLocation() { BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); - Location location = Location.from(List.of(1, 1)); + Location from = new Location(0, 0); + Location to = new Location(1, 1); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> board.validateLocationToMove(Side.HAN, location)); + .isThrownBy(() -> board.validateLocationToMove(from, to, Side.HAN)); } @Test @@ -174,11 +175,12 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); - Location location = Location.from(List.of(1, 1)); + Location from = new Location(0, 0); + Location to = new Location(1, 1); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> board.validateLocationToMove(currentSide, location)); + .isThrownBy(() -> board.validateLocationToMove(from, to, currentSide)); } @Test @@ -193,10 +195,11 @@ void shouldNotThrowExceptionWhenPieceOnSameSideExistsAtLocation() { BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); - Location location = Location.from(List.of(1, 1)); + Location from = new Location(0, 0); + Location to = new Location(1, 1); // when & then - Assertions.assertThatThrownBy(() -> board.validateLocationToMove(currentSide, location)) + Assertions.assertThatThrownBy(() -> board.validateLocationToMove(from, to, currentSide)) .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java index 5825f8efb3..0156bb59ed 100644 --- a/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java @@ -1,6 +1,7 @@ package janggi.domain.rule.route; import janggi.domain.Location; +import janggi.domain.piece.PieceType; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -20,7 +21,7 @@ void shouldReturnRouteForReachableLocation(List destination) { Location to = Location.from(destination); // when & then - Assertions.assertThat(GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(from, to)) + Assertions.assertThat(GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(PieceType.GUNG, from, to)) .isEqualTo(List.of(to)); } @@ -44,7 +45,7 @@ void shouldThrowExceptionForUnReachableLocation(List coordination) { Location to = Location.from(coordination); // when & then - Assertions.assertThatThrownBy(() -> GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(from, to)) + Assertions.assertThatThrownBy(() -> GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(PieceType.GUNG, from, to)) .isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/janggi/domain/rule/route/RouteProviderTest.java b/src/test/java/janggi/domain/rule/route/RouteProviderTest.java new file mode 100644 index 0000000000..9507acd247 --- /dev/null +++ b/src/test/java/janggi/domain/rule/route/RouteProviderTest.java @@ -0,0 +1,44 @@ +package janggi.domain.rule.route; + +import janggi.domain.Location; +import janggi.domain.piece.PieceType; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RouteProviderTest { + + @Test + @DisplayName("시작 위치에서 도착 위치까지 도달 가능하다면, 그 사이 경로 좌표를 반환한다.") + void shouldReturnLocationsWhenValidPathIsFound() { + // given + Location from = new Location(1,1); + Location to = new Location(1,5); + List possibleRoutes = List.of(Route.of(Direction.FRONT, 4)); + + // when + List locationsOfValidPath = RouteProvider.findValidPath(PieceType.CHA, from, to, possibleRoutes); + + // then + Assertions.assertThat(locationsOfValidPath).containsExactly( + new Location(1,2), + new Location(1,3), + new Location(1,4), + new Location(1,5) + ); + } + + @Test + @DisplayName("시작 위치에서 도착 위치까지 도달 불가능한 경우 예외를 발생시킨다.") + void shouldThrowExceptionWhenValidPathIsNotFound() { + // given + Location from = new Location(1,1); + Location to = new Location(5,1); + List possibleRoutes = List.of(Route.of(Direction.FRONT, 4)); + + // when & then + Assertions.assertThatThrownBy(() -> RouteProvider.findValidPath(PieceType.CHA, from, to, possibleRoutes)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/janggi/domain/rule/route/RouteTest.java b/src/test/java/janggi/domain/rule/route/RouteTest.java index afea2a27ca..ab3679742e 100644 --- a/src/test/java/janggi/domain/rule/route/RouteTest.java +++ b/src/test/java/janggi/domain/rule/route/RouteTest.java @@ -13,7 +13,7 @@ class RouteTest { void shouldReturnRouteLocationsCalculatedByCurrentLocation() { // given Location current = Location.from(List.of(0,0)); - Route route = Route.of( + Route route = Route.from( List.of( Direction.FRONT, // 0,1 Direction.FRONT_LEFT, // -1, 2 @@ -29,7 +29,7 @@ void shouldReturnRouteLocationsCalculatedByCurrentLocation() { ); // when - List routeLocations = route.apply(current); + List routeLocations = route.calculateLocationsOnPath(current); // then Assertions.assertThat(routeLocations).isEqualTo(expected); diff --git a/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java index 19c281a968..3abc60f0ea 100644 --- a/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java @@ -1,6 +1,7 @@ package janggi.domain.rule.route; import janggi.domain.Location; +import janggi.domain.piece.PieceType; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.Assertions; @@ -21,7 +22,7 @@ void shouldReturnRouteForReachableLocation(Location destination, List Location from = Location.from(List.of(2, 2)); // when & then - Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, destination)) + Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(PieceType.CHA, from, destination)) .isEqualTo(route); } @@ -55,7 +56,7 @@ void shouldThrowExceptionForUnReachableLocation(List coordination) { Location to = Location.from(coordination); // when & then - Assertions.assertThatThrownBy(() -> STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, to)) + Assertions.assertThatThrownBy(() -> STRAIGHT_ROUTE_PROVIDER.calculateRoute(PieceType.CHA, from, to)) .isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/janggi/strategy/MaSangMaSangTest.java b/src/test/java/janggi/strategy/MaSangMaSangTest.java index a6387adced..65f4e97dad 100644 --- a/src/test/java/janggi/strategy/MaSangMaSangTest.java +++ b/src/test/java/janggi/strategy/MaSangMaSangTest.java @@ -1,11 +1,10 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; -import janggi.domain.Side; -import janggi.support.TestPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,7 +19,6 @@ void shouldPlaceMaSangMaSangWhenSideHan() { // given Piece[][] grid = new Piece[10][9]; Side side = Side.HAN; - Piece expectedSidePiece = new TestPiece(side); // when STRATEGY.place(grid, side); @@ -35,10 +33,10 @@ void shouldPlaceMaSangMaSangWhenSideHan() { Assertions.assertThat(rightMa).isInstanceOf(Ma.class); Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); } @Test @@ -47,7 +45,6 @@ void shouldPlaceMaSangMaSangWhenSideCho() { // given Piece[][] grid = new Piece[10][9]; Side side = Side.CHO; - Piece expectedSidePiece = new TestPiece(side); // when STRATEGY.place(grid, side); @@ -62,9 +59,9 @@ void shouldPlaceMaSangMaSangWhenSideCho() { Assertions.assertThat(rightMa).isInstanceOf(Ma.class); Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); } } diff --git a/src/test/java/janggi/strategy/MaSangSangMaTest.java b/src/test/java/janggi/strategy/MaSangSangMaTest.java index eb840e3aab..35bc543c2f 100644 --- a/src/test/java/janggi/strategy/MaSangSangMaTest.java +++ b/src/test/java/janggi/strategy/MaSangSangMaTest.java @@ -1,11 +1,10 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; -import janggi.domain.Side; -import janggi.support.TestPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,7 +19,6 @@ void shouldPlaceMaSangSangMaWhenSideHan() { // given Piece[][] grid = new Piece[10][9]; Side side = Side.HAN; - Piece expectedSidePiece = new TestPiece(side); // when STRATEGY.place(grid, side); @@ -35,10 +33,10 @@ void shouldPlaceMaSangSangMaWhenSideHan() { Assertions.assertThat(rightSang).isInstanceOf(Sang.class); Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); } @Test @@ -47,7 +45,6 @@ void shouldPlaceMaSangSangMaWhenSideCho() { // given Piece[][] grid = new Piece[10][9]; Side side = Side.CHO; - Piece expectedSidePiece = new TestPiece(side); // when STRATEGY.place(grid, side); @@ -62,9 +59,9 @@ void shouldPlaceMaSangSangMaWhenSideCho() { Assertions.assertThat(rightSang).isInstanceOf(Sang.class); Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); } } diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java index 369998ee1e..f17cc52276 100644 --- a/src/test/java/janggi/strategy/SangMaMaSangTest.java +++ b/src/test/java/janggi/strategy/SangMaMaSangTest.java @@ -1,11 +1,10 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; -import janggi.domain.Side; -import janggi.support.TestPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,7 +19,6 @@ void shouldPlaceSangMaMaSangWhenSideHan() { // given Piece[][] grid = new Piece[10][9]; Side side = Side.HAN; - Piece expectedSidePiece = new TestPiece(side); // when STRATEGY.place(grid, side); @@ -35,10 +33,10 @@ void shouldPlaceSangMaMaSangWhenSideHan() { Assertions.assertThat(rightMa).isInstanceOf(Ma.class); Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); } @Test @@ -47,7 +45,6 @@ void shouldPlaceSangMaMaSangWhenSideCho() { // given Piece[][] grid = new Piece[10][9]; Side side = Side.CHO; - Piece expectedSidePiece = new TestPiece(side); // when STRATEGY.place(grid, side); @@ -62,9 +59,9 @@ void shouldPlaceSangMaMaSangWhenSideCho() { Assertions.assertThat(rightMa).isInstanceOf(Ma.class); Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); } } diff --git a/src/test/java/janggi/strategy/SangMaSangMaTest.java b/src/test/java/janggi/strategy/SangMaSangMaTest.java index e8c78b66a8..c3317e0db7 100644 --- a/src/test/java/janggi/strategy/SangMaSangMaTest.java +++ b/src/test/java/janggi/strategy/SangMaSangMaTest.java @@ -1,11 +1,10 @@ package janggi.strategy; +import janggi.domain.Side; import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; import janggi.domain.piece.Sang; -import janggi.domain.Side; -import janggi.support.TestPiece; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,7 +19,6 @@ void shouldPlaceSangMaSangMaWhenSideHan() { // given Piece[][] grid = new Piece[10][9]; Side side = Side.HAN; - Piece expectedSidePiece = new TestPiece(side); // when STRATEGY.place(grid, side); @@ -35,10 +33,10 @@ void shouldPlaceSangMaSangMaWhenSideHan() { Assertions.assertThat(rightSang).isInstanceOf(Sang.class); Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); } @Test @@ -47,7 +45,6 @@ void shouldPlaceSangMaSangMaWhenSideCho() { // given Piece[][] grid = new Piece[10][9]; Side side = Side.CHO; - Piece expectedSidePiece = new TestPiece(side); // when STRATEGY.place(grid, side); @@ -62,9 +59,9 @@ void shouldPlaceSangMaSangMaWhenSideCho() { Assertions.assertThat(rightSang).isInstanceOf(Sang.class); Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(expectedSidePiece)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(expectedSidePiece)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); } } diff --git a/src/test/java/janggi/support/TestArrangementStrategy.java b/src/test/java/janggi/support/TestArrangementStrategy.java index 1c18f34099..443966b445 100644 --- a/src/test/java/janggi/support/TestArrangementStrategy.java +++ b/src/test/java/janggi/support/TestArrangementStrategy.java @@ -17,7 +17,7 @@ public TestArrangementStrategy(Map customPieces) { } public TestArrangementStrategy(Map customPieces, boolean shouldClear) { - super(StrategyLabel.HEHE); + super(StrategyLabel.MSMS); this.customPieces = customPieces; this.shouldClear = shouldClear; } From c8dc4e153bd722cf3847e75d08ede14ee925da79 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 29 Mar 2026 01:01:19 +0900 Subject: [PATCH 33/45] =?UTF-8?q?refactor(strategy):=20=EA=B8=B0=EB=AC=BC?= =?UTF-8?q?=20=EB=B0=B0=EC=B9=98=20=EC=B1=85=EC=9E=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ArrangementStrategy: 기본 기물(차, 포, 궁 등)을 배치하는 공통 로직을 부모의 public 메서드(place)로 묶고, BoardAssembler에 위치해있던 기물 생성 내부 클래스(DefaultPieceFactory)를 가져옴 - BoardAssembler: 기물의 구체적인 위치 지식을 제거하고, 각 진영의 전략 객체에 보드판 조립을 위임하도록 책임 축소 - 하위 전략 클래스는 placeVariablePieces()만 구현해 변동 기물(마, 상) 배치만 담당하도록 구조 개선 - 변경된 책임에 맞게 BoardAssemblerTest는 빈칸 채우기만 검증하도록 하고, ArrangementStrategyTest에 기본 기물 배치 검증 로직 추가 --- .../janggi/strategy/ArrangementStrategy.java | 75 +++++++++++++++++-- .../java/janggi/strategy/BoardAssembler.java | 64 ---------------- .../java/janggi/strategy/MaSangMaSang.java | 4 +- .../java/janggi/strategy/MaSangSangMa.java | 4 +- .../java/janggi/strategy/SangMaMaSang.java | 4 +- .../java/janggi/strategy/SangMaSangMa.java | 4 +- .../strategy/ArrangementStrategyTest.java | 75 +++++++++++++++++++ .../janggi/strategy/BoardAssemblerTest.java | 62 +++++---------- .../support/TestArrangementStrategy.java | 22 +----- 9 files changed, 174 insertions(+), 140 deletions(-) create mode 100644 src/test/java/janggi/strategy/ArrangementStrategyTest.java diff --git a/src/main/java/janggi/strategy/ArrangementStrategy.java b/src/main/java/janggi/strategy/ArrangementStrategy.java index a4dd8ab1db..d0d96f2b08 100644 --- a/src/main/java/janggi/strategy/ArrangementStrategy.java +++ b/src/main/java/janggi/strategy/ArrangementStrategy.java @@ -1,7 +1,16 @@ package janggi.strategy; import janggi.domain.Side; +import janggi.domain.piece.Cha; +import janggi.domain.piece.Gung; +import janggi.domain.piece.Jolbyeong; +import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; +import janggi.domain.piece.Po; +import janggi.domain.piece.Sa; +import janggi.domain.piece.Sang; +import java.util.List; +import java.util.function.Function; public abstract class ArrangementStrategy { @@ -11,6 +20,34 @@ protected ArrangementStrategy(StrategyLabel label) { this.label = label; } + public void place(Piece[][] arrangement, Side side) { + placeDefaultPieces(arrangement, side); + placeVariablePieces(arrangement, side); + } + + protected abstract void placeVariablePieces(Piece[][] arrangement, Side side); + + protected int calculateInitialRow(int boardMaxLength, Side side) { + if (side.equals(Side.CHO)) { + return boardMaxLength - 1; + } + return 0; + } + + private void placeDefaultPieces(Piece[][] grid, Side side) { + for (DefaultPieceFactory factory : DefaultPieceFactory.values()) { + setUpPiece(grid, side, factory); + } + } + + private void setUpPiece(Piece[][] grid, Side side, DefaultPieceFactory factory) { + int height = grid.length; + for (int col : factory.getCols()) { + int row = factory.getRow(side, height); + grid[row][col] = factory.createPiece(side); + } + } + public boolean isDecisionNumberMatching(int decisionNumber) { return label.getDecisionNumber() == decisionNumber; } @@ -23,12 +60,38 @@ public int decisionNumber() { return label.getDecisionNumber(); } - protected int calculateRow(int boardMaxLength, Side side) { - if (side.equals(Side.CHO)) { - return boardMaxLength - 1; + private enum DefaultPieceFactory { + CHA(0, List.of(0, 8), Cha::new), + MA(0, List.of(1, 7), Ma::new), + SANG(0, List.of(2, 6), Sang::new), + SA(0, List.of(3, 5), Sa::new), + GUNG(1, List.of(4), Gung::new), + PO(2, List.of(1, 7), Po::new), + JOL(3, List.of(0, 2, 4, 6, 8), Jolbyeong::new); + + private final int row; + private final List cols; + private final Function pieceClass; + + DefaultPieceFactory(int row, List cols, Function pieceClass) { + this.row = row; + this.cols = cols; + this.pieceClass = pieceClass; } - return 0; - } - public abstract void place(Piece[][] arrangement, Side side); + private Piece createPiece(Side side) { + return pieceClass.apply(side); + } + + private int getRow(Side side, int height) { + if (side.equals(Side.CHO)) { + return height - row - 1; + } + return row; + } + + private List getCols() { + return cols; + } + } } diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java index 65e0022085..7d2549e9ea 100644 --- a/src/main/java/janggi/strategy/BoardAssembler.java +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -1,17 +1,8 @@ package janggi.strategy; import janggi.domain.Side; -import janggi.domain.piece.Cha; import janggi.domain.piece.EmptyPiece; -import janggi.domain.piece.Gung; -import janggi.domain.piece.Jolbyeong; -import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; -import janggi.domain.piece.Po; -import janggi.domain.piece.Sa; -import janggi.domain.piece.Sang; -import java.util.List; -import java.util.function.Function; public class BoardAssembler { @@ -33,8 +24,6 @@ public static BoardAssembler of(ArrangementStrategy hanStrategy, ArrangementStra public Piece[][] assemble() { Piece[][] arrangement = new Piece[DEFAULT_ROWS][DEFAULT_COLS]; - setupCommonPieces(arrangement); - hanStrategy.place(arrangement, Side.HAN); choStrategy.place(arrangement, Side.CHO); @@ -43,11 +32,6 @@ public Piece[][] assemble() { return arrangement; } - private void setupCommonPieces(Piece[][] grid) { - setUpOneSide(grid, Side.HAN); - setUpOneSide(grid, Side.CHO); - } - private void setupEmptyPieces(Piece[][] arrangement) { for (int row = 0; row < DEFAULT_ROWS; row++) { for (int col = 0; col < DEFAULT_COLS; col++) { @@ -57,52 +41,4 @@ private void setupEmptyPieces(Piece[][] arrangement) { } } } - - private void setUpOneSide(Piece[][] grid, Side side) { - for (DefaultPieceFactory factory : DefaultPieceFactory.values()) { - setUpPiece(grid, side, factory); - } - } - - private void setUpPiece(Piece[][] grid, Side side, DefaultPieceFactory factory) { - for (int col : factory.getCols()) { - int row = factory.getRow(side); - grid[row][col] = factory.createPiece(side); - } - } - - private enum DefaultPieceFactory { - CHA(0, List.of(0, 8), Cha::new), - MA(0, List.of(1, 7), Ma::new), - SANG(0, List.of(2, 6), Sang::new), - SA(0, List.of(3, 5), Sa::new), - GUNG(1, List.of(4), Gung::new), - PO(2, List.of(1, 7), Po::new), - JOL(3, List.of(0, 2, 4, 6, 8), Jolbyeong::new); - - private final int row; - private final List cols; - private final Function pieceClass; - - DefaultPieceFactory(int row, List cols, Function pieceClass) { - this.row = row; - this.cols = cols; - this.pieceClass = pieceClass; - } - - public Piece createPiece(Side side) { - return pieceClass.apply(side); - } - - public int getRow(Side side) { - if (side.equals(Side.CHO)) { - return DEFAULT_ROWS - row - 1; - } - return row; - } - - public List getCols() { - return cols; - } - } } diff --git a/src/main/java/janggi/strategy/MaSangMaSang.java b/src/main/java/janggi/strategy/MaSangMaSang.java index 2b40788a2c..9066efd022 100644 --- a/src/main/java/janggi/strategy/MaSangMaSang.java +++ b/src/main/java/janggi/strategy/MaSangMaSang.java @@ -19,9 +19,9 @@ public static MaSangMaSang getInstance() { } @Override - public void place(Piece[][] board, Side side) { + protected void placeVariablePieces(Piece[][] board, Side side) { int boardMaxLength = board.length; - int row = calculateRow(boardMaxLength, side); + int row = calculateInitialRow(boardMaxLength, side); board[row][1] = new Ma(side); board[row][2] = new Sang(side); board[row][6] = new Ma(side); diff --git a/src/main/java/janggi/strategy/MaSangSangMa.java b/src/main/java/janggi/strategy/MaSangSangMa.java index 127e862361..55f71c3bda 100644 --- a/src/main/java/janggi/strategy/MaSangSangMa.java +++ b/src/main/java/janggi/strategy/MaSangSangMa.java @@ -19,9 +19,9 @@ public static MaSangSangMa getInstance() { } @Override - public void place(Piece[][] board, Side side) { + protected void placeVariablePieces(Piece[][] board, Side side) { int boardMaxLength = board.length; - int row = calculateRow(boardMaxLength, side); + int row = calculateInitialRow(boardMaxLength, side); board[row][1] = new Ma(side); board[row][2] = new Sang(side); board[row][6] = new Sang(side); diff --git a/src/main/java/janggi/strategy/SangMaMaSang.java b/src/main/java/janggi/strategy/SangMaMaSang.java index f5da43b3d7..b9770e48c8 100644 --- a/src/main/java/janggi/strategy/SangMaMaSang.java +++ b/src/main/java/janggi/strategy/SangMaMaSang.java @@ -19,9 +19,9 @@ public static SangMaMaSang getInstance() { } @Override - public void place(Piece[][] board, Side side) { + protected void placeVariablePieces(Piece[][] board, Side side) { int boardMaxLength = board.length; - int row = calculateRow(boardMaxLength, side); + int row = calculateInitialRow(boardMaxLength, side); board[row][1] = new Sang(side); board[row][2] = new Ma(side); board[row][6] = new Ma(side); diff --git a/src/main/java/janggi/strategy/SangMaSangMa.java b/src/main/java/janggi/strategy/SangMaSangMa.java index e2bf645555..6601de8995 100644 --- a/src/main/java/janggi/strategy/SangMaSangMa.java +++ b/src/main/java/janggi/strategy/SangMaSangMa.java @@ -19,9 +19,9 @@ public static SangMaSangMa getInstance() { } @Override - public void place(Piece[][] board, Side side) { + protected void placeVariablePieces(Piece[][] board, Side side) { int boardMaxLength = board.length; - int row = calculateRow(boardMaxLength, side); + int row = calculateInitialRow(boardMaxLength, side); board[row][1] = new Sang(side); board[row][2] = new Ma(side); board[row][6] = new Sang(side); diff --git a/src/test/java/janggi/strategy/ArrangementStrategyTest.java b/src/test/java/janggi/strategy/ArrangementStrategyTest.java new file mode 100644 index 0000000000..bb576d3580 --- /dev/null +++ b/src/test/java/janggi/strategy/ArrangementStrategyTest.java @@ -0,0 +1,75 @@ +package janggi.strategy; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Side; +import janggi.domain.piece.Cha; +import janggi.domain.piece.Gung; +import janggi.domain.piece.Jolbyeong; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Po; +import janggi.domain.piece.Sa; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ArrangementStrategyTest { + + private final ArrangementStrategy dummyStrategy = new ArrangementStrategy(StrategyLabel.MSMS) { + @Override + protected void placeVariablePieces(Piece[][] arrangement, Side side) { + return; + } + }; + + @Test + @DisplayName("전략의 place를 호출하면 차, 포, 궁, 사, 졸 등 '기본 기물'이 올바른 위치에 배치된다.") + void shouldPlaceDefaultPieces() { + // given + Piece[][] board = new Piece[10][9]; + Side han = Side.HAN; + Side cho = Side.CHO; + + // when + dummyStrategy.place(board, han); + dummyStrategy.place(board, cho); + + // then: 한팀 + Piece chaOfHan = board[0][0]; + assertThat(chaOfHan).isInstanceOf(Cha.class); + assertThat(chaOfHan.isSameSide(han)).isTrue(); + + Piece saOfHan = board[0][3]; + assertThat(saOfHan).isInstanceOf(Sa.class); + assertThat(saOfHan.isSameSide(han)).isTrue(); + + Piece gungOfHan = board[1][4]; + assertThat(gungOfHan).isInstanceOf(Gung.class); + assertThat(gungOfHan.isSameSide(han)).isTrue(); + + Piece poOfHan = board[2][1]; + assertThat(poOfHan).isInstanceOf(Po.class); + assertThat(poOfHan.isSameSide(han)).isTrue(); + + Piece jolbyeongOfHan = board[3][0]; + assertThat(jolbyeongOfHan).isInstanceOf(Jolbyeong.class); + assertThat(jolbyeongOfHan.isSameSide(han)).isTrue(); + + + // then: 초팀 + Piece chaOfCho = board[9][8]; + assertThat(chaOfCho).isInstanceOf(Cha.class); + assertThat(chaOfCho.isSameSide(cho)).isTrue(); + + Piece gungOfCho = board[8][4]; + assertThat(gungOfCho).isInstanceOf(Gung.class); + assertThat(gungOfCho.isSameSide(cho)).isTrue(); + + Piece poOfCho = board[7][7]; + assertThat(poOfCho).isInstanceOf(Po.class); + assertThat(poOfCho.isSameSide(cho)).isTrue(); + + Piece jolbyeongOfCho = board[6][8]; + assertThat(jolbyeongOfCho).isInstanceOf(Jolbyeong.class); + assertThat(jolbyeongOfCho.isSameSide(cho)).isTrue(); + } +} diff --git a/src/test/java/janggi/strategy/BoardAssemblerTest.java b/src/test/java/janggi/strategy/BoardAssemblerTest.java index 0c7073d44a..3289963aa9 100644 --- a/src/test/java/janggi/strategy/BoardAssemblerTest.java +++ b/src/test/java/janggi/strategy/BoardAssemblerTest.java @@ -1,64 +1,38 @@ package janggi.strategy; -import janggi.domain.Location; +import static org.assertj.core.api.Assertions.assertThat; + import janggi.domain.Side; -import janggi.domain.piece.Cha; import janggi.domain.piece.EmptyPiece; -import janggi.domain.piece.Jolbyeong; import janggi.domain.piece.Piece; -import janggi.support.TestArrangementStrategy; -import janggi.support.TestPiece; -import java.util.Map; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class BoardAssemblerTest { @Test - @DisplayName("공통 기물과 각 팀의 전략이 합쳐져 전체 보드를 생성한다.") - void shouldAssembleFullBoard() { + @DisplayName("조립기는 전략을 실행한 후, 기물이 없는 나머지 빈 칸을 EmptyPiece로 채운다.") + void shouldFillEmptySpaces() { // given - Side han = Side.HAN; - Map customPiecesOfHan = Map.of( - new Location(4, 0), new TestPiece(han) - ); - ArrangementStrategy hanStrategy = new TestArrangementStrategy(customPiecesOfHan, false); + ArrangementStrategy doNothingStrategy = new ArrangementStrategy(StrategyLabel.MSMS) { + @Override + public void place(Piece[][] arrangement, Side side) { + return; + } + + @Override + protected void placeVariablePieces(Piece[][] arrangement, Side side) { + return; + } + }; - Side cho = Side.CHO; - Map customPiecesOfCho = Map.of( - new Location(5, 0), new TestPiece(cho) - ); - ArrangementStrategy choStrategy = new TestArrangementStrategy(customPiecesOfCho, false); - BoardAssembler assembler = BoardAssembler.of(hanStrategy, choStrategy); + BoardAssembler assembler = BoardAssembler.of(doNothingStrategy, doNothingStrategy); // when Piece[][] board = assembler.assemble(); // then - // 1. 공통 기물 검증 - Piece chaOfHan = board[0][0]; - Assertions.assertThat(chaOfHan).isInstanceOf(Cha.class); - Assertions.assertThat(chaOfHan.isSameSide(han)).isTrue(); - - Piece chaOfCho = board[9][8]; - Assertions.assertThat(chaOfCho).isInstanceOf(Cha.class); - Assertions.assertThat(chaOfCho.isSameSide(cho)).isTrue(); - - Piece jolbyeongOfHan = board[3][0]; - Assertions.assertThat(jolbyeongOfHan).isInstanceOf(Jolbyeong.class); - Assertions.assertThat(jolbyeongOfHan.isSameSide(han)).isTrue(); - - // 2. 전략 기물 검증 (전략이 실제로 동작했는지 확인) - Piece testPieceOfHan = board[4][0]; - Assertions.assertThat(testPieceOfHan).isInstanceOf(TestPiece.class); - Assertions.assertThat(testPieceOfHan.isSameSide(han)).isTrue(); - - Piece testPieceOfCho = board[5][0]; - Assertions.assertThat(testPieceOfCho).isInstanceOf(TestPiece.class); - Assertions.assertThat(testPieceOfCho.isSameSide(cho)).isTrue(); - - Piece emptyPiece = board[4][5]; - Assertions.assertThat(emptyPiece).isEqualTo(EmptyPiece.getInstance()); + assertThat(board[0][0]).isInstanceOf(EmptyPiece.class); + assertThat(board[5][5]).isInstanceOf(EmptyPiece.class); } } diff --git a/src/test/java/janggi/support/TestArrangementStrategy.java b/src/test/java/janggi/support/TestArrangementStrategy.java index 443966b445..c8802d0fce 100644 --- a/src/test/java/janggi/support/TestArrangementStrategy.java +++ b/src/test/java/janggi/support/TestArrangementStrategy.java @@ -2,7 +2,6 @@ import janggi.domain.Location; import janggi.domain.Side; -import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; import janggi.strategy.ArrangementStrategy; import janggi.strategy.StrategyLabel; @@ -10,34 +9,21 @@ public class TestArrangementStrategy extends ArrangementStrategy { private final Map customPieces; - private final boolean shouldClear; public TestArrangementStrategy(Map customPieces) { - this(customPieces, true); - } - - public TestArrangementStrategy(Map customPieces, boolean shouldClear) { super(StrategyLabel.MSMS); this.customPieces = customPieces; - this.shouldClear = shouldClear; } @Override public void place(Piece[][] arrangement, Side side) { - if (shouldClear) { - clearBoard(arrangement); - } + placeVariablePieces(arrangement, side); + } + @Override + protected void placeVariablePieces(Piece[][] arrangement, Side side) { customPieces.forEach((loc, piece) -> { arrangement[loc.x()][loc.y()] = piece; }); } - - private void clearBoard(Piece[][] arrangement) { - for (int row = 0; row < arrangement.length; row++) { - for (int col = 0; col < arrangement[row].length; col++) { - arrangement[row][col] = EmptyPiece.getInstance(); - } - } - } } From 491cfac3bb9507a71944c560c577030a8070ebdf Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 29 Mar 2026 01:25:29 +0900 Subject: [PATCH 34/45] =?UTF-8?q?feat(controller):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=EC=98=88=EC=99=B8=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EC=8B=9C=20=EC=9E=AC=EC=9E=85=EB=A0=A5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JanggiFlow: 잘못된 입력(전략, 기물 선택, 이동 좌표)으로 예외 발생 시, 에러 메시지를 출력하고 정상 입력될 때까지 다시 요청하는 로직 수정 및 추가 - RouteProvider: 기물 이동 경로 위반 에러 메시지 수정 - ConsoleWriter: 에러 메시지 출력 후, 보드판 출력 전 개행을 추가해 콘솔 화면 가독성 개선 --- .../java/janggi/controller/JanggiFlow.java | 75 ++++++++++++------- .../domain/rule/route/RouteProvider.java | 5 +- .../java/janggi/view/ApplicationView.java | 2 +- src/main/java/janggi/view/ConsoleWriter.java | 2 + 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 1e0818fbaf..48348142bd 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -28,8 +28,8 @@ public JanggiFlow(ApplicationView view) { } public void process() { - ArrangementStrategy hanStrategy = askStrategy(Side.HAN); - ArrangementStrategy choStrategy = askStrategy(Side.CHO); + ArrangementStrategy hanStrategy = repeatAskStrategyUntilSuccess(Side.HAN); + ArrangementStrategy choStrategy = repeatAskStrategyUntilSuccess(Side.CHO); Board board = Board.create(BoardAssembler.of(hanStrategy, choStrategy)); Side current = Side.HAN; @@ -38,27 +38,22 @@ public void process() { view.responseCurrentSide(current); final Side turnSide = current; - retryAction(() -> { - Location from = askLocationOfPiece(turnSide, board); - Location to = askLocationToMove(from, turnSide, board); + retryUntilPieceIsSuccessfullyMoved(() -> { + Location from = repeatAskLocationOfPieceUntilSuccess(turnSide, board); + Location to = repeatAskLocationToMoveUntilSuccess(from, turnSide, board); board.move(from, to); }); current = current.switchSide(); } } - private Location askLocationOfPiece(Side current, Board board) { - List locationOfPiece = view.requestLocationOfPiece(); - Location verifiedLocation = Location.from(locationOfPiece); - board.validateLocationOfPiece(current, verifiedLocation); - return verifiedLocation; - } - - private Location askLocationToMove(Location startingLocation, Side current, Board board) { - List locationToMove = view.requestLocationToMove(); - Location verifiedLocation = Location.from(locationToMove); - board.validateLocationToMove(startingLocation, verifiedLocation, current); - return verifiedLocation; + private ArrangementStrategy repeatAskStrategyUntilSuccess(Side side) { + try { + return askStrategy(side); + } catch (IllegalArgumentException e) { + view.responseErrorMessage(e); + return repeatAskStrategyUntilSuccess(side); + } } private ArrangementStrategy askStrategy(Side side) { @@ -73,14 +68,44 @@ private ArrangementStrategy findStrategyWithCorrespondingDecisionNumber(int deci .orElseThrow(() -> new IllegalArgumentException("일치하는 전략 번호가 없습니다: " + decisionNumber)); } - private void retryAction(Runnable runnable) { - while (true) { - try { - runnable.run(); - return; - } catch (IllegalArgumentException e) { - view.responseErrorMessage(e); - } + private Location repeatAskLocationOfPieceUntilSuccess(Side turnSide, Board board) { + try { + return askLocationOfPiece(turnSide, board); + } catch (IllegalArgumentException e) { + view.responseErrorMessage(e); + return repeatAskLocationOfPieceUntilSuccess(turnSide, board); + } + } + + private Location askLocationOfPiece(Side current, Board board) { + List locationOfPiece = view.requestLocationOfPiece(); + Location verifiedLocation = Location.from(locationOfPiece); + board.validateLocationOfPiece(current, verifiedLocation); + return verifiedLocation; + } + + private Location repeatAskLocationToMoveUntilSuccess(Location from, Side turnSide, Board board) { + try { + return askLocationToMove(from, turnSide, board); + } catch (IllegalArgumentException e) { + view.responseErrorMessage(e); + return repeatAskLocationToMoveUntilSuccess(from, turnSide, board); + } + } + + private Location askLocationToMove(Location startingLocation, Side current, Board board) { + List locationToMove = view.requestLocationToMove(); + Location verifiedLocation = Location.from(locationToMove); + board.validateLocationToMove(startingLocation, verifiedLocation, current); + return verifiedLocation; + } + + private void retryUntilPieceIsSuccessfullyMoved(Runnable runnable) { + try { + runnable.run(); + } catch (IllegalArgumentException e) { + view.responseErrorMessage(e); + retryUntilPieceIsSuccessfullyMoved(runnable); } } } diff --git a/src/main/java/janggi/domain/rule/route/RouteProvider.java b/src/main/java/janggi/domain/rule/route/RouteProvider.java index 8808817008..d1e1c24d99 100644 --- a/src/main/java/janggi/domain/rule/route/RouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/RouteProvider.java @@ -16,7 +16,10 @@ static List findValidPath(PieceType pieceType, Location from, Location } throw new IllegalArgumentException( - String.format("%s은(는) 해당 위치(%s)에 도달할 수 없습니다.", pieceType.getNameFormat(), to) + String.format("%s의 기물 이동 규칙 위반: 해당 위치(%d, %d)에 도달할 수 없습니다.", + pieceType.getNameFormat(), + to.x(), + to.y()) ); } diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java index 9fd18d1ae1..966309bcf6 100644 --- a/src/main/java/janggi/view/ApplicationView.java +++ b/src/main/java/janggi/view/ApplicationView.java @@ -44,7 +44,7 @@ public void responseCurrentSide(Side currentSide) { } public List requestLocationOfPiece() { - outputWriter.printPromptMessage("이동 시킬 기물의 좌표를 입력해주세요. (,로 구분)"); + outputWriter.printPromptMessage("이동시킬 기물의 좌표를 입력해주세요. (,로 구분)"); return retry(inputReader::readIntegers); } diff --git a/src/main/java/janggi/view/ConsoleWriter.java b/src/main/java/janggi/view/ConsoleWriter.java index 4682cfe4be..1e885ee502 100644 --- a/src/main/java/janggi/view/ConsoleWriter.java +++ b/src/main/java/janggi/view/ConsoleWriter.java @@ -12,6 +12,7 @@ public void printPromptMessage(String promptMessage) { @Override public void printErrorMessage(RuntimeException e) { System.out.println("[ERROR] " + e.getMessage()); + System.out.println(); } @Override @@ -24,6 +25,7 @@ public void printStringMatrix(List> matrix) { matrixSnapshot.append("\n"); } + System.out.println(); System.out.println(matrixSnapshot); } } From b31fd469cb93e0b9691ff597424352de2e85315d Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 29 Mar 2026 02:03:23 +0900 Subject: [PATCH 35/45] =?UTF-8?q?refactor:=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=ED=95=B4=EC=86=8C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=98=88=EC=99=B8=20=ED=9D=90=EB=A6=84=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=20=EC=A4=91=EC=95=99=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Application, JanggiFlow: 전략 객체 리스트를 외부에서 주입받도록 변경해 OCP 준수 - JanggiFlow, ApplicationView: 컨트롤러에서 도메인 객체를 순수 데이터로 변환 후 뷰에 전달하도록 수정해 강한 결합도 해소 - JanggiFlow: 뷰와 컨트롤러에 분산되어있던 재입력 로직을 컨트롤러에서 모두 관리하도록 흐름 제어 책임 일원화 --- src/main/java/janggi/Application.java | 14 ++++- .../java/janggi/controller/JanggiFlow.java | 51 ++++++++++++------- src/main/java/janggi/domain/board/Board.java | 10 +--- src/main/java/janggi/domain/piece/Ma.java | 23 ++++----- src/main/java/janggi/domain/piece/Sang.java | 23 ++++----- .../rule/route/GungSeongRouteProvider.java | 23 ++++----- .../java/janggi/view/ApplicationView.java | 44 +++++----------- .../java/janggi/domain/board/BoardTest.java | 25 +-------- 8 files changed, 94 insertions(+), 119 deletions(-) diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java index 45c52d9c1e..6448bfd668 100644 --- a/src/main/java/janggi/Application.java +++ b/src/main/java/janggi/Application.java @@ -1,15 +1,27 @@ package janggi; import janggi.controller.JanggiFlow; +import janggi.strategy.ArrangementStrategy; +import janggi.strategy.MaSangMaSang; +import janggi.strategy.MaSangSangMa; +import janggi.strategy.SangMaMaSang; +import janggi.strategy.SangMaSangMa; import janggi.view.ApplicationView; import janggi.view.ConsoleReader; import janggi.view.ConsoleWriter; +import java.util.List; public class Application { public static void main(String[] args) { + List strategies = List.of( + MaSangMaSang.getInstance(), + MaSangSangMa.getInstance(), + SangMaMaSang.getInstance(), + SangMaSangMa.getInstance() + ); ApplicationView view = new ApplicationView(new ConsoleWriter(), new ConsoleReader()); - JanggiFlow janggi = new JanggiFlow(view); + JanggiFlow janggi = new JanggiFlow(strategies, view); janggi.process(); } diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 48348142bd..a1f455dc1a 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -3,28 +3,23 @@ import janggi.domain.Location; import janggi.domain.Side; import janggi.domain.board.Board; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceType; import janggi.strategy.ArrangementStrategy; import janggi.strategy.BoardAssembler; -import janggi.strategy.MaSangMaSang; -import janggi.strategy.MaSangSangMa; -import janggi.strategy.SangMaMaSang; -import janggi.strategy.SangMaSangMa; import janggi.view.ApplicationView; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; public class JanggiFlow { - private final ApplicationView view; private final List strategies; + private final ApplicationView view; - public JanggiFlow(ApplicationView view) { + public JanggiFlow(List strategies, ApplicationView view) { + this.strategies = strategies; this.view = view; - this.strategies = List.of( - MaSangMaSang.getInstance(), - MaSangSangMa.getInstance(), - SangMaMaSang.getInstance(), - SangMaSangMa.getInstance() - ); } public void process() { @@ -34,8 +29,8 @@ public void process() { Side current = Side.HAN; while (board.isNotEmpty()) { - view.responseBoardArray(board.to2DArray()); - view.responseCurrentSide(current); + view.responseBoardArray(convertBoardStatus(board)); + view.responseCurrentSide(current.getName()); final Side turnSide = current; retryUntilPieceIsSuccessfullyMoved(() -> { @@ -47,6 +42,17 @@ public void process() { } } + private List> convertBoardStatus(Board board) { + List> boradIn2D = board.to2DArray(); + return boradIn2D.stream() + .map( + row -> row.stream() + .map(Piece::getPieceType) + .map(PieceType::getNameFormat) + .toList() + ).toList(); + } + private ArrangementStrategy repeatAskStrategyUntilSuccess(Side side) { try { return askStrategy(side); @@ -57,10 +63,19 @@ private ArrangementStrategy repeatAskStrategyUntilSuccess(Side side) { } private ArrangementStrategy askStrategy(Side side) { - int decisionNumber = view.requestArrangementStrategyDecision(side, strategies); + Map strategyInfos = convertStrategyInfo(); + int decisionNumber = view.requestArrangementStrategyDecision(side.getName(), strategyInfos); return findStrategyWithCorrespondingDecisionNumber(decisionNumber); } + private Map convertStrategyInfo() { + Map strategyInfos = new LinkedHashMap<>(); + for (ArrangementStrategy strategy : strategies) { + strategyInfos.put(strategy.decisionNumber(), strategy.name()); + } + return strategyInfos; + } + private ArrangementStrategy findStrategyWithCorrespondingDecisionNumber(int decisionNumber) { return strategies.stream() .filter(strategy -> strategy.isDecisionNumberMatching(decisionNumber)) @@ -86,17 +101,17 @@ private Location askLocationOfPiece(Side current, Board board) { private Location repeatAskLocationToMoveUntilSuccess(Location from, Side turnSide, Board board) { try { - return askLocationToMove(from, turnSide, board); + return askLocationToMove(from, board); } catch (IllegalArgumentException e) { view.responseErrorMessage(e); return repeatAskLocationToMoveUntilSuccess(from, turnSide, board); } } - private Location askLocationToMove(Location startingLocation, Side current, Board board) { + private Location askLocationToMove(Location startingLocation, Board board) { List locationToMove = view.requestLocationToMove(); Location verifiedLocation = Location.from(locationToMove); - board.validateLocationToMove(startingLocation, verifiedLocation, current); + board.validateLocationToMove(startingLocation, verifiedLocation); return verifiedLocation; } diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 72361bff38..30497ce9b1 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -79,13 +79,9 @@ private void executeMove(Location from, Location to, Piece piece) { boardState.put(from, EmptyPiece.getInstance()); } - public void validateLocationToMove(Location startingLocation, Location locationToMove, Side currentSide) { + public void validateLocationToMove(Location startingLocation, Location locationToMove) { validateLocation(locationToMove); validateMovementOccurrence(startingLocation, locationToMove); - Piece target = boardState.get(locationToMove); - if (isOccupiedBySameSide(target, currentSide)) { - throw new IllegalArgumentException("같은 편의 기물이 있는 위치로 이동할 수 없습니다."); - } } private void validateMovementOccurrence(Location from, Location to) { @@ -115,8 +111,4 @@ public List> to2DArray() { private boolean isNotSameSide(Piece piece, Side side) { return !piece.isSameSide(side); } - - private boolean isOccupiedBySameSide(Piece piece, Side side) { - return !piece.isEmpty() && piece.isSameSide(side); - } } diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java index de8730b466..80383ab30c 100644 --- a/src/main/java/janggi/domain/piece/Ma.java +++ b/src/main/java/janggi/domain/piece/Ma.java @@ -20,6 +20,16 @@ public class Ma extends Piece { private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); + private static final List POSSIBLE_ROUTES = List.of( + Route.from(List.of(FRONT, FRONT_LEFT)), + Route.from(List.of(FRONT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, BACK_RIGHT)), + Route.from(List.of(LEFT, FRONT_LEFT)), + Route.from(List.of(LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_LEFT)), + Route.from(List.of(BACK, BACK_RIGHT)) + ); public Ma(Side side) { super(PieceType.MA, side); @@ -27,18 +37,7 @@ public Ma(Side side) { @Override public List calculateRoute(Location from, Location to) { - List possibleRoutes = List.of( - Route.from(List.of(FRONT, FRONT_LEFT)), - Route.from(List.of(FRONT, FRONT_RIGHT)), - Route.from(List.of(RIGHT, FRONT_RIGHT)), - Route.from(List.of(RIGHT, BACK_RIGHT)), - Route.from(List.of(LEFT, FRONT_LEFT)), - Route.from(List.of(LEFT, BACK_LEFT)), - Route.from(List.of(BACK, BACK_LEFT)), - Route.from(List.of(BACK, BACK_RIGHT)) - ); - - return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); + return RouteProvider.findValidPath(pieceType, from, to, POSSIBLE_ROUTES); } @Override diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java index 37dd1b0ac0..2b7acd117a 100644 --- a/src/main/java/janggi/domain/piece/Sang.java +++ b/src/main/java/janggi/domain/piece/Sang.java @@ -20,6 +20,16 @@ public class Sang extends Piece { private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); + private static final List POSSIBLE_ROUTES = List.of( + Route.from(List.of(FRONT, FRONT_LEFT, FRONT_LEFT)), + Route.from(List.of(FRONT, FRONT_RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, FRONT_RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, BACK_RIGHT, BACK_RIGHT)), + Route.from(List.of(LEFT, FRONT_LEFT, FRONT_LEFT)), + Route.from(List.of(LEFT, BACK_LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_RIGHT, BACK_RIGHT)) + ); public Sang(Side side) { super(PieceType.SANG, side); @@ -27,18 +37,7 @@ public Sang(Side side) { @Override public List calculateRoute(Location from, Location to) { - List possibleRoutes = List.of( - Route.from(List.of(FRONT, FRONT_LEFT, FRONT_LEFT)), - Route.from(List.of(FRONT, FRONT_RIGHT, FRONT_RIGHT)), - Route.from(List.of(RIGHT, FRONT_RIGHT, FRONT_RIGHT)), - Route.from(List.of(RIGHT, BACK_RIGHT, BACK_RIGHT)), - Route.from(List.of(LEFT, FRONT_LEFT, FRONT_LEFT)), - Route.from(List.of(LEFT, BACK_LEFT, BACK_LEFT)), - Route.from(List.of(BACK, BACK_LEFT, BACK_LEFT)), - Route.from(List.of(BACK, BACK_RIGHT, BACK_RIGHT)) - ); - - return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); + return RouteProvider.findValidPath(pieceType, from, to, POSSIBLE_ROUTES); } @Override diff --git a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java index 63a681f563..00464462b3 100644 --- a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java @@ -17,6 +17,16 @@ public class GungSeongRouteProvider implements RouteProvider { private static final GungSeongRouteProvider INSTANCE = new GungSeongRouteProvider(); + private static final List POSSIBLE_ROUTES = List.of( + Route.from(List.of(FRONT)), + Route.from(List.of(LEFT)), + Route.from(List.of(RIGHT)), + Route.from(List.of(BACK)), + Route.from(List.of(FRONT_LEFT)), + Route.from(List.of(FRONT_RIGHT)), + Route.from(List.of(BACK_LEFT)), + Route.from(List.of(BACK_RIGHT)) + ); private GungSeongRouteProvider() { } @@ -27,17 +37,6 @@ public static GungSeongRouteProvider getInstance() { @Override public List calculateRoute(PieceType pieceType, Location from, Location to) { - List possibleRoutes = List.of( - Route.from(List.of(FRONT)), - Route.from(List.of(LEFT)), - Route.from(List.of(RIGHT)), - Route.from(List.of(BACK)), - Route.from(List.of(FRONT_LEFT)), - Route.from(List.of(FRONT_RIGHT)), - Route.from(List.of(BACK_LEFT)), - Route.from(List.of(BACK_RIGHT)) - ); - - return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); + return RouteProvider.findValidPath(pieceType, from, to, POSSIBLE_ROUTES); } } diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java index 966309bcf6..cce42e25de 100644 --- a/src/main/java/janggi/view/ApplicationView.java +++ b/src/main/java/janggi/view/ApplicationView.java @@ -1,11 +1,8 @@ package janggi.view; -import janggi.domain.Side; -import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceType; -import janggi.strategy.ArrangementStrategy; import java.util.List; -import java.util.function.Supplier; +import java.util.Map; +import java.util.Map.Entry; public class ApplicationView { @@ -17,55 +14,38 @@ public ApplicationView(Output outputWriter, Input inputReader) { this.inputReader = inputReader; } - public int requestArrangementStrategyDecision(Side side, List strategies) { - outputWriter.printPromptMessage(side.getName() + "팀의 초기화 전략 번호를 입력해주세요."); + public int requestArrangementStrategyDecision(String side, Map strategies) { + outputWriter.printPromptMessage(side + "팀의 초기화 전략 번호를 입력해주세요."); - for (ArrangementStrategy strategy : strategies) { - String strategyDecisionOption = String.format("%d. %s", strategy.decisionNumber(), strategy.name()); + for (Entry strategy : strategies.entrySet()) { + String strategyDecisionOption = String.format("%d. %s", strategy.getKey(), strategy.getValue()); outputWriter.printPromptMessage(strategyDecisionOption); } - return retry(inputReader::readInt); + return inputReader.readInt(); } - public void responseBoardArray(List> board2DArray) { - List> stringMatrix = board2DArray.stream() - .map(row -> row.stream() - .map(Piece::getPieceType) - .map(PieceType::getNameFormat) - .toList() - ).toList(); - + public void responseBoardArray(List> stringMatrix) { outputWriter.printStringMatrix(stringMatrix); } - public void responseCurrentSide(Side currentSide) { - outputWriter.printPromptMessage(currentSide.getName() + "팀의 차례입니다."); + public void responseCurrentSide(String currentSide) { + outputWriter.printPromptMessage(currentSide + "팀의 차례입니다."); } public List requestLocationOfPiece() { outputWriter.printPromptMessage("이동시킬 기물의 좌표를 입력해주세요. (,로 구분)"); - return retry(inputReader::readIntegers); + return inputReader.readIntegers(); } public List requestLocationToMove() { outputWriter.printPromptMessage("해당 기물이 이동할 좌표를 입력해주세요. (,로 구분)"); - return retry(inputReader::readIntegers); + return inputReader.readIntegers(); } public void responseErrorMessage(RuntimeException e) { outputWriter.printErrorMessage(e); } - - private T retry(Supplier supplier) { - while (true) { - try { - return supplier.get(); - } catch (IllegalArgumentException e) { - outputWriter.printErrorMessage(e); - } - } - } } diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 6cffe560b2..32c6af23c7 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -159,14 +159,13 @@ void shouldNotThrowExceptionWhenPieceDoesNotExistsAtLocation() { // when & then Assertions.assertThatNoException() - .isThrownBy(() -> board.validateLocationToMove(from, to, Side.HAN)); + .isThrownBy(() -> board.validateLocationToMove(from, to)); } @Test @DisplayName("도착 좌표에 상대 팀 기물이 존재하면 예외를 발생시키지 않는다.") void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { // given - Side currentSide = Side.HAN; Side otherSide = Side.CHO; Map initialPieces = Map.of( new Location(1, 1), new TestPiece(otherSide) @@ -180,27 +179,7 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { // when & then Assertions.assertThatNoException() - .isThrownBy(() -> board.validateLocationToMove(from, to, currentSide)); - } - - @Test - @DisplayName("도착 좌표에 같은 팀 기물이 존재하면 예외를 발생시킨다.") - void shouldNotThrowExceptionWhenPieceOnSameSideExistsAtLocation() { - // given - Side currentSide = Side.HAN; - Map initialPieces = Map.of( - new Location(1, 1), new TestPiece(currentSide) - ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); - Board board = Board.create(assembler); - Location from = new Location(0, 0); - Location to = new Location(1, 1); - - // when & then - Assertions.assertThatThrownBy(() -> board.validateLocationToMove(from, to, currentSide)) - .isInstanceOf(IllegalArgumentException.class); + .isThrownBy(() -> board.validateLocationToMove(from, to)); } } From ac709cd393fc8a4f7586be09db44e9ed92b9ebb7 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Tue, 31 Mar 2026 12:33:49 +0900 Subject: [PATCH 36/45] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EB=A7=A4=EA=B0=9C?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ApplicationView의 입출력 메서드 접두사를 의도가 더 명확한 `promptFor`, `show`로 변경해 가독성 향상 - `convertBoardStatus` 내부의 `boradIn2D` 변수명 오타를 `boardIn2D`로 수정 - `repeatAskLocationToMoveUntilSuccess` 메서드에서 사용하지 않는 불필요한 매개변수(`turnSide`) 제거 --- .../java/janggi/controller/JanggiFlow.java | 28 +++++++++---------- .../java/janggi/view/ApplicationView.java | 12 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index a1f455dc1a..ba6e87fd90 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -29,13 +29,13 @@ public void process() { Side current = Side.HAN; while (board.isNotEmpty()) { - view.responseBoardArray(convertBoardStatus(board)); - view.responseCurrentSide(current.getName()); + view.showBoardArray(convertBoardStatus(board)); + view.showCurrentSide(current.getName()); final Side turnSide = current; retryUntilPieceIsSuccessfullyMoved(() -> { Location from = repeatAskLocationOfPieceUntilSuccess(turnSide, board); - Location to = repeatAskLocationToMoveUntilSuccess(from, turnSide, board); + Location to = repeatAskLocationToMoveUntilSuccess(from, board); board.move(from, to); }); current = current.switchSide(); @@ -43,8 +43,8 @@ public void process() { } private List> convertBoardStatus(Board board) { - List> boradIn2D = board.to2DArray(); - return boradIn2D.stream() + List> boardIn2D = board.to2DArray(); + return boardIn2D.stream() .map( row -> row.stream() .map(Piece::getPieceType) @@ -57,14 +57,14 @@ private ArrangementStrategy repeatAskStrategyUntilSuccess(Side side) { try { return askStrategy(side); } catch (IllegalArgumentException e) { - view.responseErrorMessage(e); + view.showErrorMessage(e); return repeatAskStrategyUntilSuccess(side); } } private ArrangementStrategy askStrategy(Side side) { Map strategyInfos = convertStrategyInfo(); - int decisionNumber = view.requestArrangementStrategyDecision(side.getName(), strategyInfos); + int decisionNumber = view.promptForArrangementStrategyDecision(side.getName(), strategyInfos); return findStrategyWithCorrespondingDecisionNumber(decisionNumber); } @@ -87,29 +87,29 @@ private Location repeatAskLocationOfPieceUntilSuccess(Side turnSide, Board board try { return askLocationOfPiece(turnSide, board); } catch (IllegalArgumentException e) { - view.responseErrorMessage(e); + view.showErrorMessage(e); return repeatAskLocationOfPieceUntilSuccess(turnSide, board); } } private Location askLocationOfPiece(Side current, Board board) { - List locationOfPiece = view.requestLocationOfPiece(); + List locationOfPiece = view.promptForLocationOfPiece(); Location verifiedLocation = Location.from(locationOfPiece); board.validateLocationOfPiece(current, verifiedLocation); return verifiedLocation; } - private Location repeatAskLocationToMoveUntilSuccess(Location from, Side turnSide, Board board) { + private Location repeatAskLocationToMoveUntilSuccess(Location from, Board board) { try { return askLocationToMove(from, board); } catch (IllegalArgumentException e) { - view.responseErrorMessage(e); - return repeatAskLocationToMoveUntilSuccess(from, turnSide, board); + view.showErrorMessage(e); + return repeatAskLocationToMoveUntilSuccess(from, board); } } private Location askLocationToMove(Location startingLocation, Board board) { - List locationToMove = view.requestLocationToMove(); + List locationToMove = view.promptForLocationToMove(); Location verifiedLocation = Location.from(locationToMove); board.validateLocationToMove(startingLocation, verifiedLocation); return verifiedLocation; @@ -119,7 +119,7 @@ private void retryUntilPieceIsSuccessfullyMoved(Runnable runnable) { try { runnable.run(); } catch (IllegalArgumentException e) { - view.responseErrorMessage(e); + view.showErrorMessage(e); retryUntilPieceIsSuccessfullyMoved(runnable); } } diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java index cce42e25de..2df5189b7a 100644 --- a/src/main/java/janggi/view/ApplicationView.java +++ b/src/main/java/janggi/view/ApplicationView.java @@ -14,7 +14,7 @@ public ApplicationView(Output outputWriter, Input inputReader) { this.inputReader = inputReader; } - public int requestArrangementStrategyDecision(String side, Map strategies) { + public int promptForArrangementStrategyDecision(String side, Map strategies) { outputWriter.printPromptMessage(side + "팀의 초기화 전략 번호를 입력해주세요."); for (Entry strategy : strategies.entrySet()) { @@ -25,27 +25,27 @@ public int requestArrangementStrategyDecision(String side, Map return inputReader.readInt(); } - public void responseBoardArray(List> stringMatrix) { + public void showBoardArray(List> stringMatrix) { outputWriter.printStringMatrix(stringMatrix); } - public void responseCurrentSide(String currentSide) { + public void showCurrentSide(String currentSide) { outputWriter.printPromptMessage(currentSide + "팀의 차례입니다."); } - public List requestLocationOfPiece() { + public List promptForLocationOfPiece() { outputWriter.printPromptMessage("이동시킬 기물의 좌표를 입력해주세요. (,로 구분)"); return inputReader.readIntegers(); } - public List requestLocationToMove() { + public List promptForLocationToMove() { outputWriter.printPromptMessage("해당 기물이 이동할 좌표를 입력해주세요. (,로 구분)"); return inputReader.readIntegers(); } - public void responseErrorMessage(RuntimeException e) { + public void showErrorMessage(RuntimeException e) { outputWriter.printErrorMessage(e); } } From 89fc068bd47716bb50c645e74c1b666ec665eae1 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Tue, 31 Mar 2026 21:18:47 +0900 Subject: [PATCH 37/45] =?UTF-8?q?refactor(Side):=20=EC=A7=84=EC=98=81=20?= =?UTF-8?q?=EB=B0=98=EC=A0=84=20=EB=A1=9C=EC=A7=81=EC=97=90=20=EB=B0=A9?= =?UTF-8?q?=EC=96=B4=EC=A0=81=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=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 - 기존 로직에서 HAN이 아닐 경우 무조건 HAN을 반영해, NONE 등 유효하지 않은 상태에서 호출될 시 발생할 수 있는 잠재적 버그 차단 - CHO 상태일 경우에만 명시적으로 HAN을 반환하도록 조건 개선 - 유효하지 않은 진영에서 상태 반전 시도 시 UnsupportedOperationException을 던지도록 예외 처리 추가 --- src/main/java/janggi/domain/Side.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java index f5e8d2d4ea..00768b64f3 100644 --- a/src/main/java/janggi/domain/Side.java +++ b/src/main/java/janggi/domain/Side.java @@ -16,7 +16,10 @@ public Side switchSide() { if (this == HAN) { return CHO; } - return HAN; + if (this == CHO) { + return HAN; + } + throw new UnsupportedOperationException("진영이 존재하지 않아 진영을 반전시킬 수 없습니다."); } public String getName() { From 9508070bf907d6624d2b6eed9682792847212f42 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 1 Apr 2026 16:19:17 +0900 Subject: [PATCH 38/45] =?UTF-8?q?refactor(strategy):=20=EA=B8=B0=EB=AC=BC?= =?UTF-8?q?=20=EB=B0=B0=EC=B9=98=20=EC=A0=84=EB=9E=B5=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20Resolver=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 최상위 ArrangementStrategy를 인터페이스로 분리해 역할 명확화 - 마/상 배치를 제외한 고정 기물 배치를 담당하는 MaSangArrangementTemplate 추상 클래스 도입: 기물 초기화시 발생하던 덮어쓰기 문제 해결 및 OCP 확보 - 도메인이 UI 데이터를 들고 있던 StrategyLabel을 삭제하고, 입력 번호와 전략 객체를 매핑하는 ArrangementStrategyResolver 도입 - JanggiFlow 내부의 전략 객체 매핑 및 캐싱 로직을 Resolver로 위임해 컨트롤러 무상태 유지 및 책임 분리 - 하위 전략 구현체들의 네이밍을 명확하게 변경 (ex. MaSangMaSang -> MaSangMaSangStrategy) --- src/main/java/janggi/Application.java | 14 +-- .../ArrangementStrategyResolver.java | 64 +++++++++++++ .../java/janggi/controller/JanggiFlow.java | 24 +---- .../janggi/strategy/ArrangementStrategy.java | 92 +------------------ .../strategy/MaSangArrangementTemplate.java | 76 +++++++++++++++ ...gMaSang.java => MaSangMaSangStrategy.java} | 10 +- ...gSangMa.java => MaSangSangMaStrategy.java} | 10 +- ...aMaSang.java => SangMaMaSangStrategy.java} | 10 +- ...aSangMa.java => SangMaSangMaStrategy.java} | 10 +- .../java/janggi/strategy/StrategyLabel.java | 25 ----- .../java/janggi/domain/board/BoardTest.java | 26 +++--- .../janggi/strategy/BoardAssemblerTest.java | 13 +-- ...ava => MaSangArrangementTemplateTest.java} | 4 +- ...est.java => MaSangMaSangStrategyTest.java} | 4 +- ...est.java => MaSangSangMaStrategyTest.java} | 4 +- .../janggi/strategy/SangMaMaSangTest.java | 2 +- ...est.java => SangMaSangMaStrategyTest.java} | 4 +- ...ava => TestMaSangArrangementTemplate.java} | 8 +- 18 files changed, 184 insertions(+), 216 deletions(-) create mode 100644 src/main/java/janggi/controller/ArrangementStrategyResolver.java create mode 100644 src/main/java/janggi/strategy/MaSangArrangementTemplate.java rename src/main/java/janggi/strategy/{MaSangMaSang.java => MaSangMaSangStrategy.java} (70%) rename src/main/java/janggi/strategy/{MaSangSangMa.java => MaSangSangMaStrategy.java} (70%) rename src/main/java/janggi/strategy/{SangMaMaSang.java => SangMaMaSangStrategy.java} (70%) rename src/main/java/janggi/strategy/{SangMaSangMa.java => SangMaSangMaStrategy.java} (70%) delete mode 100644 src/main/java/janggi/strategy/StrategyLabel.java rename src/test/java/janggi/strategy/{ArrangementStrategyTest.java => MaSangArrangementTemplateTest.java} (94%) rename src/test/java/janggi/strategy/{MaSangMaSangTest.java => MaSangMaSangStrategyTest.java} (94%) rename src/test/java/janggi/strategy/{MaSangSangMaTest.java => MaSangSangMaStrategyTest.java} (94%) rename src/test/java/janggi/strategy/{SangMaSangMaTest.java => SangMaSangMaStrategyTest.java} (94%) rename src/test/java/janggi/support/{TestArrangementStrategy.java => TestMaSangArrangementTemplate.java} (69%) diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java index 6448bfd668..45c52d9c1e 100644 --- a/src/main/java/janggi/Application.java +++ b/src/main/java/janggi/Application.java @@ -1,27 +1,15 @@ package janggi; import janggi.controller.JanggiFlow; -import janggi.strategy.ArrangementStrategy; -import janggi.strategy.MaSangMaSang; -import janggi.strategy.MaSangSangMa; -import janggi.strategy.SangMaMaSang; -import janggi.strategy.SangMaSangMa; import janggi.view.ApplicationView; import janggi.view.ConsoleReader; import janggi.view.ConsoleWriter; -import java.util.List; public class Application { public static void main(String[] args) { - List strategies = List.of( - MaSangMaSang.getInstance(), - MaSangSangMa.getInstance(), - SangMaMaSang.getInstance(), - SangMaSangMa.getInstance() - ); ApplicationView view = new ApplicationView(new ConsoleWriter(), new ConsoleReader()); - JanggiFlow janggi = new JanggiFlow(strategies, view); + JanggiFlow janggi = new JanggiFlow(view); janggi.process(); } diff --git a/src/main/java/janggi/controller/ArrangementStrategyResolver.java b/src/main/java/janggi/controller/ArrangementStrategyResolver.java new file mode 100644 index 0000000000..1480ff4137 --- /dev/null +++ b/src/main/java/janggi/controller/ArrangementStrategyResolver.java @@ -0,0 +1,64 @@ +package janggi.controller; + +import janggi.strategy.ArrangementStrategy; +import janggi.strategy.MaSangMaSangStrategy; +import janggi.strategy.MaSangSangMaStrategy; +import janggi.strategy.SangMaMaSangStrategy; +import janggi.strategy.SangMaSangMaStrategy; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public enum ArrangementStrategyResolver { + MA_SANG_MA_SANG(1, "마상마상", MaSangMaSangStrategy.getInstance()), + SANG_MA_SANG_MA(2, "상마상마", SangMaSangMaStrategy.getInstance()), + SANG_MA_MA_SANG(3, "상마마상", SangMaMaSangStrategy.getInstance()), + MA_SANG_SANG_MA(4, "마상상마", MaSangSangMaStrategy.getInstance()); + + private static final Map STRATEGY_OPTIONS = + Collections.unmodifiableMap( + Arrays.stream(values()) + .collect(Collectors.toMap( + ArrangementStrategyResolver::getDecisionNumber, + ArrangementStrategyResolver::getDisplayName, + (existing, replacement) -> existing, + LinkedHashMap::new + )) + ); + + private final int decisionNumber; + private final String displayName; + private final ArrangementStrategy strategy; + + ArrangementStrategyResolver(int decisionNumber, String displayName, ArrangementStrategy strategy) { + this.decisionNumber = decisionNumber; + this.displayName = displayName; + this.strategy = strategy; + } + + public static ArrangementStrategy resolve(int decisionNumber) { + return Arrays.stream(values()) + .filter(resolver -> resolver.decisionNumber == decisionNumber) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 배치 전략 번호입니다.")) + .getStrategy(); + } + + public static Map getStrategyOptions() { + return STRATEGY_OPTIONS; + } + + private int getDecisionNumber() { + return decisionNumber; + } + + private String getDisplayName() { + return displayName; + } + + private ArrangementStrategy getStrategy() { + return strategy; + } +} diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index ba6e87fd90..19ad966bee 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -8,17 +8,14 @@ import janggi.strategy.ArrangementStrategy; import janggi.strategy.BoardAssembler; import janggi.view.ApplicationView; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class JanggiFlow { - private final List strategies; private final ApplicationView view; - public JanggiFlow(List strategies, ApplicationView view) { - this.strategies = strategies; + public JanggiFlow(ApplicationView view) { this.view = view; } @@ -63,24 +60,9 @@ private ArrangementStrategy repeatAskStrategyUntilSuccess(Side side) { } private ArrangementStrategy askStrategy(Side side) { - Map strategyInfos = convertStrategyInfo(); + Map strategyInfos = ArrangementStrategyResolver.getStrategyOptions(); int decisionNumber = view.promptForArrangementStrategyDecision(side.getName(), strategyInfos); - return findStrategyWithCorrespondingDecisionNumber(decisionNumber); - } - - private Map convertStrategyInfo() { - Map strategyInfos = new LinkedHashMap<>(); - for (ArrangementStrategy strategy : strategies) { - strategyInfos.put(strategy.decisionNumber(), strategy.name()); - } - return strategyInfos; - } - - private ArrangementStrategy findStrategyWithCorrespondingDecisionNumber(int decisionNumber) { - return strategies.stream() - .filter(strategy -> strategy.isDecisionNumberMatching(decisionNumber)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("일치하는 전략 번호가 없습니다: " + decisionNumber)); + return ArrangementStrategyResolver.resolve(decisionNumber); } private Location repeatAskLocationOfPieceUntilSuccess(Side turnSide, Board board) { diff --git a/src/main/java/janggi/strategy/ArrangementStrategy.java b/src/main/java/janggi/strategy/ArrangementStrategy.java index d0d96f2b08..7dfbd4284e 100644 --- a/src/main/java/janggi/strategy/ArrangementStrategy.java +++ b/src/main/java/janggi/strategy/ArrangementStrategy.java @@ -1,97 +1,9 @@ package janggi.strategy; import janggi.domain.Side; -import janggi.domain.piece.Cha; -import janggi.domain.piece.Gung; -import janggi.domain.piece.Jolbyeong; -import janggi.domain.piece.Ma; import janggi.domain.piece.Piece; -import janggi.domain.piece.Po; -import janggi.domain.piece.Sa; -import janggi.domain.piece.Sang; -import java.util.List; -import java.util.function.Function; -public abstract class ArrangementStrategy { +public interface ArrangementStrategy { - protected final StrategyLabel label; - - protected ArrangementStrategy(StrategyLabel label) { - this.label = label; - } - - public void place(Piece[][] arrangement, Side side) { - placeDefaultPieces(arrangement, side); - placeVariablePieces(arrangement, side); - } - - protected abstract void placeVariablePieces(Piece[][] arrangement, Side side); - - protected int calculateInitialRow(int boardMaxLength, Side side) { - if (side.equals(Side.CHO)) { - return boardMaxLength - 1; - } - return 0; - } - - private void placeDefaultPieces(Piece[][] grid, Side side) { - for (DefaultPieceFactory factory : DefaultPieceFactory.values()) { - setUpPiece(grid, side, factory); - } - } - - private void setUpPiece(Piece[][] grid, Side side, DefaultPieceFactory factory) { - int height = grid.length; - for (int col : factory.getCols()) { - int row = factory.getRow(side, height); - grid[row][col] = factory.createPiece(side); - } - } - - public boolean isDecisionNumberMatching(int decisionNumber) { - return label.getDecisionNumber() == decisionNumber; - } - - public String name() { - return label.getName(); - } - - public int decisionNumber() { - return label.getDecisionNumber(); - } - - private enum DefaultPieceFactory { - CHA(0, List.of(0, 8), Cha::new), - MA(0, List.of(1, 7), Ma::new), - SANG(0, List.of(2, 6), Sang::new), - SA(0, List.of(3, 5), Sa::new), - GUNG(1, List.of(4), Gung::new), - PO(2, List.of(1, 7), Po::new), - JOL(3, List.of(0, 2, 4, 6, 8), Jolbyeong::new); - - private final int row; - private final List cols; - private final Function pieceClass; - - DefaultPieceFactory(int row, List cols, Function pieceClass) { - this.row = row; - this.cols = cols; - this.pieceClass = pieceClass; - } - - private Piece createPiece(Side side) { - return pieceClass.apply(side); - } - - private int getRow(Side side, int height) { - if (side.equals(Side.CHO)) { - return height - row - 1; - } - return row; - } - - private List getCols() { - return cols; - } - } + void place(Piece[][] arrangement, Side side); } diff --git a/src/main/java/janggi/strategy/MaSangArrangementTemplate.java b/src/main/java/janggi/strategy/MaSangArrangementTemplate.java new file mode 100644 index 0000000000..0b1e78013c --- /dev/null +++ b/src/main/java/janggi/strategy/MaSangArrangementTemplate.java @@ -0,0 +1,76 @@ +package janggi.strategy; + +import janggi.domain.Side; +import janggi.domain.piece.Cha; +import janggi.domain.piece.Gung; +import janggi.domain.piece.Jolbyeong; +import janggi.domain.piece.Piece; +import janggi.domain.piece.Po; +import janggi.domain.piece.Sa; +import java.util.List; +import java.util.function.Function; + +public abstract class MaSangArrangementTemplate implements ArrangementStrategy { + + @Override + public void place(Piece[][] arrangement, Side side) { + placeDefaultPieces(arrangement, side); + placeVariablePieces(arrangement, side); + } + + protected abstract void placeVariablePieces(Piece[][] arrangement, Side side); + + protected int calculateInitialRow(int boardMaxLength, Side side) { + if (side.equals(Side.CHO)) { + return boardMaxLength - 1; + } + return 0; + } + + private void placeDefaultPieces(Piece[][] grid, Side side) { + for (DefaultPieceFactory factory : DefaultPieceFactory.values()) { + setUpPiece(grid, side, factory); + } + } + + private void setUpPiece(Piece[][] grid, Side side, DefaultPieceFactory factory) { + int height = grid.length; + for (int col : factory.getCols()) { + int row = factory.getRow(side, height); + grid[row][col] = factory.createPiece(side); + } + } + + private enum DefaultPieceFactory { + CHA(0, List.of(0, 8), Cha::new), + SA(0, List.of(3, 5), Sa::new), + GUNG(1, List.of(4), Gung::new), + PO(2, List.of(1, 7), Po::new), + JOL(3, List.of(0, 2, 4, 6, 8), Jolbyeong::new); + + private final int row; + private final List cols; + private final Function pieceClass; + + DefaultPieceFactory(int row, List cols, Function pieceClass) { + this.row = row; + this.cols = cols; + this.pieceClass = pieceClass; + } + + private Piece createPiece(Side side) { + return pieceClass.apply(side); + } + + private int getRow(Side side, int height) { + if (side.equals(Side.CHO)) { + return height - row - 1; + } + return row; + } + + private List getCols() { + return cols; + } + } +} diff --git a/src/main/java/janggi/strategy/MaSangMaSang.java b/src/main/java/janggi/strategy/MaSangMaSangStrategy.java similarity index 70% rename from src/main/java/janggi/strategy/MaSangMaSang.java rename to src/main/java/janggi/strategy/MaSangMaSangStrategy.java index 9066efd022..459d4fe592 100644 --- a/src/main/java/janggi/strategy/MaSangMaSang.java +++ b/src/main/java/janggi/strategy/MaSangMaSangStrategy.java @@ -6,15 +6,11 @@ import janggi.domain.piece.Sang; @SuppressWarnings("java:S6548") -public class MaSangMaSang extends ArrangementStrategy { +public class MaSangMaSangStrategy extends MaSangArrangementTemplate { - private static final MaSangMaSang INSTANCE = new MaSangMaSang(); + private static final MaSangMaSangStrategy INSTANCE = new MaSangMaSangStrategy(); - private MaSangMaSang() { - super(StrategyLabel.MSMS); - } - - public static MaSangMaSang getInstance() { + public static MaSangMaSangStrategy getInstance() { return INSTANCE; } diff --git a/src/main/java/janggi/strategy/MaSangSangMa.java b/src/main/java/janggi/strategy/MaSangSangMaStrategy.java similarity index 70% rename from src/main/java/janggi/strategy/MaSangSangMa.java rename to src/main/java/janggi/strategy/MaSangSangMaStrategy.java index 55f71c3bda..a6758d3d69 100644 --- a/src/main/java/janggi/strategy/MaSangSangMa.java +++ b/src/main/java/janggi/strategy/MaSangSangMaStrategy.java @@ -6,15 +6,11 @@ import janggi.domain.piece.Sang; @SuppressWarnings("java:S6548") -public class MaSangSangMa extends ArrangementStrategy { +public class MaSangSangMaStrategy extends MaSangArrangementTemplate { - private static final MaSangSangMa INSTANCE = new MaSangSangMa(); + private static final MaSangSangMaStrategy INSTANCE = new MaSangSangMaStrategy(); - private MaSangSangMa() { - super(StrategyLabel.MSSM); - } - - public static MaSangSangMa getInstance() { + public static MaSangSangMaStrategy getInstance() { return INSTANCE; } diff --git a/src/main/java/janggi/strategy/SangMaMaSang.java b/src/main/java/janggi/strategy/SangMaMaSangStrategy.java similarity index 70% rename from src/main/java/janggi/strategy/SangMaMaSang.java rename to src/main/java/janggi/strategy/SangMaMaSangStrategy.java index b9770e48c8..3445f97d0d 100644 --- a/src/main/java/janggi/strategy/SangMaMaSang.java +++ b/src/main/java/janggi/strategy/SangMaMaSangStrategy.java @@ -6,15 +6,11 @@ import janggi.domain.piece.Sang; @SuppressWarnings("java:S6548") -public class SangMaMaSang extends ArrangementStrategy { +public class SangMaMaSangStrategy extends MaSangArrangementTemplate { - private static final SangMaMaSang INSTANCE = new SangMaMaSang(); + private static final SangMaMaSangStrategy INSTANCE = new SangMaMaSangStrategy(); - private SangMaMaSang() { - super(StrategyLabel.SMMS); - } - - public static SangMaMaSang getInstance() { + public static SangMaMaSangStrategy getInstance() { return INSTANCE; } diff --git a/src/main/java/janggi/strategy/SangMaSangMa.java b/src/main/java/janggi/strategy/SangMaSangMaStrategy.java similarity index 70% rename from src/main/java/janggi/strategy/SangMaSangMa.java rename to src/main/java/janggi/strategy/SangMaSangMaStrategy.java index 6601de8995..8ab45b971e 100644 --- a/src/main/java/janggi/strategy/SangMaSangMa.java +++ b/src/main/java/janggi/strategy/SangMaSangMaStrategy.java @@ -6,15 +6,11 @@ import janggi.domain.piece.Sang; @SuppressWarnings("java:S6548") -public class SangMaSangMa extends ArrangementStrategy { +public class SangMaSangMaStrategy extends MaSangArrangementTemplate { - private static final SangMaSangMa INSTANCE = new SangMaSangMa(); + private static final SangMaSangMaStrategy INSTANCE = new SangMaSangMaStrategy(); - private SangMaSangMa() { - super(StrategyLabel.SMSM); - } - - public static SangMaSangMa getInstance() { + public static SangMaSangMaStrategy getInstance() { return INSTANCE; } diff --git a/src/main/java/janggi/strategy/StrategyLabel.java b/src/main/java/janggi/strategy/StrategyLabel.java deleted file mode 100644 index 27c14e17a9..0000000000 --- a/src/main/java/janggi/strategy/StrategyLabel.java +++ /dev/null @@ -1,25 +0,0 @@ -package janggi.strategy; - -public enum StrategyLabel { - - MSMS("마상마상", 1), - MSSM("마상상마", 2), - SMMS("상마마상", 3), - SMSM("상마상마", 4); - - private final String name; - private final int decisionNumber; - - StrategyLabel(String name, int decisionNumber) { - this.name = name; - this.decisionNumber = decisionNumber; - } - - public String getName() { - return name; - } - - public int getDecisionNumber() { - return decisionNumber; - } -} diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 32c6af23c7..81977147d4 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -6,9 +6,9 @@ import janggi.domain.Side; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; -import janggi.strategy.ArrangementStrategy; +import janggi.strategy.MaSangArrangementTemplate; import janggi.strategy.BoardAssembler; -import janggi.support.TestArrangementStrategy; +import janggi.support.TestMaSangArrangementTemplate; import janggi.support.TestPiece; import java.util.List; import java.util.Map; @@ -30,7 +30,7 @@ void shouldReturnBoardWithSelectedArrangementStrategy() { new Location(1, 1), testPiece, new Location(2, 2), emptyPiece ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); // when BoardAssembler assembler = BoardAssembler.of(strategy, strategy); @@ -52,7 +52,7 @@ void shouldNotThrowExceptionWhenLocationExists() { Map initialPieces = Map.of( new Location(1, 1), new TestPiece(currentSide) ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -70,7 +70,7 @@ void shouldThrowExceptionWhenLocationDoesNotExist() { Map initialPieces = Map.of( new Location(1, 1), new TestPiece(currentSide) ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -92,7 +92,7 @@ void shouldNotThrowExceptionWhenPieceOnSameSideExistsAtLocation() { Map initialPieces = Map.of( new Location(1, 1), new TestPiece(currentSide) ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -111,7 +111,7 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { Map initialPieces = Map.of( new Location(1, 1), new TestPiece(currentSide) ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -129,7 +129,7 @@ void shouldThrowExceptionWhenPieceDoesNotExistsAtLocation() { Map initialPieces = Map.of( new Location(5, 5), EmptyPiece.getInstance() ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -150,7 +150,7 @@ void shouldNotThrowExceptionWhenPieceDoesNotExistsAtLocation() { Map initialPieces = Map.of( new Location(1, 1), EmptyPiece.getInstance() ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -170,7 +170,7 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { Map initialPieces = Map.of( new Location(1, 1), new TestPiece(otherSide) ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -191,7 +191,7 @@ void shouldMovePieceToDestination() { Map initialPieces = Map.of( new Location(1, 1), testPiece ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -215,7 +215,7 @@ void shouldReturnTrueForNoneEmptyBoard() { new Location(1, 1), new TestPiece(Side.HAN), new Location(1, 2), EmptyPiece.getInstance() ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); @@ -231,7 +231,7 @@ void shouldReturnTrueForEmptyBoard() { new Location(1, 1), EmptyPiece.getInstance(), new Location(1, 2), EmptyPiece.getInstance() ); - ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); BoardAssembler assembler = BoardAssembler.of(strategy, strategy); Board board = Board.create(assembler); diff --git a/src/test/java/janggi/strategy/BoardAssemblerTest.java b/src/test/java/janggi/strategy/BoardAssemblerTest.java index 3289963aa9..6467cf34fd 100644 --- a/src/test/java/janggi/strategy/BoardAssemblerTest.java +++ b/src/test/java/janggi/strategy/BoardAssemblerTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import janggi.domain.Side; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; import org.junit.jupiter.api.DisplayName; @@ -14,17 +13,7 @@ class BoardAssemblerTest { @DisplayName("조립기는 전략을 실행한 후, 기물이 없는 나머지 빈 칸을 EmptyPiece로 채운다.") void shouldFillEmptySpaces() { // given - ArrangementStrategy doNothingStrategy = new ArrangementStrategy(StrategyLabel.MSMS) { - @Override - public void place(Piece[][] arrangement, Side side) { - return; - } - - @Override - protected void placeVariablePieces(Piece[][] arrangement, Side side) { - return; - } - }; + ArrangementStrategy doNothingStrategy = (arrangement, side) -> {}; BoardAssembler assembler = BoardAssembler.of(doNothingStrategy, doNothingStrategy); diff --git a/src/test/java/janggi/strategy/ArrangementStrategyTest.java b/src/test/java/janggi/strategy/MaSangArrangementTemplateTest.java similarity index 94% rename from src/test/java/janggi/strategy/ArrangementStrategyTest.java rename to src/test/java/janggi/strategy/MaSangArrangementTemplateTest.java index bb576d3580..66b4b54b6b 100644 --- a/src/test/java/janggi/strategy/ArrangementStrategyTest.java +++ b/src/test/java/janggi/strategy/MaSangArrangementTemplateTest.java @@ -12,9 +12,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class ArrangementStrategyTest { +class MaSangArrangementTemplateTest { - private final ArrangementStrategy dummyStrategy = new ArrangementStrategy(StrategyLabel.MSMS) { + private final MaSangArrangementTemplate dummyStrategy = new MaSangArrangementTemplate() { @Override protected void placeVariablePieces(Piece[][] arrangement, Side side) { return; diff --git a/src/test/java/janggi/strategy/MaSangMaSangTest.java b/src/test/java/janggi/strategy/MaSangMaSangStrategyTest.java similarity index 94% rename from src/test/java/janggi/strategy/MaSangMaSangTest.java rename to src/test/java/janggi/strategy/MaSangMaSangStrategyTest.java index 65f4e97dad..34fb416185 100644 --- a/src/test/java/janggi/strategy/MaSangMaSangTest.java +++ b/src/test/java/janggi/strategy/MaSangMaSangStrategyTest.java @@ -9,9 +9,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class MaSangMaSangTest { +class MaSangMaSangStrategyTest { - private static final ArrangementStrategy STRATEGY = MaSangMaSang.getInstance(); + private static final MaSangMaSangStrategy STRATEGY = MaSangMaSangStrategy.getInstance(); @Test @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangMaSang의 위치에 생성해 넣어준다.") diff --git a/src/test/java/janggi/strategy/MaSangSangMaTest.java b/src/test/java/janggi/strategy/MaSangSangMaStrategyTest.java similarity index 94% rename from src/test/java/janggi/strategy/MaSangSangMaTest.java rename to src/test/java/janggi/strategy/MaSangSangMaStrategyTest.java index 35bc543c2f..96e77a7800 100644 --- a/src/test/java/janggi/strategy/MaSangSangMaTest.java +++ b/src/test/java/janggi/strategy/MaSangSangMaStrategyTest.java @@ -9,9 +9,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class MaSangSangMaTest { +class MaSangSangMaStrategyTest { - private static final ArrangementStrategy STRATEGY = MaSangSangMa.getInstance(); + private static final MaSangSangMaStrategy STRATEGY = MaSangSangMaStrategy.getInstance(); @Test @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangSangMa의 위치에 생성해 넣어준다.") diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java index f17cc52276..2960441c33 100644 --- a/src/test/java/janggi/strategy/SangMaMaSangTest.java +++ b/src/test/java/janggi/strategy/SangMaMaSangTest.java @@ -11,7 +11,7 @@ class SangMaMaSangTest { - private static final ArrangementStrategy STRATEGY = SangMaMaSang.getInstance(); + private static final SangMaMaSangStrategy STRATEGY = SangMaMaSangStrategy.getInstance(); @Test @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaMaSang의 위치에 생성해 넣어준다.") diff --git a/src/test/java/janggi/strategy/SangMaSangMaTest.java b/src/test/java/janggi/strategy/SangMaSangMaStrategyTest.java similarity index 94% rename from src/test/java/janggi/strategy/SangMaSangMaTest.java rename to src/test/java/janggi/strategy/SangMaSangMaStrategyTest.java index c3317e0db7..c5e4a4f607 100644 --- a/src/test/java/janggi/strategy/SangMaSangMaTest.java +++ b/src/test/java/janggi/strategy/SangMaSangMaStrategyTest.java @@ -9,9 +9,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class SangMaSangMaTest { +class SangMaSangMaStrategyTest { - private static final ArrangementStrategy STRATEGY = SangMaSangMa.getInstance(); + private static final SangMaSangMaStrategy STRATEGY = SangMaSangMaStrategy.getInstance(); @Test @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaSangMa의 위치에 생성해 넣어준다.") diff --git a/src/test/java/janggi/support/TestArrangementStrategy.java b/src/test/java/janggi/support/TestMaSangArrangementTemplate.java similarity index 69% rename from src/test/java/janggi/support/TestArrangementStrategy.java rename to src/test/java/janggi/support/TestMaSangArrangementTemplate.java index c8802d0fce..78d1c271b7 100644 --- a/src/test/java/janggi/support/TestArrangementStrategy.java +++ b/src/test/java/janggi/support/TestMaSangArrangementTemplate.java @@ -3,15 +3,13 @@ import janggi.domain.Location; import janggi.domain.Side; import janggi.domain.piece.Piece; -import janggi.strategy.ArrangementStrategy; -import janggi.strategy.StrategyLabel; +import janggi.strategy.MaSangArrangementTemplate; import java.util.Map; -public class TestArrangementStrategy extends ArrangementStrategy { +public class TestMaSangArrangementTemplate extends MaSangArrangementTemplate { private final Map customPieces; - public TestArrangementStrategy(Map customPieces) { - super(StrategyLabel.MSMS); + public TestMaSangArrangementTemplate(Map customPieces) { this.customPieces = customPieces; } From d394f533381485791e4148960410ee95fcb44a99 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 3 Apr 2026 14:55:46 +0900 Subject: [PATCH 39/45] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=83=81=EC=86=8D=20=EA=B5=AC=EC=A1=B0=EB=A5=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=95=A9=EC=9C=BC=EB=A1=9C=20=EC=A0=84=EB=A9=B4=20=EA=B0=9C?= =?UTF-8?q?=ED=8E=B8=20=EB=B0=8F=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ▶ Piece & Movement 구조 개편 - Piece를 인터페이스로 변경하고, 구현체를 ActivePiece와 EmptyPiece로 일원화 - 기존 개별 기물 클래스(Cha, Ma, Sang 등)를 순수 이동 전략인 Movement 구현체(ChaMovement, MaMovement 등)로 분리 - PieceFactory를 도입해 PieceType과 Movement 매핑 및 객체 조립 책임 캡슐화 - Ma, Sang의 경로(possibleRoute) static 초기화 시점 꼬임으로 인한 null 할당 이슈를 방지하기 위해 생성자 내부 초기화로 수정 ▶ RouteProvider & Collision 검증 로직 개선 - RouteProvider가 경로 탐색 실패 시 예외 대신 Optional.empty()를 반환하도록 변경해 실제 예외 발생 지점을 Piece로 설정. 이에 따라 예외 메시지에서 기물 이름을 표시하기 위해 PieceType을 넘겨주던 부분을 제거할 수 있었음. - CollisionDetector가 Piece 객체 대신 Side 정보만으로 충돌을 판별하도록 결합도 완화 ▶ 초기 기물 배치 구조 개선 - ArrangementStrategyResolver를 역할에 맞게 ArrangementOption으로 이름 변경 및 직관적인 메서드명 적용 - Strategy 구현체가 생성자로 진영을 받아와 필드에 저장하고 로직에 적용하도록 구조를 개선해 BoardAssembler가 하드코딩된 진영을 내부 로직에서 사용하는 대신 List를 유연하게 받을 수 있게 함 ▶ 테스트 코드 현행화 및 가독성 향상 - 변경된 Optional 반환 타입에 맞춰 hasValue(), isEmpty() 기반으로 검증 로직 전면 수정 - 테스트 메서드명 및 @DisplayName을 구현 상세(파라미터 등)가 아닌 행위와 도메인 용어 중심으로 개선 --- .../janggi/controller/ArrangementOption.java | 67 ++++ .../ArrangementStrategyResolver.java | 64 ---- .../java/janggi/controller/JanggiFlow.java | 6 +- .../java/janggi/domain/piece/ActivePiece.java | 60 ++++ src/main/java/janggi/domain/piece/Cha.java | 29 -- .../java/janggi/domain/piece/EmptyPiece.java | 26 +- src/main/java/janggi/domain/piece/Gung.java | 29 -- .../java/janggi/domain/piece/Jolbyeong.java | 58 ---- src/main/java/janggi/domain/piece/Ma.java | 47 --- src/main/java/janggi/domain/piece/Piece.java | 30 +- .../janggi/domain/piece/PieceFactory.java | 51 +++ .../java/janggi/domain/piece/PieceType.java | 2 +- src/main/java/janggi/domain/piece/Po.java | 30 -- src/main/java/janggi/domain/piece/Sa.java | 29 -- src/main/java/janggi/domain/piece/Sang.java | 47 --- .../java/janggi/domain/rule/ChaMovement.java | 39 +++ .../java/janggi/domain/rule/GungMovement.java | 39 +++ .../janggi/domain/rule/JolbyeongMovement.java | 66 ++++ .../java/janggi/domain/rule/MaMovement.java | 59 ++++ .../java/janggi/domain/rule/Movement.java | 14 + .../java/janggi/domain/rule/PoMovement.java | 40 +++ .../java/janggi/domain/rule/SaMovement.java | 39 +++ .../java/janggi/domain/rule/SangMovement.java | 59 ++++ .../rule/collision/CollisionDetector.java | 3 +- .../collision/DefaultCollisionDetector.java | 12 +- .../rule/collision/PoCollisionDetector.java | 25 +- .../rule/route/DefaultRouteProvider.java | 19 ++ .../rule/route/GungSeongRouteProvider.java | 8 +- .../domain/rule/route/RouteProvider.java | 20 +- .../rule/route/StraightRouteProvider.java | 8 +- .../janggi/strategy/ArrangementStrategy.java | 3 +- .../java/janggi/strategy/BoardAssembler.java | 19 +- .../strategy/MaSangArrangementStrategy.java | 115 +++++++ .../strategy/MaSangArrangementTemplate.java | 76 ----- .../janggi/strategy/MaSangMaSangStrategy.java | 26 -- .../janggi/strategy/MaSangSangMaStrategy.java | 26 -- .../janggi/strategy/SangMaMaSangStrategy.java | 26 -- .../janggi/strategy/SangMaSangMaStrategy.java | 26 -- .../java/janggi/domain/board/BoardTest.java | 82 ++--- .../janggi/domain/piece/JolbyeongTest.java | 79 ----- src/test/java/janggi/domain/piece/MaTest.java | 57 ---- .../java/janggi/domain/piece/SangTest.java | 67 ---- .../domain/rule/JolbyeongMovementTest.java | 74 +++++ .../DefaultCollisionDetectorTest.java | 22 +- .../collision/PoCollisionDetectorTest.java | 38 +-- ...est.java => DefaultRouteProviderTest.java} | 18 +- .../route/GungSeongRouteProviderTest.java | 13 +- .../rule/route/StraightRouteProviderTest.java | 13 +- .../janggi/strategy/BoardAssemblerTest.java | 5 +- .../MaSangArrangementStrategyTest.java | 306 ++++++++++++++++++ .../MaSangArrangementTemplateTest.java | 75 ----- .../strategy/MaSangMaSangStrategyTest.java | 67 ---- .../strategy/MaSangSangMaStrategyTest.java | 67 ---- .../janggi/strategy/SangMaMaSangTest.java | 67 ---- .../strategy/SangMaSangMaStrategyTest.java | 67 ---- .../support/TestArrangementStrategy.java | 25 ++ .../TestMaSangArrangementTemplate.java | 27 -- src/test/java/janggi/support/TestPiece.java | 25 +- 58 files changed, 1268 insertions(+), 1268 deletions(-) create mode 100644 src/main/java/janggi/controller/ArrangementOption.java delete mode 100644 src/main/java/janggi/controller/ArrangementStrategyResolver.java create mode 100644 src/main/java/janggi/domain/piece/ActivePiece.java delete mode 100644 src/main/java/janggi/domain/piece/Cha.java delete mode 100644 src/main/java/janggi/domain/piece/Gung.java delete mode 100644 src/main/java/janggi/domain/piece/Jolbyeong.java delete mode 100644 src/main/java/janggi/domain/piece/Ma.java create mode 100644 src/main/java/janggi/domain/piece/PieceFactory.java delete mode 100644 src/main/java/janggi/domain/piece/Po.java delete mode 100644 src/main/java/janggi/domain/piece/Sa.java delete mode 100644 src/main/java/janggi/domain/piece/Sang.java create mode 100644 src/main/java/janggi/domain/rule/ChaMovement.java create mode 100644 src/main/java/janggi/domain/rule/GungMovement.java create mode 100644 src/main/java/janggi/domain/rule/JolbyeongMovement.java create mode 100644 src/main/java/janggi/domain/rule/MaMovement.java create mode 100644 src/main/java/janggi/domain/rule/Movement.java create mode 100644 src/main/java/janggi/domain/rule/PoMovement.java create mode 100644 src/main/java/janggi/domain/rule/SaMovement.java create mode 100644 src/main/java/janggi/domain/rule/SangMovement.java create mode 100644 src/main/java/janggi/domain/rule/route/DefaultRouteProvider.java create mode 100644 src/main/java/janggi/strategy/MaSangArrangementStrategy.java delete mode 100644 src/main/java/janggi/strategy/MaSangArrangementTemplate.java delete mode 100644 src/main/java/janggi/strategy/MaSangMaSangStrategy.java delete mode 100644 src/main/java/janggi/strategy/MaSangSangMaStrategy.java delete mode 100644 src/main/java/janggi/strategy/SangMaMaSangStrategy.java delete mode 100644 src/main/java/janggi/strategy/SangMaSangMaStrategy.java delete mode 100644 src/test/java/janggi/domain/piece/JolbyeongTest.java delete mode 100644 src/test/java/janggi/domain/piece/MaTest.java delete mode 100644 src/test/java/janggi/domain/piece/SangTest.java create mode 100644 src/test/java/janggi/domain/rule/JolbyeongMovementTest.java rename src/test/java/janggi/domain/rule/route/{RouteProviderTest.java => DefaultRouteProviderTest.java} (62%) create mode 100644 src/test/java/janggi/strategy/MaSangArrangementStrategyTest.java delete mode 100644 src/test/java/janggi/strategy/MaSangArrangementTemplateTest.java delete mode 100644 src/test/java/janggi/strategy/MaSangMaSangStrategyTest.java delete mode 100644 src/test/java/janggi/strategy/MaSangSangMaStrategyTest.java delete mode 100644 src/test/java/janggi/strategy/SangMaMaSangTest.java delete mode 100644 src/test/java/janggi/strategy/SangMaSangMaStrategyTest.java create mode 100644 src/test/java/janggi/support/TestArrangementStrategy.java delete mode 100644 src/test/java/janggi/support/TestMaSangArrangementTemplate.java diff --git a/src/main/java/janggi/controller/ArrangementOption.java b/src/main/java/janggi/controller/ArrangementOption.java new file mode 100644 index 0000000000..f2facf79af --- /dev/null +++ b/src/main/java/janggi/controller/ArrangementOption.java @@ -0,0 +1,67 @@ +package janggi.controller; + +import static janggi.domain.piece.PieceType.MA; +import static janggi.domain.piece.PieceType.SANG; + +import janggi.domain.Side; +import janggi.domain.piece.PieceType; +import janggi.strategy.ArrangementStrategy; +import janggi.strategy.MaSangArrangementStrategy; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public enum ArrangementOption { + MA_SANG_MA_SANG(1, "마상마상", List.of(MA, SANG, MA, SANG)), + SANG_MA_SANG_MA(2, "상마상마", List.of(SANG, MA, SANG, MA)), + SANG_MA_MA_SANG(3, "상마마상", List.of(SANG, MA, MA, SANG)), + MA_SANG_SANG_MA(4, "마상상마", List.of(MA, SANG, SANG, MA)); + + private static final Map STRATEGY_OPTIONS = + Collections.unmodifiableMap( + Arrays.stream(values()) + .collect(Collectors.toMap( + ArrangementOption::getOptionNumber, + ArrangementOption::getDisplayName, + (existing, replacement) -> existing, + LinkedHashMap::new + )) + ); + + private final int optionNumber; + private final String displayName; + private final List arrangement; + + ArrangementOption(int optionNumber, String displayName, List arrangement) { + this.optionNumber = optionNumber; + this.displayName = displayName; + this.arrangement = arrangement; + } + + public static ArrangementStrategy createStrategyOf(Side side, int optionNumber) { + return Arrays.stream(values()) + .filter(resolver -> resolver.optionNumber == optionNumber) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 배치 전략 번호입니다.")) + .toStrategy(side); + } + + public static Map getStrategyOptions() { + return STRATEGY_OPTIONS; + } + + private int getOptionNumber() { + return optionNumber; + } + + private String getDisplayName() { + return displayName; + } + + private ArrangementStrategy toStrategy(Side side) { + return MaSangArrangementStrategy.of(side, arrangement); + } +} diff --git a/src/main/java/janggi/controller/ArrangementStrategyResolver.java b/src/main/java/janggi/controller/ArrangementStrategyResolver.java deleted file mode 100644 index 1480ff4137..0000000000 --- a/src/main/java/janggi/controller/ArrangementStrategyResolver.java +++ /dev/null @@ -1,64 +0,0 @@ -package janggi.controller; - -import janggi.strategy.ArrangementStrategy; -import janggi.strategy.MaSangMaSangStrategy; -import janggi.strategy.MaSangSangMaStrategy; -import janggi.strategy.SangMaMaSangStrategy; -import janggi.strategy.SangMaSangMaStrategy; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; - -public enum ArrangementStrategyResolver { - MA_SANG_MA_SANG(1, "마상마상", MaSangMaSangStrategy.getInstance()), - SANG_MA_SANG_MA(2, "상마상마", SangMaSangMaStrategy.getInstance()), - SANG_MA_MA_SANG(3, "상마마상", SangMaMaSangStrategy.getInstance()), - MA_SANG_SANG_MA(4, "마상상마", MaSangSangMaStrategy.getInstance()); - - private static final Map STRATEGY_OPTIONS = - Collections.unmodifiableMap( - Arrays.stream(values()) - .collect(Collectors.toMap( - ArrangementStrategyResolver::getDecisionNumber, - ArrangementStrategyResolver::getDisplayName, - (existing, replacement) -> existing, - LinkedHashMap::new - )) - ); - - private final int decisionNumber; - private final String displayName; - private final ArrangementStrategy strategy; - - ArrangementStrategyResolver(int decisionNumber, String displayName, ArrangementStrategy strategy) { - this.decisionNumber = decisionNumber; - this.displayName = displayName; - this.strategy = strategy; - } - - public static ArrangementStrategy resolve(int decisionNumber) { - return Arrays.stream(values()) - .filter(resolver -> resolver.decisionNumber == decisionNumber) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 배치 전략 번호입니다.")) - .getStrategy(); - } - - public static Map getStrategyOptions() { - return STRATEGY_OPTIONS; - } - - private int getDecisionNumber() { - return decisionNumber; - } - - private String getDisplayName() { - return displayName; - } - - private ArrangementStrategy getStrategy() { - return strategy; - } -} diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 19ad966bee..d66ec2926b 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -22,7 +22,7 @@ public JanggiFlow(ApplicationView view) { public void process() { ArrangementStrategy hanStrategy = repeatAskStrategyUntilSuccess(Side.HAN); ArrangementStrategy choStrategy = repeatAskStrategyUntilSuccess(Side.CHO); - Board board = Board.create(BoardAssembler.of(hanStrategy, choStrategy)); + Board board = Board.create(BoardAssembler.from(List.of(hanStrategy, choStrategy))); Side current = Side.HAN; while (board.isNotEmpty()) { @@ -60,9 +60,9 @@ private ArrangementStrategy repeatAskStrategyUntilSuccess(Side side) { } private ArrangementStrategy askStrategy(Side side) { - Map strategyInfos = ArrangementStrategyResolver.getStrategyOptions(); + Map strategyInfos = ArrangementOption.getStrategyOptions(); int decisionNumber = view.promptForArrangementStrategyDecision(side.getName(), strategyInfos); - return ArrangementStrategyResolver.resolve(decisionNumber); + return ArrangementOption.createStrategyOf(side, decisionNumber); } private Location repeatAskLocationOfPieceUntilSuccess(Side turnSide, Board board) { diff --git a/src/main/java/janggi/domain/piece/ActivePiece.java b/src/main/java/janggi/domain/piece/ActivePiece.java new file mode 100644 index 0000000000..5b74ba60a7 --- /dev/null +++ b/src/main/java/janggi/domain/piece/ActivePiece.java @@ -0,0 +1,60 @@ +package janggi.domain.piece; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.rule.Movement; +import java.util.List; +import java.util.Optional; + +public class ActivePiece implements Piece { + + protected final PieceType pieceType; + protected final Side side; + private final Movement movement; + + public ActivePiece(PieceType pieceType, Side side, Movement movement) { + this.pieceType = pieceType; + this.side = side; + this.movement = movement; + } + + @Override + public List calculateRoute(Location from, Location to) { + Optional> calculatedRoute = movement.calculateRoute(from, to); + if (calculatedRoute.isEmpty()) { + throw new IllegalArgumentException( + String.format("%s의 기물 이동 규칙 위반: 해당 위치(%d, %d)에 도달할 수 없습니다.", + pieceType.getNameFormat(), + to.x(), + to.y()) + ); + } + + return calculatedRoute.get(); + } + + @Override + public void detectCollision(List piecesOnPath) { + movement.detectCollision(side, piecesOnPath); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean isPo() { + return this.pieceType == PieceType.PO; + } + + @Override + public boolean isSameSide(Side side) { + return this.side.equals(side); + } + + @Override + public PieceType getPieceType() { + return pieceType; + } +} diff --git a/src/main/java/janggi/domain/piece/Cha.java b/src/main/java/janggi/domain/piece/Cha.java deleted file mode 100644 index 8133bf0603..0000000000 --- a/src/main/java/janggi/domain/piece/Cha.java +++ /dev/null @@ -1,29 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.rule.collision.CollisionDetector; -import janggi.domain.rule.collision.DefaultCollisionDetector; -import janggi.domain.rule.route.RouteProvider; -import janggi.domain.rule.route.StraightRouteProvider; -import java.util.List; - -public class Cha extends Piece { - - private static final RouteProvider ROUTE_PROVIDER = StraightRouteProvider.getInstance(); - private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); - - public Cha(Side side) { - super(PieceType.CHA, side); - } - - @Override - public List calculateRoute(Location from, Location to) { - return ROUTE_PROVIDER.calculateRoute(pieceType, from, to); - } - - @Override - public void detectCollision(List piecesOnPath) { - COLLISION_DETECTOR.check(this, piecesOnPath); - } -} diff --git a/src/main/java/janggi/domain/piece/EmptyPiece.java b/src/main/java/janggi/domain/piece/EmptyPiece.java index 80fc7b816c..e97a6de40b 100644 --- a/src/main/java/janggi/domain/piece/EmptyPiece.java +++ b/src/main/java/janggi/domain/piece/EmptyPiece.java @@ -5,14 +5,10 @@ import java.util.List; @SuppressWarnings("java:S6548") -public class EmptyPiece extends Piece { +public class EmptyPiece implements Piece { private static final EmptyPiece INSTANCE = new EmptyPiece(); - private EmptyPiece() { - super(PieceType.EMPTY, Side.NONE); - } - public static EmptyPiece getInstance() { return INSTANCE; } @@ -26,4 +22,24 @@ public List calculateRoute(Location from, Location to) { public void detectCollision(List piecesOnPath) { throw new UnsupportedOperationException("빈 객체는 이동할 수 없습니다."); } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public boolean isPo() { + throw new UnsupportedOperationException("빈 객체는 포일 수 없습니다."); + } + + @Override + public boolean isSameSide(Side side) { + throw new UnsupportedOperationException("빈 객체는 진영이 존재하지 않습니다."); + } + + @Override + public PieceType getPieceType() { + return PieceType.EMPTY; + } } diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java deleted file mode 100644 index b0a7ecfbf9..0000000000 --- a/src/main/java/janggi/domain/piece/Gung.java +++ /dev/null @@ -1,29 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.rule.collision.CollisionDetector; -import janggi.domain.rule.collision.DefaultCollisionDetector; -import janggi.domain.rule.route.GungSeongRouteProvider; -import janggi.domain.rule.route.RouteProvider; -import java.util.List; - -public class Gung extends Piece { - - private static final RouteProvider ROUTE_PROVIDER = GungSeongRouteProvider.getInstance(); - private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); - - public Gung(Side side) { - super(PieceType.GUNG, side); - } - - @Override - public List calculateRoute(Location from, Location to) { - return ROUTE_PROVIDER.calculateRoute(pieceType, from, to); - } - - @Override - public void detectCollision(List piecesOnPath) { - COLLISION_DETECTOR.check(this, piecesOnPath); - } -} diff --git a/src/main/java/janggi/domain/piece/Jolbyeong.java b/src/main/java/janggi/domain/piece/Jolbyeong.java deleted file mode 100644 index 788dd46b51..0000000000 --- a/src/main/java/janggi/domain/piece/Jolbyeong.java +++ /dev/null @@ -1,58 +0,0 @@ -package janggi.domain.piece; - -import static janggi.domain.rule.route.Direction.LEFT; -import static janggi.domain.rule.route.Direction.RIGHT; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.rule.collision.CollisionDetector; -import janggi.domain.rule.collision.DefaultCollisionDetector; -import janggi.domain.rule.route.Direction; -import janggi.domain.rule.route.Route; -import janggi.domain.rule.route.RouteProvider; -import java.util.List; - -public class Jolbyeong extends Piece { - - private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); - - public Jolbyeong(Side side) { - super(determineType(side), side); - } - - private static PieceType determineType(Side side) { - if (side == Side.HAN) { - return PieceType.BYEONG; - } - if (side == Side.CHO) { - return PieceType.JOL; - } - throw new IllegalArgumentException("진영이 존재하지 않아 기물명을 정할 수 없습니다."); - } - - @Override - public List calculateRoute(Location from, Location to) { - List possibleRoutes = List.of( - Route.from(List.of(getFrontDirection())), - Route.from(List.of(LEFT)), - Route.from(List.of(RIGHT)) - ); - - return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); - } - - private Direction getFrontDirection() { - if (side == Side.HAN) { - return Direction.FRONT; - } - if (side == Side.CHO) { - return Direction.BACK; - } - throw new IllegalStateException("진영이 존재하지 않아 전진 방향을 정할 수 없습니다."); - } - - @Override - public void detectCollision(List piecesOnPath) { - COLLISION_DETECTOR.check(this, piecesOnPath); - } -} diff --git a/src/main/java/janggi/domain/piece/Ma.java b/src/main/java/janggi/domain/piece/Ma.java deleted file mode 100644 index 80383ab30c..0000000000 --- a/src/main/java/janggi/domain/piece/Ma.java +++ /dev/null @@ -1,47 +0,0 @@ -package janggi.domain.piece; - -import static janggi.domain.rule.route.Direction.BACK; -import static janggi.domain.rule.route.Direction.BACK_LEFT; -import static janggi.domain.rule.route.Direction.BACK_RIGHT; -import static janggi.domain.rule.route.Direction.FRONT; -import static janggi.domain.rule.route.Direction.FRONT_LEFT; -import static janggi.domain.rule.route.Direction.FRONT_RIGHT; -import static janggi.domain.rule.route.Direction.LEFT; -import static janggi.domain.rule.route.Direction.RIGHT; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.rule.collision.CollisionDetector; -import janggi.domain.rule.collision.DefaultCollisionDetector; -import janggi.domain.rule.route.Route; -import janggi.domain.rule.route.RouteProvider; -import java.util.List; - -public class Ma extends Piece { - - private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); - private static final List POSSIBLE_ROUTES = List.of( - Route.from(List.of(FRONT, FRONT_LEFT)), - Route.from(List.of(FRONT, FRONT_RIGHT)), - Route.from(List.of(RIGHT, FRONT_RIGHT)), - Route.from(List.of(RIGHT, BACK_RIGHT)), - Route.from(List.of(LEFT, FRONT_LEFT)), - Route.from(List.of(LEFT, BACK_LEFT)), - Route.from(List.of(BACK, BACK_LEFT)), - Route.from(List.of(BACK, BACK_RIGHT)) - ); - - public Ma(Side side) { - super(PieceType.MA, side); - } - - @Override - public List calculateRoute(Location from, Location to) { - return RouteProvider.findValidPath(pieceType, from, to, POSSIBLE_ROUTES); - } - - @Override - public void detectCollision(List piecesOnPath) { - COLLISION_DETECTOR.check(this, piecesOnPath); - } -} diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 6e49be0ceb..8af9562c3a 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -4,33 +4,17 @@ import janggi.domain.Side; import java.util.List; -public abstract class Piece { +public interface Piece { - protected final PieceType pieceType; - protected final Side side; + List calculateRoute(Location from, Location to); - protected Piece(PieceType pieceType, Side side) { - this.pieceType = pieceType; - this.side = side; - } + void detectCollision(List piecesOnPath); - public abstract List calculateRoute(Location from, Location to); + boolean isEmpty(); - public abstract void detectCollision(List piecesOnPath); + boolean isPo(); - public boolean isEmpty() { - return pieceType == PieceType.EMPTY; - } + boolean isSameSide(Side side); - public boolean isSameSide(Piece piece) { - return this.side.equals(piece.side); - } - - public boolean isSameSide(Side side) { - return this.side.equals(side); - } - - public PieceType getPieceType() { - return this.pieceType; - } + PieceType getPieceType(); } diff --git a/src/main/java/janggi/domain/piece/PieceFactory.java b/src/main/java/janggi/domain/piece/PieceFactory.java new file mode 100644 index 0000000000..84ecae1fe7 --- /dev/null +++ b/src/main/java/janggi/domain/piece/PieceFactory.java @@ -0,0 +1,51 @@ +package janggi.domain.piece; + +import static janggi.domain.piece.PieceType.*; + +import janggi.domain.Side; +import janggi.domain.rule.ChaMovement; +import janggi.domain.rule.GungMovement; +import janggi.domain.rule.JolbyeongMovement; +import janggi.domain.rule.MaMovement; +import janggi.domain.rule.Movement; +import janggi.domain.rule.PoMovement; +import janggi.domain.rule.SaMovement; +import janggi.domain.rule.SangMovement; +import java.util.EnumMap; +import java.util.Map; + +@SuppressWarnings("java:S6548") +public class PieceFactory { + + private static final PieceFactory INSTANCE = new PieceFactory(); + + private final Map matchInfo; + + private PieceFactory() { + this.matchInfo = new EnumMap<>(PieceType.class); + matchInfo.put(CHA, ChaMovement.getInstance()); + matchInfo.put(MA, MaMovement.getInstance()); + matchInfo.put(SANG, SangMovement.getInstance()); + matchInfo.put(SA, SaMovement.getInstance()); + matchInfo.put(GUNG, GungMovement.getInstance()); + matchInfo.put(PO, PoMovement.getInstance()); + matchInfo.put(JOL, JolbyeongMovement.getInstanceBySide(Side.CHO)); + matchInfo.put(BYEONG, JolbyeongMovement.getInstanceBySide(Side.HAN)); + } + + public static PieceFactory getInstance() { + return INSTANCE; + } + + public Piece createActivePiece(PieceType type, Side side) { + Movement movement = mapMovement(type); + return new ActivePiece(type, side, movement); + } + + private Movement mapMovement(PieceType type) { + if (matchInfo.containsKey(type)) { + return matchInfo.get(type); + } + throw new IllegalArgumentException("이동 규칙이 정의되지 않은 기물입니다 : " + type); + } +} diff --git a/src/main/java/janggi/domain/piece/PieceType.java b/src/main/java/janggi/domain/piece/PieceType.java index 858ff6fa66..d480415b4a 100644 --- a/src/main/java/janggi/domain/piece/PieceType.java +++ b/src/main/java/janggi/domain/piece/PieceType.java @@ -19,6 +19,6 @@ public enum PieceType { } public String getNameFormat() { - return nameFormat; + return this.nameFormat; } } diff --git a/src/main/java/janggi/domain/piece/Po.java b/src/main/java/janggi/domain/piece/Po.java deleted file mode 100644 index dd0fadd724..0000000000 --- a/src/main/java/janggi/domain/piece/Po.java +++ /dev/null @@ -1,30 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.rule.collision.CollisionDetector; -import janggi.domain.rule.collision.PoCollisionDetector; -import janggi.domain.rule.route.RouteProvider; -import janggi.domain.rule.route.StraightRouteProvider; -import java.util.List; - -public class Po extends Piece { - - private static final RouteProvider ROUTE_PROVIDER = StraightRouteProvider.getInstance(); - private static final CollisionDetector COLLISION_DETECTOR = PoCollisionDetector.getInstance(); - - public Po(Side side) { - super(PieceType.PO, side); - } - - @Override - public List calculateRoute(Location from, Location to) { - return ROUTE_PROVIDER.calculateRoute(pieceType, from, to); - } - - @Override - public void detectCollision(List piecesOnPath) { - COLLISION_DETECTOR.check(this, piecesOnPath); - } -} - diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java deleted file mode 100644 index 964853626e..0000000000 --- a/src/main/java/janggi/domain/piece/Sa.java +++ /dev/null @@ -1,29 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.rule.collision.CollisionDetector; -import janggi.domain.rule.collision.DefaultCollisionDetector; -import janggi.domain.rule.route.GungSeongRouteProvider; -import janggi.domain.rule.route.RouteProvider; -import java.util.List; - -public class Sa extends Piece { - - private static final RouteProvider ROUTE_PROVIDER = GungSeongRouteProvider.getInstance(); - private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); - - public Sa(Side side) { - super(PieceType.SA, side); - } - - @Override - public List calculateRoute(Location from, Location to) { - return ROUTE_PROVIDER.calculateRoute(pieceType, from, to); - } - - @Override - public void detectCollision(List piecesOnPath) { - COLLISION_DETECTOR.check(this, piecesOnPath); - } -} diff --git a/src/main/java/janggi/domain/piece/Sang.java b/src/main/java/janggi/domain/piece/Sang.java deleted file mode 100644 index 2b7acd117a..0000000000 --- a/src/main/java/janggi/domain/piece/Sang.java +++ /dev/null @@ -1,47 +0,0 @@ -package janggi.domain.piece; - -import static janggi.domain.rule.route.Direction.BACK; -import static janggi.domain.rule.route.Direction.BACK_LEFT; -import static janggi.domain.rule.route.Direction.BACK_RIGHT; -import static janggi.domain.rule.route.Direction.FRONT; -import static janggi.domain.rule.route.Direction.FRONT_LEFT; -import static janggi.domain.rule.route.Direction.FRONT_RIGHT; -import static janggi.domain.rule.route.Direction.LEFT; -import static janggi.domain.rule.route.Direction.RIGHT; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.rule.collision.CollisionDetector; -import janggi.domain.rule.collision.DefaultCollisionDetector; -import janggi.domain.rule.route.Route; -import janggi.domain.rule.route.RouteProvider; -import java.util.List; - -public class Sang extends Piece { - - private static final CollisionDetector COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); - private static final List POSSIBLE_ROUTES = List.of( - Route.from(List.of(FRONT, FRONT_LEFT, FRONT_LEFT)), - Route.from(List.of(FRONT, FRONT_RIGHT, FRONT_RIGHT)), - Route.from(List.of(RIGHT, FRONT_RIGHT, FRONT_RIGHT)), - Route.from(List.of(RIGHT, BACK_RIGHT, BACK_RIGHT)), - Route.from(List.of(LEFT, FRONT_LEFT, FRONT_LEFT)), - Route.from(List.of(LEFT, BACK_LEFT, BACK_LEFT)), - Route.from(List.of(BACK, BACK_LEFT, BACK_LEFT)), - Route.from(List.of(BACK, BACK_RIGHT, BACK_RIGHT)) - ); - - public Sang(Side side) { - super(PieceType.SANG, side); - } - - @Override - public List calculateRoute(Location from, Location to) { - return RouteProvider.findValidPath(pieceType, from, to, POSSIBLE_ROUTES); - } - - @Override - public void detectCollision(List piecesOnPath) { - COLLISION_DETECTOR.check(this, piecesOnPath); - } -} diff --git a/src/main/java/janggi/domain/rule/ChaMovement.java b/src/main/java/janggi/domain/rule/ChaMovement.java new file mode 100644 index 0000000000..4125667a00 --- /dev/null +++ b/src/main/java/janggi/domain/rule/ChaMovement.java @@ -0,0 +1,39 @@ +package janggi.domain.rule; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; +import janggi.domain.rule.route.RouteProvider; +import janggi.domain.rule.route.StraightRouteProvider; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("java:S6548") +public class ChaMovement implements Movement { + + private static final ChaMovement INSTANCE = new ChaMovement(); + + private final RouteProvider routeProvider; + private final CollisionDetector collisionDetector; + + private ChaMovement() { + this.routeProvider = StraightRouteProvider.getInstance(); + this.collisionDetector = DefaultCollisionDetector.getInstance(); + } + + public static ChaMovement getInstance() { + return INSTANCE; + } + + @Override + public Optional> calculateRoute(Location from, Location to) { + return routeProvider.calculateRoute(from, to); + } + + @Override + public void detectCollision(Side side, List piecesOnPath) { + collisionDetector.check(side, piecesOnPath); + } +} diff --git a/src/main/java/janggi/domain/rule/GungMovement.java b/src/main/java/janggi/domain/rule/GungMovement.java new file mode 100644 index 0000000000..5418286d4c --- /dev/null +++ b/src/main/java/janggi/domain/rule/GungMovement.java @@ -0,0 +1,39 @@ +package janggi.domain.rule; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; +import janggi.domain.rule.route.GungSeongRouteProvider; +import janggi.domain.rule.route.RouteProvider; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("java:S6548") +public class GungMovement implements Movement { + + private static final GungMovement INSTANCE = new GungMovement(); + + private final RouteProvider routeProvider; + private final CollisionDetector collisionDetector; + + private GungMovement() { + this.routeProvider = GungSeongRouteProvider.getInstance(); + this.collisionDetector = DefaultCollisionDetector.getInstance(); + } + + public static GungMovement getInstance() { + return INSTANCE; + } + + @Override + public Optional> calculateRoute(Location from, Location to) { + return routeProvider.calculateRoute(from, to); + } + + @Override + public void detectCollision(Side side, List piecesOnPath) { + collisionDetector.check(side, piecesOnPath); + } +} diff --git a/src/main/java/janggi/domain/rule/JolbyeongMovement.java b/src/main/java/janggi/domain/rule/JolbyeongMovement.java new file mode 100644 index 0000000000..7c8bba24c6 --- /dev/null +++ b/src/main/java/janggi/domain/rule/JolbyeongMovement.java @@ -0,0 +1,66 @@ +package janggi.domain.rule; + +import static janggi.domain.rule.route.Direction.LEFT; +import static janggi.domain.rule.route.Direction.RIGHT; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; +import janggi.domain.rule.route.DefaultRouteProvider; +import janggi.domain.rule.route.Direction; +import janggi.domain.rule.route.Route; +import janggi.domain.rule.route.RouteProvider; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("java:S6548") +public class JolbyeongMovement implements Movement { + + private static final JolbyeongMovement JOL_MOVEMENT_INSTANCE = new JolbyeongMovement(Side.CHO); + private static final JolbyeongMovement BYEONG_MOVEMENT_INSTANCE = new JolbyeongMovement(Side.HAN); + + private final RouteProvider routeProvider; + private final CollisionDetector collisionDetector = DefaultCollisionDetector.getInstance(); + + private JolbyeongMovement(Side side) { + List possibleRoutes = List.of( + Route.from(List.of(getFrontDirection(side))), + Route.from(List.of(LEFT)), + Route.from(List.of(RIGHT)) + ); + + this.routeProvider = new DefaultRouteProvider(possibleRoutes); + } + + private static Direction getFrontDirection(Side side) { + if (side == Side.HAN) { + return Direction.FRONT; + } + if (side == Side.CHO) { + return Direction.BACK; + } + throw new IllegalStateException("진영이 존재하지 않아 전진 방향을 정할 수 없습니다."); + } + + public static JolbyeongMovement getInstanceBySide(Side side) { + if (side == Side.HAN) { + return BYEONG_MOVEMENT_INSTANCE; + } + if (side == Side.CHO) { + return JOL_MOVEMENT_INSTANCE; + } + throw new IllegalArgumentException("진영이 존재하지 않아 이동 규칙을 정할 수 없습니다."); + } + + @Override + public Optional> calculateRoute(Location from, Location to) { + return routeProvider.calculateRoute(from, to); + } + + @Override + public void detectCollision(Side side, List piecesOnPath) { + collisionDetector.check(side, piecesOnPath); + } +} diff --git a/src/main/java/janggi/domain/rule/MaMovement.java b/src/main/java/janggi/domain/rule/MaMovement.java new file mode 100644 index 0000000000..0b202daffc --- /dev/null +++ b/src/main/java/janggi/domain/rule/MaMovement.java @@ -0,0 +1,59 @@ +package janggi.domain.rule; + +import static janggi.domain.rule.route.Direction.BACK; +import static janggi.domain.rule.route.Direction.BACK_LEFT; +import static janggi.domain.rule.route.Direction.BACK_RIGHT; +import static janggi.domain.rule.route.Direction.FRONT; +import static janggi.domain.rule.route.Direction.FRONT_LEFT; +import static janggi.domain.rule.route.Direction.FRONT_RIGHT; +import static janggi.domain.rule.route.Direction.LEFT; +import static janggi.domain.rule.route.Direction.RIGHT; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; +import janggi.domain.rule.route.DefaultRouteProvider; +import janggi.domain.rule.route.Route; +import janggi.domain.rule.route.RouteProvider; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("java:S6548") +public class MaMovement implements Movement { + + private static final MaMovement INSTANCE = new MaMovement(); + + private final RouteProvider routeProvider; + private final CollisionDetector collisionDetector; + + private MaMovement() { + List possibleRoutes = List.of( + Route.from(List.of(FRONT, FRONT_LEFT)), + Route.from(List.of(FRONT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, BACK_RIGHT)), + Route.from(List.of(LEFT, FRONT_LEFT)), + Route.from(List.of(LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_LEFT)), + Route.from(List.of(BACK, BACK_RIGHT)) + ); + this.routeProvider = new DefaultRouteProvider(possibleRoutes); + this.collisionDetector = DefaultCollisionDetector.getInstance(); + } + + public static MaMovement getInstance() { + return INSTANCE; + } + + @Override + public Optional> calculateRoute(Location from, Location to) { + return routeProvider.calculateRoute(from, to); + } + + @Override + public void detectCollision(Side side, List piecesOnPath) { + collisionDetector.check(side, piecesOnPath); + } +} diff --git a/src/main/java/janggi/domain/rule/Movement.java b/src/main/java/janggi/domain/rule/Movement.java new file mode 100644 index 0000000000..462fae1c77 --- /dev/null +++ b/src/main/java/janggi/domain/rule/Movement.java @@ -0,0 +1,14 @@ +package janggi.domain.rule; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import java.util.List; +import java.util.Optional; + +public interface Movement { + + Optional> calculateRoute(Location from, Location to); + + void detectCollision(Side side, List piecesOnPath); +} diff --git a/src/main/java/janggi/domain/rule/PoMovement.java b/src/main/java/janggi/domain/rule/PoMovement.java new file mode 100644 index 0000000000..588d27981b --- /dev/null +++ b/src/main/java/janggi/domain/rule/PoMovement.java @@ -0,0 +1,40 @@ +package janggi.domain.rule; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.PoCollisionDetector; +import janggi.domain.rule.route.RouteProvider; +import janggi.domain.rule.route.StraightRouteProvider; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("java:S6548") +public class PoMovement implements Movement { + + private static final PoMovement INSTANCE = new PoMovement(); + + private final RouteProvider routeProvider; + private final CollisionDetector collisionDetector; + + private PoMovement() { + this.routeProvider = StraightRouteProvider.getInstance(); + this.collisionDetector = PoCollisionDetector.getInstance(); + } + + public static PoMovement getInstance() { + return INSTANCE; + } + + @Override + public Optional> calculateRoute(Location from, Location to) { + return routeProvider.calculateRoute(from, to); + } + + @Override + public void detectCollision(Side side, List piecesOnPath) { + collisionDetector.check(side, piecesOnPath); + } +} + diff --git a/src/main/java/janggi/domain/rule/SaMovement.java b/src/main/java/janggi/domain/rule/SaMovement.java new file mode 100644 index 0000000000..9b20cb56ed --- /dev/null +++ b/src/main/java/janggi/domain/rule/SaMovement.java @@ -0,0 +1,39 @@ +package janggi.domain.rule; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; +import janggi.domain.rule.route.GungSeongRouteProvider; +import janggi.domain.rule.route.RouteProvider; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("java:S6548") +public class SaMovement implements Movement { + + private static final SaMovement INSTANCE = new SaMovement(); + + private final RouteProvider routeProvider; + private final CollisionDetector collisionDetector; + + private SaMovement() { + this.routeProvider = GungSeongRouteProvider.getInstance(); + this.collisionDetector = DefaultCollisionDetector.getInstance(); + } + + public static SaMovement getInstance() { + return INSTANCE; + } + + @Override + public Optional> calculateRoute(Location from, Location to) { + return routeProvider.calculateRoute(from, to); + } + + @Override + public void detectCollision(Side side, List piecesOnPath) { + collisionDetector.check(side, piecesOnPath); + } +} diff --git a/src/main/java/janggi/domain/rule/SangMovement.java b/src/main/java/janggi/domain/rule/SangMovement.java new file mode 100644 index 0000000000..d62c3744ee --- /dev/null +++ b/src/main/java/janggi/domain/rule/SangMovement.java @@ -0,0 +1,59 @@ +package janggi.domain.rule; + +import static janggi.domain.rule.route.Direction.BACK; +import static janggi.domain.rule.route.Direction.BACK_LEFT; +import static janggi.domain.rule.route.Direction.BACK_RIGHT; +import static janggi.domain.rule.route.Direction.FRONT; +import static janggi.domain.rule.route.Direction.FRONT_LEFT; +import static janggi.domain.rule.route.Direction.FRONT_RIGHT; +import static janggi.domain.rule.route.Direction.LEFT; +import static janggi.domain.rule.route.Direction.RIGHT; + +import janggi.domain.Location; +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.rule.collision.CollisionDetector; +import janggi.domain.rule.collision.DefaultCollisionDetector; +import janggi.domain.rule.route.DefaultRouteProvider; +import janggi.domain.rule.route.Route; +import janggi.domain.rule.route.RouteProvider; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("java:S6548") +public class SangMovement implements Movement { + + private static final SangMovement INSTANCE = new SangMovement(); + + private final RouteProvider routeProvider; + private final CollisionDetector collisionDetector; + + private SangMovement() { + List possibleRoutes = List.of( + Route.from(List.of(FRONT, FRONT_LEFT, FRONT_LEFT)), + Route.from(List.of(FRONT, FRONT_RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, FRONT_RIGHT, FRONT_RIGHT)), + Route.from(List.of(RIGHT, BACK_RIGHT, BACK_RIGHT)), + Route.from(List.of(LEFT, FRONT_LEFT, FRONT_LEFT)), + Route.from(List.of(LEFT, BACK_LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_LEFT, BACK_LEFT)), + Route.from(List.of(BACK, BACK_RIGHT, BACK_RIGHT)) + ); + this.routeProvider = new DefaultRouteProvider(possibleRoutes); + this.collisionDetector = DefaultCollisionDetector.getInstance(); + } + + public static SangMovement getInstance() { + return INSTANCE; + } + + @Override + public Optional> calculateRoute(Location from, Location to) { + return routeProvider.calculateRoute(from, to); + } + + @Override + public void detectCollision(Side side, List piecesOnPath) { + collisionDetector.check(side, piecesOnPath); + } +} diff --git a/src/main/java/janggi/domain/rule/collision/CollisionDetector.java b/src/main/java/janggi/domain/rule/collision/CollisionDetector.java index 9d94fe4f73..5f7b4ae25b 100644 --- a/src/main/java/janggi/domain/rule/collision/CollisionDetector.java +++ b/src/main/java/janggi/domain/rule/collision/CollisionDetector.java @@ -1,9 +1,10 @@ package janggi.domain.rule.collision; +import janggi.domain.Side; import janggi.domain.piece.Piece; import java.util.List; public interface CollisionDetector { - void check(Piece piece, List piecesOnPath); + void check(Side side, List piecesOnPath); } diff --git a/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java b/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java index dda6495c28..c91d475163 100644 --- a/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java +++ b/src/main/java/janggi/domain/rule/collision/DefaultCollisionDetector.java @@ -1,5 +1,6 @@ package janggi.domain.rule.collision; +import janggi.domain.Side; import janggi.domain.piece.Piece; import java.util.List; @@ -16,9 +17,9 @@ public static DefaultCollisionDetector getInstance() { } @Override - public void check(Piece piece, List piecesOnPath) { + public void check(Side side, List piecesOnPath) { validateMiddlePath(piecesOnPath); - validateDestination(piece, piecesOnPath); + validateDestination(side, piecesOnPath); } private void validateMiddlePath(List piecesOnPath) { @@ -34,9 +35,12 @@ private void validateNoObstacle(Piece pathPiece) { } } - private void validateDestination(Piece piece, List piecesOnPath) { + private void validateDestination(Side side, List piecesOnPath) { Piece destinationPiece = piecesOnPath.getLast(); - if (destinationPiece.isSameSide(piece)) { + if (destinationPiece.isEmpty()) { + return; + } + if (destinationPiece.isSameSide(side)) { throw new IllegalArgumentException("도착 위치에 같은 팀이 존재합니다"); } } diff --git a/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java b/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java index b36ddd9274..59721a1d44 100644 --- a/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java +++ b/src/main/java/janggi/domain/rule/collision/PoCollisionDetector.java @@ -1,5 +1,6 @@ package janggi.domain.rule.collision; +import janggi.domain.Side; import janggi.domain.piece.Piece; import java.util.List; @@ -17,10 +18,10 @@ public static PoCollisionDetector getInstance() { } @Override - public void check(Piece piece, List piecesOnPath) { + public void check(Side side, List piecesOnPath) { validateOneObstacle(piecesOnPath); - validatePoExistence(piece, piecesOnPath); - validateDestination(piece, piecesOnPath); + validatePoExistence(piecesOnPath); + validateDestination(side, piecesOnPath); } private void validateOneObstacle(List piecesOnPath) { @@ -35,21 +36,27 @@ private void validateOneObstacle(List piecesOnPath) { } } - private void validatePoExistence(Piece self, List piecesOnPath) { + private void validatePoExistence(List piecesOnPath) { for (Piece piece : piecesOnPath) { - validateNonePo(self, piece); + validateNonePo(piece); } } - private void validateNonePo(Piece self, Piece piece) { - if (self.getClass() == piece.getClass()) { + private void validateNonePo(Piece pieceOnPath) { + if (pieceOnPath.isEmpty()) { + return; + } + if (pieceOnPath.isPo()) { throw new IllegalArgumentException("이동 경로 또는 도착지에 포가 존재할 수 없습니다."); } } - private void validateDestination(Piece piece, List piecesOnPath) { + private void validateDestination(Side side, List piecesOnPath) { Piece destinationPiece = piecesOnPath.getLast(); - if (destinationPiece.isSameSide(piece)) { + if (destinationPiece.isEmpty()) { + return; + } + if (destinationPiece.isSameSide(side)) { throw new IllegalArgumentException("도착 위치에 같은 팀이 존재합니다"); } } diff --git a/src/main/java/janggi/domain/rule/route/DefaultRouteProvider.java b/src/main/java/janggi/domain/rule/route/DefaultRouteProvider.java new file mode 100644 index 0000000000..15b059dfe2 --- /dev/null +++ b/src/main/java/janggi/domain/rule/route/DefaultRouteProvider.java @@ -0,0 +1,19 @@ +package janggi.domain.rule.route; + +import janggi.domain.Location; +import java.util.List; +import java.util.Optional; + +public class DefaultRouteProvider extends RouteProvider { + + private final List possibleRoute; + + public DefaultRouteProvider(List possibleRoute) { + this.possibleRoute = possibleRoute; + } + + @Override + public Optional> calculateRoute(Location from, Location to) { + return findValidPath(from, to, possibleRoute); + } +} diff --git a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java index 00464462b3..9e15ce9c04 100644 --- a/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/GungSeongRouteProvider.java @@ -10,11 +10,11 @@ import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; -import janggi.domain.piece.PieceType; import java.util.List; +import java.util.Optional; @SuppressWarnings("java:S6548") -public class GungSeongRouteProvider implements RouteProvider { +public class GungSeongRouteProvider extends RouteProvider { private static final GungSeongRouteProvider INSTANCE = new GungSeongRouteProvider(); private static final List POSSIBLE_ROUTES = List.of( @@ -36,7 +36,7 @@ public static GungSeongRouteProvider getInstance() { } @Override - public List calculateRoute(PieceType pieceType, Location from, Location to) { - return RouteProvider.findValidPath(pieceType, from, to, POSSIBLE_ROUTES); + public Optional> calculateRoute(Location from, Location to) { + return findValidPath(from, to, POSSIBLE_ROUTES); } } diff --git a/src/main/java/janggi/domain/rule/route/RouteProvider.java b/src/main/java/janggi/domain/rule/route/RouteProvider.java index d1e1c24d99..307b1beef6 100644 --- a/src/main/java/janggi/domain/rule/route/RouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/RouteProvider.java @@ -1,27 +1,21 @@ package janggi.domain.rule.route; import janggi.domain.Location; -import janggi.domain.piece.PieceType; import java.util.List; +import java.util.Optional; -public interface RouteProvider { +public abstract class RouteProvider { - static List findValidPath(PieceType pieceType, Location from, Location to, List possibleRoutes) { + public abstract Optional> calculateRoute(Location from, Location to); + + protected Optional> findValidPath(Location from, Location to, List possibleRoutes) { for (Route route : possibleRoutes) { List locationsOnPath = route.calculateLocationsOnPath(from); Location expectedDestination = locationsOnPath.getLast(); if (expectedDestination.equals(to)) { - return locationsOnPath; + return Optional.of(locationsOnPath); } } - - throw new IllegalArgumentException( - String.format("%s의 기물 이동 규칙 위반: 해당 위치(%d, %d)에 도달할 수 없습니다.", - pieceType.getNameFormat(), - to.x(), - to.y()) - ); + return Optional.empty(); } - - List calculateRoute(PieceType pieceType, Location from, Location to); } diff --git a/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java index f9bbed0a25..e54f3a747c 100644 --- a/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java +++ b/src/main/java/janggi/domain/rule/route/StraightRouteProvider.java @@ -6,11 +6,11 @@ import static janggi.domain.rule.route.Direction.RIGHT; import janggi.domain.Location; -import janggi.domain.piece.PieceType; import java.util.List; +import java.util.Optional; @SuppressWarnings("java:S6548") -public class StraightRouteProvider implements RouteProvider { +public class StraightRouteProvider extends RouteProvider { private static final StraightRouteProvider INSTANCE = new StraightRouteProvider(); @@ -22,7 +22,7 @@ public static StraightRouteProvider getInstance() { } @Override - public List calculateRoute(PieceType pieceType, Location from, Location to) { + public Optional> calculateRoute(Location from, Location to) { int maxDistance = calculateMaxDistance(from, to); List possibleRoutes = List.of( @@ -32,7 +32,7 @@ public List calculateRoute(PieceType pieceType, Location from, Locatio Route.of(RIGHT, maxDistance) ); - return RouteProvider.findValidPath(pieceType, from, to, possibleRoutes); + return findValidPath(from, to, possibleRoutes); } private int calculateMaxDistance(Location from, Location to) { diff --git a/src/main/java/janggi/strategy/ArrangementStrategy.java b/src/main/java/janggi/strategy/ArrangementStrategy.java index 7dfbd4284e..f6ce1fc2a5 100644 --- a/src/main/java/janggi/strategy/ArrangementStrategy.java +++ b/src/main/java/janggi/strategy/ArrangementStrategy.java @@ -1,9 +1,8 @@ package janggi.strategy; -import janggi.domain.Side; import janggi.domain.piece.Piece; public interface ArrangementStrategy { - void place(Piece[][] arrangement, Side side); + void place(Piece[][] arrangement); } diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java index 7d2549e9ea..ebe8674887 100644 --- a/src/main/java/janggi/strategy/BoardAssembler.java +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -1,31 +1,30 @@ package janggi.strategy; -import janggi.domain.Side; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; +import java.util.List; public class BoardAssembler { private static final int DEFAULT_ROWS = 10; private static final int DEFAULT_COLS = 9; - private final ArrangementStrategy hanStrategy; - private final ArrangementStrategy choStrategy; + private final List strategies; - private BoardAssembler(ArrangementStrategy hanStrategy, ArrangementStrategy choStrategy) { - this.hanStrategy = hanStrategy; - this.choStrategy = choStrategy; + private BoardAssembler(List strategies) { + this.strategies = strategies; } - public static BoardAssembler of(ArrangementStrategy hanStrategy, ArrangementStrategy choStrategy) { - return new BoardAssembler(hanStrategy, choStrategy); + public static BoardAssembler from(List strategies) { + return new BoardAssembler(strategies); } public Piece[][] assemble() { Piece[][] arrangement = new Piece[DEFAULT_ROWS][DEFAULT_COLS]; - hanStrategy.place(arrangement, Side.HAN); - choStrategy.place(arrangement, Side.CHO); + for (ArrangementStrategy strategy : strategies) { + strategy.place(arrangement); + } setupEmptyPieces(arrangement); diff --git a/src/main/java/janggi/strategy/MaSangArrangementStrategy.java b/src/main/java/janggi/strategy/MaSangArrangementStrategy.java new file mode 100644 index 0000000000..0f611b751a --- /dev/null +++ b/src/main/java/janggi/strategy/MaSangArrangementStrategy.java @@ -0,0 +1,115 @@ +package janggi.strategy; + +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceFactory; +import janggi.domain.piece.PieceType; +import java.util.List; + +public class MaSangArrangementStrategy implements ArrangementStrategy { + + private static final List COLS_OF_MA_SANG = List.of(1, 2, 6, 7); + private static final PieceFactory FACTORY = PieceFactory.getInstance(); + + private final Side side; + private final List maSangArrangement; + + private MaSangArrangementStrategy(Side side, List maSangArrangement) { + this.side = side; + this.maSangArrangement = maSangArrangement; + } + + public static MaSangArrangementStrategy of(Side side, List maSangArrangement) { + return new MaSangArrangementStrategy(side, maSangArrangement); + } + + @Override + public void place(Piece[][] arrangement) { + placeDefaultPieces(arrangement, side); + placeVariablePieces(arrangement, side); + } + + private void placeVariablePieces(Piece[][] arrangement, Side side) { + int boardMaxLength = arrangement.length; + int row = calculateInitialRow(boardMaxLength, side); + + for (int index = 0; index < COLS_OF_MA_SANG.size(); index++) { + int col = COLS_OF_MA_SANG.get(index); + PieceType pieceType = maSangArrangement.get(index); + arrangement[row][col] = FACTORY.createActivePiece(pieceType, side); + } + } + + private int calculateInitialRow(int boardMaxLength, Side side) { + if (side.equals(Side.HAN)) { + return 0; + } + if (side.equals(Side.CHO)) { + return boardMaxLength - 1; + } + throw new IllegalArgumentException("진영이 존재하지 않아 시작 행을 찾을 수 없습니다."); + } + + private void placeDefaultPieces(Piece[][] grid, Side side) { + for (DefaultPieceFactory factory : DefaultPieceFactory.values()) { + setUpPiece(grid, side, factory); + } + } + + private void setUpPiece(Piece[][] grid, Side side, DefaultPieceFactory factory) { + int height = grid.length; + for (int col : factory.getCols()) { + int row = factory.getRow(side, height); + grid[row][col] = factory.createPiece(side); + } + } + + private enum DefaultPieceFactory { + CHA(0, List.of(0, 8), PieceType.CHA), + SA(0, List.of(3, 5), PieceType.SA), + GUNG(1, List.of(4), PieceType.GUNG), + PO(2, List.of(1, 7), PieceType.PO), + JOLBYEONG(3, List.of(0, 2, 4, 6, 8), PieceType.JOL); + + private final int row; + private final List cols; + private final PieceType pieceType; + + DefaultPieceFactory(int row, List cols, PieceType pieceType) { + this.row = row; + this.cols = cols; + this.pieceType = pieceType; + } + + private Piece createPiece(Side side) { + if (this == JOLBYEONG) { + return FACTORY.createActivePiece(getJolOrByeongBySide(side), side); + } + return FACTORY.createActivePiece(this.pieceType, side); + } + + private PieceType getJolOrByeongBySide(Side side) { + if (side == Side.HAN) { + return PieceType.BYEONG; + } + if (side == Side.CHO) { + return PieceType.JOL; + } + throw new IllegalArgumentException("진영이 존재하지 않아 기물명을 정할 수 없습니다."); + } + + private int getRow(Side side, int height) { + if (side.equals(Side.HAN)) { + return row; + } + if (side.equals(Side.CHO)) { + return height - row - 1; + } + throw new UnsupportedOperationException("진영이 존재하지 않아 시작행을 선택할 수 없습니다."); + } + + private List getCols() { + return cols; + } + } +} diff --git a/src/main/java/janggi/strategy/MaSangArrangementTemplate.java b/src/main/java/janggi/strategy/MaSangArrangementTemplate.java deleted file mode 100644 index 0b1e78013c..0000000000 --- a/src/main/java/janggi/strategy/MaSangArrangementTemplate.java +++ /dev/null @@ -1,76 +0,0 @@ -package janggi.strategy; - -import janggi.domain.Side; -import janggi.domain.piece.Cha; -import janggi.domain.piece.Gung; -import janggi.domain.piece.Jolbyeong; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Po; -import janggi.domain.piece.Sa; -import java.util.List; -import java.util.function.Function; - -public abstract class MaSangArrangementTemplate implements ArrangementStrategy { - - @Override - public void place(Piece[][] arrangement, Side side) { - placeDefaultPieces(arrangement, side); - placeVariablePieces(arrangement, side); - } - - protected abstract void placeVariablePieces(Piece[][] arrangement, Side side); - - protected int calculateInitialRow(int boardMaxLength, Side side) { - if (side.equals(Side.CHO)) { - return boardMaxLength - 1; - } - return 0; - } - - private void placeDefaultPieces(Piece[][] grid, Side side) { - for (DefaultPieceFactory factory : DefaultPieceFactory.values()) { - setUpPiece(grid, side, factory); - } - } - - private void setUpPiece(Piece[][] grid, Side side, DefaultPieceFactory factory) { - int height = grid.length; - for (int col : factory.getCols()) { - int row = factory.getRow(side, height); - grid[row][col] = factory.createPiece(side); - } - } - - private enum DefaultPieceFactory { - CHA(0, List.of(0, 8), Cha::new), - SA(0, List.of(3, 5), Sa::new), - GUNG(1, List.of(4), Gung::new), - PO(2, List.of(1, 7), Po::new), - JOL(3, List.of(0, 2, 4, 6, 8), Jolbyeong::new); - - private final int row; - private final List cols; - private final Function pieceClass; - - DefaultPieceFactory(int row, List cols, Function pieceClass) { - this.row = row; - this.cols = cols; - this.pieceClass = pieceClass; - } - - private Piece createPiece(Side side) { - return pieceClass.apply(side); - } - - private int getRow(Side side, int height) { - if (side.equals(Side.CHO)) { - return height - row - 1; - } - return row; - } - - private List getCols() { - return cols; - } - } -} diff --git a/src/main/java/janggi/strategy/MaSangMaSangStrategy.java b/src/main/java/janggi/strategy/MaSangMaSangStrategy.java deleted file mode 100644 index 459d4fe592..0000000000 --- a/src/main/java/janggi/strategy/MaSangMaSangStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -package janggi.strategy; - -import janggi.domain.Side; -import janggi.domain.piece.Ma; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; - -@SuppressWarnings("java:S6548") -public class MaSangMaSangStrategy extends MaSangArrangementTemplate { - - private static final MaSangMaSangStrategy INSTANCE = new MaSangMaSangStrategy(); - - public static MaSangMaSangStrategy getInstance() { - return INSTANCE; - } - - @Override - protected void placeVariablePieces(Piece[][] board, Side side) { - int boardMaxLength = board.length; - int row = calculateInitialRow(boardMaxLength, side); - board[row][1] = new Ma(side); - board[row][2] = new Sang(side); - board[row][6] = new Ma(side); - board[row][7] = new Sang(side); - } -} diff --git a/src/main/java/janggi/strategy/MaSangSangMaStrategy.java b/src/main/java/janggi/strategy/MaSangSangMaStrategy.java deleted file mode 100644 index a6758d3d69..0000000000 --- a/src/main/java/janggi/strategy/MaSangSangMaStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -package janggi.strategy; - -import janggi.domain.Side; -import janggi.domain.piece.Ma; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; - -@SuppressWarnings("java:S6548") -public class MaSangSangMaStrategy extends MaSangArrangementTemplate { - - private static final MaSangSangMaStrategy INSTANCE = new MaSangSangMaStrategy(); - - public static MaSangSangMaStrategy getInstance() { - return INSTANCE; - } - - @Override - protected void placeVariablePieces(Piece[][] board, Side side) { - int boardMaxLength = board.length; - int row = calculateInitialRow(boardMaxLength, side); - board[row][1] = new Ma(side); - board[row][2] = new Sang(side); - board[row][6] = new Sang(side); - board[row][7] = new Ma(side); - } -} diff --git a/src/main/java/janggi/strategy/SangMaMaSangStrategy.java b/src/main/java/janggi/strategy/SangMaMaSangStrategy.java deleted file mode 100644 index 3445f97d0d..0000000000 --- a/src/main/java/janggi/strategy/SangMaMaSangStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -package janggi.strategy; - -import janggi.domain.Side; -import janggi.domain.piece.Ma; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; - -@SuppressWarnings("java:S6548") -public class SangMaMaSangStrategy extends MaSangArrangementTemplate { - - private static final SangMaMaSangStrategy INSTANCE = new SangMaMaSangStrategy(); - - public static SangMaMaSangStrategy getInstance() { - return INSTANCE; - } - - @Override - protected void placeVariablePieces(Piece[][] board, Side side) { - int boardMaxLength = board.length; - int row = calculateInitialRow(boardMaxLength, side); - board[row][1] = new Sang(side); - board[row][2] = new Ma(side); - board[row][6] = new Ma(side); - board[row][7] = new Sang(side); - } -} diff --git a/src/main/java/janggi/strategy/SangMaSangMaStrategy.java b/src/main/java/janggi/strategy/SangMaSangMaStrategy.java deleted file mode 100644 index 8ab45b971e..0000000000 --- a/src/main/java/janggi/strategy/SangMaSangMaStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -package janggi.strategy; - -import janggi.domain.Side; -import janggi.domain.piece.Ma; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; - -@SuppressWarnings("java:S6548") -public class SangMaSangMaStrategy extends MaSangArrangementTemplate { - - private static final SangMaSangMaStrategy INSTANCE = new SangMaSangMaStrategy(); - - public static SangMaSangMaStrategy getInstance() { - return INSTANCE; - } - - @Override - protected void placeVariablePieces(Piece[][] board, Side side) { - int boardMaxLength = board.length; - int row = calculateInitialRow(boardMaxLength, side); - board[row][1] = new Sang(side); - board[row][2] = new Ma(side); - board[row][6] = new Sang(side); - board[row][7] = new Ma(side); - } -} diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 81977147d4..16de29566e 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -6,9 +6,10 @@ import janggi.domain.Side; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; -import janggi.strategy.MaSangArrangementTemplate; +import janggi.domain.piece.PieceType; +import janggi.strategy.ArrangementStrategy; import janggi.strategy.BoardAssembler; -import janggi.support.TestMaSangArrangementTemplate; +import janggi.support.TestArrangementStrategy; import janggi.support.TestPiece; import java.util.List; import java.util.Map; @@ -18,28 +19,29 @@ import org.junit.jupiter.api.Test; class BoardTest { + + private static final Piece EMPTY = EmptyPiece.getInstance(); @Test @DisplayName("보드가 전략에 맞춰 정상적으로 생성된다.") void shouldReturnBoardWithSelectedArrangementStrategy() { // given Side currentSide = Side.HAN; - Piece testPiece = new TestPiece(currentSide); - Piece emptyPiece = EmptyPiece.getInstance(); + Piece testPiece = new TestPiece(PieceType.CHA, currentSide); Map initialPieces = Map.of( new Location(1, 1), testPiece, - new Location(2, 2), emptyPiece + new Location(2, 2), EMPTY ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); // when - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); List> pieces = board.to2DArray(); // then Assertions.assertThat(pieces.get(1).get(1)).isEqualTo(testPiece); - Assertions.assertThat(pieces.get(2).get(2)).isEqualTo(emptyPiece); + Assertions.assertThat(pieces.get(2).get(2)).isEqualTo(EMPTY); } @Nested @@ -50,11 +52,11 @@ void shouldNotThrowExceptionWhenLocationExists() { // given Side currentSide = Side.HAN; Map initialPieces = Map.of( - new Location(1, 1), new TestPiece(currentSide) + new Location(1, 1), new TestPiece(PieceType.CHA, currentSide) ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); Location location = Location.from(List.of(1, 1)); @@ -68,11 +70,11 @@ void shouldThrowExceptionWhenLocationDoesNotExist() { // given Side currentSide = Side.HAN; Map initialPieces = Map.of( - new Location(1, 1), new TestPiece(currentSide) + new Location(1, 1), new TestPiece(PieceType.CHA, currentSide) ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); Location location = Location.from(List.of(11, 11)); @@ -90,11 +92,11 @@ void shouldNotThrowExceptionWhenPieceOnSameSideExistsAtLocation() { // given Side currentSide = Side.HAN; Map initialPieces = Map.of( - new Location(1, 1), new TestPiece(currentSide) + new Location(1, 1), new TestPiece(PieceType.CHA, currentSide) ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); Location location = Location.from(List.of(1, 1)); @@ -109,11 +111,11 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { Side currentSide = Side.HAN; Side otherSide = Side.CHO; Map initialPieces = Map.of( - new Location(1, 1), new TestPiece(currentSide) + new Location(1, 1), new TestPiece(PieceType.CHA, currentSide) ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); Location location = Location.from(List.of(1, 1)); @@ -127,11 +129,11 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { void shouldThrowExceptionWhenPieceDoesNotExistsAtLocation() { // given Map initialPieces = Map.of( - new Location(5, 5), EmptyPiece.getInstance() + new Location(5, 5), EMPTY ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); Location location = Location.from(List.of(5, 5)); @@ -148,11 +150,11 @@ class ValidateLocationToMoveTest { void shouldNotThrowExceptionWhenPieceDoesNotExistsAtLocation() { // given Map initialPieces = Map.of( - new Location(1, 1), EmptyPiece.getInstance() + new Location(1, 1), EMPTY ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); Location from = new Location(0, 0); Location to = new Location(1, 1); @@ -168,11 +170,11 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { // given Side otherSide = Side.CHO; Map initialPieces = Map.of( - new Location(1, 1), new TestPiece(otherSide) + new Location(1, 1), new TestPiece(PieceType.CHA, otherSide) ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); Location from = new Location(0, 0); Location to = new Location(1, 1); @@ -187,13 +189,13 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { @DisplayName("이동할 기물의 위치와 도착지 좌표를 받아 기물을 이동시킨다.") void shouldMovePieceToDestination() { // given - Piece testPiece = new TestPiece(Side.HAN); + Piece testPiece = new TestPiece(PieceType.CHA, Side.HAN); Map initialPieces = Map.of( new Location(1, 1), testPiece ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); Location from = new Location(1, 1); Location to = new Location(0, 0); @@ -212,11 +214,11 @@ void shouldMovePieceToDestination() { void shouldReturnTrueForNoneEmptyBoard() { // given Map initialPieces = Map.of( - new Location(1, 1), new TestPiece(Side.HAN), - new Location(1, 2), EmptyPiece.getInstance() + new Location(1, 1), new TestPiece(PieceType.CHA, Side.HAN), + new Location(1, 2), EMPTY ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); // when & then @@ -228,11 +230,11 @@ void shouldReturnTrueForNoneEmptyBoard() { void shouldReturnTrueForEmptyBoard() { // given Map initialPieces = Map.of( - new Location(1, 1), EmptyPiece.getInstance(), - new Location(1, 2), EmptyPiece.getInstance() + new Location(1, 1), EMPTY, + new Location(1, 2), EMPTY ); - MaSangArrangementTemplate strategy = new TestMaSangArrangementTemplate(initialPieces); - BoardAssembler assembler = BoardAssembler.of(strategy, strategy); + ArrangementStrategy strategy = new TestArrangementStrategy(initialPieces); + BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); // when & then diff --git a/src/test/java/janggi/domain/piece/JolbyeongTest.java b/src/test/java/janggi/domain/piece/JolbyeongTest.java deleted file mode 100644 index a9d00bbd9c..0000000000 --- a/src/test/java/janggi/domain/piece/JolbyeongTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Location; -import janggi.domain.Side; -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -class JolbyeongTest { - @Nested - class CalculateRouteTest { - @Test - @DisplayName("한팀 졸병이 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") - void shouldReturnRouteForReachableLocationWhenTeamHan() { - // given - Location from = Location.from(List.of(0, 0)); - Location to = Location.from(List.of(0, 1)); - Piece piece = new Jolbyeong(Side.HAN); - - - // when & then - Assertions.assertThat(piece.calculateRoute(from, to)) - .isEqualTo(List.of(to)); - } - - @Test - @DisplayName("초팀 졸병이 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") - void shouldReturnRouteForReachableLocationWhenTeamCho() { - // given - Location from = Location.from(List.of(0, 1)); - Location to = Location.from(List.of(0, 0)); - Piece piece = new Jolbyeong(Side.CHO); - - - // when & then - Assertions.assertThat(piece.calculateRoute(from, to)) - .isEqualTo(List.of(to)); - } - - @Test - @DisplayName("한팀 졸병이 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") - void shouldThrowExceptionForUnReachableLocationWhenTeamHan() { - // given - Location from = Location.from(List.of(0, 1)); - Location to = Location.from(List.of(0, 0)); - Piece piece = new Jolbyeong(Side.HAN); - - // when & then - Assertions.assertThatThrownBy(() -> piece.calculateRoute(from, to)) - .isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @DisplayName("초팀 졸병이 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") - @MethodSource("provideUnreachableCoordination") - void shouldThrowExceptionForUnReachableLocationWhenTeamCho(List coordination) { - // given - Location from = Location.from(List.of(0, 0)); - Location to = Location.from(coordination); - Piece piece = new Jolbyeong(Side.CHO); - - // when & then - Assertions.assertThatThrownBy(() -> piece.calculateRoute(from, to)) - .isInstanceOf(IllegalArgumentException.class); - } - - static List> provideUnreachableCoordination() { - return List.of( - List.of(0,2), // 거리가 멀어서 도달할 수 없는 경우 - List.of(1,1), // 대각선으로 이동하는 경우 - List.of(0,1) // 뒤로 이동하는 경우 - ); - } - } -} diff --git a/src/test/java/janggi/domain/piece/MaTest.java b/src/test/java/janggi/domain/piece/MaTest.java deleted file mode 100644 index 60ac17494c..0000000000 --- a/src/test/java/janggi/domain/piece/MaTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Location; -import janggi.domain.Side; -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -public class MaTest { - - @Nested - class CalculateRouteTest { - - @Test - @DisplayName("마가 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") - void shouldReturnRouteForReachableLocation() { - // given - Location from = Location.from(List.of(0, 0)); - Location to = Location.from(List.of(1,2)); - Piece piece = new Ma(Side.HAN); - - List expected = List.of( - Location.from(List.of(0,1)), - Location.from(List.of(1,2)) - ); - - // when & then - Assertions.assertThat(piece.calculateRoute(from, to)) - .isEqualTo(expected); - } - - @ParameterizedTest - @DisplayName("마가 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") - @MethodSource("provideUnreachableCoordination") - void shouldThrowExceptionForUnReachableLocation(List destination) { - // given - Location from = Location.from(List.of(0, 0)); - Location to = Location.from(destination); - Piece piece = new Ma(Side.CHO); - - // when & then - Assertions.assertThatThrownBy(() -> piece.calculateRoute(from, to)) - .isInstanceOf(IllegalArgumentException.class); - } - static List> provideUnreachableCoordination() { - return List.of( - List.of(0,3), - List.of(-1,3), - List.of(0,1) - ); - } - } -} diff --git a/src/test/java/janggi/domain/piece/SangTest.java b/src/test/java/janggi/domain/piece/SangTest.java deleted file mode 100644 index b4c6e5bd24..0000000000 --- a/src/test/java/janggi/domain/piece/SangTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Location; -import janggi.domain.Side; -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -public class SangTest { - - @Nested - class CalculateRouteTest { - - @ParameterizedTest - @DisplayName("상이 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") - @MethodSource("provideListsForTesting") - void shouldReturnRouteForReachableLocation(Location destination, List expected) { - // given - Location from = Location.from(List.of(0, 0)); - Piece piece = new Sang(Side.HAN); - - // when & then - Assertions.assertThat(piece.calculateRoute(from, destination)) - .isEqualTo(expected); - } - - static Stream provideListsForTesting() { - return Stream.of( - Arguments.of( - new Location(2, 3), - List.of(new Location(0, 1), new Location(1, 2), new Location(2, 3)) - ), - Arguments.of( - new Location(3, 2), - List.of(new Location(1, 0), new Location(2, 1), new Location(3, 2)) - ) - ); - } - - @ParameterizedTest - @DisplayName("상이 이동할 수 없는 위치를 파라미터로 받으면 예외가 발생한다.") - @MethodSource("provideUnreachableCoordination") - void shouldThrowExceptionForUnReachableLocation(List destination) { - // given - Location from = Location.from(List.of(0, 0)); - Location to = Location.from(destination); - Piece piece = new Sang(Side.CHO); - - // when & then - Assertions.assertThatThrownBy(() -> piece.calculateRoute(from, to)) - .isInstanceOf(IllegalArgumentException.class); - } - - static List> provideUnreachableCoordination() { - return List.of( - List.of(0, 4), - List.of(-1, 3), - List.of(0, 1) - ); - } - } -} diff --git a/src/test/java/janggi/domain/rule/JolbyeongMovementTest.java b/src/test/java/janggi/domain/rule/JolbyeongMovementTest.java new file mode 100644 index 0000000000..73198293f1 --- /dev/null +++ b/src/test/java/janggi/domain/rule/JolbyeongMovementTest.java @@ -0,0 +1,74 @@ +package janggi.domain.rule; + +import janggi.domain.Location; +import janggi.domain.Side; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class JolbyeongMovementTest { + + @Nested + class CalculateRouteTest { + @Test + @DisplayName("한팀 졸병이 이동할 수 있는 위치로 경로를 계산하면, 이동 경로를 반환한다.") + void shouldReturnRouteForReachableLocationWhenTeamHan() { + // given + Location from = new Location(0, 0); + Location to = new Location(0, 1); + JolbyeongMovement movement = JolbyeongMovement.getInstanceBySide(Side.HAN); + + // when & then + Assertions.assertThat(movement.calculateRoute(from, to)).hasValue(List.of(to)); + } + + @Test + @DisplayName("초팀 졸병이 이동할 수 있는 위치로 경로를 계산하면, 이동 경로를 반환한다.") + void shouldReturnRouteForReachableLocationWhenTeamCho() { + // given + Location from = new Location(0, 1); + Location to = new Location(0, 0); + JolbyeongMovement movement = JolbyeongMovement.getInstanceBySide(Side.CHO); + + // when & then + Assertions.assertThat(movement.calculateRoute(from, to)).hasValue(List.of(to)); + } + + @Test + @DisplayName("한팀 졸병이 이동할 수 없는 위치로 경로를 계산하면, 빈 결과를 반환한다.") + void shouldReturnEmptyForUnReachableLocationWhenTeamHan() { + // given + Location from = new Location(0, 1); + Location to = new Location(0, 0); + JolbyeongMovement movement = JolbyeongMovement.getInstanceBySide(Side.HAN); + + // when & then + Assertions.assertThat(movement.calculateRoute(from, to)).isEmpty(); + } + + @ParameterizedTest + @DisplayName("초팀 졸병이 이동할 수 없는 위치로 경로를 계산하면, 빈 결과를 반환한다.") + @MethodSource("provideUnreachableCoordination") + void shouldReturnEmptyForUnReachableLocationWhenTeamCho(List coordination) { + // given + Location from = new Location(0, 0); + Location to = Location.from(coordination); + JolbyeongMovement movement = JolbyeongMovement.getInstanceBySide(Side.CHO); + + // when & then + Assertions.assertThat(movement.calculateRoute(from, to)).isEmpty(); + } + + static List> provideUnreachableCoordination() { + return List.of( + List.of(0,2), // 거리가 멀어서 도달할 수 없는 경우 + List.of(1,1), // 대각선으로 이동하는 경우 + List.of(0,1) // 뒤로 이동하는 경우 + ); + } + } +} diff --git a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java index 7b41c5b0de..ab8e9990f5 100644 --- a/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java +++ b/src/test/java/janggi/domain/rule/collision/DefaultCollisionDetectorTest.java @@ -3,6 +3,7 @@ import janggi.domain.Side; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceType; import janggi.support.TestPiece; import java.util.List; import org.assertj.core.api.Assertions; @@ -12,40 +13,38 @@ class DefaultCollisionDetectorTest { private static final DefaultCollisionDetector DEFAULT_COLLISION_DETECTOR = DefaultCollisionDetector.getInstance(); + private static final Piece EMPTY = EmptyPiece.getInstance(); @Test @DisplayName("이동 경로에 장애물이 존재하지 않고, 마지막 경로에 위치한 기물이 존재하지 않으면 예외를 반환하지 않는다.") void shouldNotThrowExceptionWhenNoPieceOnPathAndNoPieceOnDestination() { // given - List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance()); - Piece piece = new TestPiece(Side.CHO); + List piecesOnPath = List.of(EMPTY, EMPTY); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(piece, piecesOnPath)); + .isThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)); } @Test @DisplayName("이동 경로에 장애물이 존재하지 않고, 마지막 경로에 위치한 기물이 상대팀이면 예외를 반환하지 않는다.") void shouldNotThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsOtherSide() { // given - List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.HAN)); - Piece piece = new TestPiece(Side.CHO); + List piecesOnPath = List.of(EMPTY, EMPTY, new TestPiece(PieceType.CHA, Side.HAN)); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(piece, piecesOnPath)); + .isThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)); } @Test @DisplayName("이동 경로에 장애물이 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenPieceOnPathExist() { // given - List piecesOnPath = List.of(new TestPiece(Side.HAN), EmptyPiece.getInstance(), EmptyPiece.getInstance()); - Piece piece = new TestPiece(Side.CHO); + List piecesOnPath = List.of(new TestPiece(PieceType.CHA, Side.HAN), EMPTY, EMPTY); // when & then - Assertions.assertThatThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -54,11 +53,10 @@ void shouldThrowExceptionWhenPieceOnPathExist() { void shouldThrowExceptionWhenNoPieceOnPathAndPieceOnDestinationIsMySide() { // given Side side = Side.HAN; - List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(side)); - Piece piece = new TestPiece(side); + List piecesOnPath = List.of(EMPTY, EMPTY, new TestPiece(PieceType.CHA, side)); // when & then - Assertions.assertThatThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> DEFAULT_COLLISION_DETECTOR.check(side, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java index 928c479d53..200348ff32 100644 --- a/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java +++ b/src/test/java/janggi/domain/rule/collision/PoCollisionDetectorTest.java @@ -3,7 +3,7 @@ import janggi.domain.Side; import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; -import janggi.domain.piece.Po; +import janggi.domain.piece.PieceType; import janggi.support.TestPiece; import java.util.List; import org.assertj.core.api.Assertions; @@ -13,40 +13,38 @@ class PoCollisionDetectorTest { private static final PoCollisionDetector PO_COLLISION_DETECTOR = PoCollisionDetector.getInstance(); + private static final Piece EMPTY = EmptyPiece.getInstance(); @Test @DisplayName("이동 경로에 포가 아닌 장애물이 1개 존재하고, 마지막 경로 좌표에 기물이 존재하지 않으면 예외를 반환하지 않는다.") void shouldNotThrowExceptionWhenOneNonPoObstacleAndEmptyDestination() { // given - List piecesOnPath = List.of(EmptyPiece.getInstance(), new TestPiece(Side.HAN), EmptyPiece.getInstance()); - Piece piece = new Po(Side.CHO); + List piecesOnPath = List.of(EMPTY, new TestPiece(PieceType.CHA, Side.HAN), EMPTY); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)); + .isThrownBy(() -> PO_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)); } @Test @DisplayName("이동 경로에 포가 아닌 장애물이 1개 존재하고, 마지막 경로에 위치한 기물이 상대팀이면 예외를 반환하지 않는다.") void shouldNotThrowExceptionWhenOneNonPoObstacleAndEnemyAtDestination() { // given - List piecesOnPath = List.of(EmptyPiece.getInstance(), new TestPiece(Side.HAN), new TestPiece(Side.HAN)); - Piece piece = new Po(Side.CHO); + List piecesOnPath = List.of(EMPTY, new TestPiece(PieceType.CHA, Side.HAN), new TestPiece(PieceType.CHA, Side.HAN)); // when & then Assertions.assertThatNoException() - .isThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)); + .isThrownBy(() -> PO_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)); } @Test @DisplayName("이동 경로에 장애물이 2개 이상 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { // given - List piecesOnPath = List.of(new TestPiece(Side.HAN), new TestPiece(Side.HAN), EmptyPiece.getInstance()); - Piece piece = new Po(Side.CHO); + List piecesOnPath = List.of(new TestPiece(PieceType.CHA, Side.HAN), new TestPiece(PieceType.CHA, Side.HAN), EMPTY); // when & then - Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -54,11 +52,10 @@ void shouldThrowExceptionWhenTwoOrMorePieceOnPathExist() { @DisplayName("이동 경로에 장애물이 존재하지 않는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenNoObstacleOnPath() { // given - List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.HAN)); - Piece piece = new Po(Side.CHO); + List piecesOnPath = List.of(EMPTY, EMPTY, new TestPiece(PieceType.CHA, Side.HAN)); // when & then - Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -66,11 +63,10 @@ void shouldThrowExceptionWhenNoObstacleOnPath() { @DisplayName("이동 경로에 포가 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenPoOnPath() { // given - List piecesOnPath = List.of(EmptyPiece.getInstance(), new Po(Side.HAN), new TestPiece(Side.HAN)); - Piece piece = new Po(Side.CHO); + List piecesOnPath = List.of(EMPTY, new TestPiece(PieceType.PO, Side.HAN), new TestPiece(PieceType.CHA, Side.HAN)); // when & then - Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -78,11 +74,10 @@ void shouldThrowExceptionWhenPoOnPath() { @DisplayName("도착지에 상대팀의 포가 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenPoOfOtherSideOnDestination() { // given - List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new Po(Side.HAN)); - Piece piece = new Po(Side.CHO); + List piecesOnPath = List.of(EMPTY, EMPTY, new TestPiece(PieceType.PO, Side.HAN)); // when & then - Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } @@ -90,11 +85,10 @@ void shouldThrowExceptionWhenPoOfOtherSideOnDestination() { @DisplayName("도착지에 우리팀 기물이 존재하는 경우 예외를 발생시킨다.") void shouldThrowExceptionWhenSameSidePieceOnDestination() { // given - List piecesOnPath = List.of(EmptyPiece.getInstance(), EmptyPiece.getInstance(), new TestPiece(Side.CHO)); - Piece piece = new Po(Side.CHO); + List piecesOnPath = List.of(EMPTY, EMPTY, new TestPiece(PieceType.CHA, Side.CHO)); // when & then - Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(piece, piecesOnPath)) + Assertions.assertThatThrownBy(() -> PO_COLLISION_DETECTOR.check(Side.CHO, piecesOnPath)) .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/janggi/domain/rule/route/RouteProviderTest.java b/src/test/java/janggi/domain/rule/route/DefaultRouteProviderTest.java similarity index 62% rename from src/test/java/janggi/domain/rule/route/RouteProviderTest.java rename to src/test/java/janggi/domain/rule/route/DefaultRouteProviderTest.java index 9507acd247..ae94454038 100644 --- a/src/test/java/janggi/domain/rule/route/RouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/DefaultRouteProviderTest.java @@ -1,13 +1,13 @@ package janggi.domain.rule.route; import janggi.domain.Location; -import janggi.domain.piece.PieceType; import java.util.List; +import java.util.Optional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class RouteProviderTest { +class DefaultRouteProviderTest { @Test @DisplayName("시작 위치에서 도착 위치까지 도달 가능하다면, 그 사이 경로 좌표를 반환한다.") @@ -16,12 +16,14 @@ void shouldReturnLocationsWhenValidPathIsFound() { Location from = new Location(1,1); Location to = new Location(1,5); List possibleRoutes = List.of(Route.of(Direction.FRONT, 4)); + RouteProvider routeProvider = new DefaultRouteProvider(possibleRoutes); // when - List locationsOfValidPath = RouteProvider.findValidPath(PieceType.CHA, from, to, possibleRoutes); + Optional> locationsOfValidPath = routeProvider.calculateRoute(from, to); // then - Assertions.assertThat(locationsOfValidPath).containsExactly( + Assertions.assertThat(locationsOfValidPath).isPresent(); + Assertions.assertThat(locationsOfValidPath.get()).containsExactly( new Location(1,2), new Location(1,3), new Location(1,4), @@ -30,15 +32,15 @@ void shouldReturnLocationsWhenValidPathIsFound() { } @Test - @DisplayName("시작 위치에서 도착 위치까지 도달 불가능한 경우 예외를 발생시킨다.") - void shouldThrowExceptionWhenValidPathIsNotFound() { + @DisplayName("시작 위치에서 도착 위치까지 도달 불가능한 경우 빈 결과를 반환한다.") + void shouldReturnEmptyWhenValidPathIsNotFound() { // given Location from = new Location(1,1); Location to = new Location(5,1); List possibleRoutes = List.of(Route.of(Direction.FRONT, 4)); + RouteProvider routeProvider = new DefaultRouteProvider(possibleRoutes); // when & then - Assertions.assertThatThrownBy(() -> RouteProvider.findValidPath(PieceType.CHA, from, to, possibleRoutes)) - .isInstanceOf(IllegalArgumentException.class); + Assertions.assertThat(routeProvider.calculateRoute(from, to)).isEmpty(); } } diff --git a/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java index 0156bb59ed..0f0c58ede7 100644 --- a/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/GungSeongRouteProviderTest.java @@ -1,7 +1,6 @@ package janggi.domain.rule.route; import janggi.domain.Location; -import janggi.domain.piece.PieceType; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -13,7 +12,7 @@ class GungSeongRouteProviderTest { private static final GungSeongRouteProvider GUNG_SEONG_ROUTE_PROVIDER = GungSeongRouteProvider.getInstance(); @ParameterizedTest - @DisplayName("궁성 안에서 이동할 수 있는 위치를 파라미터로 받으면 이동 경로를 반환한다.") + @DisplayName("궁성 안에서 이동할 수 있는 위치로 경로를 계산하면, 이동 경로를 반환한다.") @MethodSource("provideReachableCoordination") void shouldReturnRouteForReachableLocation(List destination) { // given @@ -21,8 +20,7 @@ void shouldReturnRouteForReachableLocation(List destination) { Location to = Location.from(destination); // when & then - Assertions.assertThat(GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(PieceType.GUNG, from, to)) - .isEqualTo(List.of(to)); + Assertions.assertThat(GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(from, to)).hasValue(List.of(to)); } static List> provideReachableCoordination() { @@ -37,16 +35,15 @@ static List> provideReachableCoordination() { } @ParameterizedTest - @DisplayName("궁성을 벗어난 위치를 파라미터로 받으면 예외가 발생한다.") + @DisplayName("궁성을 벗어난 위치로 경로를 계산하면, 빈 결과를 반환한다.") @MethodSource("provideUnreachableCoordination") - void shouldThrowExceptionForUnReachableLocation(List coordination) { + void shouldReturnEmptyForUnReachableLocation(List coordination) { // given Location from = Location.from(List.of(3, 2)); Location to = Location.from(coordination); // when & then - Assertions.assertThatThrownBy(() -> GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(PieceType.GUNG, from, to)) - .isInstanceOf(IllegalArgumentException.class); + Assertions.assertThat(GUNG_SEONG_ROUTE_PROVIDER.calculateRoute(from, to)).isEmpty(); } static List> provideUnreachableCoordination() { diff --git a/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java index 3abc60f0ea..160e4601df 100644 --- a/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java @@ -1,7 +1,6 @@ package janggi.domain.rule.route; import janggi.domain.Location; -import janggi.domain.piece.PieceType; import java.util.List; import java.util.stream.Stream; import org.assertj.core.api.Assertions; @@ -15,15 +14,14 @@ class StraightRouteProviderTest { private static final StraightRouteProvider STRAIGHT_ROUTE_PROVIDER = StraightRouteProvider.getInstance(); @ParameterizedTest - @DisplayName("직선으로 이동할 위치를 파라미터로 받으면 이동 경로를 반환한다.") + @DisplayName("직선으로 이동할 위치로 경로를 계산하면, 이동 경로를 반환한다.") @MethodSource("provideReachableCoordination") void shouldReturnRouteForReachableLocation(Location destination, List route) { // given Location from = Location.from(List.of(2, 2)); // when & then - Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(PieceType.CHA, from, destination)) - .isEqualTo(route); + Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, destination)).hasValue(route); } static Stream provideReachableCoordination() { @@ -48,16 +46,15 @@ static Stream provideReachableCoordination() { } @ParameterizedTest - @DisplayName("직선으로 이동이 불가능한 위치를 파라미터로 받으면 예외가 발생한다.") + @DisplayName("직선으로 이동이 불가능한 위치로 경로를 계산하면, 빈 결과를 반환한다.") @MethodSource("provideUnreachableCoordination") - void shouldThrowExceptionForUnReachableLocation(List coordination) { + void shouldReturnEmptyForUnReachableLocation(List coordination) { // given Location from = Location.from(List.of(1, 1)); Location to = Location.from(coordination); // when & then - Assertions.assertThatThrownBy(() -> STRAIGHT_ROUTE_PROVIDER.calculateRoute(PieceType.CHA, from, to)) - .isInstanceOf(IllegalArgumentException.class); + Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, to)).isEmpty(); } static List> provideUnreachableCoordination() { diff --git a/src/test/java/janggi/strategy/BoardAssemblerTest.java b/src/test/java/janggi/strategy/BoardAssemblerTest.java index 6467cf34fd..4355d90455 100644 --- a/src/test/java/janggi/strategy/BoardAssemblerTest.java +++ b/src/test/java/janggi/strategy/BoardAssemblerTest.java @@ -4,6 +4,7 @@ import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,9 +14,9 @@ class BoardAssemblerTest { @DisplayName("조립기는 전략을 실행한 후, 기물이 없는 나머지 빈 칸을 EmptyPiece로 채운다.") void shouldFillEmptySpaces() { // given - ArrangementStrategy doNothingStrategy = (arrangement, side) -> {}; + ArrangementStrategy doNothingStrategy = arrangement -> {}; - BoardAssembler assembler = BoardAssembler.of(doNothingStrategy, doNothingStrategy); + BoardAssembler assembler = BoardAssembler.from(List.of(doNothingStrategy, doNothingStrategy)); // when Piece[][] board = assembler.assemble(); diff --git a/src/test/java/janggi/strategy/MaSangArrangementStrategyTest.java b/src/test/java/janggi/strategy/MaSangArrangementStrategyTest.java new file mode 100644 index 0000000000..b5de0011d2 --- /dev/null +++ b/src/test/java/janggi/strategy/MaSangArrangementStrategyTest.java @@ -0,0 +1,306 @@ +package janggi.strategy; + +import static org.assertj.core.api.Assertions.assertThat; + +import janggi.domain.Side; +import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceType; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class MaSangArrangementStrategyTest { + + private static final List MA_SANG_MA_SANG = + List.of(PieceType.MA, PieceType.SANG, PieceType.MA, PieceType.SANG); + private static final List SANG_MA_SANG_MA = + List.of(PieceType.SANG, PieceType.MA, PieceType.SANG, PieceType.MA); + private static final List SANG_MA_MA_SANG = + List.of(PieceType.SANG, PieceType.MA, PieceType.MA, PieceType.SANG); + private static final List MA_SANG_SANG_MA = + List.of(PieceType.MA, PieceType.SANG, PieceType.SANG, PieceType.MA); + + @Test + @DisplayName("전략의 place를 호출하면 차, 포, 궁, 사, 졸 등 '기본 기물'이 올바른 위치에 배치된다.") + void shouldPlaceDefaultPieces() { + // given + Piece[][] board = new Piece[10][9]; + Side han = Side.HAN; + Side cho = Side.CHO; + MaSangArrangementStrategy hanStrategy = MaSangArrangementStrategy.of(han, MA_SANG_MA_SANG); + MaSangArrangementStrategy choStrategy = MaSangArrangementStrategy.of(cho, MA_SANG_MA_SANG); + + // when + hanStrategy.place(board); + choStrategy.place(board); + + // then: 한팀 + Piece chaOfHan = board[0][0]; + assertThat(chaOfHan.getPieceType()).isEqualTo(PieceType.CHA); + assertThat(chaOfHan.isSameSide(han)).isTrue(); + + Piece saOfHan = board[0][3]; + assertThat(saOfHan.getPieceType()).isEqualTo(PieceType.SA); + assertThat(saOfHan.isSameSide(han)).isTrue(); + + Piece gungOfHan = board[1][4]; + assertThat(gungOfHan.getPieceType()).isEqualTo(PieceType.GUNG); + assertThat(gungOfHan.isSameSide(han)).isTrue(); + + Piece poOfHan = board[2][1]; + assertThat(poOfHan.getPieceType()).isEqualTo(PieceType.PO); + assertThat(poOfHan.isSameSide(han)).isTrue(); + + Piece jolbyeongOfHan = board[3][0]; + assertThat(jolbyeongOfHan.getPieceType()).isEqualTo(PieceType.BYEONG); + assertThat(jolbyeongOfHan.isSameSide(han)).isTrue(); + + + // then: 초팀 + Piece chaOfCho = board[9][8]; + assertThat(chaOfCho.getPieceType()).isEqualTo(PieceType.CHA); + assertThat(chaOfCho.isSameSide(cho)).isTrue(); + + Piece gungOfCho = board[8][4]; + assertThat(gungOfCho.getPieceType()).isEqualTo(PieceType.GUNG); + assertThat(gungOfCho.isSameSide(cho)).isTrue(); + + Piece poOfCho = board[7][7]; + assertThat(poOfCho.getPieceType()).isEqualTo(PieceType.PO); + assertThat(poOfCho.isSameSide(cho)).isTrue(); + + Piece jolbyeongOfCho = board[6][8]; + assertThat(jolbyeongOfCho.getPieceType()).isEqualTo(PieceType.JOL); + assertThat(jolbyeongOfCho.isSameSide(cho)).isTrue(); + } + + @Nested + class MaSangMaSangTest { + @Test + @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangMaSang의 위치에 생성해 넣어준다.") + void shouldPlaceMaSangMaSangWhenSideHan() { + // given + Piece[][] grid = new Piece[10][9]; + Side side = Side.HAN; + MaSangArrangementStrategy strategy = MaSangArrangementStrategy.of(side, MA_SANG_MA_SANG); + + // when + strategy.place(grid); + Piece leftMa = grid[0][1]; + Piece leftSang = grid[0][2]; + Piece rightMa = grid[0][6]; + Piece rightSang = grid[0][7]; + + // then + Assertions.assertThat(leftMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(leftSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(rightMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(rightSang.getPieceType()).isEqualTo(PieceType.SANG); + + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + } + + @Test + @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 MaSangMaSang의 위치에 생성해 넣어준다.") + void shouldPlaceMaSangMaSangWhenSideCho() { + // given + Piece[][] grid = new Piece[10][9]; + Side side = Side.CHO; + MaSangArrangementStrategy strategy = MaSangArrangementStrategy.of(side, MA_SANG_MA_SANG); + + // when + strategy.place(grid); + Piece leftMa = grid[9][1]; + Piece leftSang = grid[9][2]; + Piece rightMa = grid[9][6]; + Piece rightSang = grid[9][7]; + + // then + Assertions.assertThat(leftMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(leftSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(rightMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(rightSang.getPieceType()).isEqualTo(PieceType.SANG); + + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + } + } + + @Nested + class SangMaSangMaTest { + @Test + @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaSangMa의 위치에 생성해 넣어준다.") + void shouldPlaceSangMaSangMaWhenSideHan() { + // given + Piece[][] grid = new Piece[10][9]; + Side side = Side.HAN; + MaSangArrangementStrategy strategy = MaSangArrangementStrategy.of(side, SANG_MA_SANG_MA); + + // when + strategy.place(grid); + Piece leftSang = grid[0][1]; + Piece leftMa = grid[0][2]; + Piece rightSang = grid[0][6]; + Piece rightMa = grid[0][7]; + + // then + Assertions.assertThat(leftSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(leftMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(rightSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(rightMa.getPieceType()).isEqualTo(PieceType.MA); + + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + } + + @Test + @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 SangMaSangMa의 위치에 생성해 넣어준다.") + void shouldPlaceSangMaSangMaWhenSideCho() { + // given + Piece[][] grid = new Piece[10][9]; + Side side = Side.CHO; + MaSangArrangementStrategy strategy = MaSangArrangementStrategy.of(side, SANG_MA_SANG_MA); + + // when + strategy.place(grid); + Piece leftSang = grid[9][1]; + Piece leftMa = grid[9][2]; + Piece rightSang = grid[9][6]; + Piece rightMa = grid[9][7]; + + // then + Assertions.assertThat(leftSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(leftMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(rightSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(rightMa.getPieceType()).isEqualTo(PieceType.MA); + + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + } + } + + @Nested + class SangMaMaSangTest { + @Test + @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaMaSang의 위치에 생성해 넣어준다.") + void shouldPlaceSangMaMaSangWhenSideHan() { + // given + Piece[][] grid = new Piece[10][9]; + Side side = Side.HAN; + MaSangArrangementStrategy strategy = MaSangArrangementStrategy.of(side, SANG_MA_MA_SANG); + + // when + strategy.place(grid); + Piece leftSang = grid[0][1]; + Piece leftMa = grid[0][2]; + Piece rightMa = grid[0][6]; + Piece rightSang = grid[0][7]; + + // then + Assertions.assertThat(leftSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(leftMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(rightMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(rightSang.getPieceType()).isEqualTo(PieceType.SANG); + + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + } + + @Test + @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 SangMaMaSang의 위치에 생성해 넣어준다.") + void shouldPlaceSangMaMaSangWhenSideCho() { + // given + Piece[][] grid = new Piece[10][9]; + Side side = Side.CHO; + MaSangArrangementStrategy strategy = MaSangArrangementStrategy.of(side, SANG_MA_MA_SANG); + + // when + strategy.place(grid); + Piece leftSang = grid[9][1]; + Piece leftMa = grid[9][2]; + Piece rightMa = grid[9][6]; + Piece rightSang = grid[9][7]; + + // then + Assertions.assertThat(leftSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(leftMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(rightMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(rightSang.getPieceType()).isEqualTo(PieceType.SANG); + + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + } + } + + @Nested + class MaSangSangMaTest { + @Test + @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangSangMa의 위치에 생성해 넣어준다.") + void shouldPlaceMaSangSangMaWhenSideHan() { + // given + Piece[][] grid = new Piece[10][9]; + Side side = Side.HAN; + MaSangArrangementStrategy strategy = MaSangArrangementStrategy.of(side, MA_SANG_SANG_MA); + + // when + strategy.place(grid); + Piece leftMa = grid[0][1]; + Piece leftSang = grid[0][2]; + Piece rightSang = grid[0][6]; + Piece rightMa = grid[0][7]; + + // then + Assertions.assertThat(leftMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(leftSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(rightSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(rightMa.getPieceType()).isEqualTo(PieceType.MA); + + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + } + + @Test + @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 MaSangSangMa의 위치에 생성해 넣어준다.") + void shouldPlaceMaSangSangMaWhenSideCho() { + // given + Piece[][] grid = new Piece[10][9]; + Side side = Side.CHO; + MaSangArrangementStrategy strategy = MaSangArrangementStrategy.of(side, MA_SANG_SANG_MA); + + // when + strategy.place(grid); + Piece leftMa = grid[9][1]; + Piece leftSang = grid[9][2]; + Piece rightSang = grid[9][6]; + Piece rightMa = grid[9][7]; + + // then + Assertions.assertThat(leftMa.getPieceType()).isEqualTo(PieceType.MA); + Assertions.assertThat(leftSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(rightSang.getPieceType()).isEqualTo(PieceType.SANG); + Assertions.assertThat(rightMa.getPieceType()).isEqualTo(PieceType.MA); + + Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); + Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); + Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); + } + } +} diff --git a/src/test/java/janggi/strategy/MaSangArrangementTemplateTest.java b/src/test/java/janggi/strategy/MaSangArrangementTemplateTest.java deleted file mode 100644 index 66b4b54b6b..0000000000 --- a/src/test/java/janggi/strategy/MaSangArrangementTemplateTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package janggi.strategy; - -import static org.assertj.core.api.Assertions.assertThat; - -import janggi.domain.Side; -import janggi.domain.piece.Cha; -import janggi.domain.piece.Gung; -import janggi.domain.piece.Jolbyeong; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Po; -import janggi.domain.piece.Sa; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class MaSangArrangementTemplateTest { - - private final MaSangArrangementTemplate dummyStrategy = new MaSangArrangementTemplate() { - @Override - protected void placeVariablePieces(Piece[][] arrangement, Side side) { - return; - } - }; - - @Test - @DisplayName("전략의 place를 호출하면 차, 포, 궁, 사, 졸 등 '기본 기물'이 올바른 위치에 배치된다.") - void shouldPlaceDefaultPieces() { - // given - Piece[][] board = new Piece[10][9]; - Side han = Side.HAN; - Side cho = Side.CHO; - - // when - dummyStrategy.place(board, han); - dummyStrategy.place(board, cho); - - // then: 한팀 - Piece chaOfHan = board[0][0]; - assertThat(chaOfHan).isInstanceOf(Cha.class); - assertThat(chaOfHan.isSameSide(han)).isTrue(); - - Piece saOfHan = board[0][3]; - assertThat(saOfHan).isInstanceOf(Sa.class); - assertThat(saOfHan.isSameSide(han)).isTrue(); - - Piece gungOfHan = board[1][4]; - assertThat(gungOfHan).isInstanceOf(Gung.class); - assertThat(gungOfHan.isSameSide(han)).isTrue(); - - Piece poOfHan = board[2][1]; - assertThat(poOfHan).isInstanceOf(Po.class); - assertThat(poOfHan.isSameSide(han)).isTrue(); - - Piece jolbyeongOfHan = board[3][0]; - assertThat(jolbyeongOfHan).isInstanceOf(Jolbyeong.class); - assertThat(jolbyeongOfHan.isSameSide(han)).isTrue(); - - - // then: 초팀 - Piece chaOfCho = board[9][8]; - assertThat(chaOfCho).isInstanceOf(Cha.class); - assertThat(chaOfCho.isSameSide(cho)).isTrue(); - - Piece gungOfCho = board[8][4]; - assertThat(gungOfCho).isInstanceOf(Gung.class); - assertThat(gungOfCho.isSameSide(cho)).isTrue(); - - Piece poOfCho = board[7][7]; - assertThat(poOfCho).isInstanceOf(Po.class); - assertThat(poOfCho.isSameSide(cho)).isTrue(); - - Piece jolbyeongOfCho = board[6][8]; - assertThat(jolbyeongOfCho).isInstanceOf(Jolbyeong.class); - assertThat(jolbyeongOfCho.isSameSide(cho)).isTrue(); - } -} diff --git a/src/test/java/janggi/strategy/MaSangMaSangStrategyTest.java b/src/test/java/janggi/strategy/MaSangMaSangStrategyTest.java deleted file mode 100644 index 34fb416185..0000000000 --- a/src/test/java/janggi/strategy/MaSangMaSangStrategyTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package janggi.strategy; - - -import janggi.domain.Side; -import janggi.domain.piece.Ma; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class MaSangMaSangStrategyTest { - - private static final MaSangMaSangStrategy STRATEGY = MaSangMaSangStrategy.getInstance(); - - @Test - @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangMaSang의 위치에 생성해 넣어준다.") - void shouldPlaceMaSangMaSangWhenSideHan() { - // given - Piece[][] grid = new Piece[10][9]; - Side side = Side.HAN; - - // when - STRATEGY.place(grid, side); - Piece leftMa = grid[0][1]; - Piece leftSang = grid[0][2]; - Piece rightMa = grid[0][6]; - Piece rightSang = grid[0][7]; - - // then - Assertions.assertThat(leftMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftSang).isInstanceOf(Sang.class); - Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - } - - @Test - @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 MaSangMaSang의 위치에 생성해 넣어준다.") - void shouldPlaceMaSangMaSangWhenSideCho() { - // given - Piece[][] grid = new Piece[10][9]; - Side side = Side.CHO; - - // when - STRATEGY.place(grid, side); - Piece leftMa = grid[9][1]; - Piece leftSang = grid[9][2]; - Piece rightMa = grid[9][6]; - Piece rightSang = grid[9][7]; - - // then - Assertions.assertThat(leftMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftSang).isInstanceOf(Sang.class); - Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - } -} diff --git a/src/test/java/janggi/strategy/MaSangSangMaStrategyTest.java b/src/test/java/janggi/strategy/MaSangSangMaStrategyTest.java deleted file mode 100644 index 96e77a7800..0000000000 --- a/src/test/java/janggi/strategy/MaSangSangMaStrategyTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package janggi.strategy; - - -import janggi.domain.Side; -import janggi.domain.piece.Ma; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class MaSangSangMaStrategyTest { - - private static final MaSangSangMaStrategy STRATEGY = MaSangSangMaStrategy.getInstance(); - - @Test - @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 MaSangSangMa의 위치에 생성해 넣어준다.") - void shouldPlaceMaSangSangMaWhenSideHan() { - // given - Piece[][] grid = new Piece[10][9]; - Side side = Side.HAN; - - // when - STRATEGY.place(grid, side); - Piece leftMa = grid[0][1]; - Piece leftSang = grid[0][2]; - Piece rightSang = grid[0][6]; - Piece rightMa = grid[0][7]; - - // then - Assertions.assertThat(leftMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftSang).isInstanceOf(Sang.class); - Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - } - - @Test - @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 MaSangSangMa의 위치에 생성해 넣어준다.") - void shouldPlaceMaSangSangMaWhenSideCho() { - // given - Piece[][] grid = new Piece[10][9]; - Side side = Side.CHO; - - // when - STRATEGY.place(grid, side); - Piece leftMa = grid[9][1]; - Piece leftSang = grid[9][2]; - Piece rightSang = grid[9][6]; - Piece rightMa = grid[9][7]; - - // then - Assertions.assertThat(leftMa).isInstanceOf(Ma.class); - Assertions.assertThat(leftSang).isInstanceOf(Sang.class); - Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - } -} diff --git a/src/test/java/janggi/strategy/SangMaMaSangTest.java b/src/test/java/janggi/strategy/SangMaMaSangTest.java deleted file mode 100644 index 2960441c33..0000000000 --- a/src/test/java/janggi/strategy/SangMaMaSangTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package janggi.strategy; - - -import janggi.domain.Side; -import janggi.domain.piece.Ma; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class SangMaMaSangTest { - - private static final SangMaMaSangStrategy STRATEGY = SangMaMaSangStrategy.getInstance(); - - @Test - @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaMaSang의 위치에 생성해 넣어준다.") - void shouldPlaceSangMaMaSangWhenSideHan() { - // given - Piece[][] grid = new Piece[10][9]; - Side side = Side.HAN; - - // when - STRATEGY.place(grid, side); - Piece leftSang = grid[0][1]; - Piece leftMa = grid[0][2]; - Piece rightMa = grid[0][6]; - Piece rightSang = grid[0][7]; - - // then - Assertions.assertThat(leftSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftMa).isInstanceOf(Ma.class); - Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - } - - @Test - @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 SangMaMaSang의 위치에 생성해 넣어준다.") - void shouldPlaceSangMaMaSangWhenSideCho() { - // given - Piece[][] grid = new Piece[10][9]; - Side side = Side.CHO; - - // when - STRATEGY.place(grid, side); - Piece leftSang = grid[9][1]; - Piece leftMa = grid[9][2]; - Piece rightMa = grid[9][6]; - Piece rightSang = grid[9][7]; - - // then - Assertions.assertThat(leftSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftMa).isInstanceOf(Ma.class); - Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - } -} diff --git a/src/test/java/janggi/strategy/SangMaSangMaStrategyTest.java b/src/test/java/janggi/strategy/SangMaSangMaStrategyTest.java deleted file mode 100644 index c5e4a4f607..0000000000 --- a/src/test/java/janggi/strategy/SangMaSangMaStrategyTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package janggi.strategy; - - -import janggi.domain.Side; -import janggi.domain.piece.Ma; -import janggi.domain.piece.Piece; -import janggi.domain.piece.Sang; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class SangMaSangMaStrategyTest { - - private static final SangMaSangMaStrategy STRATEGY = SangMaSangMaStrategy.getInstance(); - - @Test - @DisplayName("Han 팀의 Sang 객체와 Ma 객체를 SangMaSangMa의 위치에 생성해 넣어준다.") - void shouldPlaceSangMaSangMaWhenSideHan() { - // given - Piece[][] grid = new Piece[10][9]; - Side side = Side.HAN; - - // when - STRATEGY.place(grid, side); - Piece leftSang = grid[0][1]; - Piece leftMa = grid[0][2]; - Piece rightSang = grid[0][6]; - Piece rightMa = grid[0][7]; - - // then - Assertions.assertThat(leftSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftMa).isInstanceOf(Ma.class); - Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - } - - @Test - @DisplayName("Cho 팀의 Sang 객체와 Ma 객체를 SangMaSangMa의 위치에 생성해 넣어준다.") - void shouldPlaceSangMaSangMaWhenSideCho() { - // given - Piece[][] grid = new Piece[10][9]; - Side side = Side.CHO; - - // when - STRATEGY.place(grid, side); - Piece leftSang = grid[9][1]; - Piece leftMa = grid[9][2]; - Piece rightSang = grid[9][6]; - Piece rightMa = grid[9][7]; - - // then - Assertions.assertThat(leftSang).isInstanceOf(Sang.class); - Assertions.assertThat(leftMa).isInstanceOf(Ma.class); - Assertions.assertThat(rightSang).isInstanceOf(Sang.class); - Assertions.assertThat(rightMa).isInstanceOf(Ma.class); - - Assertions.assertThat(leftSang.isSameSide(side)).isTrue(); - Assertions.assertThat(leftMa.isSameSide(side)).isTrue(); - Assertions.assertThat(rightSang.isSameSide(side)).isTrue(); - Assertions.assertThat(rightMa.isSameSide(side)).isTrue(); - } -} diff --git a/src/test/java/janggi/support/TestArrangementStrategy.java b/src/test/java/janggi/support/TestArrangementStrategy.java new file mode 100644 index 0000000000..7ebd120bd3 --- /dev/null +++ b/src/test/java/janggi/support/TestArrangementStrategy.java @@ -0,0 +1,25 @@ +package janggi.support; + +import janggi.domain.Location; +import janggi.domain.piece.Piece; +import janggi.strategy.ArrangementStrategy; +import java.util.Map; + +public class TestArrangementStrategy implements ArrangementStrategy { + private final Map customPieces; + + public TestArrangementStrategy(Map customPieces) { + this.customPieces = customPieces; + } + + @Override + public void place(Piece[][] arrangement) { + placeVariablePieces(arrangement); + } + + protected void placeVariablePieces(Piece[][] arrangement) { + customPieces.forEach((loc, piece) -> { + arrangement[loc.x()][loc.y()] = piece; + }); + } +} diff --git a/src/test/java/janggi/support/TestMaSangArrangementTemplate.java b/src/test/java/janggi/support/TestMaSangArrangementTemplate.java deleted file mode 100644 index 78d1c271b7..0000000000 --- a/src/test/java/janggi/support/TestMaSangArrangementTemplate.java +++ /dev/null @@ -1,27 +0,0 @@ -package janggi.support; - -import janggi.domain.Location; -import janggi.domain.Side; -import janggi.domain.piece.Piece; -import janggi.strategy.MaSangArrangementTemplate; -import java.util.Map; - -public class TestMaSangArrangementTemplate extends MaSangArrangementTemplate { - private final Map customPieces; - - public TestMaSangArrangementTemplate(Map customPieces) { - this.customPieces = customPieces; - } - - @Override - public void place(Piece[][] arrangement, Side side) { - placeVariablePieces(arrangement, side); - } - - @Override - protected void placeVariablePieces(Piece[][] arrangement, Side side) { - customPieces.forEach((loc, piece) -> { - arrangement[loc.x()][loc.y()] = piece; - }); - } -} diff --git a/src/test/java/janggi/support/TestPiece.java b/src/test/java/janggi/support/TestPiece.java index 2ce258b31e..c63c076951 100644 --- a/src/test/java/janggi/support/TestPiece.java +++ b/src/test/java/janggi/support/TestPiece.java @@ -6,10 +6,14 @@ import janggi.domain.piece.PieceType; import java.util.List; -public class TestPiece extends Piece { +public class TestPiece implements Piece { - public TestPiece(Side side) { - super(PieceType.CHA, side); + private final PieceType pieceType; + private final Side side; + + public TestPiece(PieceType pieceType, Side side) { + this.pieceType = pieceType; + this.side = side; } @Override @@ -17,6 +21,21 @@ public boolean isEmpty() { return false; } + @Override + public boolean isPo() { + return this.pieceType.equals(PieceType.PO); + } + + @Override + public boolean isSameSide(Side side) { + return this.side.equals(side); + } + + @Override + public PieceType getPieceType() { + return pieceType; + } + @Override public List calculateRoute(Location from, Location to) { return List.of(); From b6e9e76d4f2a56b83b448759a65f320c114b3fc0 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 3 Apr 2026 19:40:35 +0900 Subject: [PATCH 40/45] =?UTF-8?q?refactor:=20View=EC=9D=98=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=98=88=EC=99=B8=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ConsoleWriter가 RuntimeException 객체 대신 String 타입의 에러 메시지를 인자로 받도록 수정 - Controller에서 예외를 캐치해 메시지만 추출한 뒤 View에 전달하도록 흐름 변경 - View가 예외 객체의 존재를 알 필요 없이 '출력'이라는 본연의 역할에만 집중하도록 결합도 완화 --- src/main/java/janggi/controller/JanggiFlow.java | 8 ++++---- src/main/java/janggi/view/ApplicationView.java | 4 ++-- src/main/java/janggi/view/ConsoleWriter.java | 4 ++-- src/main/java/janggi/view/Output.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index d66ec2926b..18ad94c13f 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -54,7 +54,7 @@ private ArrangementStrategy repeatAskStrategyUntilSuccess(Side side) { try { return askStrategy(side); } catch (IllegalArgumentException e) { - view.showErrorMessage(e); + view.showErrorMessage(e.getMessage()); return repeatAskStrategyUntilSuccess(side); } } @@ -69,7 +69,7 @@ private Location repeatAskLocationOfPieceUntilSuccess(Side turnSide, Board board try { return askLocationOfPiece(turnSide, board); } catch (IllegalArgumentException e) { - view.showErrorMessage(e); + view.showErrorMessage(e.getMessage()); return repeatAskLocationOfPieceUntilSuccess(turnSide, board); } } @@ -85,7 +85,7 @@ private Location repeatAskLocationToMoveUntilSuccess(Location from, Board board) try { return askLocationToMove(from, board); } catch (IllegalArgumentException e) { - view.showErrorMessage(e); + view.showErrorMessage(e.getMessage()); return repeatAskLocationToMoveUntilSuccess(from, board); } } @@ -101,7 +101,7 @@ private void retryUntilPieceIsSuccessfullyMoved(Runnable runnable) { try { runnable.run(); } catch (IllegalArgumentException e) { - view.showErrorMessage(e); + view.showErrorMessage(e.getMessage()); retryUntilPieceIsSuccessfullyMoved(runnable); } } diff --git a/src/main/java/janggi/view/ApplicationView.java b/src/main/java/janggi/view/ApplicationView.java index 2df5189b7a..87e12eb4f5 100644 --- a/src/main/java/janggi/view/ApplicationView.java +++ b/src/main/java/janggi/view/ApplicationView.java @@ -45,7 +45,7 @@ public List promptForLocationToMove() { return inputReader.readIntegers(); } - public void showErrorMessage(RuntimeException e) { - outputWriter.printErrorMessage(e); + public void showErrorMessage(String errorMessage) { + outputWriter.printErrorMessage(errorMessage); } } diff --git a/src/main/java/janggi/view/ConsoleWriter.java b/src/main/java/janggi/view/ConsoleWriter.java index 1e885ee502..6d791511d6 100644 --- a/src/main/java/janggi/view/ConsoleWriter.java +++ b/src/main/java/janggi/view/ConsoleWriter.java @@ -10,8 +10,8 @@ public void printPromptMessage(String promptMessage) { } @Override - public void printErrorMessage(RuntimeException e) { - System.out.println("[ERROR] " + e.getMessage()); + public void printErrorMessage(String errorMessage) { + System.out.println("[ERROR] " + errorMessage); System.out.println(); } diff --git a/src/main/java/janggi/view/Output.java b/src/main/java/janggi/view/Output.java index a467a70693..e539f5b9d6 100644 --- a/src/main/java/janggi/view/Output.java +++ b/src/main/java/janggi/view/Output.java @@ -6,7 +6,7 @@ public interface Output { void printPromptMessage(String promptMessage); - void printErrorMessage(RuntimeException e); + void printErrorMessage(String errorMessage); void printStringMatrix(List> matrix); } From f03124223a7804a722b39512aa984d166bcd538d Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 3 Apr 2026 19:52:00 +0900 Subject: [PATCH 41/45] =?UTF-8?q?refactor:=202=EC=B0=A8=EC=9B=90=20?= =?UTF-8?q?=EB=B0=B0=EC=97=B4=20=EB=84=88=EB=B9=84=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20?= =?UTF-8?q?=EB=A7=A4=EC=A7=81=20=EB=84=98=EB=B2=84=EB=A5=BC=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Board 클래스에서 열의 길이를 구하기 위해 사용된 하드코딩된 숫자 0을 FIRST_ROW_INDEX 상수로 분리해 매직 넘버 제거 및 가독성 향상 --- src/main/java/janggi/domain/board/Board.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 30497ce9b1..abf14a72a5 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -12,6 +12,8 @@ public class Board { + private static final int FIRST_ROW_INDEX = 0; + private final Map boardState; private final int height; private final int width; @@ -27,7 +29,7 @@ public static Board create(BoardAssembler assembler) { Map boardState = new HashMap<>(); int height = pieces.length; - int width = pieces[0].length; + int width = pieces[FIRST_ROW_INDEX].length; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { From d9e038ad181445409c71745f0718fcb6a8e58da0 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 3 Apr 2026 20:09:19 +0900 Subject: [PATCH 42/45] =?UTF-8?q?refactor:=20=EC=9E=AC=EA=B7=80=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=EB=A1=9C=20=EA=B5=AC=ED=98=84=EB=90=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EB=B0=98=EB=B3=B5=EB=AC=B8=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 잘못된 입력이 반복될 경우 Call Stack이 쌓여 StackOverflowError가 발생하는 위험을 방지하기 위해 재귀 대신 while 반복문 적용 - 여러 입력 로직에서 재시도 로직이 중복되는 것을 막기 위해, Supplier를 활용한 공통 retry() 메서드를 구현해 가독성과 재사용성 향상 --- .../java/janggi/controller/JanggiFlow.java | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/main/java/janggi/controller/JanggiFlow.java b/src/main/java/janggi/controller/JanggiFlow.java index 18ad94c13f..f0134c6205 100644 --- a/src/main/java/janggi/controller/JanggiFlow.java +++ b/src/main/java/janggi/controller/JanggiFlow.java @@ -10,6 +10,7 @@ import janggi.view.ApplicationView; import java.util.List; import java.util.Map; +import java.util.function.Supplier; public class JanggiFlow { @@ -39,6 +40,16 @@ public void process() { } } + private T retry(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + view.showErrorMessage(e.getMessage()); + } + } + } + private List> convertBoardStatus(Board board) { List> boardIn2D = board.to2DArray(); return boardIn2D.stream() @@ -51,12 +62,7 @@ private List> convertBoardStatus(Board board) { } private ArrangementStrategy repeatAskStrategyUntilSuccess(Side side) { - try { - return askStrategy(side); - } catch (IllegalArgumentException e) { - view.showErrorMessage(e.getMessage()); - return repeatAskStrategyUntilSuccess(side); - } + return retry(() -> askStrategy(side)); } private ArrangementStrategy askStrategy(Side side) { @@ -66,12 +72,7 @@ private ArrangementStrategy askStrategy(Side side) { } private Location repeatAskLocationOfPieceUntilSuccess(Side turnSide, Board board) { - try { - return askLocationOfPiece(turnSide, board); - } catch (IllegalArgumentException e) { - view.showErrorMessage(e.getMessage()); - return repeatAskLocationOfPieceUntilSuccess(turnSide, board); - } + return retry(() -> askLocationOfPiece(turnSide, board)); } private Location askLocationOfPiece(Side current, Board board) { @@ -82,12 +83,7 @@ private Location askLocationOfPiece(Side current, Board board) { } private Location repeatAskLocationToMoveUntilSuccess(Location from, Board board) { - try { - return askLocationToMove(from, board); - } catch (IllegalArgumentException e) { - view.showErrorMessage(e.getMessage()); - return repeatAskLocationToMoveUntilSuccess(from, board); - } + return retry(() -> askLocationToMove(from, board)); } private Location askLocationToMove(Location startingLocation, Board board) { @@ -98,11 +94,13 @@ private Location askLocationToMove(Location startingLocation, Board board) { } private void retryUntilPieceIsSuccessfullyMoved(Runnable runnable) { - try { - runnable.run(); - } catch (IllegalArgumentException e) { - view.showErrorMessage(e.getMessage()); - retryUntilPieceIsSuccessfullyMoved(runnable); + while (true) { + try { + runnable.run(); + break; + } catch (IllegalArgumentException e) { + view.showErrorMessage(e.getMessage()); + } } } } From bc27b6b51c77c92d1e3c02c54b4201b0facc842e Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 3 Apr 2026 21:20:26 +0900 Subject: [PATCH 43/45] =?UTF-8?q?refactor:=202=EC=B0=A8=EC=9B=90=20?= =?UTF-8?q?=EB=B0=B0=EC=97=B4=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=9D=B8=EB=8D=B4?= =?UTF-8?q?=ED=8A=B8=20=EA=B0=90=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BoardAssembler의 초기화 순서를 '빈칸 null 체크' 방식에서 'EmptyPiece 선배치 후 덮어쓰기' 방식으로 변경 --- src/main/java/janggi/strategy/BoardAssembler.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/janggi/strategy/BoardAssembler.java b/src/main/java/janggi/strategy/BoardAssembler.java index ebe8674887..e5d0e86368 100644 --- a/src/main/java/janggi/strategy/BoardAssembler.java +++ b/src/main/java/janggi/strategy/BoardAssembler.java @@ -2,6 +2,7 @@ import janggi.domain.piece.EmptyPiece; import janggi.domain.piece.Piece; +import java.util.Arrays; import java.util.List; public class BoardAssembler { @@ -22,22 +23,18 @@ public static BoardAssembler from(List strategies) { public Piece[][] assemble() { Piece[][] arrangement = new Piece[DEFAULT_ROWS][DEFAULT_COLS]; + setupEmptyPieces(arrangement); + for (ArrangementStrategy strategy : strategies) { strategy.place(arrangement); } - setupEmptyPieces(arrangement); - return arrangement; } private void setupEmptyPieces(Piece[][] arrangement) { - for (int row = 0; row < DEFAULT_ROWS; row++) { - for (int col = 0; col < DEFAULT_COLS; col++) { - if (arrangement[row][col] == null) { - arrangement[row][col] = EmptyPiece.getInstance(); - } - } + for (Piece[] row : arrangement) { + Arrays.fill(row, EmptyPiece.getInstance()); } } } From 3f25d1b35f1a5aca983810963c8d80d4f5f45dcb Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 3 Apr 2026 21:23:24 +0900 Subject: [PATCH 44/45] =?UTF-8?q?refactor:=202=EC=B0=A8=EC=9B=90=20?= =?UTF-8?q?=EB=B0=B0=EC=97=B4=20=EC=88=9C=ED=9A=8C=20=EC=8B=9C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8A=94=202=EC=A4=91=20for=EB=AC=B8=20inden?= =?UTF-8?q?t=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Board 초기화 시 행 단위 매핑 로직을 메서드로 추출해 indent depth를 1로 감소 - Board 출력을 위한 to2DArray() 내부의 2중 for문 역시 별도 메서드로 추출해 indent depth를 1로 감소 --- src/main/java/janggi/domain/board/Board.java | 26 +++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index abf14a72a5..055485583f 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -32,14 +32,18 @@ public static Board create(BoardAssembler assembler) { int width = pieces[FIRST_ROW_INDEX].length; for (int row = 0; row < height; row++) { - for (int col = 0; col < width; col++) { - boardState.put(new Location(row, col), pieces[row][col]); - } + mapRowToBoardState(boardState, pieces[row], row); } return new Board(boardState, height, width); } + private static void mapRowToBoardState(Map boardState, Piece[] rowPieces, int row) { + for (int col = 0; col < rowPieces.length; col++) { + boardState.put(new Location(row, col), rowPieces[col]); + } + } + public void validateLocationOfPiece(Side currentSide, Location locationOfPiece) { validateLocation(locationOfPiece); validatePieceExist(locationOfPiece); @@ -100,16 +104,20 @@ public boolean isNotEmpty() { public List> to2DArray() { List> pieces = new ArrayList<>(); for (int row = 0; row < height; row++) { - List line = new ArrayList<>(); - for (int col = 0; col < width; col++) { - Piece piece = boardState.get(new Location(row, col)); - line.add(piece); - } - pieces.add(List.copyOf(line)); + pieces.add(createRows(row)); // 메서드 분리 } return List.copyOf(pieces); } + private List createRows(int row) { + List line = new ArrayList<>(); + for (int col = 0; col < width; col++) { + Piece piece = boardState.get(new Location(row, col)); + line.add(piece); + } + return List.copyOf(line); + } + private boolean isNotSameSide(Piece piece, Side side) { return !piece.isSameSide(side); } From 6bc5a0c3cdbe2ddffece8f6e35959abf3e994d67 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 4 Apr 2026 02:04:55 +0900 Subject: [PATCH 45/45] =?UTF-8?q?feat:=20=EC=9E=A5=EA=B8=B0=ED=8C=90=20?= =?UTF-8?q?=ED=85=8C=EB=91=90=EB=A6=AC=20=EC=A2=8C=ED=91=9C(=ED=96=89,=20?= =?UTF-8?q?=EC=97=B4)=20=EC=B6=9C=EB=A0=A5=20=EB=B0=8F=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=EB=A7=A4=ED=95=91=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자가 기물의 위치를 쉽게 파악하고 입력할 수 있도록 ConsoleWriter에 행/열 좌표 출력 로직 추가 - 한글 기물과 숫자 간의 콘솔 출력 너비(시각적 크기) 차이로 인한 정렬 깨짐 문제를 해결하기 위해, 상단 열 번호에 전각 숫자를 도입해 완벽한 격자 정렬 구현 - 사용자가 확인하고 입력하는 좌표(1~10, 1~9)를 내부 2차원 배열용 좌표로 변환하는 책임을 도메인(Location)에 캡슐화 - Location 팩토리 메서드 역할 변경에 따라, 기존 테스트 코드들의 Location 생성 방식을 일괄 수정 --- src/main/java/janggi/domain/Location.java | 4 ++- src/main/java/janggi/view/ConsoleWriter.java | 26 ++++++++++++++++--- .../java/janggi/domain/board/BoardTest.java | 10 +++---- .../domain/rule/JolbyeongMovementTest.java | 13 +++++----- .../rule/route/StraightRouteProviderTest.java | 17 ++++++------ 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/main/java/janggi/domain/Location.java b/src/main/java/janggi/domain/Location.java index cf34d2773f..b2aba9233f 100644 --- a/src/main/java/janggi/domain/Location.java +++ b/src/main/java/janggi/domain/Location.java @@ -8,7 +8,9 @@ public record Location(int x, int y) { public static Location from(List coordination) { validateCount(coordination); - return new Location(coordination.get(0), coordination.get(1)); + int row = coordination.get(0) - 1; + int col = coordination.get(1) - 1; + return new Location(row, col); } private static void validateCount(List coordination) { diff --git a/src/main/java/janggi/view/ConsoleWriter.java b/src/main/java/janggi/view/ConsoleWriter.java index 6d791511d6..f1d5886607 100644 --- a/src/main/java/janggi/view/ConsoleWriter.java +++ b/src/main/java/janggi/view/ConsoleWriter.java @@ -1,9 +1,14 @@ package janggi.view; +import java.util.ArrayList; import java.util.List; public class ConsoleWriter implements Output { + private static final String BLANK = " "; + private static final List FULL_WIDTH_NUMBERS = + List.of("", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + @Override public void printPromptMessage(String promptMessage) { System.out.println(promptMessage); @@ -18,9 +23,15 @@ public void printErrorMessage(String errorMessage) { @Override public void printStringMatrix(List> matrix) { StringBuilder matrixSnapshot = new StringBuilder(); - for (List strings : matrix) { - for (String string : strings) { - matrixSnapshot.append(String.format("%-2s", string)); + + List colIndexInfo = createCoordinationIndexInfo(matrix.getFirst().size()); + matrixSnapshot.append(String.join("", colIndexInfo)).append("\n"); + + for (int row = 0; row < matrix.size(); row++) { + matrixSnapshot.append(String.format("%2d ", row + 1)); + + for (String cell : matrix.get(row)) { + matrixSnapshot.append(cell).append(" "); } matrixSnapshot.append("\n"); } @@ -28,4 +39,13 @@ public void printStringMatrix(List> matrix) { System.out.println(); System.out.println(matrixSnapshot); } + + private List createCoordinationIndexInfo(int length) { + List coordinationInfo = new ArrayList<>(); + coordinationInfo.add(BLANK); + for (int index = 1; index <= length; index++) { + coordinationInfo.add(FULL_WIDTH_NUMBERS.get(index) + " "); + } + return coordinationInfo; + } } diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 16de29566e..8dace052a5 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -58,7 +58,7 @@ void shouldNotThrowExceptionWhenLocationExists() { BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); - Location location = Location.from(List.of(1, 1)); + Location location = new Location(1, 1); // when & then assertDoesNotThrow(() -> board.validateLocationOfPiece(currentSide, location)); @@ -76,7 +76,7 @@ void shouldThrowExceptionWhenLocationDoesNotExist() { BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); - Location location = Location.from(List.of(11, 11)); + Location location = new Location(11, 11); // when & then Assertions.assertThatThrownBy(() -> board.validateLocationOfPiece(currentSide, location)) @@ -98,7 +98,7 @@ void shouldNotThrowExceptionWhenPieceOnSameSideExistsAtLocation() { BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); - Location location = Location.from(List.of(1, 1)); + Location location = new Location(1, 1); // when & then assertDoesNotThrow(() -> board.validateLocationOfPiece(currentSide, location)); @@ -117,7 +117,7 @@ void shouldThrowExceptionWhenPieceOnOtherSideExistsAtLocation() { BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); - Location location = Location.from(List.of(1, 1)); + Location location = new Location(1, 1); // when & then Assertions.assertThatThrownBy(() -> board.validateLocationOfPiece(otherSide, location)) @@ -135,7 +135,7 @@ void shouldThrowExceptionWhenPieceDoesNotExistsAtLocation() { BoardAssembler assembler = BoardAssembler.from(List.of(strategy, strategy)); Board board = Board.create(assembler); - Location location = Location.from(List.of(5, 5)); + Location location = new Location(5, 5); // when & then Assertions.assertThatThrownBy(() -> board.validateLocationOfPiece(Side.HAN, location)) diff --git a/src/test/java/janggi/domain/rule/JolbyeongMovementTest.java b/src/test/java/janggi/domain/rule/JolbyeongMovementTest.java index 73198293f1..386f36c071 100644 --- a/src/test/java/janggi/domain/rule/JolbyeongMovementTest.java +++ b/src/test/java/janggi/domain/rule/JolbyeongMovementTest.java @@ -53,21 +53,20 @@ void shouldReturnEmptyForUnReachableLocationWhenTeamHan() { @ParameterizedTest @DisplayName("초팀 졸병이 이동할 수 없는 위치로 경로를 계산하면, 빈 결과를 반환한다.") @MethodSource("provideUnreachableCoordination") - void shouldReturnEmptyForUnReachableLocationWhenTeamCho(List coordination) { + void shouldReturnEmptyForUnReachableLocationWhenTeamCho(Location destination) { // given Location from = new Location(0, 0); - Location to = Location.from(coordination); JolbyeongMovement movement = JolbyeongMovement.getInstanceBySide(Side.CHO); // when & then - Assertions.assertThat(movement.calculateRoute(from, to)).isEmpty(); + Assertions.assertThat(movement.calculateRoute(from, destination)).isEmpty(); } - static List> provideUnreachableCoordination() { + static List provideUnreachableCoordination() { return List.of( - List.of(0,2), // 거리가 멀어서 도달할 수 없는 경우 - List.of(1,1), // 대각선으로 이동하는 경우 - List.of(0,1) // 뒤로 이동하는 경우 + new Location(0,2), // 거리가 멀어서 도달할 수 없는 경우 + new Location(1,1), // 대각선으로 이동하는 경우 + new Location(0,1) // 뒤로 이동하는 경우 ); } } diff --git a/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java index 160e4601df..22643269c2 100644 --- a/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java +++ b/src/test/java/janggi/domain/rule/route/StraightRouteProviderTest.java @@ -18,7 +18,7 @@ class StraightRouteProviderTest { @MethodSource("provideReachableCoordination") void shouldReturnRouteForReachableLocation(Location destination, List route) { // given - Location from = Location.from(List.of(2, 2)); + Location from = new Location(2, 2); // when & then Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, destination)).hasValue(route); @@ -48,20 +48,19 @@ static Stream provideReachableCoordination() { @ParameterizedTest @DisplayName("직선으로 이동이 불가능한 위치로 경로를 계산하면, 빈 결과를 반환한다.") @MethodSource("provideUnreachableCoordination") - void shouldReturnEmptyForUnReachableLocation(List coordination) { + void shouldReturnEmptyForUnReachableLocation(Location destination) { // given - Location from = Location.from(List.of(1, 1)); - Location to = Location.from(coordination); + Location from = new Location(1, 1); // when & then - Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, to)).isEmpty(); + Assertions.assertThat(STRAIGHT_ROUTE_PROVIDER.calculateRoute(from, destination)).isEmpty(); } - static List> provideUnreachableCoordination() { + static List provideUnreachableCoordination() { return List.of( - List.of(3, 3), - List.of(3, 2), - List.of(2, 2) + new Location(3, 3), + new Location(3, 2), + new Location(2, 2) ); } }