-
Notifications
You must be signed in to change notification settings - Fork 39
[그리디] 이창희 사다리 미션 제출합니다! #45
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 12 commits
044397e
7aa8ae4
a85bc27
ede5b59
5256e6e
ff59238
ddf7e13
522bc1c
ba64d6f
5aca5d6
32d7b66
02e6f39
935313a
2a8ede3
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,97 @@ | ||
# 사다리 타기 🪜 | ||
> 네이버의 사다리 타기 게임과 같이 콘솔 환경에서 사다리 게임을 진행할 수 있는 프로그램 | ||
|
||
--- | ||
|
||
## 1단계 - 사다리 출력 | ||
|
||
### 📝 기능 요구사항 | ||
✅ 사다리는 4x4 크기로 고정되고, 연결 여부는 랜덤으로 결정한다. | ||
|
||
✅ 사다리 타기가 정상적으로 동작하려면 라인이 겹치지 않도록 해야 한다. | ||
|
||
### 💻 구현 전략 | ||
|
||
1. 나의 `오른쪽` 사다리와 연결 여부를 나타내는 `Link enum클래스` | ||
➡️ `LINKED` / `UNLINKED` 두 가지 타입을 갖는다. | ||
|
||
|
||
2. 사다리의 수평 구조물을 의미하는 `HorizontalLine 클래스` | ||
➡️ 필드로 Link 클래스의 리스트를 갖는다. | ||
➡️ 가로로 사다리를 건널 수 있을 지 없을 지를 Link 타입으로 알 수 있다. | ||
|
||
|
||
3. 전체 사다리를 의미하는 `Ladder 클래스` | ||
➡️ 필드로 HorizontalLine 클래스의 리스트를 갖는다. | ||
➡️ 세로가 아닌 가로로 사다리의 정보를 갖는다. | ||
|
||
|
||
<br> | ||
|
||
--- | ||
## 2단계 - 사다리 생성 | ||
|
||
### 📝 기능 요구사항 | ||
✅ 사다리는 크기를 입력 받아 생성할 수 있다. | ||
|
||
### 💻 구현 전략 | ||
|
||
1. `InputView 클래스`를 통해 사다리 높이와 넓이를 입력받는다. | ||
➡️ 컨트롤러에서 `InputView 클래스`의 메서드를 호출해 높이와 넓이를 입력받는다. | ||
|
||
|
||
2. `Ladder 클래스`의 `정적 팩토리 메서드 of`에 입력받은 크기 정보를 넘겨주어 사다리를 생성한다. | ||
|
||
|
||
--- | ||
|
||
## 3단계 - 사다리 타기 | ||
### 📝 기능 요구사항 | ||
✅ 사다리의 시작 지점과 도착 지점을 출력한다. | ||
|
||
### 💻 구현 전략 | ||
|
||
|
||
1. `MovingDirection enum 클래스`를 통해 사다리의 이동 전략을 관리한다. | ||
➡️ Enum 클래스에서 함수형 인터페이스를 통해 이동 방향별(RIGHT / LEFT / STAY) 전략을 람다식으로 설정한다. | ||
|
||
|
||
2. 사다리 게임 진행 | ||
➡️ Ladder 클래스의 `ride 메서드`를 통해 시작 위치를 전달하면 결과 위치를 반환한다. | ||
➡️ HorizontalLine 클래스의 `move 메서드`를 통해 현재 위치에서 움직일 수 있는지 판단하고 위치를 옮긴다. | ||
|
||
<br> | ||
|
||
--- | ||
|
||
## 4단계 - 게임 실행 | ||
### 📝 기능 요구사항 | ||
✅ 사다리 게임에 참여하는 사람에 이름을 최대 5글자까지 부여할 수 있다. 사다리를 출력할 때 사람 이름도 같이 출력한다. | ||
|
||
✅ 사람 이름은 쉼표(,)를 기준으로 구분한다. | ||
|
||
✅ 개인별 이름을 입력하면 개인별 결과를 출력하고, "all"을 입력하면 전체 참여자의 실행 결과를 출력한다. | ||
|
||
### 💻 구현 전략 | ||
|
||
1. 게임 참가자의 이름과 사다리 위치 정보를 갖는 `User 클래스`와 이러한 User 클래스를 리스트로 갖는 `Users 클래스`를 통해 게임에 참가하는 유저를 관리 | ||
|
||
|
||
2. 게임 결과의 이름과 사다리 위치 정보를 갖는 `Prize 클래스`와 이러한 Prize 클래스를 리스트로 갖는 `Prizes 클래스`를 통해 게임의 결과정보를 관리 | ||
|
||
|
||
3. 입력 포맷에 대한 검사는 view단에서 빠르게 처리하고, 그 외 도메인과 관련이 깊은 입력의 유효성 검사는 각 도메인 클래스에서 처리한다. | ||
➡️ 이름,결과 입력 포멧 검사 - `InputValidator 클래스` | ||
➡️ 이름 중복, 길이, 유효한 이름인지 검사 - `Users 클래스` | ||
➡️ 실행 결과 입력 개수 검사 - `Prizes 클래스` | ||
|
||
|
||
2. 사다리 게임의 결과 집계 | ||
➡️ `LadderGameResult 클래스` - <User, Prize> 를 key-value로 하는 결과MAP을 생성한다. | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import controller.LadderGameController; | ||
import model.RandomLinkGenerator; | ||
|
||
public class Application { | ||
public static void main(String[] args) { | ||
LadderGameController ladderGameController = new LadderGameController(new RandomLinkGenerator()); | ||
ladderGameController.run(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package controller; | ||
|
||
import model.*; | ||
import utils.StringSplitter; | ||
import view.InputValidator; | ||
import view.InputView; | ||
import view.OutputView; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import static utils.Constants.FINAL_QUERY_KEYWORD; | ||
|
||
public class LadderGameController { | ||
|
||
private final LinkGenerator linkGenerator; | ||
|
||
public LadderGameController(LinkGenerator linkGenerator) { | ||
this.linkGenerator = linkGenerator; | ||
} | ||
|
||
public void run() { | ||
Users users = getUsers(); | ||
Prizes prizes = getPrizes(users.size()); | ||
int height = getHeight(); | ||
|
||
Ladder ladder = Ladder.of(height, users.size(), linkGenerator); | ||
OutputView.printLadder(ladder, users, prizes); | ||
|
||
LadderGameResult ladderGameResult = LadderGameResult.of(users, prizes, ladder); | ||
handleUserResultQuery(ladderGameResult, users); | ||
} | ||
|
||
private static void handleUserResultQuery(LadderGameResult ladderGameResult, Users users) { | ||
|
||
Optional<User> findUser = getFindUsername(users); | ||
|
||
if (findUser.isEmpty()) { | ||
OutputView.printAllResults(ladderGameResult); | ||
return; | ||
} | ||
|
||
OutputView.printPrize(ladderGameResult.findByUser(findUser.get())); | ||
handleUserResultQuery(ladderGameResult, users); | ||
} | ||
|
||
private static Optional<User> getFindUsername(Users users) { | ||
OutputView.printQueryInputMessage(); | ||
String findUsername = InputView.getString(); | ||
if (findUsername.equals(FINAL_QUERY_KEYWORD)) { | ||
return Optional.empty(); | ||
} | ||
try { | ||
return Optional.of(users.findByUsername(findUsername)); | ||
} catch (Exception e) { | ||
OutputView.printErrorMessage(e.getMessage()); | ||
return getFindUsername(users); | ||
} | ||
} | ||
|
||
private static Users getUsers() { | ||
OutputView.printUsernameInputMessage(); | ||
try { | ||
return Users.from(readAndSplitInput()); | ||
} catch (Exception e) { | ||
OutputView.printErrorMessage(e.getMessage()); | ||
return getUsers(); | ||
} | ||
} | ||
|
||
private static Prizes getPrizes(int participantCount) { | ||
OutputView.printPrizeInputMessage(); | ||
try { | ||
return Prizes.of(readAndSplitInput(), participantCount); | ||
} catch (Exception e) { | ||
OutputView.printErrorMessage(e.getMessage()); | ||
return getPrizes(participantCount); | ||
} | ||
} | ||
|
||
private static List<String> readAndSplitInput() { | ||
String inputString = InputView.getString(); | ||
InputValidator.validateInputStringPattern(inputString); | ||
return StringSplitter.splitByComma(inputString); | ||
} | ||
|
||
private static int getHeight() { | ||
OutputView.printHeightInputMessage(); | ||
return InputView.getInt(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package model; | ||
|
||
import java.util.Collections; | ||
import java.util.EnumSet; | ||
import java.util.List; | ||
|
||
public class HorizontalLine { | ||
|
||
private final List<Link> points; | ||
private final EnumSet<MovingDirection> movingDirections; | ||
|
||
public HorizontalLine(List<Link> points) { | ||
this.points = points; | ||
this.movingDirections = EnumSet.allOf(MovingDirection.class); | ||
} | ||
|
||
public int move(int position) { | ||
return movingDirections.stream() | ||
.filter(movingDirection -> movingDirection.canMove(position, this)) | ||
.findFirst() | ||
.map(movingDirection -> movingDirection.move(position)) | ||
.orElse(position); | ||
} | ||
|
||
public int size() { | ||
return points.size(); | ||
} | ||
|
||
public Link getLink(int index) { | ||
return points.get(index); | ||
} | ||
|
||
public List<Link> getPoints() { | ||
return Collections.unmodifiableList(points); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package model; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.stream.IntStream; | ||
|
||
public class Ladder { | ||
|
||
private final List<HorizontalLine> lines; | ||
|
||
private Ladder(List<HorizontalLine> horizontalLines) { | ||
this.lines = horizontalLines; | ||
} | ||
|
||
public static Ladder of(int height, int participantCount, LinkGenerator generator) { | ||
return new Ladder(getRandomLines(height, participantCount, generator)); | ||
} | ||
|
||
private static List<HorizontalLine> getRandomLines(int height, int participantCount, LinkGenerator generator) { | ||
return IntStream.range(0, height) | ||
.mapToObj(position -> LineGenerator.generateHorizontalLine(participantCount, generator)) | ||
.toList(); | ||
} | ||
|
||
public Prize ride(User user, Prizes prizes) { | ||
int position = user.getPosition(); | ||
for (HorizontalLine line : lines) { | ||
position = line.move(position); | ||
} | ||
return prizes.getPrizeAtPosition(position); | ||
} | ||
|
||
public List<HorizontalLine> getLines() { | ||
return Collections.unmodifiableList(lines); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package model; | ||
|
||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
|
||
public class LadderGameResult { | ||
|
||
private final Map<User, Prize> gameResults; | ||
|
||
public LadderGameResult(Map<User, Prize> gameResults) { | ||
this.gameResults = gameResults; | ||
} | ||
|
||
public static LadderGameResult of(Users users, Prizes prizes, Ladder ladder) { | ||
return new LadderGameResult(calculateResult(users, prizes, ladder)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. of는 객체 생성에 초점이 있다고 생각하는데.. static 메서드인 calculateResult를 이용해 계산 결과 값을 전달하면 단일 책임 원칙을 위반할 수 있을 것 같아요! 혹시 어떻게 생각하시나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제 생각을 말씀 드리면, 저는 이 미션을 설계 당시 그렇기 때문에 클래스 단위로 볼때 서희님이 말씀해 주신 정팩메의 SRP 위반 여부는 메서드 단위의 책임을 뜻하는것 같아요 저는 각 메서드의 책임을 이렇게 생각하고 코드를 짰어요
각각 책임이 나뉘어 있다고 생각하는데 어떻게 생각하시나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이해가 되었어요! 설명을 듣고 보니 적절히 나뉜 것 같다는 생각이 듭니당 |
||
} | ||
|
||
private static Map<User, Prize> calculateResult(Users users, Prizes prizes, Ladder ladder) { | ||
return users.getUsers().stream() | ||
.collect(Collectors.toMap( | ||
user -> user, | ||
user -> ladder.ride(user, prizes) | ||
)); | ||
} | ||
|
||
public Prize findByUser(User user) { | ||
return Optional.ofNullable(gameResults.get(user)) | ||
.orElseThrow(() -> new IllegalArgumentException("결과를 조회하려는 유저가 존재하지 않습니다!")); | ||
} | ||
|
||
public Map<User, Prize> getGameResults() { | ||
return Collections.unmodifiableMap(gameResults); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package model; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class LineGenerator { | ||
|
||
public static HorizontalLine generateHorizontalLine(int width, LinkGenerator generator) { | ||
Link prev = Link.UNLINKED; | ||
List<Link> link = new ArrayList<>(); | ||
|
||
for (int i = 0; i < width - 1; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. width-1 대신 MAX_LINK_INDEX 이런 식으로 상수로 바꾼다면 더 직관적일 것 같습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. width 값은 그래도 파라미터의 변수명을 직관적으로 바꾸면은 이해가 더욱 쉬울것 같군요! |
||
prev = addLink(prev, generator.generate(), link); | ||
} | ||
link.add(Link.UNLINKED); | ||
return new HorizontalLine(link); | ||
} | ||
|
||
private static Link addLink(Link prev, Link now, List<Link> link) { | ||
if (prev.isLinked()) { | ||
now = Link.UNLINKED; | ||
} | ||
link.add(now); | ||
return now; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package model; | ||
|
||
public enum Link { | ||
LINKED, | ||
UNLINKED; | ||
|
||
public static Link from(boolean linked) { | ||
if (linked) { | ||
return LINKED; | ||
} | ||
return UNLINKED; | ||
} | ||
|
||
public boolean isLinked() { | ||
return this == LINKED; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package model; | ||
|
||
public interface LinkGenerator { | ||
Link generate(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package model; | ||
|
||
@FunctionalInterface | ||
public interface MoveCondition { | ||
boolean canMove(int index, HorizontalLine line); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package model; | ||
|
||
public enum MovingDirection { | ||
|
||
RIGHT(1, (index, line) -> index < line.size() && line.getLink(index) == Link.LINKED), | ||
LEFT(-1, (index, line) -> index > 0 && line.getLink(index-1) == Link.LINKED), | ||
STAY(0, (index, line) -> true); | ||
|
||
|
||
private final int direction; | ||
private final MoveCondition condition; | ||
|
||
MovingDirection(int direction, MoveCondition condition) { | ||
this.direction = direction; | ||
this.condition = condition; | ||
} | ||
|
||
public boolean canMove(int index, HorizontalLine line) { | ||
return condition.canMove(index, line); | ||
} | ||
|
||
public int move(int index) { | ||
return index + direction; | ||
} | ||
} |
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.
이 부분은 정팩메를 쓰니까 private으로 바꿔도 괜찮을 것 같습니다..!