Skip to content

[사다리 미션] 정상희 미션 제출합니다. #19

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

Open
wants to merge 54 commits into
base: sangheejeong
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
1196afa
[FEAT] Ladder & Line 클래스 생성
SANGHEEJEONG Oct 24, 2024
8b96693
[FEAT] Line 생성 메서드 작성 (1단계)
SANGHEEJEONG Oct 24, 2024
f20be9f
[FEAT] LineView Enum 클래스 생성 (1단계)
SANGHEEJEONG Oct 24, 2024
19995ef
[REFACTOR] domain과 view 분리 (1단계)
SANGHEEJEONG Oct 24, 2024
49d105c
[FEAT] Ladder 생성 메서드 작성 (1단계)
SANGHEEJEONG Oct 24, 2024
090cb0f
[FEAT] OutputView 메서드 작성 및 Main 구현 (1단계)
SANGHEEJEONG Oct 24, 2024
06a00a7
[FEAT] InputView 메서드 작성 및 Main 구현 (2단계)
SANGHEEJEONG Oct 25, 2024
3e47a2a
[FEAT] 사다리 방향 확인 메서드 작성 (3단계)
SANGHEEJEONG Oct 26, 2024
07fe975
[FEAT] 사다리 게임 실행 메서드 작성 (3단계)
SANGHEEJEONG Oct 26, 2024
2265787
[FEAT] 사다리 게임 실행 메서드 수정 (3단계)
SANGHEEJEONG Oct 26, 2024
2fef17c
[FIX] 사다리 방향 결정 인덱스 오류 수정(3단계)
SANGHEEJEONG Oct 26, 2024
f7ab094
[FEAT] 4단계 입력 메서드 작성 (4단계)
SANGHEEJEONG Oct 26, 2024
dbbb328
[FEAT] Players 클래스 생성 (4단계)
SANGHEEJEONG Oct 26, 2024
65d58d2
[FEAT] GameResult 클래스 생성 및 OutputView 메서드 작성 (4단계)
SANGHEEJEONG Oct 26, 2024
56b093f
[FEAT] main 작성 (4단계)
SANGHEEJEONG Oct 26, 2024
5c6b21e
[REFACTOR] decideWhereToGo 메서드 분리 (5단계)
SANGHEEJEONG Oct 26, 2024
67fcc5a
[REFACTOR] Player 객체 분리 (5단계)
SANGHEEJEONG Oct 29, 2024
74f098d
[REFACTOR] LadderGenerator 메서드 수정 (5단계)
SANGHEEJEONG Oct 29, 2024
9763754
[REFACTOR] LadderGame forEach 람다 형식으로 바꿔보기(5단계)
SANGHEEJEONG Oct 29, 2024
a16726a
[REFACTOR] OutputView 메서드 순서 수정(5단계)
SANGHEEJEONG Oct 29, 2024
69871ca
[REFACTOR] findPlayerByName 메서드를 Players로 책임 분리(5단계)
SANGHEEJEONG Oct 30, 2024
169ec00
[REFACTOR] OutputView 주석 및 수정 (5단계)
SANGHEEJEONG Oct 30, 2024
ea543eb
[REFACTOR] Name, Position 원시값 포장 (5단계)
SANGHEEJEONG Oct 30, 2024
e81fa34
[REFACTOR] Name 예외처리 (5단계)
SANGHEEJEONG Oct 30, 2024
047d483
[REFACTOR] ResultTypes 예외처리 (5단계)
SANGHEEJEONG Oct 30, 2024
2dfe464
[REFACTOR] Players 예외처리 (5단계)
SANGHEEJEONG Oct 30, 2024
42cdbd6
[REFACTOR] 입력 예외처리 (5단계)
SANGHEEJEONG Oct 30, 2024
62f451d
[REFACTOR] 불변객체 적용 (5단계)
SANGHEEJEONG Oct 30, 2024
422af42
[REFACTOR] 단위테스트 작성 (5단계)
SANGHEEJEONG Oct 30, 2024
494e2d9
[REFACTOR] 코드 정렬 (5단계)
SANGHEEJEONG Oct 30, 2024
b2b1220
Create README.md
SANGHEEJEONG Nov 2, 2024
7dbb8cd
[REFACTOR] 리뷰 반영
SANGHEEJEONG Nov 2, 2024
67b09e4
[REFACTOR] README 작성
SANGHEEJEONG Nov 2, 2024
5c280c4
[REFACTOR] README 수정
SANGHEEJEONG Nov 6, 2024
8eac61b
[REFACTOR] Controller 메서드 분리
SANGHEEJEONG Nov 6, 2024
beeb626
[REFACTOR] 미사용 import 정리
SANGHEEJEONG Nov 6, 2024
570c4d7
[REFACTOR] LadderGame 메서드 책임에 따른 클래스 분리
SANGHEEJEONG Nov 7, 2024
6f6b09a
[REFACTOR] 접근지정자 수정 public -> private
SANGHEEJEONG Nov 7, 2024
95f8ec1
[REFACTOR] Boolaen -> Point ENUM 사용
SANGHEEJEONG Nov 8, 2024
f31cacc
[REFACTOR] position 메서드 수정
SANGHEEJEONG Nov 8, 2024
e43ee2d
[REFACTOR] 입력 메서드 수정
SANGHEEJEONG Nov 8, 2024
7e4cd52
[REFACTOR] LadderController 수정
SANGHEEJEONG Nov 9, 2024
f8dce70
[REFACTOR] LadderGenerator 삭제 및 Controller 메서드 분리
SANGHEEJEONG Nov 16, 2024
ac02d9c
[REFACTOR] moveLeft & moveRight 예외 처리
SANGHEEJEONG Nov 16, 2024
d48bce2
[REFACTOR] Controller 와 Application 책임 분리
SANGHEEJEONG Nov 18, 2024
ea12de3
[REFACTOR] Ladder 클래스 책임 부여
SANGHEEJEONG Nov 18, 2024
01b8efe
[REFACTOR] decideWhereToGo 메서드 line 클래스 이동
SANGHEEJEONG Nov 18, 2024
7cf78a6
[REFACTOR] Point -> LadderStep 이름 변경 및 학습테스트(익명 클래스 & 인터페이스) 적용
SANGHEEJEONG Nov 18, 2024
174ff32
[REFACTOR] nextStep 미사용 인자 삭제
SANGHEEJEONG Nov 19, 2024
6b26030
[REFACTOR] 동명이인 예외 처리
SANGHEEJEONG Nov 19, 2024
1d96fb1
[REFACTOR] PlayerName VO로 만들기
SANGHEEJEONG Nov 20, 2024
0bd1928
[REFACTOR] test 수정
SANGHEEJEONG Nov 20, 2024
76e1d31
[REFACTOR] test 수정
SANGHEEJEONG Nov 20, 2024
6473cca
[REFACTOR] 코드 정렬
SANGHEEJEONG Nov 20, 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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 기능 목록

