-
Notifications
You must be signed in to change notification settings - Fork 60
[그리디] 김태우 사다리 미션 제출합니다. #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 27 commits
ec68a0e
ab34af1
b5d6346
6425261
b3e4558
e0819f4
8421f74
7bd2af2
94e6bde
af22352
ecac217
1514d48
54ddd4d
99aa533
d8ce27f
cbd131e
5d6dfca
fb63a02
a7b8c81
ffe782d
fb624ea
851055c
6a287b1
5d7fbc9
2ff5f21
6fa0b0d
bf10258
5326b93
9317192
339963b
5570d3e
9507b6b
f5e4d9d
370c1c8
b4a8608
ab293fe
fdb8c20
4f5bc72
4db9002
e23bafd
7e4d366
ff4df5e
33e9c62
e023d6d
ff8a8db
bf23911
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # 사다리 게임 | ||
|
|
||
| ## 💡 기능 요구사항 | ||
|
|
||
| 1. 사다리 게임에 참여하는 사람에 이름을 최대 5글자까지 부여할 수 있다. | ||
| 2. 사다리를 출력할 때 사람 이름도 같이 출력한다. | ||
| 3. 사람 이름은 쉼표(,)를 기준으로 구분한다. | ||
| 4. 개인별 이름을 입력하면 개인별 결과를 출력한다. | ||
| 5. `"all"`을 입력하면 전체 참여자의 실행 결과를 출력한다. | ||
|
|
||
| --- | ||
|
|
||
| ## 클래스 소개 | ||
|
|
||
| | 클래스 | 역할| | ||
| |----------------------------|---| | ||
| | `Name` | 이름 단위를 표현 | | ||
| | `Names` | 이름들의 일급 컬렉션 | | ||
| | `Players` | 참가자 이름 목록 관리 및 중복 제거 | | ||
| | `Results` | 실행 결과 목록 관리 | | ||
| | `Height` | 사다리 높이 검증 및 저장 | | ||
| | `Connect` | `CONNECTED` / `DISCONNECTED` 상태 Enum | | ||
| | `Point` | 연결 여부(`Connect`) 표현 | | ||
| | `Line` | 한 줄의 연결 상태(`Point`) 관리 | | ||
| | `Ladder` | 사다리 전체를 구성 (여러 Line의 집합) | | ||
| | `LadderGame` | 이동 로직 및 결과 매핑 수행 | | ||
| | `LadderController` | 전체 게임 실행 흐름 제어 | | ||
| | `InputView` / `OutputView` | 입출력 담당 | | ||
| | `Main` | 프로그램 실행의 진입점 담당 | | ||
|
|
||
| --- | ||
|
|
||
| ## 프로그램 실행 흐름 | ||
|
|
||
| 1. **입력 단계** | ||
| - 참여자 이름, 실행 결과, 사다리 높이를 순서대로 입력받는다. | ||
| - 이름과 결과의 개수가 다르면 예외를 발생시킨다. | ||
| - 이름이 5글자를 초과하면 예외를 발생한다. | ||
| - 사다리 높이가 양수가 아니거나 숫자가 아닌 입력이 들어오면 예외를 발생시킨다. | ||
|
|
||
| --- | ||
|
|
||
| 2. **사다리 생성 단계** | ||
| - 입력받은 높이와 인원 수를 기반으로 `Ladder` 객체를 생성한다. | ||
| - 내부적으로 `Line.create()`를 호출하여 각 Line의 연결 상태를 무작위로 생성한다. | ||
| - 높이가 0인 경우에도 1줄의 사다리가 생성된다. | ||
|
|
||
| --- | ||
|
|
||
| 3. **결과 조회 단계** | ||
| - 사용자가 이름을 입력하면 해당 참가자의 결과를 출력한다. | ||
| - `"all"`을 입력하면 전체 참가자의 결과를 한 번에 출력한다. | ||
|
|
||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import controller.LadderController; | ||
| import domain.Height; | ||
| import domain.LadderGame; | ||
| import domain.Players; | ||
| import domain.Results; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| public class Main { | ||
| public static void main(String[] args) { | ||
| InputView inputView = new InputView(); | ||
| OutputView outputView = new OutputView(); | ||
| LadderController controller = new LadderController(inputView, outputView); | ||
|
|
||
| Players players = controller.inputPlayers(); | ||
| Results results = controller.inputResults(players); | ||
| Height height = controller.inputHeight(); | ||
|
|
||
| LadderGame game = controller.startLadderGame(height, players, results); | ||
| controller.showResult(game, players); | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| package controller; | ||
|
|
||
| import domain.Height; | ||
| import domain.Ladder; | ||
| import domain.LadderGame; | ||
| import domain.Players; | ||
| import domain.Results; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| import java.util.InputMismatchException; | ||
| import java.util.Random; | ||
|
|
||
| public class LadderController { | ||
| private final InputView inputView; | ||
| private final OutputView outputView; | ||
|
|
||
| public LadderController(InputView inputView, OutputView outputView) { | ||
| this.inputView = inputView; | ||
| this.outputView = outputView; | ||
| } | ||
|
|
||
| public Players inputPlayers() { | ||
| outputView.printAskPlayers(); | ||
| while (true) { | ||
| try { | ||
| return new Players(inputView.readString()); | ||
| } catch (IllegalArgumentException e) { | ||
| outputView.printException(e); | ||
| outputView.printRetryInputMessage(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public Results inputResults(Players players) { | ||
| outputView.printAskResults(); | ||
| while (true) { | ||
| try { | ||
| Results results = new Results(inputView.readString()); | ||
| LadderGame.validatePlayerAndResultCount(players, results); | ||
| return results; | ||
| } catch (IllegalArgumentException e) { | ||
| outputView.printException(e); | ||
| outputView.printRetryInputMessage(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public Height inputHeight() { | ||
| outputView.printAskHeight(); | ||
| while (true) { | ||
| try { | ||
| return new Height(inputView.readInt()); | ||
| } catch (InputMismatchException | IllegalArgumentException e) { | ||
| outputView.printException(e); | ||
| outputView.printRetryInputMessage(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public LadderGame startLadderGame(Height height, Players players, Results results) { | ||
| Ladder ladder = new Ladder(height, players.size(), new Random()); | ||
| LadderGame game = new LadderGame(ladder, players, results); | ||
| outputView.printLadderResultTitle(); | ||
| outputView.printLadder(ladder | ||
| , players.getPlayers().getValues() | ||
| , results.getResults().getValues()); | ||
| return game; | ||
| } | ||
|
|
||
| public void showResult(LadderGame game, Players players) { | ||
| boolean run = true; | ||
| while (run) { | ||
| outputView.printAskResultByPlayer(); | ||
| String name = inputView.readString(); | ||
| run = validateRun(game, players, name); | ||
| } | ||
| } | ||
|
|
||
| private boolean validateRun(LadderGame game, Players players, String name) { | ||
| if (name.equals("all")) { | ||
| outputView.printAllResults(game.findAll(), players); | ||
| return false; | ||
| } | ||
| String result = game.findResultByPlayer(name); | ||
| outputView.printSingleResult(result); | ||
| return true; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package domain; | ||
|
|
||
| public enum Connect { | ||
| CONNECTED(true), | ||
| DISCONNECTED(false); | ||
|
|
||
| private final boolean value; | ||
|
|
||
| Connect(boolean value){ | ||
| this.value = value; | ||
| } | ||
|
|
||
| public boolean isConnected(){ | ||
| return value; | ||
| } | ||
|
|
||
| public static Connect from(boolean value){ | ||
| return value ? CONNECTED : DISCONNECTED; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package domain; | ||
|
|
||
| public class Height { | ||
| private static final int HEIGHT_MIN = 0; | ||
| private final int height; | ||
|
|
||
| public Height(int height) { | ||
| if (height < HEIGHT_MIN) { | ||
| throw new IllegalArgumentException("사다리 높이는 양수여야 합니다."); | ||
| } | ||
|
|
||
| this.height = height; | ||
| } | ||
|
||
|
|
||
| public int getHeight() { | ||
| return height; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return String.valueOf(height); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object obj) { | ||
| if (this == obj) return true; | ||
| if (!(obj instanceof Height)) return false; | ||
| Height other = (Height) obj; | ||
| return height == other.getHeight(); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Integer.hashCode(height); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Random; | ||
|
|
||
| public class Ladder { | ||
| private final List<Line> lines; | ||
|
|
||
| public Ladder(Height height, int playerCount, Random random) { | ||
| List<Line> temp = new ArrayList<>(); | ||
|
|
||
| for (int i = 0; i < height.getHeight(); i++) { | ||
| temp.add(Line.create(playerCount, random)); | ||
| } | ||
| if (height.getHeight() == 0) { | ||
| temp.add(Line.create(playerCount, random)); | ||
| } | ||
| this.lines = List.copyOf(temp); | ||
| } | ||
|
|
||
| public List<Line> getLines() { | ||
| return lines; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package domain; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class LadderGame { | ||
| private final Ladder ladder; | ||
| private final Players players; | ||
| private final Results results; | ||
|
|
||
| public LadderGame(Ladder ladder, Players players, Results results) { | ||
| this.ladder = ladder; | ||
| this.players = players; | ||
| this.results = results; | ||
| } | ||
|
|
||
| public static void validatePlayerAndResultCount(Players players, Results results) { | ||
| if (players.size() != results.size()) { | ||
| throw new IllegalArgumentException("참가자와 결과 수는 같아야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private int move(int position) { | ||
| for (Line line : ladder.getLines()) { | ||
| position += calculateNextPosition(line, position); | ||
| } | ||
| return position; | ||
| } | ||
|
|
||
| private int calculateNextPosition(Line line, int position) { | ||
| if (line.validateMoveRight(position).isConnected()) return 1; | ||
| if (line.validateMoveLeft(position).isConnected()) return -1; | ||
| return 0; | ||
| } | ||
|
|
||
| public String findResultByPlayer(String name) { | ||
| List<Name> player = players.getPlayers().getValues(); | ||
| List<Name> result = results.getResults().getValues(); | ||
|
|
||
| int index = player.indexOf(new Name(name)); | ||
| index = move(index); | ||
| return result.get(index).value(); | ||
| } | ||
|
|
||
| public Map<String, String> findAll() { | ||
| Map<String, String> map = new HashMap<>(); | ||
| List<Name> player = players.getPlayers().getValues(); | ||
| for (Name name : player) { | ||
| map.put(name.value(), findResultByPlayer(name.value())); | ||
| } | ||
| return map; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Random; | ||
|
|
||
| public class Line { | ||
| private final List<Point> points; | ||
|
|
||
| private Line(List<Point> points) { | ||
| this.points = List.copyOf(points); | ||
| } | ||
|
|
||
| public static Line create(int playerCount, Random random) { | ||
| List<Point> points = new ArrayList<>(); | ||
| Connect prev = Connect.DISCONNECTED; | ||
|
|
||
| for (int i = 0; i < playerCount - 1; i++) { | ||
| Connect next = Connect.from(random.nextBoolean()); | ||
| next = checkPrev(prev, next); | ||
| points.add(new Point(next)); | ||
| prev = next; | ||
| } | ||
| return new Line(points); | ||
| } | ||
|
|
||
| private static Connect checkPrev(Connect prev, Connect next) { | ||
| if (prev.isConnected()) { | ||
| return Connect.DISCONNECTED; | ||
| } | ||
| return next; | ||
| } | ||
|
|
||
| public Connect validateMoveRight(int index) { | ||
| if (index >= points.size()) return Connect.DISCONNECTED; | ||
| return points.get(index).point(); | ||
| } | ||
|
|
||
| public Connect validateMoveLeft(int index) { | ||
| if (index == 0) return Connect.DISCONNECTED; | ||
| return points.get(index - 1).point(); | ||
| } | ||
|
||
|
|
||
| public List<Point> getPoints() { | ||
| return points; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package domain; | ||
|
|
||
| public class Name { | ||
| private final String value; | ||
|
|
||
| public Name(String value) { | ||
| if (value == null || value.trim().isEmpty()) { | ||
| throw new IllegalArgumentException("비어 있을 수 없습니다."); | ||
| } | ||
| this.value = value.trim(); | ||
| } | ||
|
|
||
| public String value() { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object obj) { | ||
| if (this == obj) return true; | ||
| if (!(obj instanceof Name)) return false; | ||
| Name name = (Name) obj; | ||
| return value.equals(name.value); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return value.hashCode(); | ||
| } | ||
| } | ||
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 Comment
사실 이번 PR에서 가장 신경이 쓰이는 부분은 이쪽인데요.
enum은 여러 프로그래밍 언어에서 제공하는 객체 정의 방식인데요.
이를 이용해서 java에서는 정말 기발한 방법으로 좋은 코드를 짤 수 있습니다.
단지 true false 밸류를 저장하기엔 너무 아까워요!
사실 어떤 리뷰를 드려야될지 모르겠어서 이렇게 아쉬움만 표현했는데요.
NEXT STEP 학습 테스트를 진행해보셨다면, 아마 다른 코멘트를 반영하시다보면 아이디어가 떠오를 것이라 생각합니다.
꼭 생각나지 않으셔도 좋습니다. 다음 코멘트 때 제가 제안을 드려볼게요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
말씀 주신 힌트 기반으로 방향성을 잡아봤습니다!
"나 지금 N번째야" 라고 Line에게 물어보면 → Line이 이동해야 할 방향을 판단하고,
실제 이동은 각 상태(enum)가 담당하도록 책임을 분리했습니다.
이렇게 나누니 Line은 판단만 / Connect는 실제 이동만 담당하게 되어
역할이 더 명확해지고 유지보수도 쉬워진 것 같습니다.
제가 선택한 이 방향도 충분히 올바른 해결 방식 중 하나일까요?
다음 리뷰 때 추가로 조언 주시면 감사하겠습니다