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

Step3 (사다리 게임 실행) 리뷰 요청드립니다. #446

Merged
merged 18 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0d1deab
refactor (Ladder) : 피드백에 따라서 Drawing 인터페이스를 생성자 주입
xlffm3 Jun 11, 2020
217b772
test & feat (Direction) : 상수의 좌표 변경 인터페이스 속성 추가
xlffm3 Jun 11, 2020
8bfaead
test & feat (Point & RandomDrawingStrategy) : Point 객체 생성 정적 팩토리 메소드 구현
xlffm3 Jun 11, 2020
e23e9f3
test & feat (InputView & GamePrize) : 게임 실행 결과 객체 생성 예외처리 및 기능 구현
xlffm3 Jun 12, 2020
3ac8194
test & feat (GamePrizesGroup) : 객체 생성 및 예외 발생 테스트와 기능 구현
xlffm3 Jun 12, 2020
3cda61d
test & feat (Point) : 방향에 따라 index가 변화하는 move 기능 구현
xlffm3 Jun 12, 2020
dc1b0de
test & feat (Line) : 한 선의 특정 point를 움직이는 기능 구현
xlffm3 Jun 12, 2020
d09edc8
test & feat (LadderGame) : 반복문 순회하며 게임을 실행하고 그 결과를 Map에 담음
xlffm3 Jun 12, 2020
6c90afb
refactor (LadderGame) : Map<String, String>을 Map<Player, GamePrize>로 변경
xlffm3 Jun 12, 2020
2754c3d
refactor (GameResult) : Map<String, String>에 따른 리팩토링
xlffm3 Jun 12, 2020
dbbcfd6
refactor (RandomDrawingStrategy) : Point 찍는 기능에서의 예외 처리
xlffm3 Jun 12, 2020
362f466
test & refactor (Point 및 PointTest) : 메소드 수정을 위한 테스트 분리
xlffm3 Jun 12, 2020
a13601e
refactor : 코드 전반의 상수 부분 분리
xlffm3 Jun 12, 2020
3d760d1
refactor (OutputView) : 메소드 분리
xlffm3 Jun 12, 2020
e4ab0a6
style : 오타 수정
xlffm3 Jun 12, 2020
8a5b88b
refactor (GameResult) : 잘못된 Key값에 대한 예외 처리 추가
xlffm3 Jun 13, 2020
65b9224
refactor (PlayersGroup) : redundant한 호출 수정
xlffm3 Jun 15, 2020
9ea7bb0
refactor (Point) : 3항 연산자 사용
xlffm3 Jun 15, 2020
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
18 changes: 14 additions & 4 deletions src/main/java/ladder/Application.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
package ladder;

import ladder.domain.Ladder;
import ladder.domain.LadderFactory;
import ladder.domain.PlayersGroup;
import ladder.domain.*;
import ladder.view.InputView;
import ladder.view.OutputView;

public class Application {

public static void main(String[] args) {
PlayersGroup playersGroup = PlayersGroup.of(InputView.inputPlayerNames());
GamePrizesGroup gamePrizesGroup = GamePrizesGroup.of(InputView.inputGamePrizeNames(), playersGroup);

LadderFactory ladderFactory = new LadderFactory(InputView.inputLadderHeight());
Ladder ladder = ladderFactory.buildLadder(playersGroup);
DrawingLineStrategy drawingLineStrategy = new RandomDrawingLineStrategy();
Ladder ladder = ladderFactory.buildLadder(playersGroup, drawingLineStrategy);

LadderGame ladderGame = new LadderGame();
GameResult gameResult = ladderGame.play(playersGroup, ladder, gamePrizesGroup);

OutputView.printPlayerNames(playersGroup);
OutputView.printLadder(ladder);
OutputView.printGamePrizeNames(gamePrizesGroup);

while (true) {
String resultPlayerName = InputView.inputResultPlayerName();
OutputView.printGameResult(gameResult, resultPlayerName);
}
}
}
101 changes: 95 additions & 6 deletions src/main/java/ladder/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,61 @@
## 사다리 게임 구현 기능 목록
## 사다리 게임

### 기능 요구 사항