### 1. 플레이어 및 사다리 정보 입력 기능
+ 플레이어 이름과 결과 종류를 입력한다.
+ 사다리 넓이 : (플레이어 수 - 1)
+ 사다리 높이 : 사용자 입력
### 2. 사다리 실행 기능
+ |-----|-----| 가로 라인이 겹치지 않게 사다리를 생성한다.
=> 즉, 앞 라인이 true면 다음 라인은 무조건 false이다.
### 3. 사다리 출력 기능
+ 플레이어 이름 입력 순으로 출력
+ 생성된 사다리 출력
+ 결과 종류 입력 순으로 출력
+ 보고 싶은 실행 결과 출력 ("all"일 때는 모두 출력)
Comment on lines +3 to +14

Choose a reason for hiding this comment

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

[Comment]

🎮 컨트롤러 나누기 5

다시 리드미로 돌아와서 생각해보면 좋을 것 같아요.

  • 리드미의 기능 목록이 잘 나눠져있는지? (제가 예시로 든 내용을 믿지 마세요. 상희님을 믿으세요.)
  • 잘 작성된 리드미로 어떤 컨트롤러 동작들이 존재해야하는지 (이게 웹 어플리케이션으로 가면 API 명세가 될거에요)


### <사다리 구현>
+ Boolean (다리)
+ ture : 연결
+ false : 연결 X
+ List&lt;Boolean&gt (한 층의 다리 모음)
+ 사다리의 넒이만큼
+ 다리가 연결되어 있으면(=true) 수평 방향으로 이동이 가능하다.
+ List&lt;Line&gt (모든 층의 다리 모음)
+ 사다리의 높이만큼
Comment on lines +1 to +24

Choose a reason for hiding this comment

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

[Comment]

제가 코드를 잘 이해할 수 있도록 리드미에 의도를 잘 담아주셔서 감사합니다.😄

이 리드미는 훌륭한 가치와 내용을 담은 리드미라고 생각해요.

기능을 명세화할 떄 여러가지 방법 있겠죠?

  1. 유저 시나리오 대로 동작을 정의한다 (1-3번)
  2. 각각의 요소(모델)들이 갖는 규칙과 동작을 정의한다 (<사다리 구현>)

이런 측면에서 잘 구조화 해주신 것 같습니다.
개선할 점이라고 한다면, 조금 더 코드보단 비즈니스 규칙에 가치를 두는 것이 읽는 사람의 이해를 도울 수 있을 것입니다.
코드는 개발자의 의도에 따라 설계 등이 바뀔 수 있지만, 더 명확하게 전달해야되는 것은 비즈니스 요구사항이니까요.

