Skip to content
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

[2단계 - 사다리 게임] 에버(손채영) 미션 제출합니다. #408

Merged
merged 72 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
2df96df
docs: 결과 관련 요구사항 목록 추가
helenason Feb 27, 2024
2fd81f2
test: 결과 도메인 생성 테스트 코드 작성
helenason Feb 27, 2024
e93f2fc
feat: 결과 도메인 생성 및 검증
helenason Feb 27, 2024
13613fc
test: 에러메시지 수정 및 매직넘버 상수값 재사용
helenason Feb 27, 2024
c38f3fe
test: 결과들 도메인 관련 테스트 코드 작성
helenason Feb 27, 2024
1e0448c
feat: 결과들 도메인 생성
helenason Feb 27, 2024
fb9b37c
test: 게임 도메인 테스트 코드 작성
helenason Feb 27, 2024
99daf5b
feat: 사다리 게임 결과 매칭 로직 추가
helenason Feb 27, 2024
4911b06
feat: 결과 및 타겟 입력받기
helenason Feb 27, 2024
5a5628d
refactor: 재사용 변수를 인스턴스 변수로 묶어 사용
helenason Feb 27, 2024
c9f0918
refactor: Results 객체 가공 로직 추가를 위해 정적 팩토리 메서드 구조 채택
helenason Feb 27, 2024
e0ff233
feat: 매핑할 결과 입력받는 로직 컨트롤러에 추가
helenason Feb 27, 2024
2102a7b
refactor: Results 객체 Game 클래스 내 인스턴스 변수로 관리
helenason Feb 27, 2024
2c3c901
feat: 사다리 결과에 매칭될 결과 출력 로직 추가
helenason Feb 27, 2024
facd52d
feat: 사다리 결과 출력 시 불필요한 개행 제거
helenason Feb 27, 2024
beeef31
feat: 참여자의 이름을 통해 Member 객체 찾기
helenason Feb 28, 2024
f910203
refactor: GameResult 클래스 도입하여 게임 결과 관리
helenason Feb 28, 2024
1dd91dd
feat: 게임 실행 결과 출력 로직 추가
helenason Feb 28, 2024
ce49a3b
feat: 해당 이름을 가진 참여자가 존재하는지 체크하는 메서드 추가
helenason Feb 28, 2024
803e167
feat: 결과 도출 및 출력 로직 컨트롤러에 합치기
helenason Feb 28, 2024
ed1f1a1
fix: 비정상적인 게임 결과 도출하는 메서드 수정
helenason Feb 28, 2024
9748681
test: 사다리 게임 테스트 케이스 추가
helenason Feb 28, 2024
8d4519c
refactor: 게임 결과 출력 시 입력받은 사용자 순서로 출력하기 위해 LinkedHashMap 사용
helenason Feb 28, 2024
3a47aee
feat: 게임 결과 최대 50번까지만 출력 가능하도록 변경
helenason Feb 28, 2024
c9cd9f5
refactor: Name 클래스 MemberName으로 이름 변경
helenason Feb 28, 2024
d47f231
refactor: Lines 클래스 Ladder 클래스로 이름 변경
helenason Feb 28, 2024
1111cf5
refactor: Height 클래스 제거 후 Ladder 클래스에서 높이 검증
helenason Feb 28, 2024
fe27c4e
refactor: 문자열 파싱 역할 도메인에서 분리
helenason Feb 28, 2024
3c16742
refactor: name으로 Member 객체 만드는 함수 stream 사용하도록 수정
helenason Feb 28, 2024
d2e8627
refactor: Results 클래스 validate 메서드 호출 위치 변경
helenason Feb 28, 2024
04f84d0
refactor: Ladder 객체 생성 메서드 파라미터 순서 변경
helenason Feb 28, 2024
5593b5e
refactor: Line 객체 생성 메서드 이름 변경
helenason Feb 28, 2024
21c7749
refactor: Game 클래스 결과 도출 메서드 역할 분리
helenason Feb 28, 2024
d16073e
refactor: 참여자 이름으로 존재하는 참여자인지 확인 후 이름을 반환하도록 수정
helenason Feb 29, 2024
88bac51
refactor: 들여쓰기 줄이기 위해 for문 stream 사용하도록 변경
helenason Feb 29, 2024
de2aae2
test: 대상 객체 관련 테스트 코드 작성
helenason Feb 29, 2024
8150934
feat: 결과를 보고 싶은 대상 참여자 도메인 생성
helenason Feb 29, 2024
43de917
refactor: 결과를 보고 싶은 대상에 대한 검증 로직 Target 클래스로 분리 후 의존 코드 수정
helenason Feb 29, 2024
846cf65
refactor: Target 클래스 ResultTarget 으로 이름 변경
helenason Feb 29, 2024
de79f99
refactor: while문 깊이 줄이기 위해 메서드 분리 및 역할 조정
helenason Feb 29, 2024
35fcabd
refactor: 게임 결과를 개인 및 전체 참여자의 경우 모두 하나의 메서드 내에서 출력
helenason Feb 29, 2024
383377c
fix: 결과 출력 대상 입력 메서드 에러 핸들링 과정 추가
helenason Feb 29, 2024
0f55255
refactor: target 입력 받고 객체 생성하는 메서드명 수정
helenason Feb 29, 2024
0ef6218
test: StringParser 클래스 테스트 코드 작성
helenason Feb 29, 2024
7118a36
test: 연결상태 관련 테스트 코드 작성
helenason Feb 29, 2024
b2b9cc5
test: 도메인 클래스 내 상수 재사용 및 에러 메시지 체크 로직 구체화
helenason Feb 29, 2024
aabdef1
test: 상수 재사용 및 에러 메시지 출력 검증 로직 구체화
helenason Feb 29, 2024
1bd1c79
test: 리스트 길이 검증 로직 hasSize() 사용하도록 수정
helenason Feb 29, 2024
57cd2a7
test: Results 객체 생성 성공 로직 검증 구체화
helenason Feb 29, 2024
855c812
test: Result 클래스 테스트 객체 생성 성공 및 에러메시지 검증 로직 구체화
helenason Feb 29, 2024
b0da8e1
refactor: 결과 출력 메서드 private으로 변경 및 gameResult 전달 시 Member 대신 String 타…
helenason Feb 29, 2024
b8f8ba6
feat: 참여자 이름이 될 수 없는 형태 검증 로직 추가
helenason Mar 1, 2024
742a584
refactor: 매직넘버 상수 처리 및 ResultTarget의 변수 이름 변경
helenason Mar 1, 2024
0c1b40c
docs: 구현 완료된 기능 요구사항 체크 표시
helenason Mar 1, 2024
af3d268
refactor: 불필요한 인스턴스 변수 제거
helenason Mar 3, 2024
cbae669
refactor: 이름 동일 여부 비교하는 역할 Member 객체로 전달
helenason Mar 3, 2024
010a7d7
refactor: 가독성을 위해 부정문 줄이기
helenason Mar 3, 2024
3258ff9
refactor: 명확한 의미 전달을 위해 두 개의 변수로 분리
helenason Mar 3, 2024
ff4ef9f
refactor: 메서드 위치 기준 통일
helenason Mar 3, 2024
6f957d0
feat: 게임 결과 전달을 위한 클래스 생성
helenason Mar 3, 2024
8abe223
refactor: DTO를 사용하여 게임 결과 반환 및 원활한 테스트를 위해 equals 재정의
helenason Mar 3, 2024
9d1d287
refactor: 가독성을 위해 메서드 추출
helenason Mar 3, 2024
2c189ae
refactor: 문자열 분리 및 타입 변환 메서드 호출 역할 컨트롤러에서 뷰로 이전
helenason Mar 3, 2024
8b03eb1
refactor: do-while문을 사용하여 코드의 중복 제거
helenason Mar 3, 2024
199bc66
refactor: 테스트를 위해 열어두었던 상수의 접근제어자 닫기 및 관련 테스트 코드 수정
helenason Mar 3, 2024
9089a1e
refactor: 모든 멤버의 결과 반환하는 메서드 불필요한 코드 정리
helenason Mar 3, 2024
29840d2
refactor: getName 메서드 재사용하도록 수정
helenason Mar 3, 2024
006cf63
refactor: lines -> ladder 메서드명 수정 보완
helenason Mar 3, 2024
ad0a4ef
refactor: 일급컬렉션 수정 불가능하도록 반환
helenason Mar 3, 2024
b57d70d
refactor: 구분자 상수화 및 스캐너 생성자 선언
helenason Mar 3, 2024
0ee1469
refactor: 메서드 순서 변경 및 stringBuilder 사용하도록 수정
helenason Mar 3, 2024
209d116
refactor: 매칭될 결과 클래스 이름 변경
helenason Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,38 @@
### 사다리
- [x] 사다리 가로줄의 생성 여부는 랜덤으로 결정된다.
- [x] 사다리 가로줄 라인이 겹치지 않도록 해야 한다.
- [x] `|-----|-----|` 와 같은 모양은 허용하지 않는다.
- [x] `|-----|-----|` 와 같은 모양은 허용하지 않는다.
- [x] 사다리 높이는 1부터 20까지 가능하다.