* 사다리 게임에 참여하는 사람에 이름을 최대5글자까지 부여할 수 있다. 사다리를 출력할 때 사람 이름도 같이 출력한다.
* 사람 이름은 쉼표(,)를 기준으로 구분한다.
* 사람 이름을 5자 기준으로 출력하기 때문에 사다리 폭도 넓어져야 한다.
* 사다리 타기가 정상적으로 동작하려면 라인이 겹치지 않도록 해야 한다.
* |-----|-----| 모양과 같이 가로 라인이 겹치는 경우 어느 방향으로 이동할지 결정할 수 없다.
* 사다리 실행 결과를 출력해야 한다.
* 개인별 이름을 입력하면 개인별 결과를 출력하고, "all"을 입력하면 전체 참여자의 실행 결과를 출력한다.

### 프로그래밍 요구사항

* 자바 8의 스트림과 람다를 적용해 프로그래밍한다.
* 규칙 6: 모든 엔티티를 작게 유지한다.
* 규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.

### 실행 결과
* 위 요구사항에 따라 4명의 사람을 위한 5개 높이 사다리를 만들 경우, 프로그램을 실행한 결과는 다음과 같다.

```
참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)
pobi,honux,crong,jk

실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)
꽝,5000,꽝,3000

최대 사다리 높이는 몇 개인가요?
5

사다리 결과

pobi honux crong jk
|-----| |-----|
| |-----| |
|-----| | |
| |-----| |
|-----| |-----|
꽝 5000 꽝 3000

결과를 보고 싶은 사람은?
pobi

실행 결과

결과를 보고 싶은 사람은?
all

실행 결과
pobi : 꽝
honux : 3000
crong : 꽝
jk : 5000
```

## 기능 구현 목록

### Domain

Expand All @@ -9,19 +66,37 @@

* PlayersGroup
* 게임 참가지 객체의 리스트(List<Player>) 일급 컬렉션
* Player들의 이름 중 중복이 있을 경우 예외 발생
* 참가자 수가 2명 미만일 때도 예외 발생
* 참가자 객체의 이름 목록을 파라미터로 받아 객체 생성
* Player들의 이름 명단(List<String>) 반환
* Player들 수를 반환

* GamePrize
* 게임 실행 결과를 담고 있음
* 이름이 Null이거나 빈 문자열일 경우 예외 발생

* GamePrizes
* List<GamePrize> 일급 컬렉션
* 개수가 게임 참가자와 일치하지 않을 경우 예외 발생

* Point
* 사다리 한 수평 라인의 한 점을 구성하는 객체
* 해당 부분에 값이 존재하는지 아닌지(boolean)를 결정함.
* 난수를 받아 객체 생성 시, 해당 부분에 값이 존재하는지 아닌지를 결정함.
* 사다리 한 수평 라인에서 세로선(|) 위치의 한 점을 구성하는 객체
* 해당 부분의 x축 좌표(Index)와 해당 점에서 갈 수 있는 방향을 가짐.
* Index에 대한 예외 처리(음수값 등)
* Direction은 별도의 객체.
* 처음 점을 찍는 경우 / 마지막 점을 찍는 경우 / 그 외의 경우를 구분해서 객체 생성
* 현재 위치의 Point가 Direction에 따라 이동하면 변화된 좌표(index)를 반환

* Direction
* Left, Right, Stop 3가지로 나뉨.
* 각 상수는 index가 주어지면 해당 인덱스 좌표가 주어지면 방향에 맞게 수정시켜 리턴함.

* Line
* 사다리의 수평 라인 한 줄을 의미하는 객체
* 수평 라인이 존재하는 부분과 아닌 부분을 List<Point>으로 가짐
* 생성된 Line을 구성하는 각 Point들의 정보를 담은 List<Boolean> 반환
* 생성된 Line을 구성하는 각 Point들의 방향 정보를 담은 List<Direction> 반환
* parameter로 한 라인의 특정 point의 index를 받으면 해당 point를 검색해 move시킨 좌표를 반환.

* DrawingLineStrategy
* 선을 그리는 전략 인터페이스
Expand All @@ -34,15 +109,29 @@
* Ladder
* 사다리 2차원 배열의 정보를 List<Line>으로 가지고 있는 일급 컬렉션.
* 플레이어 명단과 사다리 높이를 바탕으로 사다리를 생성함.
* 사다리의 설계도면을 List<List<Boolean>>으로 반환함
* 사다리의 설계도면을 List<List<Direction>>으로 반환함
* 시작 위치가 주어지면 사다리를 타고 그 결과 위치를 반환함.

