Skip to content

Commit ea81da0

Browse files
authored
고생하셨습니다.
🎉 PR 머지 완료! 🎉
1 parent fa13b03 commit ea81da0

34 files changed

+1082
-0
lines changed

README.md

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# 사다리 타기 🪜
2+
> 네이버의 사다리 타기 게임과 같이 콘솔 환경에서 사다리 게임을 진행할 수 있는 프로그램
3+
4+
---
5+
6+
## 1단계 - 사다리 출력
7+
8+
### 📝 기능 요구사항
9+
✅ 사다리는 4x4 크기로 고정되고, 연결 여부는 랜덤으로 결정한다.
10+
11+
✅ 사다리 타기가 정상적으로 동작하려면 라인이 겹치지 않도록 해야 한다.
12+
13+
### 💻 구현 전략
14+
15+
1. 나의 `오른쪽` 사다리와 연결 여부를 나타내는 `Link enum클래스`
16+
➡️ `LINKED` / `UNLINKED` 두 가지 타입을 갖는다.
17+
18+
19+
2. 사다리의 수평 구조물을 의미하는 `HorizontalLine 클래스`
20+
➡️ 필드로 Link 클래스의 리스트를 갖는다.
21+
➡️ 가로로 사다리를 건널 수 있을 지 없을 지를 Link 타입으로 알 수 있다.
22+
23+
24+
3. 전체 사다리를 의미하는 `Ladder 클래스`
25+
➡️ 필드로 HorizontalLine 클래스의 리스트를 갖는다.
26+
➡️ 세로가 아닌 가로로 사다리의 정보를 갖는다.
27+
28+
29+
<br>
30+
31+
---
32+
## 2단계 - 사다리 생성
33+
34+
### 📝 기능 요구사항
35+
✅ 사다리는 크기를 입력 받아 생성할 수 있다.
36+
37+
### 💻 구현 전략
38+
39+
1. `InputView 클래스`를 통해 사다리 높이와 넓이를 입력받는다.
40+
➡️ 컨트롤러에서 `InputView 클래스`의 메서드를 호출해 높이와 넓이를 입력받는다.
41+
42+
43+
2. `Ladder 클래스``정적 팩토리 메서드 of`에 입력받은 크기 정보를 넘겨주어 사다리를 생성한다.
44+
45+
46+
---
47+
48+
## 3단계 - 사다리 타기
49+
### 📝 기능 요구사항
50+
✅ 사다리의 시작 지점과 도착 지점을 출력한다.
51+
52+
### 💻 구현 전략
53+
54+
55+
1. `MovingDirection enum 클래스`를 통해 사다리의 이동 전략을 관리한다.
56+
➡️ Enum 클래스에서 함수형 인터페이스를 통해 이동 방향별(RIGHT / LEFT / STAY) 전략을 람다식으로 설정한다.
57+
58+
59+
2. 사다리 게임 진행
60+
➡️ Ladder 클래스의 `ride 메서드`를 통해 시작 위치를 전달하면 결과 위치를 반환한다.
61+
➡️ HorizontalLine 클래스의 `move 메서드`를 통해 현재 위치에서 움직일 수 있는지 판단하고 위치를 옮긴다.
62+
63+
<br>
64+
65+
---
66+
67+
## 4단계 - 게임 실행
68+
### 📝 기능 요구사항
69+
✅ 사다리 게임에 참여하는 사람에 이름을 최대 5글자까지 부여할 수 있다. 사다리를 출력할 때 사람 이름도 같이 출력한다.
70+
71+
✅ 사람 이름은 쉼표(,)를 기준으로 구분한다.
72+
73+
✅ 개인별 이름을 입력하면 개인별 결과를 출력하고, "all"을 입력하면 전체 참여자의 실행 결과를 출력한다.
74+
75+
### 💻 구현 전략
76+
77+
1. 게임 참가자의 이름과 사다리 위치 정보를 갖는 `User 클래스`와 이러한 User 클래스를 리스트로 갖는 `Users 클래스`를 통해 게임에 참가하는 유저를 관리
78+
79+
80+
2. 게임 결과의 이름과 사다리 위치 정보를 갖는 `Prize 클래스`와 이러한 Prize 클래스를 리스트로 갖는 `Prizes 클래스`를 통해 게임의 결과정보를 관리
81+
82+
83+
3. 입력 포맷에 대한 검사는 view단에서 빠르게 처리하고, 그 외 도메인과 관련이 깊은 입력의 유효성 검사는 각 도메인 클래스에서 처리한다.
84+
➡️ 이름,결과 입력 포멧 검사 - `InputValidator 클래스`
85+
➡️ 이름 중복, 길이, 유효한 이름인지 검사 - `Users 클래스`
86+
➡️ 실행 결과 입력 개수 검사 - `Prizes 클래스`
87+
88+
89+
2. 사다리 게임의 결과 집계
90+
➡️ `LadderGameResult 클래스` - <User, Prize> 를 key-value로 하는 결과MAP을 생성한다.
91+
92+
93+
94+
95+
96+
97+