### 게임
- [x] 참여자와 결과를 사다리를 통해 매칭한다.
- [x] 높이 횟수만큼 아래의 동작을 반복한다.
- [x] 가로줄이 있는 경우 해당 방향으로 한 칸 이동한다.
- [x] 가로줄이 없는 경우 밑으로 한 칸 이동한다.

### 결과
- [x] 결과는 1글자부터 5글자까지 부여할 수 있다.
- [x] 결과는 쉼표(,)를 기준으로 구분한다.
- [x] 결과의 수는 참여자의 수와 일치하다.

### 입력
- [x] 참여할 사람의 이름을 쉼표(,)로 구분하여 입력받는다.
- [x] 참여할 사람의 이름은 "all"이 될 수 없다.
- [x] 최대 사다리 높이를 입력받는다.
- [x] 잘못된 형태로 입력된 경우 다시 입력받는다.
- [x] 참여자가 매칭될 결과를 입력받는다.
- [x] 결과를 보고 싶은 사람을 입력받는다. (개인 이름 또는 all)
- [x] 모든 입력에 대해 잘못된 형태로 입력된 경우 다시 입력받는다.

### 출력
- [x] 사람 이름이 출력되고, 그 다음줄부터 사다리가 출력된다.
- [x] 사람 이름이 5자 미만인 경우 이름의 마지막 글자가 사다리 세로줄의 바로 왼쪽 열에 위치하게 한다.
- [x] 사람 이름이 5자인 경우 이름의 마지막 글자가 사다리 세로줄과 같은 열에 위치하게 한다.
- [x] 사다리 각 행의 가장 왼쪽에는 4칸의 공백이 있다.
- [x] 사다리 세로줄 간의 간격은 5칸으로 한다.
- [x] 개인별 실행 결과를 출력한다.
- [x] 전체 참여자의 실행 결과를 출력한다.
- [x] 입력받은 참여자 순서대로 출력한다.

