From b717753372829e5c07a204419379b7e8895537d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Wed, 21 May 2025 19:55:43 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat(step1)=20:=201=EB=8B=A8=EA=B3=84=20-?= =?UTF-8?q?=20=EC=82=AC=EB=8B=A4=EB=A6=AC=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 ++++ src/main/java/.gitkeep | 0 src/main/java/Application.java | 10 ++ .../java/controller/LadderController.java | 23 ++++ src/main/java/domain/BridgeUnit.java | 24 +++++ src/main/java/domain/LadderBoard.java | 100 ++++++++++++++++++ src/main/java/domain/LadderRow.java | 48 +++++++++ src/main/java/domain/PillarUnit.java | 9 ++ src/main/java/dto/LadderBuildRequest.java | 4 + src/main/java/dto/LadderBuildResponse.java | 6 ++ src/main/java/view/OutputView.java | 19 ++++ 11 files changed, 266 insertions(+) create mode 100644 README.md delete mode 100644 src/main/java/.gitkeep create mode 100644 src/main/java/Application.java create mode 100644 src/main/java/controller/LadderController.java create mode 100644 src/main/java/domain/BridgeUnit.java create mode 100644 src/main/java/domain/LadderBoard.java create mode 100644 src/main/java/domain/LadderRow.java create mode 100644 src/main/java/domain/PillarUnit.java create mode 100644 src/main/java/dto/LadderBuildRequest.java create mode 100644 src/main/java/dto/LadderBuildResponse.java create mode 100644 src/main/java/view/OutputView.java diff --git a/README.md b/README.md new file mode 100644 index 00000000..058845a0 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# 1단계 - 사다리 출력 + +## 기능 요구사항 + +- 네이버 사다리 게임을 참고하여 도메인을 분석하여 구현한다. +- 사다리는 4X4 크기로 고정되고, 연결 여부는 랜덤으로 결정한다. +- 사다리 타기가 정상적으로 동작하려면 라인이 겹치지 않도록 해야 한다. + +### 실행 결과 +- 프로그램을 실행한 결과는 다음과 같다. + +```text +실행결과 + + |-----| |-----| + | |-----| | + |-----| | | + | |-----| | +``` + +## 추가된 요구 사항 +- 모든 엔티티를 작게 유지한다. +- 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..fd0b3d70 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,10 @@ +import controller.LadderController; +import view.OutputView; + +public class Application { + public static void main(String[] args) { + OutputView outputView = new OutputView(); + LadderController controller = new LadderController(outputView); + controller.run(4); + } +} diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java new file mode 100644 index 00000000..be037db3 --- /dev/null +++ b/src/main/java/controller/LadderController.java @@ -0,0 +1,23 @@ +package controller; + +import domain.LadderBoard; +import dto.LadderBuildRequest; +import dto.LadderBuildResponse; +import view.OutputView; + +public class LadderController { + + private final OutputView outputView; + + public LadderController(OutputView outputView) { + this.outputView = outputView; + } + + public void run(int columnCount) { + LadderBuildRequest request = new LadderBuildRequest(columnCount); + LadderBuildResponse response = LadderBoard.build(request); + + outputView.printLadderTitle(); + outputView.printLadder(response); + } +} diff --git a/src/main/java/domain/BridgeUnit.java b/src/main/java/domain/BridgeUnit.java new file mode 100644 index 00000000..4c97097e --- /dev/null +++ b/src/main/java/domain/BridgeUnit.java @@ -0,0 +1,24 @@ +package domain; + +public class BridgeUnit { + + private static final String CONNECTED = "-----"; + private static final String NOT_CONNECTED = " "; + + private final boolean connected; + + public BridgeUnit(boolean connected) { + this.connected = connected; + } + + public boolean isConnected() { + return connected; + } + + public String draw() { + if (connected) { + return CONNECTED; + } + return NOT_CONNECTED; + } +} diff --git a/src/main/java/domain/LadderBoard.java b/src/main/java/domain/LadderBoard.java new file mode 100644 index 00000000..1a0eff71 --- /dev/null +++ b/src/main/java/domain/LadderBoard.java @@ -0,0 +1,100 @@ +package domain; + +import dto.LadderBuildRequest; +import dto.LadderBuildResponse; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +public class LadderBoard { + + private static final Random random = new Random(); + + public static LadderBuildResponse build(LadderBuildRequest request) { + int columnCount = request.count(); + int middleIndex = (columnCount - 1) / 2; + + while (true) { + LadderBuildResponse response = generateValidBoardRows(columnCount, middleIndex); + LadderBuildResponse validBoard = returnIfValidBoard(response); + if (validBoard != null) return validBoard; + } + } + + private static LadderBuildResponse generateValidBoardRows(int columnCount, int middleIndex) { + List rows = new ArrayList<>(); + boolean middleBridgeExists = false; + Set connectedPositions = new HashSet<>(); + + for (int i = 0; i < columnCount; i++) { + List bridgeConnectionStates = generateBridgeConnectionStates(columnCount); + LadderRow row = new LadderRow(columnCount, bridgeConnectionStates); + rows.add(row.draw()); + + middleBridgeExists = updateMiddleBridgePresence(middleBridgeExists, row, middleIndex); + recordConnectedPositions(connectedPositions, bridgeConnectionStates); + } + + if (!allPositionsConnected(connectedPositions, columnCount - 1)) return null; + return returnIfMiddleBridgeExists(rows, middleBridgeExists); + } + + private static List generateBridgeConnectionStates(int columnCount) { + List connectionStates = new ArrayList<>(); + + for (int index = 0; index < columnCount - 1; index++) { + boolean connectable = isConnectable(connectionStates, index); + boolean shouldPlaceBridge = decideBridgePlacement(connectable); + connectionStates.add(shouldPlaceBridge); + } + + return connectionStates; + } + + private static boolean isConnectable(List connectionStates, int index) { + if (index == 0) return true; + return !connectionStates.get(index - 1); + } + + private static boolean decideBridgePlacement(boolean connectable) { + if (!connectable) return false; + return random.nextBoolean(); + } + + private static boolean updateMiddleBridgePresence( + boolean currentState, + LadderRow row, + int middleIndex + ) { + if (row.hasMiddleBridge(middleIndex)) return true; + return currentState; + } + + private static void recordConnectedPositions(Set connected, List currentLine) { + for (int i = 0; i < currentLine.size(); i++) { + addIfConnected(connected, currentLine.get(i), i); + } + } + + private static void addIfConnected(Set connected, boolean isConnected, int index) { + if (!isConnected) return; + connected.add(index); + } + + private static boolean allPositionsConnected(Set connected, int bridgeCount) { + return connected.size() == bridgeCount; + } + + private static LadderBuildResponse returnIfMiddleBridgeExists(List rows, boolean exists) { + if (!exists) return null; + return new LadderBuildResponse(rows); + } + + private static LadderBuildResponse returnIfValidBoard(LadderBuildResponse response) { + if (response == null) return null; + return response; + } +} diff --git a/src/main/java/domain/LadderRow.java b/src/main/java/domain/LadderRow.java new file mode 100644 index 00000000..a7985843 --- /dev/null +++ b/src/main/java/domain/LadderRow.java @@ -0,0 +1,48 @@ +package domain; + +import java.util.List; + +public class LadderRow { + + private final int columnCount; + private final List bridges; + + public LadderRow(int columnCount, List bridgeConnectionStates) { + this.columnCount = columnCount; + this.bridges = createBridgesFromStates(bridgeConnectionStates); + } + + public String draw() { + StringBuilder line = new StringBuilder(); + + for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) { + line.append(PillarUnit.SYMBOL); + appendBridgeIfPresent(line, columnIndex); + } + + return line.toString(); + } + + public boolean hasMiddleBridge(int middleIndex) { + if (isIndexOutOfBridgeRange(middleIndex)) { + return false; + } + + return bridges.get(middleIndex).isConnected(); + } + + private List createBridgesFromStates(List connectionStates) { + return connectionStates.stream() + .map(BridgeUnit::new) + .toList(); + } + + private void appendBridgeIfPresent(StringBuilder line, int index) { + if (isIndexOutOfBridgeRange(index)) return; + line.append(bridges.get(index).draw()); + } + + private boolean isIndexOutOfBridgeRange(int index) { + return index >= bridges.size(); + } +} diff --git a/src/main/java/domain/PillarUnit.java b/src/main/java/domain/PillarUnit.java new file mode 100644 index 00000000..7a57b01e --- /dev/null +++ b/src/main/java/domain/PillarUnit.java @@ -0,0 +1,9 @@ +package domain; + +public class PillarUnit { + + public static final String SYMBOL = "|"; + + private PillarUnit() { + } +} diff --git a/src/main/java/dto/LadderBuildRequest.java b/src/main/java/dto/LadderBuildRequest.java new file mode 100644 index 00000000..a722b619 --- /dev/null +++ b/src/main/java/dto/LadderBuildRequest.java @@ -0,0 +1,4 @@ +package dto; + +public record LadderBuildRequest(int count) { +} diff --git a/src/main/java/dto/LadderBuildResponse.java b/src/main/java/dto/LadderBuildResponse.java new file mode 100644 index 00000000..30743ffb --- /dev/null +++ b/src/main/java/dto/LadderBuildResponse.java @@ -0,0 +1,6 @@ +package dto; + +import java.util.List; + +public record LadderBuildResponse(List rows) { +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..6924d531 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,19 @@ +package view; + +import dto.LadderBuildResponse; + +public class OutputView { + + private static final String LADDER_OUTPUT_TITLE = "실행결과"; + + public void printLadderTitle() { + System.out.println(LADDER_OUTPUT_TITLE); + System.out.println(); + } + + public void printLadder(LadderBuildResponse response) { + for (String row : response.rows()) { + System.out.println(" " + row); + } + } +} From 46e32f5bc2ac099adb8b61d25a44474b680ea2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Wed, 21 May 2025 20:32:03 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat(step2)=20:=202=EB=8B=A8=EA=B3=84=20-?= =?UTF-8?q?=20=EC=82=AC=EB=8B=A4=EB=A6=AC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 +++++++++++++++++++ src/main/java/Application.java | 6 ++++-- .../java/controller/LadderController.java | 12 ++++++++--- src/main/java/domain/LadderBoard.java | 9 ++++---- src/main/java/dto/LadderBuildRequest.java | 2 +- src/main/java/view/InputView.java | 20 ++++++++++++++++++ 6 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 src/main/java/view/InputView.java diff --git a/README.md b/README.md index 058845a0..2cd2d2e5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ +# 2단계 - 사다리 생성 + +## 기능 요구사항 +- 사다리는 크기를 입력 받아 생성할 수 있다. + +```text +사다리의 넓이는 몇 개인가요? +4 + +사다리의 높이는 몇 개인가요? +5 + +실행결과 + + |-----| |-----| + | |-----| | + |-----| | | + | |-----| | + |-----| |-----| +``` + # 1단계 - 사다리 출력 ## 기능 요구사항 diff --git a/src/main/java/Application.java b/src/main/java/Application.java index fd0b3d70..e948443b 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,10 +1,12 @@ import controller.LadderController; +import view.InputView; import view.OutputView; public class Application { public static void main(String[] args) { + InputView inputView = new InputView(); OutputView outputView = new OutputView(); - LadderController controller = new LadderController(outputView); - controller.run(4); + LadderController controller = new LadderController(inputView, outputView); + controller.run(); } } diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java index be037db3..d618ad21 100644 --- a/src/main/java/controller/LadderController.java +++ b/src/main/java/controller/LadderController.java @@ -3,18 +3,24 @@ import domain.LadderBoard; import dto.LadderBuildRequest; import dto.LadderBuildResponse; +import view.InputView; import view.OutputView; public class LadderController { + private final InputView inputView; private final OutputView outputView; - public LadderController(OutputView outputView) { + public LadderController(InputView inputView, OutputView outputView) { + this.inputView = inputView; this.outputView = outputView; } - public void run(int columnCount) { - LadderBuildRequest request = new LadderBuildRequest(columnCount); + public void run() { + int columnCount = inputView.readColumnCount(); + int rowCount = inputView.readRowCount(); + + LadderBuildRequest request = new LadderBuildRequest(columnCount, rowCount); LadderBuildResponse response = LadderBoard.build(request); outputView.printLadderTitle(); diff --git a/src/main/java/domain/LadderBoard.java b/src/main/java/domain/LadderBoard.java index 1a0eff71..a126be37 100644 --- a/src/main/java/domain/LadderBoard.java +++ b/src/main/java/domain/LadderBoard.java @@ -14,22 +14,23 @@ public class LadderBoard { private static final Random random = new Random(); public static LadderBuildResponse build(LadderBuildRequest request) { - int columnCount = request.count(); + int columnCount = request.columnCount(); + int rowCount = request.rowCount(); int middleIndex = (columnCount - 1) / 2; while (true) { - LadderBuildResponse response = generateValidBoardRows(columnCount, middleIndex); + LadderBuildResponse response = generateValidBoardRows(columnCount, rowCount, middleIndex); LadderBuildResponse validBoard = returnIfValidBoard(response); if (validBoard != null) return validBoard; } } - private static LadderBuildResponse generateValidBoardRows(int columnCount, int middleIndex) { + private static LadderBuildResponse generateValidBoardRows(int columnCount, int rowCount, int middleIndex) { List rows = new ArrayList<>(); boolean middleBridgeExists = false; Set connectedPositions = new HashSet<>(); - for (int i = 0; i < columnCount; i++) { + for (int i = 0; i < rowCount; i++) { List bridgeConnectionStates = generateBridgeConnectionStates(columnCount); LadderRow row = new LadderRow(columnCount, bridgeConnectionStates); rows.add(row.draw()); diff --git a/src/main/java/dto/LadderBuildRequest.java b/src/main/java/dto/LadderBuildRequest.java index a722b619..27c4c24a 100644 --- a/src/main/java/dto/LadderBuildRequest.java +++ b/src/main/java/dto/LadderBuildRequest.java @@ -1,4 +1,4 @@ package dto; -public record LadderBuildRequest(int count) { +public record LadderBuildRequest(int columnCount, int rowCount) { } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..ccf6a4d7 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,20 @@ +package view; + +import java.util.Scanner; + +public class InputView { + + private static final Scanner scanner = new Scanner(System.in); + private static final String COLUMN_PROMPT = "사다리의 넓이는 몇 개인가요?"; + private static final String ROW_PROMPT = "사다리의 높이는 몇 개인가요?"; + + public int readColumnCount() { + System.out.println(COLUMN_PROMPT); + return Integer.parseInt(scanner.nextLine().trim()); + } + + public int readRowCount() { + System.out.println(ROW_PROMPT); + return Integer.parseInt(scanner.nextLine().trim()); + } +} From 15a679f344d44bd8c08c7290ed7631a45dca0dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Fri, 23 May 2025 12:31:24 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor(step2)=20:=EC=9D=91=EC=A7=91?= =?UTF-8?q?=EB=8F=84=20=EB=86=92=EC=9D=80=20=EA=B5=AC=EC=A1=B0=EB=A1=9C=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 --- src/main/java/domain/BridgeLine.java | 24 ++++++++++ src/main/java/domain/BridgeUnit.java | 24 ---------- src/main/java/domain/LadderBoard.java | 52 +++++++--------------- src/main/java/domain/LadderRow.java | 48 -------------------- src/main/java/domain/PillarUnit.java | 9 ---- src/main/java/dto/LadderBuildResponse.java | 4 +- src/main/java/view/OutputView.java | 36 ++++++++++++++- 7 files changed, 77 insertions(+), 120 deletions(-) create mode 100644 src/main/java/domain/BridgeLine.java delete mode 100644 src/main/java/domain/BridgeUnit.java delete mode 100644 src/main/java/domain/LadderRow.java delete mode 100644 src/main/java/domain/PillarUnit.java diff --git a/src/main/java/domain/BridgeLine.java b/src/main/java/domain/BridgeLine.java new file mode 100644 index 00000000..183c512c --- /dev/null +++ b/src/main/java/domain/BridgeLine.java @@ -0,0 +1,24 @@ +package domain; + +import java.util.List; + +public class BridgeLine { + + private final List connections; + + public BridgeLine(List connections) { + this.connections = connections; + } + + public boolean isConnected(int index) { + return connections.get(index); + } + + public int size() { + return connections.size(); + } + + public List connections() { + return connections; + } +} diff --git a/src/main/java/domain/BridgeUnit.java b/src/main/java/domain/BridgeUnit.java deleted file mode 100644 index 4c97097e..00000000 --- a/src/main/java/domain/BridgeUnit.java +++ /dev/null @@ -1,24 +0,0 @@ -package domain; - -public class BridgeUnit { - - private static final String CONNECTED = "-----"; - private static final String NOT_CONNECTED = " "; - - private final boolean connected; - - public BridgeUnit(boolean connected) { - this.connected = connected; - } - - public boolean isConnected() { - return connected; - } - - public String draw() { - if (connected) { - return CONNECTED; - } - return NOT_CONNECTED; - } -} diff --git a/src/main/java/domain/LadderBoard.java b/src/main/java/domain/LadderBoard.java index a126be37..dc28a446 100644 --- a/src/main/java/domain/LadderBoard.java +++ b/src/main/java/domain/LadderBoard.java @@ -19,28 +19,30 @@ public static LadderBuildResponse build(LadderBuildRequest request) { int middleIndex = (columnCount - 1) / 2; while (true) { - LadderBuildResponse response = generateValidBoardRows(columnCount, rowCount, middleIndex); - LadderBuildResponse validBoard = returnIfValidBoard(response); - if (validBoard != null) return validBoard; + List lines = generateValidBridgeLines(columnCount, rowCount, middleIndex); + if (lines != null) { + return new LadderBuildResponse(columnCount, lines); + } } } - private static LadderBuildResponse generateValidBoardRows(int columnCount, int rowCount, int middleIndex) { - List rows = new ArrayList<>(); + private static List generateValidBridgeLines(int columnCount, int rowCount, int middleIndex) { + List lines = new ArrayList<>(); boolean middleBridgeExists = false; Set connectedPositions = new HashSet<>(); for (int i = 0; i < rowCount; i++) { List bridgeConnectionStates = generateBridgeConnectionStates(columnCount); - LadderRow row = new LadderRow(columnCount, bridgeConnectionStates); - rows.add(row.draw()); + BridgeLine bridgeLine = new BridgeLine(bridgeConnectionStates); + lines.add(bridgeLine); - middleBridgeExists = updateMiddleBridgePresence(middleBridgeExists, row, middleIndex); + middleBridgeExists |= isMiddleBridgeConnected(bridgeLine, middleIndex); recordConnectedPositions(connectedPositions, bridgeConnectionStates); } if (!allPositionsConnected(connectedPositions, columnCount - 1)) return null; - return returnIfMiddleBridgeExists(rows, middleBridgeExists); + if (!middleBridgeExists) return null; + return lines; } private static List generateBridgeConnectionStates(int columnCount) { @@ -56,46 +58,24 @@ private static List generateBridgeConnectionStates(int columnCount) { } private static boolean isConnectable(List connectionStates, int index) { - if (index == 0) return true; - return !connectionStates.get(index - 1); + return index == 0 || !connectionStates.get(index - 1); } private static boolean decideBridgePlacement(boolean connectable) { - if (!connectable) return false; - return random.nextBoolean(); + return connectable && random.nextBoolean(); } - private static boolean updateMiddleBridgePresence( - boolean currentState, - LadderRow row, - int middleIndex - ) { - if (row.hasMiddleBridge(middleIndex)) return true; - return currentState; + private static boolean isMiddleBridgeConnected(BridgeLine line, int middleIndex) { + return middleIndex < line.size() && line.isConnected(middleIndex); } private static void recordConnectedPositions(Set connected, List currentLine) { for (int i = 0; i < currentLine.size(); i++) { - addIfConnected(connected, currentLine.get(i), i); + if (currentLine.get(i)) connected.add(i); } } - private static void addIfConnected(Set connected, boolean isConnected, int index) { - if (!isConnected) return; - connected.add(index); - } - private static boolean allPositionsConnected(Set connected, int bridgeCount) { return connected.size() == bridgeCount; } - - private static LadderBuildResponse returnIfMiddleBridgeExists(List rows, boolean exists) { - if (!exists) return null; - return new LadderBuildResponse(rows); - } - - private static LadderBuildResponse returnIfValidBoard(LadderBuildResponse response) { - if (response == null) return null; - return response; - } } diff --git a/src/main/java/domain/LadderRow.java b/src/main/java/domain/LadderRow.java deleted file mode 100644 index a7985843..00000000 --- a/src/main/java/domain/LadderRow.java +++ /dev/null @@ -1,48 +0,0 @@ -package domain; - -import java.util.List; - -public class LadderRow { - - private final int columnCount; - private final List bridges; - - public LadderRow(int columnCount, List bridgeConnectionStates) { - this.columnCount = columnCount; - this.bridges = createBridgesFromStates(bridgeConnectionStates); - } - - public String draw() { - StringBuilder line = new StringBuilder(); - - for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) { - line.append(PillarUnit.SYMBOL); - appendBridgeIfPresent(line, columnIndex); - } - - return line.toString(); - } - - public boolean hasMiddleBridge(int middleIndex) { - if (isIndexOutOfBridgeRange(middleIndex)) { - return false; - } - - return bridges.get(middleIndex).isConnected(); - } - - private List createBridgesFromStates(List connectionStates) { - return connectionStates.stream() - .map(BridgeUnit::new) - .toList(); - } - - private void appendBridgeIfPresent(StringBuilder line, int index) { - if (isIndexOutOfBridgeRange(index)) return; - line.append(bridges.get(index).draw()); - } - - private boolean isIndexOutOfBridgeRange(int index) { - return index >= bridges.size(); - } -} diff --git a/src/main/java/domain/PillarUnit.java b/src/main/java/domain/PillarUnit.java deleted file mode 100644 index 7a57b01e..00000000 --- a/src/main/java/domain/PillarUnit.java +++ /dev/null @@ -1,9 +0,0 @@ -package domain; - -public class PillarUnit { - - public static final String SYMBOL = "|"; - - private PillarUnit() { - } -} diff --git a/src/main/java/dto/LadderBuildResponse.java b/src/main/java/dto/LadderBuildResponse.java index 30743ffb..61d87b9a 100644 --- a/src/main/java/dto/LadderBuildResponse.java +++ b/src/main/java/dto/LadderBuildResponse.java @@ -1,6 +1,8 @@ package dto; +import domain.BridgeLine; + import java.util.List; -public record LadderBuildResponse(List rows) { +public record LadderBuildResponse(int columnCount, List lines) { } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 6924d531..1107199a 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,10 +1,16 @@ package view; +import domain.BridgeLine; import dto.LadderBuildResponse; +import java.util.List; + public class OutputView { private static final String LADDER_OUTPUT_TITLE = "실행결과"; + private static final String PILLAR = "|"; + private static final String CONNECTED = "-----"; + private static final String NOT_CONNECTED = " "; public void printLadderTitle() { System.out.println(LADDER_OUTPUT_TITLE); @@ -12,8 +18,34 @@ public void printLadderTitle() { } public void printLadder(LadderBuildResponse response) { - for (String row : response.rows()) { - System.out.println(" " + row); + int columnCount = response.columnCount(); + + for (BridgeLine line : response.lines()) { + String renderedRow = renderRow(line.connections(), columnCount); + System.out.println(" " + renderedRow); } } + + private String renderRow(List connections, int columnCount) { + StringBuilder row = new StringBuilder(); + + for (int col = 0; col < columnCount; col++) { + row.append(PILLAR); + row.append(renderBridge(connections, col)); + } + + return row.toString(); + } + + private String renderBridge(List connections, int index) { + if (index >= connections.size()) { + return ""; + } + + if (connections.get(index)) { + return CONNECTED; + } + + return NOT_CONNECTED; + } } From d9ace48083dfdc485c3ab54dd1c3b4e871c741ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Fri, 23 May 2025 12:46:40 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat(step2)=20:=203=EB=8B=A8=EA=B3=84=20-?= =?UTF-8?q?=20=EC=82=AC=EB=8B=A4=EB=A6=AC=20=ED=83=80=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/LadderController.java | 7 +++ src/main/java/domain/LadderBoard.java | 13 +++-- src/main/java/domain/LadderResult.java | 48 +++++++++++++++++++ src/main/java/dto/LadderResultResponse.java | 6 +++ src/main/java/view/OutputView.java | 8 ++++ 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/main/java/domain/LadderResult.java create mode 100644 src/main/java/dto/LadderResultResponse.java diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java index d618ad21..827e767f 100644 --- a/src/main/java/controller/LadderController.java +++ b/src/main/java/controller/LadderController.java @@ -1,8 +1,10 @@ package controller; import domain.LadderBoard; +import domain.LadderResult; import dto.LadderBuildRequest; import dto.LadderBuildResponse; +import dto.LadderResultResponse; import view.InputView; import view.OutputView; @@ -25,5 +27,10 @@ public void run() { outputView.printLadderTitle(); outputView.printLadder(response); + + LadderResult result = new LadderResult(response.lines(), response.columnCount()); + LadderResultResponse resultResponse = new LadderResultResponse(result.mapStartToEnd()); + + outputView.printResult(resultResponse); } } diff --git a/src/main/java/domain/LadderBoard.java b/src/main/java/domain/LadderBoard.java index dc28a446..356d4fac 100644 --- a/src/main/java/domain/LadderBoard.java +++ b/src/main/java/domain/LadderBoard.java @@ -18,15 +18,18 @@ public static LadderBuildResponse build(LadderBuildRequest request) { int rowCount = request.rowCount(); int middleIndex = (columnCount - 1) / 2; + List validLines = createValidBridgeLines(columnCount, rowCount, middleIndex); + return new LadderBuildResponse(columnCount, validLines); + } + + private static List createValidBridgeLines(int columnCount, int rowCount, int middleIndex) { while (true) { - List lines = generateValidBridgeLines(columnCount, rowCount, middleIndex); - if (lines != null) { - return new LadderBuildResponse(columnCount, lines); - } + List lines = generateBridgeLines(columnCount, rowCount, middleIndex); + if (lines != null) return lines; } } - private static List generateValidBridgeLines(int columnCount, int rowCount, int middleIndex) { + private static List generateBridgeLines(int columnCount, int rowCount, int middleIndex) { List lines = new ArrayList<>(); boolean middleBridgeExists = false; Set connectedPositions = new HashSet<>(); diff --git a/src/main/java/domain/LadderResult.java b/src/main/java/domain/LadderResult.java new file mode 100644 index 00000000..f40d49b9 --- /dev/null +++ b/src/main/java/domain/LadderResult.java @@ -0,0 +1,48 @@ +package domain; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class LadderResult { + + private final List lines; + private final int columnCount; + + public LadderResult(List lines, int columnCount) { + this.lines = lines; + this.columnCount = columnCount; + } + + public Map mapStartToEnd() { + Map result = new LinkedHashMap<>(); + + for (int start = 0; start < columnCount; start++) { + result.put(start, traverse(start)); + } + + return result; + } + + private int traverse(int startIndex) { + int position = startIndex; + + for (BridgeLine line : lines) { + position = move(position, line); + } + + return position; + } + + private int move(int position, BridgeLine line) { + if (position > 0 && line.isConnected(position - 1)) { + return position - 1; + } + + if (position < line.size() && line.isConnected(position)) { + return position + 1; + } + + return position; + } +} diff --git a/src/main/java/dto/LadderResultResponse.java b/src/main/java/dto/LadderResultResponse.java new file mode 100644 index 00000000..d58069ad --- /dev/null +++ b/src/main/java/dto/LadderResultResponse.java @@ -0,0 +1,6 @@ +package dto; + +import java.util.Map; + +public record LadderResultResponse(Map positionMap) { +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 1107199a..15ad5d75 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -2,8 +2,10 @@ import domain.BridgeLine; import dto.LadderBuildResponse; +import dto.LadderResultResponse; import java.util.List; +import java.util.Map; public class OutputView { @@ -26,6 +28,12 @@ public void printLadder(LadderBuildResponse response) { } } + public void printResult(LadderResultResponse response) { + for (Map.Entry entry : response.positionMap().entrySet()) { + System.out.println(entry.getKey() + " -> " + entry.getValue()); + } + } + private String renderRow(List connections, int columnCount) { StringBuilder row = new StringBuilder(); From 7b7616126e5fc37b9eba70efc07ac39f4e3d5464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Fri, 23 May 2025 17:38:41 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat(step4)=20:=204=EB=8B=A8=EA=B3=84=20-?= =?UTF-8?q?=20=EA=B2=8C=EC=9E=84=20=EC=8B=A4=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 70 ++++++++++ .../java/controller/LadderController.java | 84 ++++++++++-- src/main/java/domain/BridgeLine.java | 18 +-- src/main/java/domain/LadderBoard.java | 77 +++++------ src/main/java/domain/LadderPath.java | 42 ++++++ src/main/java/domain/LadderResult.java | 48 ------- src/main/java/domain/PlayerResults.java | 37 ++++++ src/main/java/util/ValidatedInputParser.java | 64 +++++++++ src/main/java/view/InputView.java | 24 ++-- src/main/java/view/OutputView.java | 122 ++++++++++++++---- src/test/java/domain/BridgeLineTest.java | 45 +++++++ src/test/java/domain/LadderBoardTest.java | 43 ++++++ src/test/java/domain/LadderPathTest.java | 35 +++++ src/test/java/domain/PlayerResultsTest.java | 46 +++++++ .../java/util/ValidatedInputParserTest.java | 89 +++++++++++++ 15 files changed, 699 insertions(+), 145 deletions(-) create mode 100644 src/main/java/domain/LadderPath.java delete mode 100644 src/main/java/domain/LadderResult.java create mode 100644 src/main/java/domain/PlayerResults.java create mode 100644 src/main/java/util/ValidatedInputParser.java create mode 100644 src/test/java/domain/BridgeLineTest.java create mode 100644 src/test/java/domain/LadderBoardTest.java create mode 100644 src/test/java/domain/LadderPathTest.java create mode 100644 src/test/java/domain/PlayerResultsTest.java create mode 100644 src/test/java/util/ValidatedInputParserTest.java diff --git a/README.md b/README.md index 2cd2d2e5..0a14083d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,73 @@ +# 4단계 - 게임 실행 + +## 기능 요구사항 +- 사다리 게임에 참여하는 사람에 이름을 최대 5글자까지 부여할 수 있다. 사다리를 출력할 때 사람 이름도 같이 출력한다. +- 사람 이름은 쉼표(,)를 기준으로 구분한다. +- 개인별 이름을 입력하면 개인별 결과를 출력하고, "all"을 입력하면 전체 참여자의 실행 결과를 출력한다. + +```text +참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요) +neo,brown,brie,tommy + +실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요) +꽝,5000,꽝,3000 + +최대 사다리 높이는 몇 개인가요? +5 + +사다리 결과 + + neo brown brie tommy + |-----| |-----| + | |-----| | + |-----| | | + | |-----| | + |-----| |-----| + 꽝 5000 꽝 3000 + +결과를 보고 싶은 사람은? +neo + +실행 결과 +꽝 + +결과를 보고 싶은 사람은? +all + +실행 결과 +neo : 꽝 +brown : 3000 +brie : 꽝 +tommy : 5000 + +``` + +# 3단계 - 사다리 타기 + +## 기능 요구사항 +- 사다리의 시작 지점과 도착 지점을 출력한다. + +```text +사다리의 넓이는 몇 개인가요? +4 + +사다리의 높이는 몇 개인가요? +5 + +실행결과 + + |-----| |-----| + | |-----| | + |-----| | | + | |-----| | + |-----| |-----| + +0 -> 0 +1 -> 3 +2 -> 2 +3 -> 1 +``` + # 2단계 - 사다리 생성 ## 기능 요구사항 diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java index 827e767f..abb5b0ba 100644 --- a/src/main/java/controller/LadderController.java +++ b/src/main/java/controller/LadderController.java @@ -1,13 +1,17 @@ package controller; import domain.LadderBoard; -import domain.LadderResult; +import domain.LadderPath; +import domain.PlayerResults; import dto.LadderBuildRequest; import dto.LadderBuildResponse; import dto.LadderResultResponse; +import util.ValidatedInputParser; import view.InputView; import view.OutputView; +import java.util.List; + public class LadderController { private final InputView inputView; @@ -19,18 +23,80 @@ public LadderController(InputView inputView, OutputView outputView) { } public void run() { - int columnCount = inputView.readColumnCount(); - int rowCount = inputView.readRowCount(); + List participantNames = getValidatedParticipantNames(); + List resultLabels = getValidatedResultLabels(participantNames.size()); + int ladderHeight = getValidatedLadderHeight(); + + LadderBuildResponse ladder = buildLadder(participantNames.size(), ladderHeight); + + displayLadder(participantNames, resultLabels, ladder); + + PlayerResults playerResults = mapPlayerResults(participantNames, resultLabels, ladder); + + handleResultQueries(playerResults); + } - LadderBuildRequest request = new LadderBuildRequest(columnCount, rowCount); - LadderBuildResponse response = LadderBoard.build(request); + private List getValidatedParticipantNames() { + outputView.printParticipantPrompt(); + String rawParticipantInput = inputView.readParticipantNames(); + return ValidatedInputParser.parseParticipantNames(rawParticipantInput); + } + + private List getValidatedResultLabels(int expectedCount) { + outputView.printResultPrompt(); + String rawResultInput = inputView.readResultLabels(); + return ValidatedInputParser.parseResultLabels(rawResultInput, expectedCount); + } + private int getValidatedLadderHeight() { + outputView.printHeightPrompt(); + String rawHeightInput = inputView.readLadderHeight(); + return ValidatedInputParser.parseRowCount(rawHeightInput); + } + + private LadderBuildResponse buildLadder(int numberOfColumns, int numberOfRows) { + LadderBuildRequest buildRequest = new LadderBuildRequest(numberOfColumns, numberOfRows); + return LadderBoard.build(buildRequest); + } + + private void displayLadder(List participantNames, List resultLabels, LadderBuildResponse ladder) { outputView.printLadderTitle(); - outputView.printLadder(response); + outputView.printParticipantNames(participantNames); + outputView.printBridgeLines(ladder); + outputView.printResultLabels(resultLabels); + } + + private PlayerResults mapPlayerResults(List participantNames, List resultLabels, LadderBuildResponse ladder) { + LadderPath ladderPath = new LadderPath(ladder.lines(), ladder.columnCount()); + LadderResultResponse resultMapping = new LadderResultResponse(ladderPath.mapStartToEndIndex()); + return PlayerResults.from(participantNames, resultLabels, resultMapping.positionMap()); + } + + private void handleResultQueries(PlayerResults playerResults) { + while (true) { + outputView.printResultSelectionPrompt(); + String queryName = inputView.readResultRequest(); + if (printIfMatchedPlayer(playerResults, queryName)) continue; + if (printIfAllResults(playerResults, queryName)) break; + outputView.printNameNotFound(); + } + } - LadderResult result = new LadderResult(response.lines(), response.columnCount()); - LadderResultResponse resultResponse = new LadderResultResponse(result.mapStartToEnd()); + private boolean printIfMatchedPlayer(PlayerResults playerResults, String playerName) { + if (!playerResults.hasPlayer(playerName)) return false; + outputView.printLadderTitle(); + outputView.printSingleResult(playerResults.resultOf(playerName)); + return true; + } + + private boolean printIfAllResults(PlayerResults playerResults, String queryName) { + if (!isAllCommand(queryName)) return false; + outputView.printLadderTitle(); + outputView.printAllResults(playerResults.allResults()); + return true; + } - outputView.printResult(resultResponse); + private boolean isAllCommand(String name) { + return "all".equals(name); } } diff --git a/src/main/java/domain/BridgeLine.java b/src/main/java/domain/BridgeLine.java index 183c512c..0682c71c 100644 --- a/src/main/java/domain/BridgeLine.java +++ b/src/main/java/domain/BridgeLine.java @@ -4,21 +4,21 @@ public class BridgeLine { - private final List connections; + private final List horizontalConnections; - public BridgeLine(List connections) { - this.connections = connections; + public BridgeLine(List horizontalConnections) { + this.horizontalConnections = horizontalConnections; } - public boolean isConnected(int index) { - return connections.get(index); + public boolean isConnectedAt(int index) { + return horizontalConnections.get(index); } - public int size() { - return connections.size(); + public int width() { + return horizontalConnections.size(); } - public List connections() { - return connections; + public List getConnections() { + return horizontalConnections; } } diff --git a/src/main/java/domain/LadderBoard.java b/src/main/java/domain/LadderBoard.java index 356d4fac..30813591 100644 --- a/src/main/java/domain/LadderBoard.java +++ b/src/main/java/domain/LadderBoard.java @@ -14,71 +14,62 @@ public class LadderBoard { private static final Random random = new Random(); public static LadderBuildResponse build(LadderBuildRequest request) { - int columnCount = request.columnCount(); - int rowCount = request.rowCount(); - int middleIndex = (columnCount - 1) / 2; + int participantCount = request.columnCount(); + int ladderHeight = request.rowCount(); + int centerColumnIndex = (participantCount - 1) / 2; - List validLines = createValidBridgeLines(columnCount, rowCount, middleIndex); - return new LadderBuildResponse(columnCount, validLines); + List bridgeLines = generateValidBridgeLines(participantCount, ladderHeight, centerColumnIndex); + return new LadderBuildResponse(participantCount, bridgeLines); } - private static List createValidBridgeLines(int columnCount, int rowCount, int middleIndex) { + private static List generateValidBridgeLines(int participantCount, int ladderHeight, int centerColumnIndex) { while (true) { - List lines = generateBridgeLines(columnCount, rowCount, middleIndex); - if (lines != null) return lines; + List candidateBridgeLines = tryGenerateBridgeLines(participantCount, ladderHeight, centerColumnIndex); + if (candidateBridgeLines != null) return candidateBridgeLines; } } - private static List generateBridgeLines(int columnCount, int rowCount, int middleIndex) { - List lines = new ArrayList<>(); - boolean middleBridgeExists = false; - Set connectedPositions = new HashSet<>(); + private static List tryGenerateBridgeLines(int participantCount, int ladderHeight, int centerColumnIndex) { + List generatedBridgeLines = new ArrayList<>(); + boolean isCenterBridgePresent = false; + Set connectedColumnIndices = new HashSet<>(); - for (int i = 0; i < rowCount; i++) { - List bridgeConnectionStates = generateBridgeConnectionStates(columnCount); - BridgeLine bridgeLine = new BridgeLine(bridgeConnectionStates); - lines.add(bridgeLine); + for (int row = 0; row < ladderHeight; row++) { + List connectionStates = generateConnectionStates(participantCount); + BridgeLine line = new BridgeLine(connectionStates); + generatedBridgeLines.add(line); - middleBridgeExists |= isMiddleBridgeConnected(bridgeLine, middleIndex); - recordConnectedPositions(connectedPositions, bridgeConnectionStates); + isCenterBridgePresent |= isConnectedAtCenter(line, centerColumnIndex); + recordConnectedIndices(connectedColumnIndices, connectionStates); } - if (!allPositionsConnected(connectedPositions, columnCount - 1)) return null; - if (!middleBridgeExists) return null; - return lines; + if (!areAllColumnsConnected(connectedColumnIndices, participantCount - 1)) return null; + if (!isCenterBridgePresent) return null; + + return generatedBridgeLines; } - private static List generateBridgeConnectionStates(int columnCount) { + private static List generateConnectionStates(int participantCount) { List connectionStates = new ArrayList<>(); - - for (int index = 0; index < columnCount - 1; index++) { - boolean connectable = isConnectable(connectionStates, index); - boolean shouldPlaceBridge = decideBridgePlacement(connectable); - connectionStates.add(shouldPlaceBridge); + for (int index = 0; index < participantCount - 1; index++) { + boolean canConnect = index == 0 || !connectionStates.get(index - 1); + boolean shouldConnect = canConnect && random.nextBoolean(); + connectionStates.add(shouldConnect); } - return connectionStates; } - private static boolean isConnectable(List connectionStates, int index) { - return index == 0 || !connectionStates.get(index - 1); - } - - private static boolean decideBridgePlacement(boolean connectable) { - return connectable && random.nextBoolean(); - } - - private static boolean isMiddleBridgeConnected(BridgeLine line, int middleIndex) { - return middleIndex < line.size() && line.isConnected(middleIndex); + private static boolean isConnectedAtCenter(BridgeLine line, int centerIndex) { + return centerIndex < line.width() && line.isConnectedAt(centerIndex); } - private static void recordConnectedPositions(Set connected, List currentLine) { - for (int i = 0; i < currentLine.size(); i++) { - if (currentLine.get(i)) connected.add(i); + private static void recordConnectedIndices(Set connectedColumnIndices, List connectionStates) { + for (int i = 0; i < connectionStates.size(); i++) { + if (connectionStates.get(i)) connectedColumnIndices.add(i); } } - private static boolean allPositionsConnected(Set connected, int bridgeCount) { - return connected.size() == bridgeCount; + private static boolean areAllColumnsConnected(Set connectedColumnIndices, int expectedCount) { + return connectedColumnIndices.size() == expectedCount; } } diff --git a/src/main/java/domain/LadderPath.java b/src/main/java/domain/LadderPath.java new file mode 100644 index 00000000..366ae31c --- /dev/null +++ b/src/main/java/domain/LadderPath.java @@ -0,0 +1,42 @@ +package domain; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class LadderPath { + + private final List bridgeLines; + private final int numberOfColumns; + + public LadderPath(List bridgeLines, int numberOfColumns) { + this.bridgeLines = bridgeLines; + this.numberOfColumns = numberOfColumns; + } + + public Map mapStartToEndIndex() { + Map startToEndMap = new LinkedHashMap<>(); + + for (int startIndex = 0; startIndex < numberOfColumns; startIndex++) { + startToEndMap.put(startIndex, traverseFrom(startIndex)); + } + + return startToEndMap; + } + + private int traverseFrom(int startColumnIndex) { + int current = startColumnIndex; + + for (BridgeLine line : bridgeLines) { + current = moveAlongLine(current, line); + } + + return current; + } + + private int moveAlongLine(int position, BridgeLine line) { + if (position > 0 && line.isConnectedAt(position - 1)) return position - 1; + if (position < line.width() && line.isConnectedAt(position)) return position + 1; + return position; + } +} diff --git a/src/main/java/domain/LadderResult.java b/src/main/java/domain/LadderResult.java deleted file mode 100644 index f40d49b9..00000000 --- a/src/main/java/domain/LadderResult.java +++ /dev/null @@ -1,48 +0,0 @@ -package domain; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class LadderResult { - - private final List lines; - private final int columnCount; - - public LadderResult(List lines, int columnCount) { - this.lines = lines; - this.columnCount = columnCount; - } - - public Map mapStartToEnd() { - Map result = new LinkedHashMap<>(); - - for (int start = 0; start < columnCount; start++) { - result.put(start, traverse(start)); - } - - return result; - } - - private int traverse(int startIndex) { - int position = startIndex; - - for (BridgeLine line : lines) { - position = move(position, line); - } - - return position; - } - - private int move(int position, BridgeLine line) { - if (position > 0 && line.isConnected(position - 1)) { - return position - 1; - } - - if (position < line.size() && line.isConnected(position)) { - return position + 1; - } - - return position; - } -} diff --git a/src/main/java/domain/PlayerResults.java b/src/main/java/domain/PlayerResults.java new file mode 100644 index 00000000..a93f6dba --- /dev/null +++ b/src/main/java/domain/PlayerResults.java @@ -0,0 +1,37 @@ +package domain; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class PlayerResults { + + private final Map resultByPlayerName; + + private PlayerResults(Map resultByPlayerName) { + this.resultByPlayerName = resultByPlayerName; + } + + public static PlayerResults from(List playerNames, List outcomeLabels, Map startToEndIndexMap) { + Map mappedResults = new LinkedHashMap<>(); + for (int playerIndex = 0; playerIndex < playerNames.size(); playerIndex++) { + int resultIndex = startToEndIndexMap.get(playerIndex); + String playerName = playerNames.get(playerIndex); + String outcomeLabel = outcomeLabels.get(resultIndex); + mappedResults.put(playerName, outcomeLabel); + } + return new PlayerResults(mappedResults); + } + + public String resultOf(String playerName) { + return resultByPlayerName.get(playerName); + } + + public Map allResults() { + return resultByPlayerName; + } + + public boolean hasPlayer(String playerName) { + return resultByPlayerName.containsKey(playerName); + } +} diff --git a/src/main/java/util/ValidatedInputParser.java b/src/main/java/util/ValidatedInputParser.java new file mode 100644 index 00000000..78e2e652 --- /dev/null +++ b/src/main/java/util/ValidatedInputParser.java @@ -0,0 +1,64 @@ +package util; + +import java.util.Arrays; +import java.util.List; + +public class ValidatedInputParser { + + private static final int MAX_NAME_LENGTH = 5; + private static final String ERROR_NO_PARTICIPANTS = "[ERROR] 참여자는 한 명 이상이어야 합니다."; + private static final String ERROR_NAME_TOO_LONG = "[ERROR] 참여자 이름은 5자 이하만 가능합니다: "; + private static final String ERROR_RESULT_COUNT_MISMATCH = "[ERROR] 실행 결과 수가 참여자 수와 일치해야 합니다."; + private static final String ERROR_INVALID_ROW_COUNT = "[ERROR] 사다리 높이는 1 이상이어야 합니다."; + private static final String ERROR_INVALID_NUMBER_FORMAT = "[ERROR] 숫자를 입력해 주세요."; + + public static List parseParticipantNames(String rawInput) { + List participantNames = Arrays.asList(rawInput.trim().split(",")); + validateParticipantNamesExist(participantNames); + validateParticipantNameLengths(participantNames); + return participantNames; + } + + public static List parseResultLabels(String rawInput, int expectedCount) { + List resultLabels = Arrays.asList(rawInput.trim().split(",")); + validateResultLabelCount(resultLabels, expectedCount); + return resultLabels; + } + + public static int parseRowCount(String rawInput) { + try { + int rowCount = Integer.parseInt(rawInput.trim()); + validateRowCountPositive(rowCount); + return rowCount; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ERROR_INVALID_NUMBER_FORMAT); + } + } + + private static void validateParticipantNamesExist(List participantNames) { + if (participantNames.isEmpty()) { + throw new IllegalArgumentException(ERROR_NO_PARTICIPANTS); + } + } + + private static void validateParticipantNameLengths(List participantNames) { + participantNames.stream() + .filter(name -> name.length() > MAX_NAME_LENGTH) + .findFirst() + .ifPresent(name -> { + throw new IllegalArgumentException(ERROR_NAME_TOO_LONG + name); + }); + } + + private static void validateResultLabelCount(List resultLabels, int expectedCount) { + if (resultLabels.size() != expectedCount) { + throw new IllegalArgumentException(ERROR_RESULT_COUNT_MISMATCH); + } + } + + private static void validateRowCountPositive(int rowCount) { + if (rowCount <= 0) { + throw new IllegalArgumentException(ERROR_INVALID_ROW_COUNT); + } + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index ccf6a4d7..3475e704 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -5,16 +5,24 @@ public class InputView { private static final Scanner scanner = new Scanner(System.in); - private static final String COLUMN_PROMPT = "사다리의 넓이는 몇 개인가요?"; - private static final String ROW_PROMPT = "사다리의 높이는 몇 개인가요?"; - public int readColumnCount() { - System.out.println(COLUMN_PROMPT); - return Integer.parseInt(scanner.nextLine().trim()); + public String readParticipantNames() { + return readLine(); } - public int readRowCount() { - System.out.println(ROW_PROMPT); - return Integer.parseInt(scanner.nextLine().trim()); + public String readResultLabels() { + return readLine(); + } + + public String readLadderHeight() { + return readLine(); + } + + public String readResultRequest() { + return readLine(); + } + + private String readLine() { + return scanner.nextLine().trim(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 15ad5d75..c5190e4a 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,59 +1,125 @@ package view; -import domain.BridgeLine; import dto.LadderBuildResponse; -import dto.LadderResultResponse; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class OutputView { - private static final String LADDER_OUTPUT_TITLE = "실행결과"; - private static final String PILLAR = "|"; - private static final String CONNECTED = "-----"; - private static final String NOT_CONNECTED = " "; + private static final String PARTICIPANT_PROMPT = "참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"; + private static final String RESULT_PROMPT = "실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)"; + private static final String HEIGHT_PROMPT = "사다리의 높이는 몇 칸인가요?"; + private static final String RESULT_SELECTION_PROMPT = "결과를 보고 싶은 사람은?"; + private static final String LADDER_RESULT_TITLE = "실행 결과"; + private static final String NAME_NOT_FOUND_MESSAGE = "해당 이름은 존재하지 않습니다."; + + private static final String VERTICAL_BAR = "|"; + private static final String CONNECTED_LINE = "-----"; + private static final String EMPTY_LINE = " "; + private static final int DISPLAY_CELL_WIDTH = 6; + private static final String EMPTY_PREFIX = " "; + private static final String CELL_FORMAT = "%-" + DISPLAY_CELL_WIDTH + "s"; + private static final String RESULT_FORMAT = "%s : %s"; + + public void printParticipantPrompt() { + System.out.println(PARTICIPANT_PROMPT); + } + + public void printResultPrompt() { + System.out.println(RESULT_PROMPT); + } + + public void printHeightPrompt() { + System.out.println(HEIGHT_PROMPT); + } + + public void printResultSelectionPrompt() { + System.out.println(RESULT_SELECTION_PROMPT); + } public void printLadderTitle() { - System.out.println(LADDER_OUTPUT_TITLE); - System.out.println(); + System.out.println(LADDER_RESULT_TITLE); + } + + public void printParticipantNames(List participantNames) { + String alignedNames = participantNames.stream() + .map(this::formatCell) + .collect(Collectors.joining()); + System.out.println(alignedNames); + } + + public void printResultLabels(List resultLabels) { + String alignedResults = resultLabels.stream() + .map(this::formatCell) + .collect(Collectors.joining()); + System.out.println(alignedResults); } - public void printLadder(LadderBuildResponse response) { - int columnCount = response.columnCount(); + public void printBridgeLines(LadderBuildResponse response) { + List> allBridgeLines = response.lines().stream() + .map(line -> line.getConnections()) + .collect(Collectors.toList()); - for (BridgeLine line : response.lines()) { - String renderedRow = renderRow(line.connections(), columnCount); - System.out.println(" " + renderedRow); + int totalColumns = response.columnCount(); + + for (List bridgeConnections : allBridgeLines) { + String formattedLine = renderBridgeLine(bridgeConnections, totalColumns); + System.out.println(EMPTY_PREFIX + formattedLine); } } - public void printResult(LadderResultResponse response) { - for (Map.Entry entry : response.positionMap().entrySet()) { - System.out.println(entry.getKey() + " -> " + entry.getValue()); + public void printAllResults(Map participantResults) { + for (Map.Entry entry : participantResults.entrySet()) { + System.out.println(formatResult(entry.getKey(), entry.getValue())); } } - private String renderRow(List connections, int columnCount) { - StringBuilder row = new StringBuilder(); + public void printSingleResult(String resultValue) { + System.out.println(resultValue); + } + + public void printNameNotFound() { + System.out.println(NAME_NOT_FOUND_MESSAGE); + } + + private String formatResult(String name, String result) { + return String.format(RESULT_FORMAT, name, result); + } + + private String renderBridgeLine(List connections, int totalColumns) { + StringBuilder lineBuilder = new StringBuilder(); - for (int col = 0; col < columnCount; col++) { - row.append(PILLAR); - row.append(renderBridge(connections, col)); + for (int columnIndex = 0; columnIndex < totalColumns; columnIndex++) { + lineBuilder.append(VERTICAL_BAR); + lineBuilder.append(renderBridgeBetweenColumns(connections, columnIndex)); } - return row.toString(); + return lineBuilder.toString(); } - private String renderBridge(List connections, int index) { - if (index >= connections.size()) { - return ""; + private String renderBridgeBetweenColumns(List connections, int columnIndex) { + if (isOutOfConnectionRange(connections, columnIndex)) { + return EMPTY_LINE; } - if (connections.get(index)) { - return CONNECTED; + if (isConnected(connections, columnIndex)) { + return CONNECTED_LINE; } - return NOT_CONNECTED; + return EMPTY_LINE; + } + + private boolean isOutOfConnectionRange(List connections, int columnIndex) { + return columnIndex >= connections.size(); + } + + private boolean isConnected(List connections, int columnIndex) { + return connections.get(columnIndex); + } + + private String formatCell(String value) { + return String.format(CELL_FORMAT, value); } } diff --git a/src/test/java/domain/BridgeLineTest.java b/src/test/java/domain/BridgeLineTest.java new file mode 100644 index 00000000..0bb272e2 --- /dev/null +++ b/src/test/java/domain/BridgeLineTest.java @@ -0,0 +1,45 @@ +package domain; + +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; + +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class BridgeLineTest { + + @ParameterizedTest(name = "index={0}일 때 연결 여부는 {1}이다") + @MethodSource("connectionTestCases") + @DisplayName("isConnectedAt는 주어진 인덱스의 연결 상태를 반환한다") + void isConnectedAt_returnsCorrectStatus(int index, boolean expected) { + BridgeLine line = new BridgeLine(List.of(false, true, false)); + assertThat(line.isConnectedAt(index)).isEqualTo(expected); + } + + @ParameterizedTest(name = "연결 리스트 {0}의 크기는 {1}이다") + @MethodSource("widthTestCases") + @DisplayName("width는 연결 리스트의 크기를 반환한다") + void width_returnsCorrectSize(List connections, int expectedWidth) { + BridgeLine line = new BridgeLine(connections); + assertThat(line.width()).isEqualTo(expectedWidth); + } + + private static Stream connectionTestCases() { + return Stream.of( + Arguments.of(0, false), + Arguments.of(1, true), + Arguments.of(2, false) + ); + } + + private static Stream widthTestCases() { + return Stream.of( + Arguments.of(List.of(true, false, true), 3), + Arguments.of(List.of(false, false, false, false), 4) + ); + } +} diff --git a/src/test/java/domain/LadderBoardTest.java b/src/test/java/domain/LadderBoardTest.java new file mode 100644 index 00000000..e492ca42 --- /dev/null +++ b/src/test/java/domain/LadderBoardTest.java @@ -0,0 +1,43 @@ +package domain; + +import dto.LadderBuildRequest; +import dto.LadderBuildResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class LadderBoardTest { + + @Test + @DisplayName("요청에 따라 사다리를 생성하고 높이와 열 수가 일치해야 한다") + void build_createsValidLadderStructure() { + int columnCount = 4; + int rowCount = 5; + LadderBuildRequest request = new LadderBuildRequest(columnCount, rowCount); + + LadderBuildResponse response = LadderBoard.build(request); + + assertThat(response.columnCount()).isEqualTo(columnCount); + assertThat(response.lines()).hasSize(rowCount); + } + + @Test + @DisplayName("생성된 사다리는 가운데 열에 적어도 하나의 연결이 포함되어야 한다") + void build_containsAtLeastOneCenterConnection() { + int columnCount = 5; + int rowCount = 10; + int centerIndex = (columnCount - 1) / 2; + LadderBuildRequest request = new LadderBuildRequest(columnCount, rowCount); + + LadderBuildResponse response = LadderBoard.build(request); + List lines = response.lines(); + + boolean hasCenterConnection = lines.stream() + .anyMatch(line -> line.isConnectedAt(centerIndex)); + + assertThat(hasCenterConnection).isTrue(); + } +} diff --git a/src/test/java/domain/LadderPathTest.java b/src/test/java/domain/LadderPathTest.java new file mode 100644 index 00000000..00f162a1 --- /dev/null +++ b/src/test/java/domain/LadderPathTest.java @@ -0,0 +1,35 @@ +package domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.LinkedHashMap; + +import static org.assertj.core.api.Assertions.assertThat; + +class LadderPathTest { + + @Test + @DisplayName("3줄 사다리에서 각 참가자의 최종 위치를 정확히 계산한다") + void mapStartToEndIndex_correctlyMapsPath() { + List bridgeLines = List.of( + new BridgeLine(List.of(true, false, true)), + new BridgeLine(List.of(false, true, false)), + new BridgeLine(List.of(true, false, false)) + ); + + LadderPath path = new LadderPath(bridgeLines, 4); + + Map result = path.mapStartToEndIndex(); + + Map expected = new LinkedHashMap<>(); + expected.put(0, 2); + expected.put(1, 1); + expected.put(2, 3); + expected.put(3, 0); + + assertThat(result).containsExactlyEntriesOf(expected); + } +} diff --git a/src/test/java/domain/PlayerResultsTest.java b/src/test/java/domain/PlayerResultsTest.java new file mode 100644 index 00000000..9b2f24fa --- /dev/null +++ b/src/test/java/domain/PlayerResultsTest.java @@ -0,0 +1,46 @@ +package domain; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.LinkedHashMap; + +class PlayerResultsTest { + + @Test + @DisplayName("참가자 이름, 결과, 인덱스 맵으로 결과를 생성한다") + void from_createsCorrectMapping() { + List playerNames = List.of("neo", "brown", "brie", "tommy"); + List outcomeLabels = List.of("꽝", "5000", "꽝", "3000"); + + Map startToEndIndexMap = Map.of( + 0, 1, + 1, 3, + 2, 0, + 3, 2 + ); + + PlayerResults results = PlayerResults.from(playerNames, outcomeLabels, startToEndIndexMap); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(results.resultOf("neo")).isEqualTo("5000"); + softly.assertThat(results.resultOf("brown")).isEqualTo("3000"); + softly.assertThat(results.resultOf("brie")).isEqualTo("꽝"); + softly.assertThat(results.resultOf("tommy")).isEqualTo("꽝"); + + softly.assertThat(results.hasPlayer("neo")).isTrue(); + softly.assertThat(results.hasPlayer("noname")).isFalse(); + + Map expected = new LinkedHashMap<>(); + expected.put("neo", "5000"); + expected.put("brown", "3000"); + expected.put("brie", "꽝"); + expected.put("tommy", "꽝"); + + softly.assertThat(results.allResults()).containsExactlyEntriesOf(expected); + softly.assertAll(); + } +} diff --git a/src/test/java/util/ValidatedInputParserTest.java b/src/test/java/util/ValidatedInputParserTest.java new file mode 100644 index 00000000..829a3524 --- /dev/null +++ b/src/test/java/util/ValidatedInputParserTest.java @@ -0,0 +1,89 @@ +package util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ValidatedInputParserTest { + + @Nested + @DisplayName("참여자 이름 파싱") + class ParseParticipantNames { + + @Test + @DisplayName("쉼표로 구분된 참여자 이름들을 파싱한다") + void parsesParticipantNames() { + List result = ValidatedInputParser.parseParticipantNames("neo,brown"); + assertThat(result).containsExactly("neo", "brown"); + } + + @Test + @DisplayName("참여자가 없는 경우 예외를 던진다") + void throwsIfNoParticipants() { + assertThatThrownBy(() -> ValidatedInputParser.parseParticipantNames("")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 참여자는 한 명 이상이어야 합니다."); + } + + @Test + @DisplayName("이름이 5자를 초과하면 예외를 던진다") + void throwsIfNameTooLong() { + assertThatThrownBy(() -> ValidatedInputParser.parseParticipantNames("neo,longname")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 참여자 이름은 5자 이하만 가능합니다: longname"); + } + } + + @Nested + @DisplayName("결과 라벨 파싱") + class ParseResultLabels { + + @Test + @DisplayName("결과 수가 참여자 수와 다르면 예외를 던진다") + void throwsIfResultCountMismatch() { + assertThatThrownBy(() -> ValidatedInputParser.parseResultLabels("꽝,당첨", 3)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 실행 결과 수가 참여자 수와 일치해야 합니다."); + } + + @Test + @DisplayName("결과 라벨들을 파싱한다") + void parsesResultLabels() { + List result = ValidatedInputParser.parseResultLabels("꽝,5000,3000", 3); + assertThat(result).containsExactly("꽝", "5000", "3000"); + } + } + + @Nested + @DisplayName("사다리 높이 파싱") + class ParseRowCount { + + @Test + @DisplayName("숫자가 아니면 예외를 던진다") + void throwsIfNotNumber() { + assertThatThrownBy(() -> ValidatedInputParser.parseRowCount("five")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 숫자를 입력해 주세요."); + } + + @Test + @DisplayName("0 이하의 숫자면 예외를 던진다") + void throwsIfZeroOrNegative() { + assertThatThrownBy(() -> ValidatedInputParser.parseRowCount("0")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 사다리 높이는 1 이상이어야 합니다."); + } + + @Test + @DisplayName("숫자를 정상적으로 파싱한다") + void parsesRowCount() { + int result = ValidatedInputParser.parseRowCount("7"); + assertThat(result).isEqualTo(7); + } + } +} From 87e4fae6211ad827662c257796e489073cd0dd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Sat, 24 May 2025 20:44:39 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor(review)=20:=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/LadderController.java | 74 ++++++++------- src/main/java/domain/BridgeLine.java | 50 ++++++++++- src/main/java/domain/LadderBoard.java | 71 +++++++++++---- src/main/java/domain/LadderPath.java | 20 ++--- src/main/java/domain/PlayerResults.java | 53 +++++++++-- src/main/java/dto/LadderBuildResponse.java | 5 ++ src/main/java/util/NumericParser.java | 14 +++ src/main/java/util/ValidatedInputParser.java | 64 ------------- src/main/java/view/InputView.java | 14 +-- src/main/java/view/OutputView.java | 51 ++--------- src/test/java/domain/BridgeLineTest.java | 33 +++++-- src/test/java/domain/LadderBoardTest.java | 31 +++++-- src/test/java/domain/PlayerResultsTest.java | 12 +++ src/test/java/util/NumericParserTest.java | 24 +++++ .../java/util/ValidatedInputParserTest.java | 89 ------------------- 15 files changed, 309 insertions(+), 296 deletions(-) create mode 100644 src/main/java/util/NumericParser.java delete mode 100644 src/main/java/util/ValidatedInputParser.java create mode 100644 src/test/java/util/NumericParserTest.java delete mode 100644 src/test/java/util/ValidatedInputParserTest.java diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java index abb5b0ba..b15394b3 100644 --- a/src/main/java/controller/LadderController.java +++ b/src/main/java/controller/LadderController.java @@ -3,10 +3,9 @@ import domain.LadderBoard; import domain.LadderPath; import domain.PlayerResults; -import dto.LadderBuildRequest; import dto.LadderBuildResponse; import dto.LadderResultResponse; -import util.ValidatedInputParser; +import util.NumericParser; import view.InputView; import view.OutputView; @@ -23,80 +22,77 @@ public LadderController(InputView inputView, OutputView outputView) { } public void run() { - List participantNames = getValidatedParticipantNames(); - List resultLabels = getValidatedResultLabels(participantNames.size()); - int ladderHeight = getValidatedLadderHeight(); + List participantNames = promptParticipants(); + List resultLabels = promptResultLabels(); + int ladderHeight = promptAndParseLadderHeight(); LadderBuildResponse ladder = buildLadder(participantNames.size(), ladderHeight); + displayLadderBoard(participantNames, resultLabels, ladder); - displayLadder(participantNames, resultLabels, ladder); - - PlayerResults playerResults = mapPlayerResults(participantNames, resultLabels, ladder); - - handleResultQueries(playerResults); + PlayerResults playerResults = mapResults(participantNames, resultLabels, ladder); + handleResultRequestLoop(playerResults); } - private List getValidatedParticipantNames() { + private List promptParticipants() { outputView.printParticipantPrompt(); - String rawParticipantInput = inputView.readParticipantNames(); - return ValidatedInputParser.parseParticipantNames(rawParticipantInput); + return inputView.readParticipantNames(); } - private List getValidatedResultLabels(int expectedCount) { + private List promptResultLabels() { outputView.printResultPrompt(); - String rawResultInput = inputView.readResultLabels(); - return ValidatedInputParser.parseResultLabels(rawResultInput, expectedCount); + return inputView.readResultLabels(); } - private int getValidatedLadderHeight() { + private int promptAndParseLadderHeight() { outputView.printHeightPrompt(); - String rawHeightInput = inputView.readLadderHeight(); - return ValidatedInputParser.parseRowCount(rawHeightInput); + String input = inputView.readResultRequest(); + return NumericParser.parse(input); } - private LadderBuildResponse buildLadder(int numberOfColumns, int numberOfRows) { - LadderBuildRequest buildRequest = new LadderBuildRequest(numberOfColumns, numberOfRows); - return LadderBoard.build(buildRequest); + private LadderBuildResponse buildLadder(int columnCount, int rowCount) { + LadderBoard ladderBoard = LadderBoard.build(columnCount, rowCount); + return LadderBuildResponse.from(ladderBoard); } - private void displayLadder(List participantNames, List resultLabels, LadderBuildResponse ladder) { + private void displayLadderBoard(List participants, List results, LadderBuildResponse ladder) { outputView.printLadderTitle(); - outputView.printParticipantNames(participantNames); + outputView.printParticipantNames(participants); outputView.printBridgeLines(ladder); - outputView.printResultLabels(resultLabels); + outputView.printResultLabels(results); } - private PlayerResults mapPlayerResults(List participantNames, List resultLabels, LadderBuildResponse ladder) { + private PlayerResults mapResults(List participants, List results, LadderBuildResponse ladder) { LadderPath ladderPath = new LadderPath(ladder.lines(), ladder.columnCount()); LadderResultResponse resultMapping = new LadderResultResponse(ladderPath.mapStartToEndIndex()); - return PlayerResults.from(participantNames, resultLabels, resultMapping.positionMap()); + return PlayerResults.from(participants, results, resultMapping.positionMap()); } - private void handleResultQueries(PlayerResults playerResults) { + private void handleResultRequestLoop(PlayerResults playerResults) { while (true) { outputView.printResultSelectionPrompt(); - String queryName = inputView.readResultRequest(); - if (printIfMatchedPlayer(playerResults, queryName)) continue; - if (printIfAllResults(playerResults, queryName)) break; + String name = inputView.readResultRequest(); + + if (handleSingleResult(playerResults, name)) continue; + if (handleAllResults(playerResults, name)) return; + outputView.printNameNotFound(); } } - private boolean printIfMatchedPlayer(PlayerResults playerResults, String playerName) { - if (!playerResults.hasPlayer(playerName)) return false; - outputView.printLadderTitle(); - outputView.printSingleResult(playerResults.resultOf(playerName)); + private boolean handleSingleResult(PlayerResults playerResults, String name) { + if (!playerResults.hasPlayer(name)) return false; + outputView.printSingleResultWithTitle(playerResults.resultOf(name)); return true; } - private boolean printIfAllResults(PlayerResults playerResults, String queryName) { - if (!isAllCommand(queryName)) return false; + private boolean handleAllResults(PlayerResults playerResults, String name) { + if (!isAllCommandAndNotPlayer(name, playerResults)) return false; outputView.printLadderTitle(); outputView.printAllResults(playerResults.allResults()); return true; } - private boolean isAllCommand(String name) { - return "all".equals(name); + private boolean isAllCommandAndNotPlayer(String name, PlayerResults playerResults) { + return "all".equals(name) && !playerResults.hasPlayer(name); } } diff --git a/src/main/java/domain/BridgeLine.java b/src/main/java/domain/BridgeLine.java index 0682c71c..9c389af0 100644 --- a/src/main/java/domain/BridgeLine.java +++ b/src/main/java/domain/BridgeLine.java @@ -1,13 +1,20 @@ package domain; +import java.util.Collections; import java.util.List; public class BridgeLine { + private static final String VERTICAL_BAR = "|"; + private static final String CONNECTED_LINE = "-----"; + private static final String EMPTY_LINE = " "; + private static final String ERROR_INVALID_CONNECTIONS = "[ERROR] 가로줄 연결 상태는 null이거나 비어 있을 수 없습니다."; + private final List horizontalConnections; public BridgeLine(List horizontalConnections) { - this.horizontalConnections = horizontalConnections; + validate(horizontalConnections); + this.horizontalConnections = Collections.unmodifiableList(horizontalConnections); } public boolean isConnectedAt(int index) { @@ -18,7 +25,44 @@ public int width() { return horizontalConnections.size(); } - public List getConnections() { - return horizontalConnections; + public int nextPositionFrom(int position) { + if (position > 0 && isConnectedAt(position - 1)) { + return position - 1; + } + + if (position < width() && isConnectedAt(position)) { + return position + 1; + } + + return position; + } + + public String drawLineFormat(int columnCount) { + StringBuilder builder = new StringBuilder(); + + for (int i = 0; i < columnCount; i++) { + builder.append(VERTICAL_BAR); + builder.append(renderBridge(i)); + } + + return builder.toString(); + } + + private String renderBridge(int index) { + if (index >= horizontalConnections.size()) { + return EMPTY_LINE; + } + + if (horizontalConnections.get(index)) { + return CONNECTED_LINE; + } + + return EMPTY_LINE; + } + + private void validate(List connections) { + if (connections == null || connections.isEmpty()) { + throw new IllegalArgumentException(ERROR_INVALID_CONNECTIONS); + } } } diff --git a/src/main/java/domain/LadderBoard.java b/src/main/java/domain/LadderBoard.java index 30813591..02a9d172 100644 --- a/src/main/java/domain/LadderBoard.java +++ b/src/main/java/domain/LadderBoard.java @@ -1,32 +1,64 @@ package domain; -import dto.LadderBuildRequest; -import dto.LadderBuildResponse; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; +import java.util.*; public class LadderBoard { - private static final Random random = new Random(); + private static final Random RANDOM = new Random(); + private static final int MAX_ATTEMPTS = 100; + private static final int MIN_PARTICIPANTS = 2; + private static final int MIN_HEIGHT = 1; + + private static final String ERROR_TOO_FEW_PARTICIPANTS = "[ERROR] 사다리는 최소 2명 이상의 참가자가 필요합니다."; + private static final String ERROR_INVALID_HEIGHT = "[ERROR] 사다리 높이는 1 이상이어야 합니다."; + private static final String ERROR_GENERATION_FAILED = "[ERROR] 사다리를 생성하지 못했습니다. 참가자 수나 높이를 확인해 주세요."; - public static LadderBuildResponse build(LadderBuildRequest request) { - int participantCount = request.columnCount(); - int ladderHeight = request.rowCount(); - int centerColumnIndex = (participantCount - 1) / 2; + private final int columnCount; + private final List lines; - List bridgeLines = generateValidBridgeLines(participantCount, ladderHeight, centerColumnIndex); - return new LadderBuildResponse(participantCount, bridgeLines); + private LadderBoard(int columnCount, List lines) { + this.columnCount = columnCount; + this.lines = lines; + } + + public static LadderBoard build(int columnCount, int rowCount) { + validate(columnCount, rowCount); + int centerColumnIndex = (columnCount - 1) / 2; + List bridgeLines = generateValidBridgeLines(columnCount, rowCount, centerColumnIndex); + return new LadderBoard(columnCount, bridgeLines); + } + + public int getColumnCount() { + return columnCount; + } + + public List getLines() { + return lines; + } + + private static void validate(int columnCount, int rowCount) { + validateParticipantCount(columnCount); + validateLadderHeight(rowCount); + } + + private static void validateParticipantCount(int columnCount) { + if (columnCount < MIN_PARTICIPANTS) { + throw new IllegalArgumentException(ERROR_TOO_FEW_PARTICIPANTS); + } + } + + private static void validateLadderHeight(int rowCount) { + if (rowCount < MIN_HEIGHT) { + throw new IllegalArgumentException(ERROR_INVALID_HEIGHT); + } } private static List generateValidBridgeLines(int participantCount, int ladderHeight, int centerColumnIndex) { - while (true) { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { List candidateBridgeLines = tryGenerateBridgeLines(participantCount, ladderHeight, centerColumnIndex); if (candidateBridgeLines != null) return candidateBridgeLines; } + throw new IllegalStateException(ERROR_GENERATION_FAILED); } private static List tryGenerateBridgeLines(int participantCount, int ladderHeight, int centerColumnIndex) { @@ -39,7 +71,10 @@ private static List tryGenerateBridgeLines(int participantCount, int BridgeLine line = new BridgeLine(connectionStates); generatedBridgeLines.add(line); - isCenterBridgePresent |= isConnectedAtCenter(line, centerColumnIndex); + if (isConnectedAtCenter(line, centerColumnIndex)) { + isCenterBridgePresent = true; + } + recordConnectedIndices(connectedColumnIndices, connectionStates); } @@ -53,7 +88,7 @@ private static List generateConnectionStates(int participantCount) { List connectionStates = new ArrayList<>(); for (int index = 0; index < participantCount - 1; index++) { boolean canConnect = index == 0 || !connectionStates.get(index - 1); - boolean shouldConnect = canConnect && random.nextBoolean(); + boolean shouldConnect = canConnect && RANDOM.nextBoolean(); connectionStates.add(shouldConnect); } return connectionStates; diff --git a/src/main/java/domain/LadderPath.java b/src/main/java/domain/LadderPath.java index 366ae31c..18f5d890 100644 --- a/src/main/java/domain/LadderPath.java +++ b/src/main/java/domain/LadderPath.java @@ -17,26 +17,20 @@ public LadderPath(List bridgeLines, int numberOfColumns) { public Map mapStartToEndIndex() { Map startToEndMap = new LinkedHashMap<>(); - for (int startIndex = 0; startIndex < numberOfColumns; startIndex++) { - startToEndMap.put(startIndex, traverseFrom(startIndex)); + for (int startColumnIndex = 0; startColumnIndex < numberOfColumns; startColumnIndex++) { + startToEndMap.put(startColumnIndex, tracePathFrom(startColumnIndex)); } return startToEndMap; } - private int traverseFrom(int startColumnIndex) { - int current = startColumnIndex; + private int tracePathFrom(int startColumnIndex) { + int currentPosition = startColumnIndex; - for (BridgeLine line : bridgeLines) { - current = moveAlongLine(current, line); + for (BridgeLine bridgeLine : bridgeLines) { + currentPosition = bridgeLine.nextPositionFrom(currentPosition); } - return current; - } - - private int moveAlongLine(int position, BridgeLine line) { - if (position > 0 && line.isConnectedAt(position - 1)) return position - 1; - if (position < line.width() && line.isConnectedAt(position)) return position + 1; - return position; + return currentPosition; } } diff --git a/src/main/java/domain/PlayerResults.java b/src/main/java/domain/PlayerResults.java index a93f6dba..84306830 100644 --- a/src/main/java/domain/PlayerResults.java +++ b/src/main/java/domain/PlayerResults.java @@ -6,6 +6,12 @@ public class PlayerResults { + private static final int MAX_NAME_LENGTH = 5; + + private static final String ERROR_NO_PARTICIPANTS = "[ERROR] 참여자는 한 명 이상이어야 합니다."; + private static final String ERROR_NAME_TOO_LONG = "[ERROR] 참여자 이름은 5자 이하만 가능합니다: "; + private static final String ERROR_RESULT_COUNT_MISMATCH = "[ERROR] 실행 결과 수가 참여자 수와 일치해야 합니다."; + private final Map resultByPlayerName; private PlayerResults(Map resultByPlayerName) { @@ -13,16 +19,49 @@ private PlayerResults(Map resultByPlayerName) { } public static PlayerResults from(List playerNames, List outcomeLabels, Map startToEndIndexMap) { - Map mappedResults = new LinkedHashMap<>(); - for (int playerIndex = 0; playerIndex < playerNames.size(); playerIndex++) { - int resultIndex = startToEndIndexMap.get(playerIndex); - String playerName = playerNames.get(playerIndex); - String outcomeLabel = outcomeLabels.get(resultIndex); - mappedResults.put(playerName, outcomeLabel); - } + validate(playerNames, outcomeLabels); + Map mappedResults = mapResults(playerNames, outcomeLabels, startToEndIndexMap); return new PlayerResults(mappedResults); } + private static void validate(List playerNames, List outcomeLabels) { + validateNotEmpty(playerNames); + validateNameLength(playerNames); + validateSizeMatch(playerNames, outcomeLabels); + } + + private static void validateNotEmpty(List playerNames) { + if (playerNames.isEmpty()) throw new IllegalArgumentException(ERROR_NO_PARTICIPANTS); + } + + private static void validateNameLength(List playerNames) { + for (String name : playerNames) { + validateSingleNameLength(name); + } + } + + private static void validateSingleNameLength(String name) { + if (name.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException(ERROR_NAME_TOO_LONG + name); + } + } + + private static void validateSizeMatch(List playerNames, List outcomeLabels) { + if (playerNames.size() != outcomeLabels.size()) { + throw new IllegalArgumentException(ERROR_RESULT_COUNT_MISMATCH); + } + } + + private static Map mapResults(List names, List labels, Map indexMap) { + Map resultMap = new LinkedHashMap<>(); + for (int i = 0; i < names.size(); i++) { + String name = names.get(i); + String label = labels.get(indexMap.get(i)); + resultMap.put(name, label); + } + return resultMap; + } + public String resultOf(String playerName) { return resultByPlayerName.get(playerName); } diff --git a/src/main/java/dto/LadderBuildResponse.java b/src/main/java/dto/LadderBuildResponse.java index 61d87b9a..f17596e5 100644 --- a/src/main/java/dto/LadderBuildResponse.java +++ b/src/main/java/dto/LadderBuildResponse.java @@ -1,8 +1,13 @@ package dto; import domain.BridgeLine; +import domain.LadderBoard; import java.util.List; public record LadderBuildResponse(int columnCount, List lines) { + public static LadderBuildResponse from(LadderBoard board) { + return new LadderBuildResponse(board.getColumnCount(), board.getLines()); + } } + diff --git a/src/main/java/util/NumericParser.java b/src/main/java/util/NumericParser.java new file mode 100644 index 00000000..8c36c5e4 --- /dev/null +++ b/src/main/java/util/NumericParser.java @@ -0,0 +1,14 @@ +package util; + +public class NumericParser { + + private static final String ERROR_INVALID_NUMBER = "[ERROR] 숫자를 입력해 주세요."; + + public static int parse(String input) { + try { + return Integer.parseInt(input.trim()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ERROR_INVALID_NUMBER); + } + } +} diff --git a/src/main/java/util/ValidatedInputParser.java b/src/main/java/util/ValidatedInputParser.java deleted file mode 100644 index 78e2e652..00000000 --- a/src/main/java/util/ValidatedInputParser.java +++ /dev/null @@ -1,64 +0,0 @@ -package util; - -import java.util.Arrays; -import java.util.List; - -public class ValidatedInputParser { - - private static final int MAX_NAME_LENGTH = 5; - private static final String ERROR_NO_PARTICIPANTS = "[ERROR] 참여자는 한 명 이상이어야 합니다."; - private static final String ERROR_NAME_TOO_LONG = "[ERROR] 참여자 이름은 5자 이하만 가능합니다: "; - private static final String ERROR_RESULT_COUNT_MISMATCH = "[ERROR] 실행 결과 수가 참여자 수와 일치해야 합니다."; - private static final String ERROR_INVALID_ROW_COUNT = "[ERROR] 사다리 높이는 1 이상이어야 합니다."; - private static final String ERROR_INVALID_NUMBER_FORMAT = "[ERROR] 숫자를 입력해 주세요."; - - public static List parseParticipantNames(String rawInput) { - List participantNames = Arrays.asList(rawInput.trim().split(",")); - validateParticipantNamesExist(participantNames); - validateParticipantNameLengths(participantNames); - return participantNames; - } - - public static List parseResultLabels(String rawInput, int expectedCount) { - List resultLabels = Arrays.asList(rawInput.trim().split(",")); - validateResultLabelCount(resultLabels, expectedCount); - return resultLabels; - } - - public static int parseRowCount(String rawInput) { - try { - int rowCount = Integer.parseInt(rawInput.trim()); - validateRowCountPositive(rowCount); - return rowCount; - } catch (NumberFormatException e) { - throw new IllegalArgumentException(ERROR_INVALID_NUMBER_FORMAT); - } - } - - private static void validateParticipantNamesExist(List participantNames) { - if (participantNames.isEmpty()) { - throw new IllegalArgumentException(ERROR_NO_PARTICIPANTS); - } - } - - private static void validateParticipantNameLengths(List participantNames) { - participantNames.stream() - .filter(name -> name.length() > MAX_NAME_LENGTH) - .findFirst() - .ifPresent(name -> { - throw new IllegalArgumentException(ERROR_NAME_TOO_LONG + name); - }); - } - - private static void validateResultLabelCount(List resultLabels, int expectedCount) { - if (resultLabels.size() != expectedCount) { - throw new IllegalArgumentException(ERROR_RESULT_COUNT_MISMATCH); - } - } - - private static void validateRowCountPositive(int rowCount) { - if (rowCount <= 0) { - throw new IllegalArgumentException(ERROR_INVALID_ROW_COUNT); - } - } -} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 3475e704..cceb4feb 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,21 +1,23 @@ package view; +import java.util.Arrays; +import java.util.List; import java.util.Scanner; public class InputView { private static final Scanner scanner = new Scanner(System.in); - public String readParticipantNames() { - return readLine(); + public List readParticipantNames() { + return Arrays.asList(readLine().split(",")); } - public String readResultLabels() { - return readLine(); + public List readResultLabels() { + return Arrays.asList(readLine().split(",")); } - public String readLadderHeight() { - return readLine(); + public int readLadderHeight() { + return Integer.parseInt(readLine()); } public String readResultRequest() { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index c5190e4a..2ee53b88 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -15,11 +15,7 @@ public class OutputView { private static final String LADDER_RESULT_TITLE = "실행 결과"; private static final String NAME_NOT_FOUND_MESSAGE = "해당 이름은 존재하지 않습니다."; - private static final String VERTICAL_BAR = "|"; - private static final String CONNECTED_LINE = "-----"; - private static final String EMPTY_LINE = " "; private static final int DISPLAY_CELL_WIDTH = 6; - private static final String EMPTY_PREFIX = " "; private static final String CELL_FORMAT = "%-" + DISPLAY_CELL_WIDTH + "s"; private static final String RESULT_FORMAT = "%s : %s"; @@ -58,16 +54,11 @@ public void printResultLabels(List resultLabels) { } public void printBridgeLines(LadderBuildResponse response) { - List> allBridgeLines = response.lines().stream() - .map(line -> line.getConnections()) - .collect(Collectors.toList()); - int totalColumns = response.columnCount(); - for (List bridgeConnections : allBridgeLines) { - String formattedLine = renderBridgeLine(bridgeConnections, totalColumns); - System.out.println(EMPTY_PREFIX + formattedLine); - } + response.lines().forEach(line -> { + System.out.println(" " + line.drawLineFormat(totalColumns)); + }); } public void printAllResults(Map participantResults) { @@ -80,6 +71,11 @@ public void printSingleResult(String resultValue) { System.out.println(resultValue); } + public void printSingleResultWithTitle(String resultValue) { + printLadderTitle(); + printSingleResult(resultValue); + } + public void printNameNotFound() { System.out.println(NAME_NOT_FOUND_MESSAGE); } @@ -88,37 +84,6 @@ private String formatResult(String name, String result) { return String.format(RESULT_FORMAT, name, result); } - private String renderBridgeLine(List connections, int totalColumns) { - StringBuilder lineBuilder = new StringBuilder(); - - for (int columnIndex = 0; columnIndex < totalColumns; columnIndex++) { - lineBuilder.append(VERTICAL_BAR); - lineBuilder.append(renderBridgeBetweenColumns(connections, columnIndex)); - } - - return lineBuilder.toString(); - } - - private String renderBridgeBetweenColumns(List connections, int columnIndex) { - if (isOutOfConnectionRange(connections, columnIndex)) { - return EMPTY_LINE; - } - - if (isConnected(connections, columnIndex)) { - return CONNECTED_LINE; - } - - return EMPTY_LINE; - } - - private boolean isOutOfConnectionRange(List connections, int columnIndex) { - return columnIndex >= connections.size(); - } - - private boolean isConnected(List connections, int columnIndex) { - return connections.get(columnIndex); - } - private String formatCell(String value) { return String.format(CELL_FORMAT, value); } diff --git a/src/test/java/domain/BridgeLineTest.java b/src/test/java/domain/BridgeLineTest.java index 0bb272e2..c44a4108 100644 --- a/src/test/java/domain/BridgeLineTest.java +++ b/src/test/java/domain/BridgeLineTest.java @@ -1,6 +1,7 @@ package domain; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -12,12 +13,25 @@ class BridgeLineTest { - @ParameterizedTest(name = "index={0}일 때 연결 여부는 {1}이다") - @MethodSource("connectionTestCases") - @DisplayName("isConnectedAt는 주어진 인덱스의 연결 상태를 반환한다") - void isConnectedAt_returnsCorrectStatus(int index, boolean expected) { + @Test + @DisplayName("index 0은 연결되어 있지 않다") + void isConnectedAt_index0_false() { BridgeLine line = new BridgeLine(List.of(false, true, false)); - assertThat(line.isConnectedAt(index)).isEqualTo(expected); + assertThat(line.isConnectedAt(0)).isFalse(); + } + + @Test + @DisplayName("index 1은 연결되어 있다") + void isConnectedAt_index1_true() { + BridgeLine line = new BridgeLine(List.of(false, true, false)); + assertThat(line.isConnectedAt(1)).isTrue(); + } + + @Test + @DisplayName("index 2는 연결되어 있지 않다") + void isConnectedAt_index2_false() { + BridgeLine line = new BridgeLine(List.of(false, true, false)); + assertThat(line.isConnectedAt(2)).isFalse(); } @ParameterizedTest(name = "연결 리스트 {0}의 크기는 {1}이다") @@ -28,6 +42,15 @@ void width_returnsCorrectSize(List connections, int expectedWidth) { assertThat(line.width()).isEqualTo(expectedWidth); } + @Test + @DisplayName("drawLineFormat은 사다리 가로줄을 시각화된 문자열로 반환한다") + void drawLineFormat_returnsVisualRepresentation() { + BridgeLine line = new BridgeLine(List.of(true, false, true)); + String result = line.drawLineFormat(4); + + assertThat(result).isEqualTo("|-----| |-----| "); + } + private static Stream connectionTestCases() { return Stream.of( Arguments.of(0, false), diff --git a/src/test/java/domain/LadderBoardTest.java b/src/test/java/domain/LadderBoardTest.java index e492ca42..b909e6b2 100644 --- a/src/test/java/domain/LadderBoardTest.java +++ b/src/test/java/domain/LadderBoardTest.java @@ -1,13 +1,12 @@ package domain; -import dto.LadderBuildRequest; -import dto.LadderBuildResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; class LadderBoardTest { @@ -16,12 +15,11 @@ class LadderBoardTest { void build_createsValidLadderStructure() { int columnCount = 4; int rowCount = 5; - LadderBuildRequest request = new LadderBuildRequest(columnCount, rowCount); - LadderBuildResponse response = LadderBoard.build(request); + LadderBoard board = LadderBoard.build(columnCount, rowCount); - assertThat(response.columnCount()).isEqualTo(columnCount); - assertThat(response.lines()).hasSize(rowCount); + assertThat(board.getColumnCount()).isEqualTo(columnCount); + assertThat(board.getLines()).hasSize(rowCount); } @Test @@ -30,14 +28,29 @@ void build_containsAtLeastOneCenterConnection() { int columnCount = 5; int rowCount = 10; int centerIndex = (columnCount - 1) / 2; - LadderBuildRequest request = new LadderBuildRequest(columnCount, rowCount); - LadderBuildResponse response = LadderBoard.build(request); - List lines = response.lines(); + LadderBoard board = LadderBoard.build(columnCount, rowCount); + List lines = board.getLines(); boolean hasCenterConnection = lines.stream() .anyMatch(line -> line.isConnectedAt(centerIndex)); assertThat(hasCenterConnection).isTrue(); } + + @DisplayName("참가자 수가 2명 미만이면 예외를 던진다") + @Test + void throwsIfParticipantsLessThanTwo() { + assertThatThrownBy(() -> LadderBoard.build(1, 5)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 사다리는 최소 2명 이상의 참가자가 필요합니다."); + } + + @DisplayName("사다리 높이가 1 미만이면 예외를 던진다") + @Test + void throwsIfHeightLessThanOne() { + assertThatThrownBy(() -> LadderBoard.build(4, 0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 사다리 높이는 1 이상이어야 합니다."); + } } diff --git a/src/test/java/domain/PlayerResultsTest.java b/src/test/java/domain/PlayerResultsTest.java index 9b2f24fa..c3849bed 100644 --- a/src/test/java/domain/PlayerResultsTest.java +++ b/src/test/java/domain/PlayerResultsTest.java @@ -8,6 +8,8 @@ import java.util.Map; import java.util.LinkedHashMap; +import static org.junit.jupiter.api.Assertions.assertThrows; + class PlayerResultsTest { @Test @@ -43,4 +45,14 @@ void from_createsCorrectMapping() { softly.assertThat(results.allResults()).containsExactlyEntriesOf(expected); softly.assertAll(); } + + @Test + @DisplayName("참가자가 없을 경우 예외 발생") + void throwsIfEmptyParticipants() { + List empty = List.of(); + List labels = List.of(); + Map indexMap = Map.of(); + assertThrows(IllegalArgumentException.class, () -> + PlayerResults.from(empty, labels, indexMap)); + } } diff --git a/src/test/java/util/NumericParserTest.java b/src/test/java/util/NumericParserTest.java new file mode 100644 index 00000000..7b6bcd7d --- /dev/null +++ b/src/test/java/util/NumericParserTest.java @@ -0,0 +1,24 @@ +package util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class NumericParserTest { + + @Test + @DisplayName("정수 문자열을 숫자로 파싱한다") + void parsesValidInteger() { + int result = NumericParser.parse("42"); + assertThat(result).isEqualTo(42); + } + + @Test + @DisplayName("숫자가 아닌 문자열을 파싱하면 예외를 던진다") + void throwsIfNotNumber() { + assertThatThrownBy(() -> NumericParser.parse("abc")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 숫자를 입력해 주세요."); + } +} diff --git a/src/test/java/util/ValidatedInputParserTest.java b/src/test/java/util/ValidatedInputParserTest.java deleted file mode 100644 index 829a3524..00000000 --- a/src/test/java/util/ValidatedInputParserTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package util; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class ValidatedInputParserTest { - - @Nested - @DisplayName("참여자 이름 파싱") - class ParseParticipantNames { - - @Test - @DisplayName("쉼표로 구분된 참여자 이름들을 파싱한다") - void parsesParticipantNames() { - List result = ValidatedInputParser.parseParticipantNames("neo,brown"); - assertThat(result).containsExactly("neo", "brown"); - } - - @Test - @DisplayName("참여자가 없는 경우 예외를 던진다") - void throwsIfNoParticipants() { - assertThatThrownBy(() -> ValidatedInputParser.parseParticipantNames("")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 참여자는 한 명 이상이어야 합니다."); - } - - @Test - @DisplayName("이름이 5자를 초과하면 예외를 던진다") - void throwsIfNameTooLong() { - assertThatThrownBy(() -> ValidatedInputParser.parseParticipantNames("neo,longname")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 참여자 이름은 5자 이하만 가능합니다: longname"); - } - } - - @Nested - @DisplayName("결과 라벨 파싱") - class ParseResultLabels { - - @Test - @DisplayName("결과 수가 참여자 수와 다르면 예외를 던진다") - void throwsIfResultCountMismatch() { - assertThatThrownBy(() -> ValidatedInputParser.parseResultLabels("꽝,당첨", 3)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 실행 결과 수가 참여자 수와 일치해야 합니다."); - } - - @Test - @DisplayName("결과 라벨들을 파싱한다") - void parsesResultLabels() { - List result = ValidatedInputParser.parseResultLabels("꽝,5000,3000", 3); - assertThat(result).containsExactly("꽝", "5000", "3000"); - } - } - - @Nested - @DisplayName("사다리 높이 파싱") - class ParseRowCount { - - @Test - @DisplayName("숫자가 아니면 예외를 던진다") - void throwsIfNotNumber() { - assertThatThrownBy(() -> ValidatedInputParser.parseRowCount("five")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 숫자를 입력해 주세요."); - } - - @Test - @DisplayName("0 이하의 숫자면 예외를 던진다") - void throwsIfZeroOrNegative() { - assertThatThrownBy(() -> ValidatedInputParser.parseRowCount("0")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 사다리 높이는 1 이상이어야 합니다."); - } - - @Test - @DisplayName("숫자를 정상적으로 파싱한다") - void parsesRowCount() { - int result = ValidatedInputParser.parseRowCount("7"); - assertThat(result).isEqualTo(7); - } - } -} From b75fd2a5891119a7a67b0245bf818a647b3e1571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Sun, 25 May 2025 15:28:54 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refactor(step5)=20:=205=EB=8B=A8=EA=B3=84?= =?UTF-8?q?=20-=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/LadderController.java | 4 +--- src/main/java/domain/BridgeLine.java | 13 +++++------- src/main/java/domain/LadderBoard.java | 20 +++++++++---------- src/main/java/domain/LadderPath.java | 15 +++++++------- src/main/java/domain/PlayerResults.java | 15 +++++++------- src/main/java/view/OutputView.java | 5 ++--- 6 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java index b15394b3..7d24a62a 100644 --- a/src/main/java/controller/LadderController.java +++ b/src/main/java/controller/LadderController.java @@ -5,7 +5,6 @@ import domain.PlayerResults; import dto.LadderBuildResponse; import dto.LadderResultResponse; -import util.NumericParser; import view.InputView; import view.OutputView; @@ -45,8 +44,7 @@ private List promptResultLabels() { private int promptAndParseLadderHeight() { outputView.printHeightPrompt(); - String input = inputView.readResultRequest(); - return NumericParser.parse(input); + return inputView.readLadderHeight(); } private LadderBuildResponse buildLadder(int columnCount, int rowCount) { diff --git a/src/main/java/domain/BridgeLine.java b/src/main/java/domain/BridgeLine.java index 9c389af0..d3984958 100644 --- a/src/main/java/domain/BridgeLine.java +++ b/src/main/java/domain/BridgeLine.java @@ -2,6 +2,8 @@ import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public class BridgeLine { @@ -38,14 +40,9 @@ public int nextPositionFrom(int position) { } public String drawLineFormat(int columnCount) { - StringBuilder builder = new StringBuilder(); - - for (int i = 0; i < columnCount; i++) { - builder.append(VERTICAL_BAR); - builder.append(renderBridge(i)); - } - - return builder.toString(); + return IntStream.range(0, columnCount) + .mapToObj(i -> VERTICAL_BAR + renderBridge(i)) + .collect(Collectors.joining()); } private String renderBridge(int index) { diff --git a/src/main/java/domain/LadderBoard.java b/src/main/java/domain/LadderBoard.java index 02a9d172..38a72bae 100644 --- a/src/main/java/domain/LadderBoard.java +++ b/src/main/java/domain/LadderBoard.java @@ -1,6 +1,7 @@ package domain; import java.util.*; +import java.util.stream.IntStream; public class LadderBoard { @@ -85,13 +86,12 @@ private static List tryGenerateBridgeLines(int participantCount, int } private static List generateConnectionStates(int participantCount) { - List connectionStates = new ArrayList<>(); - for (int index = 0; index < participantCount - 1; index++) { - boolean canConnect = index == 0 || !connectionStates.get(index - 1); - boolean shouldConnect = canConnect && RANDOM.nextBoolean(); - connectionStates.add(shouldConnect); - } - return connectionStates; + return IntStream.range(0, participantCount - 1) + .boxed() + .collect(ArrayList::new, (list, i) -> { + boolean canConnect = i == 0 || !list.get(i - 1); + list.add(canConnect && RANDOM.nextBoolean()); + }, List::addAll); } private static boolean isConnectedAtCenter(BridgeLine line, int centerIndex) { @@ -99,9 +99,9 @@ private static boolean isConnectedAtCenter(BridgeLine line, int centerIndex) { } private static void recordConnectedIndices(Set connectedColumnIndices, List connectionStates) { - for (int i = 0; i < connectionStates.size(); i++) { - if (connectionStates.get(i)) connectedColumnIndices.add(i); - } + IntStream.range(0, connectionStates.size()) + .filter(i -> connectionStates.get(i)) + .forEach(connectedColumnIndices::add); } private static boolean areAllColumnsConnected(Set connectedColumnIndices, int expectedCount) { diff --git a/src/main/java/domain/LadderPath.java b/src/main/java/domain/LadderPath.java index 18f5d890..e98437fb 100644 --- a/src/main/java/domain/LadderPath.java +++ b/src/main/java/domain/LadderPath.java @@ -3,6 +3,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.IntStream; public class LadderPath { @@ -15,13 +16,13 @@ public LadderPath(List bridgeLines, int numberOfColumns) { } public Map mapStartToEndIndex() { - Map startToEndMap = new LinkedHashMap<>(); - - for (int startColumnIndex = 0; startColumnIndex < numberOfColumns; startColumnIndex++) { - startToEndMap.put(startColumnIndex, tracePathFrom(startColumnIndex)); - } - - return startToEndMap; + return IntStream.range(0, numberOfColumns) + .boxed() + .collect( + LinkedHashMap::new, + (map, i) -> map.put(i, tracePathFrom(i)), + Map::putAll + ); } private int tracePathFrom(int startColumnIndex) { diff --git a/src/main/java/domain/PlayerResults.java b/src/main/java/domain/PlayerResults.java index 84306830..3d9d95b3 100644 --- a/src/main/java/domain/PlayerResults.java +++ b/src/main/java/domain/PlayerResults.java @@ -3,6 +3,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.IntStream; public class PlayerResults { @@ -53,13 +54,13 @@ private static void validateSizeMatch(List playerNames, List out } private static Map mapResults(List names, List labels, Map indexMap) { - Map resultMap = new LinkedHashMap<>(); - for (int i = 0; i < names.size(); i++) { - String name = names.get(i); - String label = labels.get(indexMap.get(i)); - resultMap.put(name, label); - } - return resultMap; + return IntStream.range(0, names.size()) + .boxed() + .collect( + LinkedHashMap::new, + (map, i) -> map.put(names.get(i), labels.get(indexMap.get(i))), + Map::putAll + ); } public String resultOf(String playerName) { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 2ee53b88..13433047 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -62,9 +62,8 @@ public void printBridgeLines(LadderBuildResponse response) { } public void printAllResults(Map participantResults) { - for (Map.Entry entry : participantResults.entrySet()) { - System.out.println(formatResult(entry.getKey(), entry.getValue())); - } + participantResults.forEach((name, result) -> + System.out.println(formatResult(name, result))); } public void printSingleResult(String resultValue) { From 9e0bdb9505ff94b7ea6e1dab1783fff1ec984806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Mon, 26 May 2025 16:17:53 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor(review2)=20:=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/LadderController.java | 65 ++++++++--------- src/main/java/domain/BridgeLine.java | 26 +------ src/main/java/domain/LadderBoard.java | 72 +++++++++---------- src/main/java/domain/Participants.java | 59 +++++++++++++++ src/main/java/domain/PlayerResults.java | 55 ++++---------- src/main/java/view/InputView.java | 4 +- src/main/java/view/OutputView.java | 49 ++++++++----- src/test/java/domain/BridgeLineTest.java | 57 ++++++--------- src/test/java/domain/ParticipantsTest.java | 35 +++++++++ src/test/java/domain/PlayerResultsTest.java | 16 +---- 10 files changed, 235 insertions(+), 203 deletions(-) create mode 100644 src/main/java/domain/Participants.java create mode 100644 src/test/java/domain/ParticipantsTest.java diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java index 7d24a62a..e3c434b9 100644 --- a/src/main/java/controller/LadderController.java +++ b/src/main/java/controller/LadderController.java @@ -2,6 +2,7 @@ import domain.LadderBoard; import domain.LadderPath; +import domain.Participants; import domain.PlayerResults; import dto.LadderBuildResponse; import dto.LadderResultResponse; @@ -12,6 +13,8 @@ public class LadderController { + private static final String ALL_COMMAND = "all"; + private final InputView inputView; private final OutputView outputView; @@ -21,28 +24,29 @@ public LadderController(InputView inputView, OutputView outputView) { } public void run() { - List participantNames = promptParticipants(); - List resultLabels = promptResultLabels(); - int ladderHeight = promptAndParseLadderHeight(); + Participants participants = readParticipants(); + List resultLabels = readResultLabels(); + int ladderHeight = readLadderHeight(); - LadderBuildResponse ladder = buildLadder(participantNames.size(), ladderHeight); - displayLadderBoard(participantNames, resultLabels, ladder); + LadderBuildResponse ladder = buildLadder(participants.getCount(), ladderHeight); + printLadderBoard(participants.getNames(), resultLabels, ladder); - PlayerResults playerResults = mapResults(participantNames, resultLabels, ladder); - handleResultRequestLoop(playerResults); + PlayerResults playerResults = mapPlayerResults(participants, resultLabels, ladder); + handleResultRequest(playerResults); } - private List promptParticipants() { + private Participants readParticipants() { outputView.printParticipantPrompt(); - return inputView.readParticipantNames(); + List names = inputView.readParticipantNames(); + return Participants.of(names); } - private List promptResultLabels() { + private List readResultLabels() { outputView.printResultPrompt(); return inputView.readResultLabels(); } - private int promptAndParseLadderHeight() { + private int readLadderHeight() { outputView.printHeightPrompt(); return inputView.readLadderHeight(); } @@ -52,45 +56,34 @@ private LadderBuildResponse buildLadder(int columnCount, int rowCount) { return LadderBuildResponse.from(ladderBoard); } - private void displayLadderBoard(List participants, List results, LadderBuildResponse ladder) { + private void printLadderBoard(List participants, List results, LadderBuildResponse ladder) { outputView.printLadderTitle(); outputView.printParticipantNames(participants); outputView.printBridgeLines(ladder); outputView.printResultLabels(results); } - private PlayerResults mapResults(List participants, List results, LadderBuildResponse ladder) { + private PlayerResults mapPlayerResults(Participants participants, List results, LadderBuildResponse ladder) { LadderPath ladderPath = new LadderPath(ladder.lines(), ladder.columnCount()); LadderResultResponse resultMapping = new LadderResultResponse(ladderPath.mapStartToEndIndex()); return PlayerResults.from(participants, results, resultMapping.positionMap()); } - private void handleResultRequestLoop(PlayerResults playerResults) { - while (true) { - outputView.printResultSelectionPrompt(); - String name = inputView.readResultRequest(); - - if (handleSingleResult(playerResults, name)) continue; - if (handleAllResults(playerResults, name)) return; + private void handleResultRequest(PlayerResults playerResults) { + outputView.printResultSelectionPrompt(); + String name = inputView.readResultRequest(); - outputView.printNameNotFound(); + if (ALL_COMMAND.equals(name)) { + outputView.printLadderTitle(); + outputView.printAllResults(playerResults.allResults()); + return; } - } - - private boolean handleSingleResult(PlayerResults playerResults, String name) { - if (!playerResults.hasPlayer(name)) return false; - outputView.printSingleResultWithTitle(playerResults.resultOf(name)); - return true; - } - private boolean handleAllResults(PlayerResults playerResults, String name) { - if (!isAllCommandAndNotPlayer(name, playerResults)) return false; - outputView.printLadderTitle(); - outputView.printAllResults(playerResults.allResults()); - return true; - } + if (playerResults.hasPlayer(name)) { + outputView.printSingleResultWithTitle(playerResults.resultOf(name)); + return; + } - private boolean isAllCommandAndNotPlayer(String name, PlayerResults playerResults) { - return "all".equals(name) && !playerResults.hasPlayer(name); + outputView.printNameNotFound(); } } diff --git a/src/main/java/domain/BridgeLine.java b/src/main/java/domain/BridgeLine.java index d3984958..ee621da2 100644 --- a/src/main/java/domain/BridgeLine.java +++ b/src/main/java/domain/BridgeLine.java @@ -2,14 +2,9 @@ import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; public class BridgeLine { - private static final String VERTICAL_BAR = "|"; - private static final String CONNECTED_LINE = "-----"; - private static final String EMPTY_LINE = " "; private static final String ERROR_INVALID_CONNECTIONS = "[ERROR] 가로줄 연결 상태는 null이거나 비어 있을 수 없습니다."; private final List horizontalConnections; @@ -20,6 +15,9 @@ public BridgeLine(List horizontalConnections) { } public boolean isConnectedAt(int index) { + if (index < 0 || index >= horizontalConnections.size()) { + return false; + } return horizontalConnections.get(index); } @@ -39,24 +37,6 @@ public int nextPositionFrom(int position) { return position; } - public String drawLineFormat(int columnCount) { - return IntStream.range(0, columnCount) - .mapToObj(i -> VERTICAL_BAR + renderBridge(i)) - .collect(Collectors.joining()); - } - - private String renderBridge(int index) { - if (index >= horizontalConnections.size()) { - return EMPTY_LINE; - } - - if (horizontalConnections.get(index)) { - return CONNECTED_LINE; - } - - return EMPTY_LINE; - } - private void validate(List connections) { if (connections == null || connections.isEmpty()) { throw new IllegalArgumentException(ERROR_INVALID_CONNECTIONS); diff --git a/src/main/java/domain/LadderBoard.java b/src/main/java/domain/LadderBoard.java index 38a72bae..2e878191 100644 --- a/src/main/java/domain/LadderBoard.java +++ b/src/main/java/domain/LadderBoard.java @@ -1,6 +1,11 @@ package domain; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.IntStream; public class LadderBoard { @@ -25,7 +30,8 @@ private LadderBoard(int columnCount, List lines) { public static LadderBoard build(int columnCount, int rowCount) { validate(columnCount, rowCount); int centerColumnIndex = (columnCount - 1) / 2; - List bridgeLines = generateValidBridgeLines(columnCount, rowCount, centerColumnIndex); + + List bridgeLines = tryGenerateBridgeLines(columnCount, rowCount, centerColumnIndex); return new LadderBoard(columnCount, bridgeLines); } @@ -38,51 +44,47 @@ public List getLines() { } private static void validate(int columnCount, int rowCount) { - validateParticipantCount(columnCount); - validateLadderHeight(rowCount); - } - - private static void validateParticipantCount(int columnCount) { if (columnCount < MIN_PARTICIPANTS) { throw new IllegalArgumentException(ERROR_TOO_FEW_PARTICIPANTS); } - } - - private static void validateLadderHeight(int rowCount) { if (rowCount < MIN_HEIGHT) { throw new IllegalArgumentException(ERROR_INVALID_HEIGHT); } } - private static List generateValidBridgeLines(int participantCount, int ladderHeight, int centerColumnIndex) { - for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { - List candidateBridgeLines = tryGenerateBridgeLines(participantCount, ladderHeight, centerColumnIndex); - if (candidateBridgeLines != null) return candidateBridgeLines; - } - throw new IllegalStateException(ERROR_GENERATION_FAILED); - } - private static List tryGenerateBridgeLines(int participantCount, int ladderHeight, int centerColumnIndex) { - List generatedBridgeLines = new ArrayList<>(); - boolean isCenterBridgePresent = false; - Set connectedColumnIndices = new HashSet<>(); + return IntStream.range(0, MAX_ATTEMPTS) + .mapToObj(i -> generateAttempt(participantCount, ladderHeight)) + .filter(lines -> isValidBridgeLines(lines, participantCount, centerColumnIndex)) + .findFirst() + .orElseThrow(() -> new IllegalStateException(ERROR_GENERATION_FAILED)); + } - for (int row = 0; row < ladderHeight; row++) { - List connectionStates = generateConnectionStates(participantCount); - BridgeLine line = new BridgeLine(connectionStates); - generatedBridgeLines.add(line); + private static List generateAttempt(int participantCount, int ladderHeight) { + return IntStream.range(0, ladderHeight) + .mapToObj(i -> new BridgeLine(generateConnectionStates(participantCount))) + .collect(Collectors.toList()); + } - if (isConnectedAtCenter(line, centerColumnIndex)) { - isCenterBridgePresent = true; - } + private static boolean isValidBridgeLines(List lines, int participantCount, int centerColumnIndex) { + return hasCenterBridge(lines, centerColumnIndex) && + hasAllColumnConnections(lines, participantCount - 1); + } - recordConnectedIndices(connectedColumnIndices, connectionStates); - } + private static boolean hasCenterBridge(List lines, int centerIndex) { + return lines.stream().anyMatch(line -> isConnectedAtCenter(line, centerIndex)); + } - if (!areAllColumnsConnected(connectedColumnIndices, participantCount - 1)) return null; - if (!isCenterBridgePresent) return null; + private static boolean hasAllColumnConnections(List lines, int expectedCount) { + Set connectedIndices = new HashSet<>(); + lines.forEach(line -> recordConnectedIndices(connectedIndices, getConnectionStates(line))); + return connectedIndices.size() == expectedCount; + } - return generatedBridgeLines; + private static List getConnectionStates(BridgeLine line) { + return IntStream.range(0, line.width()) + .mapToObj(line::isConnectedAt) + .collect(Collectors.toList()); } private static List generateConnectionStates(int participantCount) { @@ -103,8 +105,4 @@ private static void recordConnectedIndices(Set connectedColumnIndices, .filter(i -> connectionStates.get(i)) .forEach(connectedColumnIndices::add); } - - private static boolean areAllColumnsConnected(Set connectedColumnIndices, int expectedCount) { - return connectedColumnIndices.size() == expectedCount; - } } diff --git a/src/main/java/domain/Participants.java b/src/main/java/domain/Participants.java new file mode 100644 index 00000000..80ef5cd9 --- /dev/null +++ b/src/main/java/domain/Participants.java @@ -0,0 +1,59 @@ +package domain; + +import java.util.List; + +public class Participants { + + private static final int MAX_NAME_LENGTH = 5; + private static final String RESERVED_KEYWORD = "all"; + + private static final String ERROR_NO_PARTICIPANTS = "[ERROR] 참여자는 한 명 이상이어야 합니다."; + private static final String ERROR_NAME_TOO_LONG = "[ERROR] 참여자 이름은 5자 이하만 가능합니다: "; + private static final String ERROR_RESERVED_NAME = "[ERROR] 'all'은 사용할 수 없는 이름입니다."; + + private final List names; + + private Participants(List names) { + this.names = names; + } + + public static Participants of(List names) { + validate(names); + return new Participants(names); + } + + public List getNames() { + return names; + } + + public int getCount() { + return names.size(); + } + + private static void validate(List names) { + validateNotEmpty(names); + validateNameLength(names); + validateReservedKeyword(names); + } + + private static void validateNotEmpty(List names) { + if (names.isEmpty()) { + throw new IllegalArgumentException(ERROR_NO_PARTICIPANTS); + } + } + + private static void validateNameLength(List names) { + names.stream() + .filter(name -> name.length() > MAX_NAME_LENGTH) + .findFirst() + .ifPresent(name -> { + throw new IllegalArgumentException(ERROR_NAME_TOO_LONG + name); + }); + } + + private static void validateReservedKeyword(List names) { + if (names.contains(RESERVED_KEYWORD)) { + throw new IllegalArgumentException(ERROR_RESERVED_NAME); + } + } +} diff --git a/src/main/java/domain/PlayerResults.java b/src/main/java/domain/PlayerResults.java index 3d9d95b3..4da46a53 100644 --- a/src/main/java/domain/PlayerResults.java +++ b/src/main/java/domain/PlayerResults.java @@ -7,10 +7,6 @@ public class PlayerResults { - private static final int MAX_NAME_LENGTH = 5; - - private static final String ERROR_NO_PARTICIPANTS = "[ERROR] 참여자는 한 명 이상이어야 합니다."; - private static final String ERROR_NAME_TOO_LONG = "[ERROR] 참여자 이름은 5자 이하만 가능합니다: "; private static final String ERROR_RESULT_COUNT_MISMATCH = "[ERROR] 실행 결과 수가 참여자 수와 일치해야 합니다."; private final Map resultByPlayerName; @@ -19,59 +15,34 @@ private PlayerResults(Map resultByPlayerName) { this.resultByPlayerName = resultByPlayerName; } - public static PlayerResults from(List playerNames, List outcomeLabels, Map startToEndIndexMap) { - validate(playerNames, outcomeLabels); - Map mappedResults = mapResults(playerNames, outcomeLabels, startToEndIndexMap); + public static PlayerResults from(Participants participants, List outcomeLabels, Map startToEndIndexMap) { + validateSizeMatch(participants, outcomeLabels); + Map mappedResults = mapResults(participants.getNames(), outcomeLabels, startToEndIndexMap); return new PlayerResults(mappedResults); } - private static void validate(List playerNames, List outcomeLabels) { - validateNotEmpty(playerNames); - validateNameLength(playerNames); - validateSizeMatch(playerNames, outcomeLabels); - } - - private static void validateNotEmpty(List playerNames) { - if (playerNames.isEmpty()) throw new IllegalArgumentException(ERROR_NO_PARTICIPANTS); + public String resultOf(String playerName) { + return resultByPlayerName.get(playerName); } - private static void validateNameLength(List playerNames) { - for (String name : playerNames) { - validateSingleNameLength(name); - } + public Map allResults() { + return resultByPlayerName; } - private static void validateSingleNameLength(String name) { - if (name.length() > MAX_NAME_LENGTH) { - throw new IllegalArgumentException(ERROR_NAME_TOO_LONG + name); - } + public boolean hasPlayer(String playerName) { + return resultByPlayerName.containsKey(playerName); } - private static void validateSizeMatch(List playerNames, List outcomeLabels) { - if (playerNames.size() != outcomeLabels.size()) { + private static void validateSizeMatch(Participants participants, List outcomeLabels) { + if (participants.getCount() != outcomeLabels.size()) { throw new IllegalArgumentException(ERROR_RESULT_COUNT_MISMATCH); } } private static Map mapResults(List names, List labels, Map indexMap) { return IntStream.range(0, names.size()) - .boxed() - .collect( - LinkedHashMap::new, + .collect(LinkedHashMap::new, (map, i) -> map.put(names.get(i), labels.get(indexMap.get(i))), - Map::putAll - ); - } - - public String resultOf(String playerName) { - return resultByPlayerName.get(playerName); - } - - public Map allResults() { - return resultByPlayerName; - } - - public boolean hasPlayer(String playerName) { - return resultByPlayerName.containsKey(playerName); + Map::putAll); } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index cceb4feb..6cb0a30b 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,5 +1,7 @@ package view; +import util.NumericParser; + import java.util.Arrays; import java.util.List; import java.util.Scanner; @@ -17,7 +19,7 @@ public List readResultLabels() { } public int readLadderHeight() { - return Integer.parseInt(readLine()); + return NumericParser.parse(readLine()); } public String readResultRequest() { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 13433047..83196195 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,10 +1,12 @@ package view; +import domain.BridgeLine; import dto.LadderBuildResponse; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.IntStream; public class OutputView { @@ -19,6 +21,11 @@ public class OutputView { private static final String CELL_FORMAT = "%-" + DISPLAY_CELL_WIDTH + "s"; private static final String RESULT_FORMAT = "%s : %s"; + private static final String VERTICAL_BAR = "|"; + private static final String CONNECTED_LINE = "-----"; + private static final String EMPTY_LINE = " "; + private static final String INDENT = " "; + public void printParticipantPrompt() { System.out.println(PARTICIPANT_PROMPT); } @@ -39,26 +46,23 @@ public void printLadderTitle() { System.out.println(LADDER_RESULT_TITLE); } + public void printNameNotFound() { + System.out.println(NAME_NOT_FOUND_MESSAGE); + } + public void printParticipantNames(List participantNames) { - String alignedNames = participantNames.stream() - .map(this::formatCell) - .collect(Collectors.joining()); - System.out.println(alignedNames); + System.out.println(joinAligned(participantNames)); } public void printResultLabels(List resultLabels) { - String alignedResults = resultLabels.stream() - .map(this::formatCell) - .collect(Collectors.joining()); - System.out.println(alignedResults); + System.out.println(joinAligned(resultLabels)); } public void printBridgeLines(LadderBuildResponse response) { - int totalColumns = response.columnCount(); + int columnCount = response.columnCount(); + List lines = response.lines(); - response.lines().forEach(line -> { - System.out.println(" " + line.drawLineFormat(totalColumns)); - }); + lines.forEach(line -> System.out.println(INDENT + formatBridgeLine(line, columnCount))); } public void printAllResults(Map participantResults) { @@ -75,15 +79,28 @@ public void printSingleResultWithTitle(String resultValue) { printSingleResult(resultValue); } - public void printNameNotFound() { - System.out.println(NAME_NOT_FOUND_MESSAGE); + private String formatBridgeLine(BridgeLine line, int columnCount) { + return IntStream.range(0, columnCount) + .mapToObj(i -> VERTICAL_BAR + bridgeRepresentation(line, i)) + .collect(Collectors.joining()); } - private String formatResult(String name, String result) { - return String.format(RESULT_FORMAT, name, result); + private String bridgeRepresentation(BridgeLine line, int index) { + if (line.isConnectedAt(index)) return CONNECTED_LINE; + return EMPTY_LINE; + } + + private String joinAligned(List values) { + return values.stream() + .map(this::formatCell) + .collect(Collectors.joining()); } private String formatCell(String value) { return String.format(CELL_FORMAT, value); } + + private String formatResult(String name, String result) { + return String.format(RESULT_FORMAT, name, result); + } } diff --git a/src/test/java/domain/BridgeLineTest.java b/src/test/java/domain/BridgeLineTest.java index c44a4108..337f518a 100644 --- a/src/test/java/domain/BridgeLineTest.java +++ b/src/test/java/domain/BridgeLineTest.java @@ -13,56 +13,45 @@ class BridgeLineTest { - @Test - @DisplayName("index 0은 연결되어 있지 않다") - void isConnectedAt_index0_false() { - BridgeLine line = new BridgeLine(List.of(false, true, false)); - assertThat(line.isConnectedAt(0)).isFalse(); - } - - @Test - @DisplayName("index 1은 연결되어 있다") - void isConnectedAt_index1_true() { + @ParameterizedTest(name = "index {0}는 연결 상태가 {1}이다") + @MethodSource("connectionCases") + @DisplayName("isConnectedAt은 연결 여부를 반환한다") + void isConnectedAt_returnsExpected(int index, boolean expected) { BridgeLine line = new BridgeLine(List.of(false, true, false)); - assertThat(line.isConnectedAt(1)).isTrue(); + assertThat(line.isConnectedAt(index)).isEqualTo(expected); } @Test - @DisplayName("index 2는 연결되어 있지 않다") - void isConnectedAt_index2_false() { - BridgeLine line = new BridgeLine(List.of(false, true, false)); - assertThat(line.isConnectedAt(2)).isFalse(); - } - - @ParameterizedTest(name = "연결 리스트 {0}의 크기는 {1}이다") - @MethodSource("widthTestCases") - @DisplayName("width는 연결 리스트의 크기를 반환한다") - void width_returnsCorrectSize(List connections, int expectedWidth) { - BridgeLine line = new BridgeLine(connections); - assertThat(line.width()).isEqualTo(expectedWidth); + @DisplayName("width는 연결 수를 반환한다") + void width_returnsConnectionSize() { + BridgeLine line = new BridgeLine(List.of(true, false, true)); + assertThat(line.width()).isEqualTo(3); } - @Test - @DisplayName("drawLineFormat은 사다리 가로줄을 시각화된 문자열로 반환한다") - void drawLineFormat_returnsVisualRepresentation() { + @ParameterizedTest(name = "위치 {0}에서 다음 위치는 {1}이다") + @MethodSource("nextPositionCases") + @DisplayName("nextPositionFrom은 연결에 따라 위치를 이동한다") + void nextPositionFrom_returnsExpected(int current, int expected) { BridgeLine line = new BridgeLine(List.of(true, false, true)); - String result = line.drawLineFormat(4); - - assertThat(result).isEqualTo("|-----| |-----| "); + assertThat(line.nextPositionFrom(current)).isEqualTo(expected); } - private static Stream connectionTestCases() { + private static Stream connectionCases() { return Stream.of( Arguments.of(0, false), Arguments.of(1, true), - Arguments.of(2, false) + Arguments.of(2, false), + Arguments.of(-1, false), + Arguments.of(3, false) ); } - private static Stream widthTestCases() { + private static Stream nextPositionCases() { return Stream.of( - Arguments.of(List.of(true, false, true), 3), - Arguments.of(List.of(false, false, false, false), 4) + Arguments.of(0, 1), + Arguments.of(1, 0), + Arguments.of(2, 3), + Arguments.of(3, 2) ); } } diff --git a/src/test/java/domain/ParticipantsTest.java b/src/test/java/domain/ParticipantsTest.java new file mode 100644 index 00000000..3ba604e7 --- /dev/null +++ b/src/test/java/domain/ParticipantsTest.java @@ -0,0 +1,35 @@ +package domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ParticipantsTest { + + @Test + @DisplayName("참가자가 없을 경우 예외 발생") + void throwsIfEmptyParticipants() { + List empty = List.of(); + assertThrows(IllegalArgumentException.class, () -> + Participants.of(empty)); + } + + @Test + @DisplayName("이름이 5자를 초과할 경우 예외 발생") + void throwsIfNameTooLong() { + List names = List.of("toolongname"); + assertThrows(IllegalArgumentException.class, () -> + Participants.of(names)); + } + + @Test + @DisplayName("이름으로 예약어 'all'이 포함되면 예외 발생") + void throwsIfContainsReservedKeyword() { + List names = List.of("neo", "all", "brie"); + assertThrows(IllegalArgumentException.class, () -> + Participants.of(names)); + } +} diff --git a/src/test/java/domain/PlayerResultsTest.java b/src/test/java/domain/PlayerResultsTest.java index c3849bed..d22d809a 100644 --- a/src/test/java/domain/PlayerResultsTest.java +++ b/src/test/java/domain/PlayerResultsTest.java @@ -8,14 +8,12 @@ import java.util.Map; import java.util.LinkedHashMap; -import static org.junit.jupiter.api.Assertions.assertThrows; - class PlayerResultsTest { @Test @DisplayName("참가자 이름, 결과, 인덱스 맵으로 결과를 생성한다") void from_createsCorrectMapping() { - List playerNames = List.of("neo", "brown", "brie", "tommy"); + Participants participants = Participants.of(List.of("neo", "brown", "brie", "tommy")); List outcomeLabels = List.of("꽝", "5000", "꽝", "3000"); Map startToEndIndexMap = Map.of( @@ -25,7 +23,7 @@ void from_createsCorrectMapping() { 3, 2 ); - PlayerResults results = PlayerResults.from(playerNames, outcomeLabels, startToEndIndexMap); + PlayerResults results = PlayerResults.from(participants, outcomeLabels, startToEndIndexMap); SoftAssertions softly = new SoftAssertions(); softly.assertThat(results.resultOf("neo")).isEqualTo("5000"); @@ -45,14 +43,4 @@ void from_createsCorrectMapping() { softly.assertThat(results.allResults()).containsExactlyEntriesOf(expected); softly.assertAll(); } - - @Test - @DisplayName("참가자가 없을 경우 예외 발생") - void throwsIfEmptyParticipants() { - List empty = List.of(); - List labels = List.of(); - Map indexMap = Map.of(); - assertThrows(IllegalArgumentException.class, () -> - PlayerResults.from(empty, labels, indexMap)); - } }