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

3단계 - 사다리(게임 실행) #2034

Open
wants to merge 8 commits into
base: hvoiunq
Choose a base branch
from
23 changes: 23 additions & 0 deletions STEP3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## STEP3 기능 요구사항
* 사다리 실행 결과를 출력해야 한다.
* 개인별 이름을 입력하면 개인별 결과를 출력하고, "all"을 입력하면 전체 참여자의 실행 결과를 출력한다.

## 프로그래밍 요구사항
자바 8의 스트림과 람다를 적용해 프로그래밍한다.
규칙 6: 모든 엔티티를 작게 유지한다.
규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. (= 2개까지만 허용)

## STEP2 보완사항
* [X] Ladder 생성, List<Boolean> -> 이전상태와 현재상태를 가지고 있는 객체

## Step3 기능분해
* [X] 실행 결과를 입력할 수 있다.
* [X] 참가자보다 적게 입력하는 경우 Exception 발생
* [X] 참가자는 현재 위치를 가지고 있는다.
* [X] 참가자가 여러명인 경우 자동으로 현재 위치를 가지고 있는다.
* [X] 참가자가 위치한 라인의 이전상태가 true면 뒤로 이동한다.
* [X] 제일 왼쪽에 위치한 참가자가 한칸 더 왼쪽으로 이동하는 경우 Exception 발생
* [X] 참가자가 위치한 라인의 현재상태가 true면 앞으로 이동한다.
* [X] 제일 오른쪽에 위치한 참가자가 한칸 더 오른쪽으로 이동하는 경우 Exception 발생
* [X] 특정 참가자의 이름을 입력하는 경우 현재 위치의 실행결과를 보여준다.
* [X] 전체 결과를 보는 경우 각 참가자의 모든 실행결과를 보여준다.
4 changes: 4 additions & 0 deletions src/main/java/nextstep/ladder/domain/Ladder.java
Original file line number Diff line number Diff line change
@@ -35,6 +35,10 @@ public List<Line> getLines() {
return lines;
}

public Line getLineByHeight(int height) {
return lines.get(height);
}