22 changes: 22 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import controller.LadderController;
import domain.Ladder;
import domain.Players;
import domain.ResultTypes;

public class Application {
public static void main(String[] args) {
runLadderGame();
}

private static void runLadderGame() {
LadderController ladderController = new LadderController();

Players players = ladderController.initializePlayers();
ResultTypes resultTypes = ladderController.initializeResults(players.getPlayersSize());
Ladder ladder = ladderController.initializeLadder(players.getPlayersSize());

ladderController.playGame(players, ladder);

ladderController.displayAllOutput(players, resultTypes, ladder);
}
}
49 changes: 49 additions & 0 deletions src/main/java/controller/LadderController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package controller;

import domain.Ladder;
import domain.Players;
import domain.ResultTypes;
import view.InputView;
import view.OutputView;

import java.util.List;

public class LadderController {

public Players initializePlayers() {
List<String> playerNames = InputView.splitString(InputView.inputNames());

return new Players(playerNames);
}

public ResultTypes initializeResults(int resultsSize) {
List<String> kindOfResults = InputView.splitString(InputView.inputLadderResults());

return new ResultTypes(kindOfResults, resultsSize);
}

public Ladder initializeLadder(int width) {
int height = InputView.inputHeight();

return new Ladder(width, height);
}

public void playGame(Players players, Ladder ladder) {
ladder.determineLadderResults(players);
}

public void displayAllOutput(Players players, ResultTypes resultTypes, Ladder ladder) {
displayLadder(players, resultTypes, ladder);
displayResults(resultTypes, players);
}

private void displayLadder(Players players, ResultTypes resultTypes, Ladder ladder) {
OutputView.printPlayers(players);
OutputView.drawLadder(ladder);
OutputView.printResultTypes(resultTypes.getResultTypes());
}

private void displayResults(ResultTypes resultTypes, Players players) {
OutputView.printResult(players, resultTypes.getResultTypes());
}
}
29 changes: 29 additions & 0 deletions src/main/java/domain/Ladder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package domain;

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

public class Ladder {

private final List<Line> lines;

public Ladder(int width, int height) {
this.lines = new ArrayList<>();
createLadder(width, height);
}

public void createLadder(int width, int height) {
for (int i = 0; i < height; i++) {
lines.add(new Line(width));
}
}

public void determineLadderResults(Players players) {
players.getPlayers().forEach(player -> player.moveAlongLadder(lines));
}

public List<Line> getLadder() {
return Collections.unmodifiableList(lines);
}
}
43 changes: 43 additions & 0 deletions src/main/java/domain/LadderStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package domain;

import java.util.function.Function;

public enum LadderStep {
CONNECTED {
@Override
public boolean canMove() {
return true;
}

@Override
public LadderStep nextStep() {
return NOT_CONNECTED;
}
},
NOT_CONNECTED {
@Override
public boolean canMove() {
return false;
}

@Override
public LadderStep nextStep() {
return FROM_BOOLEAN.apply(randomTrueOrFalse());
}
};

public abstract boolean canMove();

public abstract LadderStep nextStep();

public static final Function<Boolean, LadderStep> FROM_BOOLEAN = value -> {
if (value) {
return CONNECTED;
}
return NOT_CONNECTED;
};

public static boolean randomTrueOrFalse() {
return Math.random() < 0.5;
}
}
49 changes: 49 additions & 0 deletions src/main/java/domain/Line.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package domain;

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

import static domain.LadderStep.randomTrueOrFalse;

public class Line {
private final List<LadderStep> ladderSteps;

public Line(int width) {
this.ladderSteps = new ArrayList<>();
createLine(width);
}

public void createLine(int width) {
ladderSteps.add(LadderStep.FROM_BOOLEAN.apply(randomTrueOrFalse()));

for (int i = 1; i < width - 1; i++) {
ladderSteps.add(ladderSteps.get(i - 1).nextStep());
}
}

public void decideWhereToGo(Position position) {
int ladderOrder = position.getPosition();

if (canMoveRight(ladderOrder)) {
position.moveRight(ladderSteps.size());
return;
}

if (canMoveLeft(ladderOrder)) {
position.moveLeft();
}
}

private boolean canMoveRight(int ladderOrder) {
return ladderOrder < ladderSteps.size() && ladderSteps.get(ladderOrder).canMove();
}

private boolean canMoveLeft(int ladderOrder) {
return ladderOrder != 0 && ladderSteps.get(ladderOrder - 1).canMove();
}

public List<LadderStep> getLine() {
return Collections.unmodifiableList(ladderSteps);
}
}
26 changes: 26 additions & 0 deletions src/main/java/domain/Player.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package domain;

import java.util.List;

