diff --git a/STEP3.md b/STEP3.md new file mode 100644 index 0000000000..7686fe5f1a --- /dev/null +++ b/STEP3.md @@ -0,0 +1,23 @@ +## STEP3 기능 요구사항 +* 사다리 실행 결과를 출력해야 한다. +* 개인별 이름을 입력하면 개인별 결과를 출력하고, "all"을 입력하면 전체 참여자의 실행 결과를 출력한다. + +## 프로그래밍 요구사항 +자바 8의 스트림과 람다를 적용해 프로그래밍한다. +규칙 6: 모든 엔티티를 작게 유지한다. +규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. (= 2개까지만 허용) + +## STEP2 보완사항 +* [X] Ladder 생성, List -> 이전상태와 현재상태를 가지고 있는 객체 + +## Step3 기능분해 +* [X] 실행 결과를 입력할 수 있다. + * [X] 참가자보다 적게 입력하는 경우 Exception 발생 +* [X] 참가자는 현재 위치를 가지고 있는다. +* [X] 참가자가 여러명인 경우 자동으로 현재 위치를 가지고 있는다. +* [X] 참가자가 위치한 라인의 이전상태가 true면 뒤로 이동한다. + * [X] 제일 왼쪽에 위치한 참가자가 한칸 더 왼쪽으로 이동하는 경우 Exception 발생 +* [X] 참가자가 위치한 라인의 현재상태가 true면 앞으로 이동한다. + * [X] 제일 오른쪽에 위치한 참가자가 한칸 더 오른쪽으로 이동하는 경우 Exception 발생 +* [X] 특정 참가자의 이름을 입력하는 경우 현재 위치의 실행결과를 보여준다. +* [X] 전체 결과를 보는 경우 각 참가자의 모든 실행결과를 보여준다. \ No newline at end of file diff --git a/src/main/java/nextstep/ladder/domain/Ladder.java b/src/main/java/nextstep/ladder/domain/Ladder.java index 0d80c55f8e..a22827501d 100644 --- a/src/main/java/nextstep/ladder/domain/Ladder.java +++ b/src/main/java/nextstep/ladder/domain/Ladder.java @@ -35,6 +35,10 @@ public List getLines() { return lines; } + public Line getLineByHeight(int height) { + return lines.get(height); + } + @Override public String toString() { return "Ladder{" + diff --git a/src/main/java/nextstep/ladder/domain/Line.java b/src/main/java/nextstep/ladder/domain/Line.java index 4732bee05e..9a6181df6f 100644 --- a/src/main/java/nextstep/ladder/domain/Line.java +++ b/src/main/java/nextstep/ladder/domain/Line.java @@ -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 points; + private final ArrayList 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 generateRandomLine(int countOfParticipant) { - LineGenerator lineGenerator = new RandomLineGenerator(); - ArrayList line = new ArrayList<>(); - line.add(false); // 사다리 라인의 맨 왼쪽은 생성될 수 없다. + private static ArrayList generateRandomLine(int countOfParticipant) { + ArrayList 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 generateRandomLine(int countOfParticipant, LineGenerator lineGenerator) { - ArrayList 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 points) { + this.points = points; } - private static void checkForConsecutiveTrues(List points) { - IntStream.range(0, points.size() - 1) - .filter(i -> points.get(i) && points.get(i + 1)) - .findFirst() - .ifPresent(i -> { - throw new IllegalArgumentException("사다리 라인은 연속으로 겹칠 수 없습니다."); - }); + public List getPoints() { + return points; } - public Line(List points) { - this.points = points; - } - public List getPoints() { - return points; + public LineState getPointsByIndex(int index) { + return points.get(index); } @Override diff --git a/src/main/java/nextstep/ladder/domain/LineState.java b/src/main/java/nextstep/ladder/domain/LineState.java new file mode 100644 index 0000000000..044287a619 --- /dev/null +++ b/src/main/java/nextstep/ladder/domain/LineState.java @@ -0,0 +1,46 @@ +package nextstep.ladder.domain; + +import nextstep.ladder.utils.RandomLineGenerator; + +public class LineState { + private final boolean previous; + private final boolean current; + + + 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) { + checkForConsecutiveTrue(previous, current); + this.previous = previous; + this.current = current; + } + + 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 + + '}'; + } +} diff --git a/src/main/java/nextstep/ladder/domain/Participant.java b/src/main/java/nextstep/ladder/domain/Participant.java index 3f6f3341a7..4bd7a80f60 100644 --- a/src/main/java/nextstep/ladder/domain/Participant.java +++ b/src/main/java/nextstep/ladder/domain/Participant.java @@ -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 + '}'; } } diff --git a/src/main/java/nextstep/ladder/domain/Participants.java b/src/main/java/nextstep/ladder/domain/Participants.java index 8d647ddeec..26efe1f38a 100644 --- a/src/main/java/nextstep/ladder/domain/Participants.java +++ b/src/main/java/nextstep/ladder/domain/Participants.java @@ -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 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 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 getParticipants() { return Collections.unmodifiableList(players); } diff --git a/src/main/java/nextstep/ladder/domain/ResultInfo.java b/src/main/java/nextstep/ladder/domain/ResultInfo.java new file mode 100644 index 0000000000..d11cd3e655 --- /dev/null +++ b/src/main/java/nextstep/ladder/domain/ResultInfo.java @@ -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 results; + + public ResultInfo(String result, int participantCount) { + this(checkResultSize(Arrays.stream(result.split(",")).collect(Collectors.toList()), participantCount)); + + } + + private static List checkResultSize(List result, int participantCount) { + checkResultAndParticipantCount(result, participantCount); + return result; + } + + public ResultInfo(List results) { + this.results = results; + } + + private static void checkResultAndParticipantCount(List result, int participantCount) { + if (result.size() != participantCount) { + throw new IllegalArgumentException("결과 갯수와 참가자 수와 다릅니다."); + } + } + + public List getResults() { + return results; + } + + public String getResult(int position) { + return results.get(position); + } +} diff --git a/src/main/java/nextstep/ladder/exception/CanNotMoveException.java b/src/main/java/nextstep/ladder/exception/CanNotMoveException.java new file mode 100644 index 0000000000..bb16cb80f5 --- /dev/null +++ b/src/main/java/nextstep/ladder/exception/CanNotMoveException.java @@ -0,0 +1,7 @@ +package nextstep.ladder.exception; + +public class CanNotMoveException extends RuntimeException{ + public CanNotMoveException(String message) { + super(message); + } +} diff --git a/src/main/java/nextstep/ladder/exception/NotFoundException.java b/src/main/java/nextstep/ladder/exception/NotFoundException.java new file mode 100644 index 0000000000..a9be4a2f1b --- /dev/null +++ b/src/main/java/nextstep/ladder/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package nextstep.ladder.exception; + +public class NotFoundException extends RuntimeException{ + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/nextstep/ladder/service/LadderGameHandler.java b/src/main/java/nextstep/ladder/service/LadderGameHandler.java index 643c4bf115..6d8ef65065 100644 --- a/src/main/java/nextstep/ladder/service/LadderGameHandler.java +++ b/src/main/java/nextstep/ladder/service/LadderGameHandler.java @@ -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); } } diff --git a/src/main/java/nextstep/ladder/view/InputView.java b/src/main/java/nextstep/ladder/view/InputView.java index 6659a7341c..b0c5347bcb 100644 --- a/src/main/java/nextstep/ladder/view/InputView.java +++ b/src/main/java/nextstep/ladder/view/InputView.java @@ -9,7 +9,12 @@ 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(); } @@ -17,4 +22,10 @@ public static int inputLadderHeight() { System.out.println("최대 사다리 높이는 몇 개인가요?"); return scanner.nextInt(); } + + public static String inputForParticipantResult() { + System.out.println("결과를 보고 싶은 사람은?"); + scanner.nextLine(); + return scanner.next(); + } } diff --git a/src/main/java/nextstep/ladder/view/ResultView.java b/src/main/java/nextstep/ladder/view/ResultView.java index 65a7775d5c..42f0dab2d0 100644 --- a/src/main/java/nextstep/ladder/view/ResultView.java +++ b/src/main/java/nextstep/ladder/view/ResultView.java @@ -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,7 +29,7 @@ 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); }); } @@ -40,4 +37,32 @@ private static void printPoints(Line line) { 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(); + } } diff --git a/src/test/java/nextstep/ladder/domain/LineTest.java b/src/test/java/nextstep/ladder/domain/LineTest.java index 8a4a61b2d6..47105d33d0 100644 --- a/src/test/java/nextstep/ladder/domain/LineTest.java +++ b/src/test/java/nextstep/ladder/domain/LineTest.java @@ -16,6 +16,6 @@ public boolean generateLine() { } }; - assertThrows(IllegalArgumentException.class, () -> new Line (3, lineGenerator)); + assertThrows(IllegalArgumentException.class, () -> LineState.previousOf(true , lineGenerator)); } } diff --git a/src/test/java/nextstep/ladder/domain/ParticipantTest.java b/src/test/java/nextstep/ladder/domain/ParticipantTest.java index e700771096..bc4bf60fb7 100644 --- a/src/test/java/nextstep/ladder/domain/ParticipantTest.java +++ b/src/test/java/nextstep/ladder/domain/ParticipantTest.java @@ -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()); } } diff --git a/src/test/java/nextstep/ladder/domain/ParticipantsTest.java b/src/test/java/nextstep/ladder/domain/ParticipantsTest.java new file mode 100644 index 0000000000..90f00e1c89 --- /dev/null +++ b/src/test/java/nextstep/ladder/domain/ParticipantsTest.java @@ -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")); + } + +} diff --git a/src/test/java/nextstep/ladder/domain/ResultInfoTest.java b/src/test/java/nextstep/ladder/domain/ResultInfoTest.java new file mode 100644 index 0000000000..06f6439a16 --- /dev/null +++ b/src/test/java/nextstep/ladder/domain/ResultInfoTest.java @@ -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())); + } +}