src/main/java/Application.java

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import controller.LadderGameController;
2+
import model.ladder.RandomLinkGenerator;
3+
4+
public class Application {
5+
public static void main(String[] args) {
6+
LadderGameController ladderGameController = new LadderGameController(new RandomLinkGenerator());
7+
ladderGameController.run();
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package controller;
2+
3+
import model.*;
4+
import model.Prize.Prizes;
5+
import model.ladder.Ladder;
6+
import model.ladder.LinkGenerator;
7+
import model.user.User;
8+
import model.user.Users;
9+
import utils.StringSplitter;
10+
import view.InputValidator;
11+
import view.InputView;
12+
import view.OutputView;
13+
14+
import java.util.List;
15+
import java.util.Optional;
16+
17+
import static utils.Constants.FINAL_QUERY_KEYWORD;
18+
19+
public class LadderGameController {
20+
21+
private final LinkGenerator linkGenerator;
22+
private final InputValidator inputValidator = new InputValidator();
23+
private final InputView inputView = new InputView();
24+
private final OutputView outputView = new OutputView();
25+
26+
public LadderGameController(LinkGenerator linkGenerator) {
27+
this.linkGenerator = linkGenerator;
28+
}
29+
30+
public void run() {
31+
Users users = getUsers();
32+
Prizes prizes = getPrizes(users.size());
33+
int height = getHeight();
34+
35+
Ladder ladder = Ladder.of(height, users.size(), linkGenerator);
36+
outputView.printLadder(ladder, users, prizes);
37+
38+
LadderGameResult ladderGameResult = LadderGameResult.of(users, prizes, ladder);
39+
handleUserResultQuery(ladderGameResult, users);
40+
}
41+
42+
private void handleUserResultQuery(LadderGameResult ladderGameResult, Users users) {
43+
44+
Optional<User> findUser = getFindUsername(users);
45+
46+
if (findUser.isEmpty()) {
47+
outputView.printAllResults(ladderGameResult);
48+
return;
49+
}
50+
51+
outputView.printPrize(ladderGameResult.findByUser(findUser.get()));
52+
handleUserResultQuery(ladderGameResult, users);
53+
}
54+
55+
private Optional<User> getFindUsername(Users users) {
56+
outputView.printQueryInputMessage();
57+
String findUsername = inputView.getString();
58+
if (findUsername.equals(FINAL_QUERY_KEYWORD)) {
59+
return Optional.empty();
60+
}
61+
try {
62+
return Optional.of(users.findByUsername(findUsername));
63+
} catch (Exception e) {
64+
outputView.printErrorMessage(e.getMessage());
65+
return getFindUsername(users);
66+
}
67+
}
68+
69+
private Users getUsers() {
70+
outputView.printUsernameInputMessage();
71+
try {
72+
return Users.from(readAndSplitInput());
73+
} catch (Exception e) {
74+
outputView.printErrorMessage(e.getMessage());
75+
return getUsers();
76+
}
77+
}
78+
79+
private Prizes getPrizes(int participantCount) {
80+
outputView.printPrizeInputMessage();
81+
try {
82+
return Prizes.of(readAndSplitInput(), participantCount);
83+
} catch (Exception e) {
84+
outputView.printErrorMessage(e.getMessage());
85+
return getPrizes(participantCount);
86+
}
87+
}
88+
89+
private List<String> readAndSplitInput() {
90+
String inputString = inputView.getString();
91+
inputValidator.validateInputStringPattern(inputString);
92+
return StringSplitter.splitByComma(inputString);
93+
}
94+
95+
private int getHeight() {
96+
outputView.printHeightInputMessage();
97+
return inputView.getInt();
98+
}
99+
}
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package model;
2+
3+
import model.Prize.Prize;
4+
import model.Prize.Prizes;
5+
import model.ladder.Ladder;
6+
import model.user.User;
7+
import model.user.Users;
8+
9+
import java.util.Collections;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
import java.util.stream.Collectors;
13+
14+
public class LadderGameResult {
15+
16+
private final Map<User, Prize> gameResults;
17+
18+
private LadderGameResult(Map<User, Prize> gameResults) {
19+
this.gameResults = gameResults;
20+
}
21+
22+
public static LadderGameResult of(Users users, Prizes prizes, Ladder ladder) {
23+
return new LadderGameResult(calculateResult(users, prizes, ladder));
24+
}
25+
26+
private static Map<User, Prize> calculateResult(Users users, Prizes prizes, Ladder ladder) {
27+
return users.getUsers().stream()
28+
.collect(Collectors.toMap(
29+
user -> user,
30+
user -> ladder.ride(user, prizes)
31+
));
32+
}
33+
34+
public Prize findByUser(User user) {
35+
return Optional.ofNullable(gameResults.get(user))
36+
.orElseThrow(() -> new IllegalArgumentException("결과를 조회하려는 유저가 존재하지 않습니다!"));
37+
}
38+
39+
public Map<User, Prize> getGameResults() {
40+
return Collections.unmodifiableMap(gameResults);
41+
}
42+
}
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package model;
2+
3+
import model.ladder.HorizontalLine;
4+
5+
@FunctionalInterface
6+
public interface MoveCondition {
7+
boolean canMove(int index, HorizontalLine line);
8+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package model;
2+
3+
import model.ladder.HorizontalLine;
4+
import model.ladder.Link;
5+
6+
public enum MovingDirection {
7+
8+
RIGHT(1, (index, line) -> index < line.size() && line.getLink(index) == Link.LINKED),
9+
LEFT(-1, (index, line) -> index > 0 && line.getLink(index-1) == Link.LINKED),
10+
STAY(0, (index, line) -> true);
11+
12+
13+
private final int direction;
14+
private final MoveCondition condition;
15+
16+
MovingDirection(int direction, MoveCondition condition) {
17+
this.direction = direction;
18+
this.condition = condition;
19+
}
20+
21+
public boolean canMove(int index, HorizontalLine line) {
22+
return condition.canMove(index, line);
23+
}
24+
25+
public int move(int index) {
26+
return index + direction;
27+
}
28+
}

src/main/java/model/Prize/Prize.java

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package model.Prize;
2+
3+
public class Prize {
4+
5+
private final String prizeName;
6+
private final int position;
7+
8+
public Prize(String prizeName, int position) {
9+
this.prizeName = prizeName;
10+
this.position = position;
11+
}
12+
13+
public String getPrizeName() {
14+
return prizeName;
15+
}
16+
17+
public int getPosition() {
18+
return position;
19+
}
20+
}

src/main/java/model/Prize/Prizes.java

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package model.Prize;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
import java.util.stream.IntStream;
6+
7+
public class Prizes {
8+
9+
private final List<Prize> prizes;
10+
11+
private Prizes(List<Prize> prizes) {
12+
this.prizes = prizes;
13+
}
14+
15+
public static Prizes of(List<String> prizeList, int participantCount) {
16+
validatePrizeCount(prizeList, participantCount);
17+
return new Prizes(
18+
IntStream.range(0, prizeList.size())
19+
.mapToObj(position -> new Prize(prizeList.get(position), position))
20+
.toList()
21+
);
22+
}
23+
24+
private static void validatePrizeCount(List<String> prizeList, int participantCount) {
25+
if (prizeList.size() != participantCount) {
26+
throw new IllegalArgumentException("참가자 인원만큼 실행결과를 입력해야 합니다!");
27+
}
28+
}
29+
30+
public int size() {
31+
return prizes.size();
32+
}
33+
34+
public Prize getPrizeAtPosition(int position) {
35+
return prizes.stream()
36+
.filter(prize -> prize.getPosition() == position)
37+
.findFirst()
38+
.orElseThrow(() -> new IllegalArgumentException("해당하는 위치의 결과가 존재하지 않습니다!"));
39+
}
40+
41+
public List<Prize> getPrizes() {
42+
return Collections.unmodifiableList(prizes);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package model.ladder;
2+
3+
import model.MovingDirection;
4+
5+
import java.util.Collections;
6+
import java.util.EnumSet;
7+
import java.util.List;
8+
9+
public class HorizontalLine {
10+
11+
private final List<Link> points;
12+
private final EnumSet<MovingDirection> movingDirections;
13+
14+
public HorizontalLine(List<Link> points) {
15+
this.points = points;
16+
this.movingDirections = EnumSet.allOf(MovingDirection.class);
17+
}
18+
19+
public int move(int position) {
20+
return movingDirections.stream()
21+
.filter(movingDirection -> movingDirection.canMove(position, this))
22+
.findFirst()
23+
.map(movingDirection -> movingDirection.move(position))
24+
.orElse(position);
25+
}
26+
27+
public int size() {
28+
return points.size();
29+
}
30+
31+
public Link getLink(int index) {
32+
return points.get(index);
33+
}
34+
35+
public List<Link> getPoints() {
36+
return Collections.unmodifiableList(points);
37+
}
38+
}

0 commit comments

Comments
 (0)