@Override
public String toString() {
return "Ladder{" +
49 changes: 13 additions & 36 deletions src/main/java/nextstep/ladder/domain/Line.java
Original file line number Diff line number Diff line change
@@ -1,60 +1,37 @@
package nextstep.ladder.domain;

import nextstep.ladder.utils.RandomLineGenerator;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public class Line {
private final List<Boolean> points;
private final ArrayList<LineState> points;

public Line(int countOfParticipant) {
this(generateRandomLine(countOfParticipant));
checkForConsecutiveTrues(this.getPoints());
}

public Line(int countOfParticipant, LineGenerator lineGenerator) {
this(generateRandomLine(countOfParticipant, lineGenerator));
checkForConsecutiveTrues(this.getPoints());
}

private static ArrayList<Boolean> generateRandomLine(int countOfParticipant) {
LineGenerator lineGenerator = new RandomLineGenerator();
ArrayList<Boolean> line = new ArrayList<>();
line.add(false); // 사다리 라인의 맨 왼쪽은 생성될 수 없다.
private static ArrayList<LineState> generateRandomLine(int countOfParticipant) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private static ArrayList<LineState> generateRandomLine(int countOfParticipant) {
private static List<LineState> generateRandomLine(int countOfParticipant) {

가능하면 구현체보다 인터페이스 사용할 것을 추천
인터페이스로 구현하는 것이 왜 좋은지 고민해 보면 좋겠다.

ArrayList<LineState> line = new ArrayList<>();
LineState firstPoint = new LineState(false, false); // 사다리 라인의 맨 왼쪽은 생성될 수 없다.
line.add(firstPoint);
for (int i = 1; i < countOfParticipant; i++) {
boolean shouldGenerateLine = !line.get(i - 1) && lineGenerator.generateLine();
line.add(shouldGenerateLine);
LineState point = LineState.previousOf(line.get(i-1).getCurrent());
line.add(point);
}
return line;
}

private static ArrayList<Boolean> generateRandomLine(int countOfParticipant, LineGenerator lineGenerator) {
ArrayList<Boolean> line = new ArrayList<>();
line.add(false); // 사다리 라인의 맨 왼쪽은 생성될 수 없다.
for (int i = 1; i < countOfParticipant; i++) {
boolean shouldGenerateLine = lineGenerator.generateLine();
line.add(shouldGenerateLine);
}
return line;
public Line(ArrayList<LineState> points) {
this.points = points;
}

private static void checkForConsecutiveTrues(List<Boolean> points) {
IntStream.range(0, points.size() - 1)
.filter(i -> points.get(i) && points.get(i + 1))
.findFirst()
.ifPresent(i -> {
throw new IllegalArgumentException("사다리 라인은 연속으로 겹칠 수 없습니다.");
});
public List<LineState> getPoints() {
return points;
}

public Line(List<Boolean> points) {
this.points = points;
}

public List<Boolean> getPoints() {
return points;
public LineState getPointsByIndex(int index) {
return points.get(index);
}

@Override
46 changes: 46 additions & 0 deletions src/main/java/nextstep/ladder/domain/LineState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package nextstep.ladder.domain;

import nextstep.ladder.utils.RandomLineGenerator;

public class LineState {
private final boolean previous;
private final boolean current;
Comment on lines +6 to +7
Copy link
Contributor

Choose a reason for hiding this comment

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

👍



public static LineState previousOf(boolean previous) {
LineGenerator lineGenerator = new RandomLineGenerator();
return new LineState(previous, !previous && lineGenerator.generateLine());
}

public static LineState previousOf(boolean previous, LineGenerator lineGenerator) {
return new LineState(previous, lineGenerator.generateLine());
}

public LineState(boolean previous, boolean current) {
Copy link
Contributor

Choose a reason for hiding this comment

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

생성자는 클래스 메서드 앞에 위치하는 것이 관례임

checkForConsecutiveTrue(previous, current);
Copy link
Contributor

Choose a reason for hiding this comment

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

extract method를 통해 분리한 private 메서드는 가능하면 바로 아래 위치하도록 구현하는 것을 추천

this.previous = previous;
this.current = current;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

next LineState를 생성하기 위해 다음과 같이 구현하는 것은 어떨까?

    public LineState next() {
        return new LineState(current, lineGenerator.generateLine())
    }

public boolean getPrevious() {
return previous;
}

public boolean getCurrent() {
return current;
}

private void checkForConsecutiveTrue(boolean previous, boolean current) {
if (previous && current) {
throw new IllegalArgumentException("사다리 라인은 연속으로 겹칠 수 없습니다.");
}
}

@Override
public String toString() {
return "LineState{" +
"previous=" + previous +
", current=" + current +
'}';
}
}
38 changes: 34 additions & 4 deletions src/main/java/nextstep/ladder/domain/Participant.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
package nextstep.ladder.domain;

import nextstep.ladder.exception.CanNotMoveException;
import nextstep.ladder.exception.CannotRegisterNameException;

public class Participant {

public static final int MIN_NAME_LENGTH = 1;
public static final int MAX_NAME_LENGTH = 5;
private String name;
private final String name;
private int position;


public static Participant nameOf(String name) {
public static Participant nameOf(String name, int position) {
validateNameLength(name.trim());
return new Participant(name.trim());
return new Participant(name.trim(), position);
}

private Participant(String name) {
private Participant(String name, int position) {
this.name = name;
this.position = position;
}

private static void validateNameLength(String name) {
@@ -27,14 +30,41 @@ private static void validateNameLength(String name) {
}
}

public void moveFront(int participantCount) {
canMoveFront(participantCount);
this.position++;
}

private void canMoveFront(int participantCount) {
if (position == participantCount - 1) {
throw new CanNotMoveException("제일 오른쪽에 위치해, 더 이상 오른쪽으로 이동할 수 없습니다.");
}
}

public void moveBack() {
canMoveBack();
this.position--;
}

private void canMoveBack() {
if (this.position == 0) {
throw new CanNotMoveException("제일 왼쪽에 위치해, 더 이상 왼쪽으로 이동할 수 없습니다.");
}
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}

@Override
public String toString() {
return "Participant{" +
"name='" + name + '\'' +
", position=" + position +
'}';
}
}
22 changes: 21 additions & 1 deletion src/main/java/nextstep/ladder/domain/Participants.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
package nextstep.ladder.domain;

import nextstep.ladder.exception.NotFoundException;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class Participants {
private final List<Participant> players;
private static final AtomicInteger sequence = new AtomicInteger(0);

public Participants(String names) {
this(Arrays.stream(names.split(","))
.map(Participant::nameOf).collect(Collectors.toList()));
.map(name -> Participant.nameOf(name, sequence.getAndIncrement())).collect(Collectors.toList()));
}

public Participants(List<Participant> players) {
this.players = players;
}

public Participant getParticipantByName(String inputName) {
return validateInputName(inputName);
}

private Participant validateInputName(String inputName) {
return players.stream().filter(participant -> participant.getName().equals(inputName.trim()))
.findAny()
.orElseThrow(() -> new NotFoundException("입력하신 이름과 일치하는 참가자가 없습니다."));
}

public Participant getParticipantByPosition(int position) {
return players.stream()
.filter(player -> player.getPosition() == position)
.findAny().orElseThrow(() -> new NotFoundException("해당하는 위치의 참가자가 없습니다."));
}

public List<Participant> getParticipants() {
return Collections.unmodifiableList(players);
}
38 changes: 38 additions & 0 deletions src/main/java/nextstep/ladder/domain/ResultInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package nextstep.ladder.domain;

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

public class ResultInfo {

private final List<String> results;

public ResultInfo(String result, int participantCount) {
this(checkResultSize(Arrays.stream(result.split(",")).collect(Collectors.toList()), participantCount));

}

private static List<String> checkResultSize(List<String> result, int participantCount) {
checkResultAndParticipantCount(result, participantCount);
return result;
}

public ResultInfo(List<String> results) {
this.results = results;
}

private static void checkResultAndParticipantCount(List<String> result, int participantCount) {
if (result.size() != participantCount) {
throw new IllegalArgumentException("결과 갯수와 참가자 수와 다릅니다.");
}
}

public List<String> getResults() {
return results;
}

public String getResult(int position) {
return results.get(position);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nextstep.ladder.exception;

public class CanNotMoveException extends RuntimeException{
public CanNotMoveException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nextstep.ladder.exception;

public class NotFoundException extends RuntimeException{
public NotFoundException(String message) {
super(message);
}
}
54 changes: 46 additions & 8 deletions src/main/java/nextstep/ladder/service/LadderGameHandler.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package nextstep.ladder.service;

import nextstep.ladder.domain.Ladder;
import nextstep.ladder.domain.LadderInfo;
import nextstep.ladder.domain.Participants;
import nextstep.ladder.domain.*;
import nextstep.ladder.view.InputView;
import nextstep.ladder.view.ResultView;

@@ -13,17 +11,56 @@ private LadderGameHandler() { // 인스턴스화 방지

public static void runGame() {
Participants participants = inputAndRegisterParticipant();
ResultView.enter();

ResultInfo resultInfo = inputGameResultInfo(participants.count());
Ladder ladder = drawLadder(participants);

printLadder(participants, ladder);
moveParticipants(participants, ladder);

printLadder(participants, ladder, resultInfo);
printResult(participants, resultInfo);
}

private static void moveParticipants(Participants participants, Ladder ladder) {
for (int i = 0; i < ladder.getHeight(); i++) {
Line lineByHeight = ladder.getLineByHeight(i);
moveParticipantsOnLadder(participants, lineByHeight);
}
}

private static void moveParticipantsOnLadder(Participants participants, Line line) {
for (int i = 0; i < participants.count(); i++) {
if (line.getPointsByIndex(i).getCurrent()) {
Participant currentParticipant = participants.getParticipantByPosition(i);
currentParticipant.moveBack();
Participant previousParticipant = participants.getParticipantByPosition(i - 1);
previousParticipant.moveFront(participants.count());
}
}
}

private static void printLadder(Participants participants, Ladder ladder) {
ResultView.printResultWord();
private static void printResult(Participants participants, ResultInfo resultInfo) {
while (true) {
String inputName = InputView.inputForParticipantResult();
if (inputName.trim().equals("all")) {
ResultView.printResultAll(participants, resultInfo);
return;
}
Participant participant = participants.getParticipantByName(inputName);
ResultView.printResultOfParticipant(resultInfo.getResult(participant.getPosition()));
}
}

private static ResultInfo inputGameResultInfo(int participantCount) {
String gameResults = InputView.inputGameResult();
ResultView.enter();
return new ResultInfo(gameResults, participantCount);
}

private static void printLadder(Participants participants, Ladder ladder, ResultInfo resultInfo) {
ResultView.printLadderWord();
ResultView.printParticipantsName(participants);
ResultView.printLadder(ladder);
ResultView.printResultInfo(resultInfo);
}

private static Ladder drawLadder(Participants participants) {
@@ -36,6 +73,7 @@ private static Ladder drawLadder(Participants participants) {

private static Participants inputAndRegisterParticipant() {
String inputParticipantName = InputView.inputParticipantName();
ResultView.enter();
return new Participants(inputParticipantName);
}
}
13 changes: 12 additions & 1 deletion src/main/java/nextstep/ladder/view/InputView.java
Original file line number Diff line number Diff line change
@@ -9,12 +9,23 @@ private InputView() { // 인스턴스화 방지
}

public static String inputParticipantName() {
System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)");
System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요.)");
return scanner.nextLine();
}

public static String inputGameResult() {
System.out.println("실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요.)");
return scanner.nextLine();
}

public static int inputLadderHeight() {
System.out.println("최대 사다리 높이는 몇 개인가요?");
return scanner.nextInt();
}

public static String inputForParticipantResult() {
System.out.println("결과를 보고 싶은 사람은?");
scanner.nextLine();
return scanner.next();
}
}
45 changes: 35 additions & 10 deletions src/main/java/nextstep/ladder/view/ResultView.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package nextstep.ladder.view;

import nextstep.ladder.domain.Ladder;
import nextstep.ladder.domain.Line;
import nextstep.ladder.domain.Participants;
import nextstep.ladder.domain.*;

public class ResultView {

@@ -13,14 +11,13 @@ public class ResultView {
private ResultView() { // 인스턴스화 방지
}

public static void printResultWord() {
System.out.println("실행결과");
System.out.println();
}

public static void printParticipantsName(Participants participants) {
participants.getParticipants().forEach(participant -> System.out.print(String.format("%-8s", participant.getName())));
System.out.println();
enter();
}
public static void printLadderWord() {
System.out.println("사다리 결과");
enter();
}

public static void printLadder(Ladder ladder) {
@@ -32,12 +29,40 @@ public static void printLadder(Ladder ladder) {

private static void printPoints(Line line) {
line.getPoints().forEach(point -> {
System.out.print(point ? LINE : BLANK);
System.out.print(point.getCurrent() ? LINE : BLANK);
System.out.print(COLUMN);
});
}

public static void enter() {
System.out.println();
}

public static void printResultWord() {
System.out.println("실행결과");
}

public static void printResultInfo(ResultInfo resultInfo) {
resultInfo.getResults().forEach(result -> System.out.print(String.format("%-8s", result)));
enter();
enter();
}

public static void printResultAll(Participants participants, ResultInfo resultInfo) {
enter();
printResultWord();
for (int i = 0; i < participants.count(); i++) {
Participant participant = participants.getParticipants().get(i);
System.out.println(participant.getName()
+ " : " + resultInfo.getResults().get(participant.getPosition()));
}
enter();
}

public static void printResultOfParticipant(String result) {
enter();
printResultWord();
System.out.println(result);
enter();
}
}
2 changes: 1 addition & 1 deletion src/test/java/nextstep/ladder/domain/LineTest.java
Original file line number Diff line number Diff line change
@@ -16,6 +16,6 @@ public boolean generateLine() {
}
};

assertThrows(IllegalArgumentException.class, () -> new Line (3, lineGenerator));
assertThrows(IllegalArgumentException.class, () -> LineState.previousOf(true , lineGenerator));
}
}
50 changes: 45 additions & 5 deletions src/test/java/nextstep/ladder/domain/ParticipantTest.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,69 @@
package nextstep.ladder.domain;

import nextstep.ladder.exception.CanNotMoveException;
import nextstep.ladder.exception.CannotRegisterNameException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ParticipantTest {
@Test
@DisplayName("참가자의 이름이 5글자를 초과하는 경우 Exception Throw")
@DisplayName("참가자의 이름이 5글자를 초과하는 경우 Exception 밝생")
void participantName_5글자초과_Test() {
String name = "hvoiunq";
assertThrows(CannotRegisterNameException.class, () -> Participant.nameOf(name));
assertThrows(CannotRegisterNameException.class, () -> Participant.nameOf(name, 0));
}
@Test
@DisplayName("참가자의 이름이 1글자 미만인 경우 Exception Throw")
@DisplayName("참가자의 이름이 1글자 미만인 경우 Exception 발생")
void participantName_1글자미만_Test() {
String name = " ";
assertThrows(CannotRegisterNameException.class, () -> Participant.nameOf(name));
assertThrows(CannotRegisterNameException.class, () -> Participant.nameOf(name, 0));
}
@Test
@DisplayName("참가자의 이름이 1글자~5글자인 경우 정상")
void participantNameTest() {
String name = "hvo";
assertDoesNotThrow(() -> Participant.nameOf(name));
assertDoesNotThrow(() -> Participant.nameOf(name, 0));
}

@Test
@DisplayName("참가자는 현재 위치를 가질 수 있다.")
void ParticipantPositionTest() {
Participant participant = Participant.nameOf("test", 1);
assertThat(participant.getPosition()).isEqualTo(1);
}

@Test
@DisplayName("참가자는 오른쪽으로 이동할 수 있다.")
void moveFrontTest() {
Participant participant = Participant.nameOf("test", 1);
participant.moveFront(1);

assertThat(participant.getPosition()).isEqualTo(2);
}
@Test
@DisplayName("제일 왼쪽에 위치한 참가자가 한칸 더 왼쪽으로 이동하는 경우 Exception 발생")
void canNotMoveFrontTest() {
Participants participants = new Participants("a,b");

assertThrows(CanNotMoveException.class, () -> participants.getParticipantByName("b").moveFront(participants.count()));
}
@Test
@DisplayName("참가자는 왼쪽으로 이동할 수 있다.")
void moveBackTest() {
Participant participant = Participant.nameOf("test", 1);
participant.moveBack();

assertThat(participant.getPosition()).isEqualTo(0);
}
@Test
@DisplayName("제일 왼쪽에 위치한 참가자가 한칸 더 왼쪽으로 이동하는 경우 Exception 발생")
void canNotMoveBackTest() {
Participant participant = Participant.nameOf("test", 0);

assertThrows(CanNotMoveException.class, () -> participant.moveBack());
}
}
26 changes: 26 additions & 0 deletions src/test/java/nextstep/ladder/domain/ParticipantsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.ladder.domain;

import nextstep.ladder.exception.NotFoundException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ParticipantsTest {
@Test
@DisplayName("참가자가 여러명인 경우 자동으로 현재 위치를 가지고 있는다.")
void participantsPositionTest() {
Participants participants = new Participants("a,b");
assertThat(participants.getParticipants().get(0).getPosition()).isEqualTo(1);
assertThat(participants.getParticipants().get(1).getPosition()).isEqualTo(2);
}

@Test
@DisplayName("참가자 명단에 없는 이름을 입력하는 경우 Exception 발생")
void wrongInputParticipantName() {
Participants participants = new Participants("a,b");
assertThrows(NotFoundException.class, () -> participants.getParticipantByName("c"));
}

}
25 changes: 25 additions & 0 deletions src/test/java/nextstep/ladder/domain/ResultInfoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package nextstep.ladder.domain;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ResultInfoTest {
@Test
@DisplayName("실행 결과를 입력할 수 있다.")
void saveResultInfoTest() {
ResultInfo resultInfo = new ResultInfo("a,b", 2);

assertThat(resultInfo.getResults().size()).isEqualTo(2);
}

@Test
@DisplayName("참가자보다 적게 입력하는 경우 Exception 발생")
void wrongResultInfoExceptionTest() {
Participants participants = new Participants("a,b");

assertThrows(IllegalArgumentException.class, () -> new ResultInfo("a", participants.count()));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

LineState에 대한 단위 테스트도 추가해 보면 어떨까?