* LadderGame
* 사다리 타기 게임을 진행하기 위한 객체
* 게임을 진행하고 GameResult를 기록하여 반환함.

* GameResult
* Key(Player) - Value(Prize) Map을 감싸는 일급 컬렉션

### View

* InputView
* 쉼표로 구분되는 게임 참가자(Player) 명단을 입력 받음.
* 사다리 높이를 입력 받음.
* 게임 실행 결과를 입력 받음.
* 게임 결과를 확인하고 싶은 참가자의 이름일 입력 받음.

* OutputView
* Ladder 객체를 바탕으로 사다리의 모습을 그림
* Player 및 실행 결과 이름 목록을 출력함.
* 사람의 입력을 입력 받으면 그에 해당하는 게임 결과를 출력함.

### Application

* Controller
19 changes: 19 additions & 0 deletions src/main/java/ladder/domain/Direction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ladder.domain;

import java.util.function.UnaryOperator;

public enum Direction {
LEFT(index -> index -= Point.NEXT_INDEX),
RIGHT(index -> index += Point.NEXT_INDEX),
DOWN(index -> index);

private final UnaryOperator<Integer> moveIndexByDirection;

private Direction(UnaryOperator<Integer> moveIndexByDirection) {
this.moveIndexByDirection = moveIndexByDirection;
}

public int moveIndexByDirection(int index) {
return this.moveIndexByDirection.apply(index);
}
}
21 changes: 21 additions & 0 deletions src/main/java/ladder/domain/GamePrize.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ladder.domain;

public class GamePrize {

private final String name;

public GamePrize(String name) {
validateName(name);
this.name = name;
}

private void validateName(String name) {
if (name == null || name.isEmpty()) {
throw new LadderBuildingException(LadderBuildingException.INVALID_GAME_PRIZE_NAME);
}
}

public String getName() {
return name;
}
}
37 changes: 37 additions & 0 deletions src/main/java/ladder/domain/GamePrizesGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ladder.domain;

import java.util.List;
import java.util.stream.Collectors;

public class GamePrizesGroup {

private final List<GamePrize> gamePrizes;

private GamePrizesGroup(List<GamePrize> gamePrizes) {
this.gamePrizes = gamePrizes;
}

public static GamePrizesGroup of(List<String> gamePrizeNames, PlayersGroup playersGroup) {
validateGamePrizeCounts(gamePrizeNames, playersGroup);
List<GamePrize> gamePrizes = gamePrizeNames.stream()
.map(GamePrize::new)
.collect(Collectors.toList());
return new GamePrizesGroup(gamePrizes);
}

private static void validateGamePrizeCounts(List<String> gamePrizeNames, PlayersGroup playersGroup) {
if (gamePrizeNames.size() != playersGroup.getPlayerCounts()) {
throw new LadderBuildingException(LadderBuildingException.INVALID_GAME_PRIZE_COUNTS);
}
}

public String getGamePrizeNameByIndex(int index) {
return gamePrizes.get(index).getName();
}

public List<String> getGamePrizeNames() {
return gamePrizes.stream()
.map(GamePrize::getName)
.collect(Collectors.toList());
}
}
22 changes: 22 additions & 0 deletions src/main/java/ladder/domain/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ladder.domain;

import java.util.Map;
import java.util.Set;

public class GameResult {
private static final String INVALID_PLAYER_NAME_MESSAGE = "해당 이름의 참가자는 존재하지 않습니다.";

private final Map<String, String> gameResult;

public GameResult(Map<String, String> gameResult) {
this.gameResult = gameResult;
}

public Set<String> getKeySet() {
return gameResult.keySet();
}

public String getGamePrizeNameByPlayerName(String playerName) {
return gameResult.getOrDefault(playerName, INVALID_PLAYER_NAME_MESSAGE);
}
}
15 changes: 11 additions & 4 deletions src/main/java/ladder/domain/Ladder.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,32 @@
import java.util.stream.Stream;

