-
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 all 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.ladder.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,99 @@ | ||
package controller; | ||
|
||
import model.*; | ||
import model.Prize.Prizes; | ||
import model.ladder.Ladder; | ||
import model.ladder.LinkGenerator; | ||
import model.user.User; | ||
import model.user.Users; | ||
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; | ||
private final InputValidator inputValidator = new InputValidator(); | ||
private final InputView inputView = new InputView(); | ||
private final OutputView outputView = new OutputView(); | ||
|
||
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 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 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 Users getUsers() { | ||
outputView.printUsernameInputMessage(); | ||
try { | ||
return Users.from(readAndSplitInput()); | ||
} catch (Exception e) { | ||
outputView.printErrorMessage(e.getMessage()); | ||
return getUsers(); | ||
} | ||
} | ||
|
||
private Prizes getPrizes(int participantCount) { | ||
outputView.printPrizeInputMessage(); | ||
try { | ||
return Prizes.of(readAndSplitInput(), participantCount); | ||
} catch (Exception e) { | ||
outputView.printErrorMessage(e.getMessage()); | ||
return getPrizes(participantCount); | ||
} | ||
} | ||
|
||
private List<String> readAndSplitInput() { | ||
String inputString = inputView.getString(); | ||
inputValidator.validateInputStringPattern(inputString); | ||
return StringSplitter.splitByComma(inputString); | ||
} | ||
|
||
private int getHeight() { | ||
outputView.printHeightInputMessage(); | ||
return inputView.getInt(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package model; | ||
|
||
import model.Prize.Prize; | ||
import model.Prize.Prizes; | ||
import model.ladder.Ladder; | ||
import model.user.User; | ||
import model.user.Users; | ||
|
||
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; | ||
|
||
private 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,8 @@ | ||
package model; | ||
|
||
import model.ladder.HorizontalLine; | ||
|
||
@FunctionalInterface | ||
public interface MoveCondition { | ||
boolean canMove(int index, HorizontalLine line); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package model; | ||
|
||
import model.ladder.HorizontalLine; | ||
import model.ladder.Link; | ||
|
||
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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package model.Prize; | ||
|
||
public class Prize { | ||
|
||
private final String prizeName; | ||
private final int position; | ||
|
||
public Prize(String prizeName, int position) { | ||
this.prizeName = prizeName; | ||
this.position = position; | ||
} | ||
|
||
public String getPrizeName() { | ||
return prizeName; | ||
} | ||
|
||
public int getPosition() { | ||
return position; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package model.Prize; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.stream.IntStream; | ||
|
||
public class Prizes { | ||
|
||
private final List<Prize> prizes; | ||
|
||
private Prizes(List<Prize> prizes) { | ||
this.prizes = prizes; | ||
} | ||
|
||
public static Prizes of(List<String> prizeList, int participantCount) { | ||
validatePrizeCount(prizeList, participantCount); | ||
return new Prizes( | ||
IntStream.range(0, prizeList.size()) | ||
.mapToObj(position -> new Prize(prizeList.get(position), position)) | ||
.toList() | ||
); | ||
} | ||
|
||
private static void validatePrizeCount(List<String> prizeList, int participantCount) { | ||
if (prizeList.size() != participantCount) { | ||
throw new IllegalArgumentException("참가자 인원만큼 실행결과를 입력해야 합니다!"); | ||
} | ||
} | ||
|
||
public int size() { | ||
return prizes.size(); | ||
} | ||
|
||
public Prize getPrizeAtPosition(int position) { | ||
return prizes.stream() | ||
.filter(prize -> prize.getPosition() == position) | ||
.findFirst() | ||
.orElseThrow(() -> new IllegalArgumentException("해당하는 위치의 결과가 존재하지 않습니다!")); | ||
} | ||
|
||
public List<Prize> getPrizes() { | ||
return Collections.unmodifiableList(prizes); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package model.ladder; | ||
|
||
import model.MovingDirection; | ||
|
||
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); | ||
} | ||
} |
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.
구경하던 중에 혹시 높이는 어디서 검증하는지 궁금합니다!