public class Player {

private final PlayerName playerName;
private final Position position;

public Player(PlayerName name, Position position) {
this.playerName = name;
this.position = position;
}

public void moveAlongLadder(List<Line> ladder) {
ladder.forEach(line -> line.decideWhereToGo(this.position));
}

public PlayerName getPlayerName() {
return playerName.getName();
}

public int getPosition() {
return position.getPosition();
}
}
59 changes: 59 additions & 0 deletions src/main/java/domain/PlayerName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package domain;

public class PlayerName {

private final String name;
private final static int MAX_LENGTH = 5;
private final static String INVALID_NAME = "all";
Comment on lines +6 to +7

Choose a reason for hiding this comment

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

[Approve]

객체의 규칙을 한눈에 볼 수 있는 상수 정말 좋은데요? 👍


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

private void validateName(String name) {
validateNotBlank(name);
validateMaxLength(name);
validateNotEqualAll(name);
}

private void validateNotBlank(String name) {
if (name.isBlank()) {
throw new IllegalArgumentException("이름은 공백일 수 없습니다.");
}
}

private void validateMaxLength(String name) {
if (name.length() > MAX_LENGTH) {
throw new IllegalArgumentException("이름은 " + MAX_LENGTH + "자를 초과할 수 없습니다.");
}
}

private void validateNotEqualAll(String name) {
if (name.equals(INVALID_NAME)) {
throw new IllegalArgumentException("이름은 " + INVALID_NAME + "일 수 없습니다.");
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true; // 메모리 주소 비교
if (o == null || getClass() != o.getClass()) return false;
PlayerName that = (PlayerName) o;
return name.equals(that.name);
}

@Override
public int hashCode() {
return name.hashCode();
}

@Override
public String toString() {
return name;
}

public PlayerName getName() {
return this;
}
}
60 changes: 60 additions & 0 deletions src/main/java/domain/Players.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

public class Players {

private final List<Player> players;
private final static int MIN_PLAYER_SIZE = 2;

public Players(List<String> playerNames) {
validateSize(playerNames);
validateDuplicateName(playerNames);
this.players = createPlayer(playerNames);
}

private void validateSize(List<String> playerNames) {
if (playerNames.size() < MIN_PLAYER_SIZE) {
throw new IllegalArgumentException("플레이어 수는 2명 이상이어야 합니다.");
}
}

private void validateDuplicateName(List<String> playerNames) {
Set<String> playerNamesSet = new HashSet<>(playerNames);

if (playerNames.size() != playerNamesSet.size()) {
throw new IllegalArgumentException("동명이인인 플레이어가 존재합니다.");
}
}
Comment on lines +27 to +33

Choose a reason for hiding this comment

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

[Approve]

놓칠 수 있지만, 현재 서비스에선 중요한 로직이죠!
훌륭합니다


private List<Player> createPlayer(List<String> playerNames) {
List<Player> players = new ArrayList<>();
for (int i = 0; i < playerNames.size(); i++) {
players.add(new Player(new PlayerName(playerNames.get(i)), new Position(i)));
}

return players;
}

public Player findByName(String viewerName) {
PlayerName playerName = new PlayerName(viewerName);

return players.stream()
.filter(player -> player.getPlayerName().equals(playerName))
.findFirst()
.orElseThrow(() -> new NoSuchElementException("플레이어 이름 '" + viewerName + "' 이 존재하지 않습니다."));
}

public List<Player> getPlayers() {
return Collections.unmodifiableList(players);
}
Comment on lines +53 to +55

Choose a reason for hiding this comment

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

[Approve]

방어적 복사 👍👍


public int getPlayersSize() {
return players.size();
}
}
28 changes: 28 additions & 0 deletions src/main/java/domain/Position.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package domain;

public class Position {

private int position;

public Position(int position) {
this.position = position;
}

public void moveLeft() {
if (position == 0) {
throw new IllegalStateException("가장 왼쪽 사다리이므로 왼쪽으로 이동이 불가합니다.");
}
position--;
}

public void moveRight(int maxPosition) {
if (position == maxPosition) {
throw new IllegalStateException("가장 오른쪽 사다리이므로 오른쪽으로 이동이 불가합니다.");
}
position++;
}
Comment on lines +18 to +23

Choose a reason for hiding this comment

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

[Approve]

꼼꼼한 예외처리를 이용해 예상치못한 버그를 방지하는 것 매우 좋습니다!

추가적으로 해당 예외 상황에 대한 테스트를 추가해두면 누군가 해당 로직을 수정했을 떄, 잘못됨을 알아차릴수 있겠네요!!


public int getPosition() {
return position;
}
Comment on lines +25 to +27

Choose a reason for hiding this comment

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

[Comment]

position을 getter로 꺼내는 방식 보다 조금 더 이 값에 대해서 판단을 이 Position이란 객체에게 맡겨보는 것은 어떨까요?

위의 리뷰들을 참고해서 한번 변경해보면 좋을 것 같아요

}
Loading