public class Ladder {
private static final DrawingLineStrategy drawingLineStrategy = new RandomDrawingLineStrategy();

private final List<Line> lines;

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

public static Ladder buildLadder(PlayersGroup playersGroup, int ladderHeight) {
public static Ladder buildLadder(PlayersGroup playersGroup, int ladderHeight,
DrawingLineStrategy drawingLineStrategy) {
int playerCounts = playersGroup.getPlayerCounts();
List<Line> ladder = Stream.generate(() -> Line.drawLine(playerCounts, drawingLineStrategy))
.limit(ladderHeight)
.collect(Collectors.toList());
return new Ladder(ladder);
}

public List<List<Boolean>> getLadderBluePrint() {
public int climb(int index) {
for (Line line : lines) {
index = line.movePointOnLine(index);
}
return index;
}

public List<List<Direction>> getLadderBluePrint() {
return lines.stream()
.map(Line::getPointPositions)
.map(Line::getPointDirections)
.collect(Collectors.toList());
}

Expand Down
6 changes: 5 additions & 1 deletion src/main/java/ladder/domain/LadderBuildingException.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

public class LadderBuildingException extends RuntimeException {
public static final String INVALID_PLAYER_NAME = "게임 참가자의 이름은 빈 문자열이 아닌 1 ~ 5자리의 문자만 가능합니다.";
public static final String INVALID_PLAYER_COUNTS = "게임 참가자는 1명 이상이어야 합니다.";
public static final String INVALID_PLAYER_COUNTS = "게임 참가자는 2명 이상이어야 합니다.";
public static final String INVALID_LADDER_HEIGHT = "사다리 높이는 1 이상이어야 합니다.";
public static final String DUPLICATE_PLAYER_NAMES = "게임 참가자의 이름은 중복될 수 없습니다.";
public static final String INVALID_GAME_PRIZE_NAME = "게임 실행 결과는 빈문자열이 아니어야 합니다.";
public static final String INVALID_GAME_PRIZE_COUNTS = "게임 실행 결과의 개수는 참가자의 수와 일치해야 합니다.";
public static final String NEGATIVE_INDEX = "점의 좌표는 음수값이 될 수 없습니다.";

public LadderBuildingException() {
super();
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/ladder/domain/LadderFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ private void validateLadderHeight(int ladderHeight) {
}
}

public Ladder buildLadder(PlayersGroup playersGroup) {
return Ladder.buildLadder(playersGroup, ladderHeight);
public Ladder buildLadder(PlayersGroup playersGroup, DrawingLineStrategy drawingLineStrategy) {
return Ladder.buildLadder(playersGroup, ladderHeight, drawingLineStrategy);
}
}
20 changes: 20 additions & 0 deletions src/main/java/ladder/domain/LadderGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ladder.domain;

import java.util.HashMap;
import java.util.Map;

public class LadderGame {
private static final int LOOP_ZERO = 0;

public GameResult play(PlayersGroup playersGroup, Ladder ladder, GamePrizesGroup gamePrizesGroup) {
Map<String, String> gameResult = new HashMap<>();
int playerCounts = playersGroup.getPlayerCounts();
for (int i = LOOP_ZERO; i < playerCounts; i++) {
String currentPlayerName = playersGroup.getPlayerNameByIndex(i);
int destinationIndex = ladder.climb(i);
String gamePrizeName = gamePrizesGroup.getGamePrizeNameByIndex(destinationIndex);
gameResult.put(currentPlayerName, gamePrizeName);
}
return new GameResult(gameResult);
}
}
12 changes: 8 additions & 4 deletions src/main/java/ladder/domain/Line.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.stream.Collectors;

public class Line {
private static final int MINIMUM_PLAYER_COUNTS = 1;

private final List<Point> points;

Expand All @@ -19,14 +18,19 @@ public static Line drawLine(int playerCounts, DrawingLineStrategy drawingLineStr
}

private static void validatePlayerCounts(int playerCounts) {
if (playerCounts < MINIMUM_PLAYER_COUNTS) {
if (playerCounts < PlayersGroup.MINIMUM_PLAYER_COUNTS) {
throw new LadderBuildingException(LadderBuildingException.INVALID_PLAYER_COUNTS);
}
}

public List<Boolean> getPointPositions() {
public int movePointOnLine(int index) {
Point point = points.get(index);
return point.moveByDirection();
}

public List<Direction> getPointDirections() {
return points.stream()
.map(Point::getIsExisting)
.map(Point::getDirection)
.collect(Collectors.toList());
}
}
Loading