### 컨트롤러
- [x] "all"을 통해 전체 참여자가 출력되면 게임을 종료한다.
- [x] 실행 결과 출력은 최대 50번까지만 허용한다.
71 changes: 54 additions & 17 deletions src/main/java/controller/GameController.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package controller;

import domain.Game;
import domain.Lines;
import domain.GameResult;
import domain.Ladder;
import domain.Members;
import domain.Result;
import domain.Results;
import domain.StringParser;
import domain.ResultTarget;
import error.ErrorHandler;
import java.util.List;
import java.util.Map;
import strategy.RandomConnectionStrategy;
import view.InputView;
import view.OutputView;

public class GameController {

public static final int MAX_GAME_COUNT = 50;

private final InputView inputView;
private final OutputView outputView;
private final ErrorHandler errorHandler;
Expand All @@ -23,26 +31,55 @@ public GameController(InputView inputView, OutputView outputView, ErrorHandler e

public void run() {

Members members = makeMembers();
Members members = errorHandler.readUntilNoError(this::makeMembers);

Results results = errorHandler.readUntilNoError(() -> makeResults(members));

Ladder ladder = errorHandler.readUntilNoError(() -> makeLadder(members));

Game game = new Game(members, ladder, results);
outputView.printLadder(game);

Lines lines = makeLines(members);
GameResult gameResult = game.matchResult();

Game game = new Game(members, lines);
outputView.printResult(game);
manageResult(members, gameResult);
}

private Members makeMembers() {
return errorHandler.readUntilNoError(() -> {
String rawNames = inputView.readNames();
return Members.from(rawNames);
});
}

private Lines makeLines(Members members) {
return errorHandler.readUntilNoError(() -> {
String rawHeight = inputView.readHeight();
int height = StringParser.stringToInt(rawHeight);
return Lines.of(members.getCount(), height, new RandomConnectionStrategy());
});
String rawNames = inputView.readNames();
List<String> names = StringParser.splitByDelimiter(rawNames, ",");
yxxnghwan marked this conversation as resolved.
Show resolved Hide resolved
return Members.from(names);
}

private Results makeResults(Members members) {
String rawResults = inputView.readResults();
List<String> results = StringParser.splitByDelimiter(rawResults, ",");
return Results.of(results, members.getCount());
}

private Ladder makeLadder(Members members) {
String rawHeight = inputView.readHeight();
int height = StringParser.stringToInt(rawHeight);
return Ladder.of(height, members.getCount(), new RandomConnectionStrategy());
}

private ResultTarget makeResultTarget(Members members) {
String rawTargetName = inputView.readTarget();
return ResultTarget.of(rawTargetName, members.getMembers());
}

private void manageResult(Members members, GameResult gameResult) {
ResultTarget resultTarget = showResult(members, gameResult);
int count = MAX_GAME_COUNT;
while (count-- > 0 && !resultTarget.isAllMembers()) {
resultTarget = showResult(members, gameResult);
}
}

private ResultTarget showResult(Members members, GameResult gameResult) {
ResultTarget resultTarget = errorHandler.readUntilNoError(() -> makeResultTarget(members));
Map<String, Result> result = gameResult.getResultByTarget(resultTarget);
outputView.printResult(result);
return resultTarget;
}
yxxnghwan marked this conversation as resolved.
Show resolved Hide resolved
}
64 changes: 59 additions & 5 deletions src/main/java/domain/Game.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,74 @@
package domain;

import java.util.List;

public class Game {

private final Members members;
private final Lines lines;
private final Ladder ladder;
private final Results results;
private final GameResult gameResult;

public Game(Members members, Lines lines) {
public Game(Members members, Ladder ladder, Results results) {
this.members = members;
this.lines = lines;
this.ladder = ladder;
this.results = results;
this.gameResult = new GameResult();
}

public GameResult matchResult() {
for (Member member : members.getMembers()) {
int index = members.findPositionOfMember(member);
index = tryMoveAll(index);
yxxnghwan marked this conversation as resolved.
Show resolved Hide resolved
Result result = results.findResultByPosition(index);
gameResult.addGameResult(member, result);
}
return gameResult;
}

private int tryMoveAll(int index) {
for (Line line : ladder.getLines()) {
List<Connection> connections = line.getConnections();
index = tryMove(connections, index);
}
return index;
}

private int tryMove(List<Connection> connections, int index) {
if (canMoveLeft(connections, index)) {
return index - 1;
}
if (canMoveRight(connections, index)) {
return index + 1;
}
return index;
}

private boolean canMoveLeft(List<Connection> connections, int index) {
if (index <= 0) {
return false;
}
Connection left = connections.get(index - 1);
return left.equals(Connection.CONNECTED);
}

private boolean canMoveRight(List<Connection> connections, int index) {
if (index > connections.size() - 1) {
return false;
}
Connection right = connections.get(index);
return right.equals(Connection.CONNECTED);
}

public Members getMembers() {
return members;
}

public Lines getLines() {
return lines;
public Ladder getLines() {
return ladder;
}

public Results getResults() {
return results;
}
}
48 changes: 48 additions & 0 deletions src/main/java/domain/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package domain;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

public class GameResult {

private final HashMap<Member, Result> gameResult;

public GameResult() {
this.gameResult = new LinkedHashMap<>();
}

public void addGameResult(Member member, Result result) {
gameResult.put(member, result);
}

public Map<String, Result> getResultByTarget(ResultTarget target) {
if (target.isAllMembers()) {
return getResultOfAllMember();
}
String memberName = target.getValue();
Result result = getResultByMemberName(target.getValue());
return new LinkedHashMap<>() {{
put(memberName, result);
}};
}

private Result getResultByMemberName(String name) {
return gameResult.entrySet().stream()
.filter(memberResult -> memberResult.getKey().getName().equals(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("해당 이름을 가진 참여자가 없습니다."))
.getValue();
}

private Map<String, Result> getResultOfAllMember() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메소드의 시그니처만 보았을 때 String으로 되어있는 key가 어떤걸 의미하는지 알 수 없을 것 같아요.
Map으로 응답해도 괜찮을까요? 어떻게 응답을 주면 의미가 명확해질까요?

Copy link
Member Author

@helenason helenason Mar 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 구현하면서도 Member로 반환할지 String으로 반환할지와,
Map으로 반환할지 새로운 객체로 감싸 반환할지 고민을 하였습니다.

고민의 결과, 객체로 포장할 경우 출력 과정에서 불필요하게 추가되는 로직이 많을 것이라고 판단하였습니다.
또한 포장하지 않을 경우의 테스트 작성이 더욱 원활하여 위와 같이 반환하도록 결정하였습니다.

하지만 지금 다시 생각해보니, 불명확한 의미와 유지보수의 어려움이 문제가 될 것 같습니다.

따라서 오직 도메인 간 전달만을 위한 DTO 클래스를 새로 생성하고,
어려워진 테스트를 해결하기 위해서 Member와 MemberName의 equals를 재정의하였습니다!

Map<String, Result> resolvedResult = new LinkedHashMap<>();
for (Entry<Member, Result> memberResult : gameResult.entrySet()) {
String memberName = memberResult.getKey().getName();
Result result = memberResult.getValue();
resolvedResult.put(memberName, result);
}
return resolvedResult;
}
}
24 changes: 0 additions & 24 deletions src/main/java/domain/Height.java

This file was deleted.

37 changes: 37 additions & 0 deletions src/main/java/domain/Ladder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import strategy.ConnectionStrategy;

public class Ladder {

public static final int MIN_HEIGHT = 1;
public static final int MAX_HEIGHT = 20;

private final List<Line> lines;

private Ladder(List<Line> lines) {
validateHeight(lines.size());
this.lines = lines;
}

private void validateHeight(int height) {
if (height < MIN_HEIGHT || height > MAX_HEIGHT) {
throw new IllegalArgumentException(MIN_HEIGHT + " 이상 " + MAX_HEIGHT + " 이하의 숫자를 입력해 주세요.");
}
}
yxxnghwan marked this conversation as resolved.
Show resolved Hide resolved

public static Ladder of(int height, int memberCount, ConnectionStrategy connectionStrategy) {
List<Line> lines = new ArrayList<>();
for (int i = 0; i < height; i++) {
lines.add(Line.of(memberCount, connectionStrategy));
}
return new Ladder(lines);
}

public List<Line> getLines() {
return Collections.unmodifiableList(lines);
}
}
2 changes: 1 addition & 1 deletion src/main/java/domain/Line.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ private Line(List<Connection> connections) {
this.connections = connections;
}

public static Line from(int memberCount, ConnectionStrategy connectionStrategy) {
public static Line of(int memberCount, ConnectionStrategy connectionStrategy) {
List<Connection> connections = new ArrayList<>();
connections.add(connectionStrategy.generateConnection());

Expand Down
27 changes: 0 additions & 27 deletions src/main/java/domain/Lines.java

This file was deleted.

4 changes: 2 additions & 2 deletions src/main/java/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

public class Member {

private final Name name;
private final MemberName name;

public Member(String rawName) {
this.name = new Name(rawName);
this.name = new MemberName(rawName);
}

public String getName() {
Expand Down
Loading