diff --git a/README.md b/README.md
new file mode 100644
index 00000000..bd04af5b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,97 @@
+# 사다리 타기 🪜
+> 네이버의 사다리 타기 게임과 같이 콘솔 환경에서 사다리 게임을 진행할 수 있는 프로그램
+
+---
+
+## 1단계 - 사다리 출력
+
+### 📝 기능 요구사항
+✅ 사다리는 4x4 크기로 고정되고, 연결 여부는 랜덤으로 결정한다.
+
+✅ 사다리 타기가 정상적으로 동작하려면 라인이 겹치지 않도록 해야 한다.
+
+### 💻 구현 전략
+
+1. 나의 `오른쪽` 사다리와 연결 여부를 나타내는 `Link enum클래스`
+ ➡️ `LINKED` / `UNLINKED` 두 가지 타입을 갖는다.
+
+
+2. 사다리의 수평 구조물을 의미하는 `HorizontalLine 클래스`
+ ➡️ 필드로 Link 클래스의 리스트를 갖는다.
+ ➡️ 가로로 사다리를 건널 수 있을 지 없을 지를 Link 타입으로 알 수 있다.
+
+
+3. 전체 사다리를 의미하는 `Ladder 클래스`
+ ➡️ 필드로 HorizontalLine 클래스의 리스트를 갖는다.
+ ➡️ 세로가 아닌 가로로 사다리의 정보를 갖는다.
+
+
+
+
+---
+## 2단계 - 사다리 생성
+
+### 📝 기능 요구사항
+✅ 사다리는 크기를 입력 받아 생성할 수 있다.
+
+### 💻 구현 전략
+
+1. `InputView 클래스`를 통해 사다리 높이와 넓이를 입력받는다.
+ ➡️ 컨트롤러에서 `InputView 클래스`의 메서드를 호출해 높이와 넓이를 입력받는다.
+
+
+2. `Ladder 클래스`의 `정적 팩토리 메서드 of`에 입력받은 크기 정보를 넘겨주어 사다리를 생성한다.
+
+
+---
+
+## 3단계 - 사다리 타기
+### 📝 기능 요구사항
+✅ 사다리의 시작 지점과 도착 지점을 출력한다.
+
+### 💻 구현 전략
+
+
+1. `MovingDirection enum 클래스`를 통해 사다리의 이동 전략을 관리한다.
+ ➡️ Enum 클래스에서 함수형 인터페이스를 통해 이동 방향별(RIGHT / LEFT / STAY) 전략을 람다식으로 설정한다.
+
+
+2. 사다리 게임 진행
+ ➡️ Ladder 클래스의 `ride 메서드`를 통해 시작 위치를 전달하면 결과 위치를 반환한다.
+ ➡️ HorizontalLine 클래스의 `move 메서드`를 통해 현재 위치에서 움직일 수 있는지 판단하고 위치를 옮긴다.
+
+
+
+---
+
+## 4단계 - 게임 실행
+### 📝 기능 요구사항
+✅ 사다리 게임에 참여하는 사람에 이름을 최대 5글자까지 부여할 수 있다. 사다리를 출력할 때 사람 이름도 같이 출력한다.
+
+✅ 사람 이름은 쉼표(,)를 기준으로 구분한다.
+
+✅ 개인별 이름을 입력하면 개인별 결과를 출력하고, "all"을 입력하면 전체 참여자의 실행 결과를 출력한다.
+
+### 💻 구현 전략
+
+1. 게임 참가자의 이름과 사다리 위치 정보를 갖는 `User 클래스`와 이러한 User 클래스를 리스트로 갖는 `Users 클래스`를 통해 게임에 참가하는 유저를 관리
+
+
+2. 게임 결과의 이름과 사다리 위치 정보를 갖는 `Prize 클래스`와 이러한 Prize 클래스를 리스트로 갖는 `Prizes 클래스`를 통해 게임의 결과정보를 관리
+
+
+3. 입력 포맷에 대한 검사는 view단에서 빠르게 처리하고, 그 외 도메인과 관련이 깊은 입력의 유효성 검사는 각 도메인 클래스에서 처리한다.
+ ➡️ 이름,결과 입력 포멧 검사 - `InputValidator 클래스`
+ ➡️ 이름 중복, 길이, 유효한 이름인지 검사 - `Users 클래스`
+ ➡️ 실행 결과 입력 개수 검사 - `Prizes 클래스`
+
+
+2. 사다리 게임의 결과 집계
+ ➡️ `LadderGameResult 클래스` - 를 key-value로 하는 결과MAP을 생성한다.
+
+
+
+
+
+
+
diff --git a/src/main/java/Application.java b/src/main/java/Application.java
new file mode 100644
index 00000000..604124c7
--- /dev/null
+++ b/src/main/java/Application.java
@@ -0,0 +1,9 @@
+import controller.LadderGameController;
+import model.ladder.RandomLinkGenerator;
+
+public class Application {
+ public static void main(String[] args) {
+ LadderGameController ladderGameController = new LadderGameController(new RandomLinkGenerator());
+ ladderGameController.run();
+ }
+}
diff --git a/src/main/java/controller/LadderGameController.java b/src/main/java/controller/LadderGameController.java
new file mode 100644
index 00000000..cef2758d
--- /dev/null
+++ b/src/main/java/controller/LadderGameController.java
@@ -0,0 +1,99 @@
+package controller;
+
+import model.*;
+import model.Prize.Prizes;
+import model.ladder.Ladder;
+import model.ladder.LinkGenerator;
+import model.user.User;
+import model.user.Users;
+import utils.StringSplitter;
+import view.InputValidator;
+import view.InputView;
+import view.OutputView;
+
+import java.util.List;
+import java.util.Optional;
+
+import static utils.Constants.FINAL_QUERY_KEYWORD;
+
+public class LadderGameController {
+
+ private final LinkGenerator linkGenerator;
+ private final InputValidator inputValidator = new InputValidator();
+ private final InputView inputView = new InputView();
+ private final OutputView outputView = new OutputView();
+
+ public LadderGameController(LinkGenerator linkGenerator) {
+ this.linkGenerator = linkGenerator;
+ }
+
+ public void run() {
+ Users users = getUsers();
+ Prizes prizes = getPrizes(users.size());
+ int height = getHeight();
+
+ Ladder ladder = Ladder.of(height, users.size(), linkGenerator);
+ outputView.printLadder(ladder, users, prizes);
+
+ LadderGameResult ladderGameResult = LadderGameResult.of(users, prizes, ladder);
+ handleUserResultQuery(ladderGameResult, users);
+ }
+
+ private void handleUserResultQuery(LadderGameResult ladderGameResult, Users users) {
+
+ Optional findUser = getFindUsername(users);
+
+ if (findUser.isEmpty()) {
+ outputView.printAllResults(ladderGameResult);
+ return;
+ }
+
+ outputView.printPrize(ladderGameResult.findByUser(findUser.get()));
+ handleUserResultQuery(ladderGameResult, users);
+ }
+
+ private Optional getFindUsername(Users users) {
+ outputView.printQueryInputMessage();
+ String findUsername = inputView.getString();
+ if (findUsername.equals(FINAL_QUERY_KEYWORD)) {
+ return Optional.empty();
+ }
+ try {
+ return Optional.of(users.findByUsername(findUsername));
+ } catch (Exception e) {
+ outputView.printErrorMessage(e.getMessage());
+ return getFindUsername(users);
+ }
+ }
+
+ private Users getUsers() {
+ outputView.printUsernameInputMessage();
+ try {
+ return Users.from(readAndSplitInput());
+ } catch (Exception e) {
+ outputView.printErrorMessage(e.getMessage());
+ return getUsers();
+ }
+ }
+
+ private Prizes getPrizes(int participantCount) {
+ outputView.printPrizeInputMessage();
+ try {
+ return Prizes.of(readAndSplitInput(), participantCount);
+ } catch (Exception e) {
+ outputView.printErrorMessage(e.getMessage());
+ return getPrizes(participantCount);
+ }
+ }
+
+ private List readAndSplitInput() {
+ String inputString = inputView.getString();
+ inputValidator.validateInputStringPattern(inputString);
+ return StringSplitter.splitByComma(inputString);
+ }
+
+ private int getHeight() {
+ outputView.printHeightInputMessage();
+ return inputView.getInt();
+ }
+}
diff --git a/src/main/java/model/LadderGameResult.java b/src/main/java/model/LadderGameResult.java
new file mode 100644
index 00000000..3cb1bd59
--- /dev/null
+++ b/src/main/java/model/LadderGameResult.java
@@ -0,0 +1,42 @@
+package model;
+
+import model.Prize.Prize;
+import model.Prize.Prizes;
+import model.ladder.Ladder;
+import model.user.User;
+import model.user.Users;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class LadderGameResult {
+
+ private final Map gameResults;
+
+ private LadderGameResult(Map gameResults) {
+ this.gameResults = gameResults;
+ }
+
+ public static LadderGameResult of(Users users, Prizes prizes, Ladder ladder) {
+ return new LadderGameResult(calculateResult(users, prizes, ladder));
+ }
+
+ private static Map calculateResult(Users users, Prizes prizes, Ladder ladder) {
+ return users.getUsers().stream()
+ .collect(Collectors.toMap(
+ user -> user,
+ user -> ladder.ride(user, prizes)
+ ));
+ }
+
+ public Prize findByUser(User user) {
+ return Optional.ofNullable(gameResults.get(user))
+ .orElseThrow(() -> new IllegalArgumentException("결과를 조회하려는 유저가 존재하지 않습니다!"));
+ }
+
+ public Map getGameResults() {
+ return Collections.unmodifiableMap(gameResults);
+ }
+}
diff --git a/src/main/java/model/MoveCondition.java b/src/main/java/model/MoveCondition.java
new file mode 100644
index 00000000..5b95f00c
--- /dev/null
+++ b/src/main/java/model/MoveCondition.java
@@ -0,0 +1,8 @@
+package model;
+
+import model.ladder.HorizontalLine;
+
+@FunctionalInterface
+public interface MoveCondition {
+ boolean canMove(int index, HorizontalLine line);
+}
diff --git a/src/main/java/model/MovingDirection.java b/src/main/java/model/MovingDirection.java
new file mode 100644
index 00000000..fbd67a2d
--- /dev/null
+++ b/src/main/java/model/MovingDirection.java
@@ -0,0 +1,28 @@
+package model;
+
+import model.ladder.HorizontalLine;
+import model.ladder.Link;
+
+public enum MovingDirection {
+
+ RIGHT(1, (index, line) -> index < line.size() && line.getLink(index) == Link.LINKED),
+ LEFT(-1, (index, line) -> index > 0 && line.getLink(index-1) == Link.LINKED),
+ STAY(0, (index, line) -> true);
+
+
+ private final int direction;
+ private final MoveCondition condition;
+
+ MovingDirection(int direction, MoveCondition condition) {
+ this.direction = direction;
+ this.condition = condition;
+ }
+
+ public boolean canMove(int index, HorizontalLine line) {
+ return condition.canMove(index, line);
+ }
+
+ public int move(int index) {
+ return index + direction;
+ }
+}
diff --git a/src/main/java/model/Prize/Prize.java b/src/main/java/model/Prize/Prize.java
new file mode 100644
index 00000000..09832693
--- /dev/null
+++ b/src/main/java/model/Prize/Prize.java
@@ -0,0 +1,20 @@
+package model.Prize;
+
+public class Prize {
+
+ private final String prizeName;
+ private final int position;
+
+ public Prize(String prizeName, int position) {
+ this.prizeName = prizeName;
+ this.position = position;
+ }
+
+ public String getPrizeName() {
+ return prizeName;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+}
diff --git a/src/main/java/model/Prize/Prizes.java b/src/main/java/model/Prize/Prizes.java
new file mode 100644
index 00000000..8f91e1eb
--- /dev/null
+++ b/src/main/java/model/Prize/Prizes.java
@@ -0,0 +1,44 @@
+package model.Prize;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.IntStream;
+
+public class Prizes {
+
+ private final List prizes;
+
+ private Prizes(List prizes) {
+ this.prizes = prizes;
+ }
+
+ public static Prizes of(List prizeList, int participantCount) {
+ validatePrizeCount(prizeList, participantCount);
+ return new Prizes(
+ IntStream.range(0, prizeList.size())
+ .mapToObj(position -> new Prize(prizeList.get(position), position))
+ .toList()
+ );
+ }
+
+ private static void validatePrizeCount(List prizeList, int participantCount) {
+ if (prizeList.size() != participantCount) {
+ throw new IllegalArgumentException("참가자 인원만큼 실행결과를 입력해야 합니다!");
+ }
+ }
+
+ public int size() {
+ return prizes.size();
+ }
+
+ public Prize getPrizeAtPosition(int position) {
+ return prizes.stream()
+ .filter(prize -> prize.getPosition() == position)
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("해당하는 위치의 결과가 존재하지 않습니다!"));
+ }
+
+ public List getPrizes() {
+ return Collections.unmodifiableList(prizes);
+ }
+}
diff --git a/src/main/java/model/ladder/HorizontalLine.java b/src/main/java/model/ladder/HorizontalLine.java
new file mode 100644
index 00000000..15fef8fc
--- /dev/null
+++ b/src/main/java/model/ladder/HorizontalLine.java
@@ -0,0 +1,38 @@
+package model.ladder;
+
+import model.MovingDirection;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+public class HorizontalLine {
+
+ private final List points;
+ private final EnumSet movingDirections;
+
+ public HorizontalLine(List points) {
+ this.points = points;
+ this.movingDirections = EnumSet.allOf(MovingDirection.class);
+ }
+
+ public int move(int position) {
+ return movingDirections.stream()
+ .filter(movingDirection -> movingDirection.canMove(position, this))
+ .findFirst()
+ .map(movingDirection -> movingDirection.move(position))
+ .orElse(position);
+ }
+
+ public int size() {
+ return points.size();
+ }
+
+ public Link getLink(int index) {
+ return points.get(index);
+ }
+
+ public List getPoints() {
+ return Collections.unmodifiableList(points);
+ }
+}
diff --git a/src/main/java/model/ladder/Ladder.java b/src/main/java/model/ladder/Ladder.java
new file mode 100644
index 00000000..9826f6b2
--- /dev/null
+++ b/src/main/java/model/ladder/Ladder.java
@@ -0,0 +1,40 @@
+package model.ladder;
+
+import model.Prize.Prize;
+import model.Prize.Prizes;
+import model.user.User;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.IntStream;
+
+public class Ladder {
+
+ private final List lines;
+
+ private Ladder(List horizontalLines) {
+ this.lines = horizontalLines;
+ }
+
+ public static Ladder of(int height, int participantCount, LinkGenerator generator) {
+ return new Ladder(getRandomLines(height, participantCount, generator));
+ }
+
+ private static List getRandomLines(int height, int participantCount, LinkGenerator generator) {
+ return IntStream.range(0, height)
+ .mapToObj(position -> LineGenerator.generateHorizontalLine(participantCount, generator))
+ .toList();
+ }
+
+ public Prize ride(User user, Prizes prizes) {
+ int position = user.getPosition();
+ for (HorizontalLine line : lines) {
+ position = line.move(position);
+ }
+ return prizes.getPrizeAtPosition(position);
+ }
+
+ public List getLines() {
+ return Collections.unmodifiableList(lines);
+ }
+}
diff --git a/src/main/java/model/ladder/LineGenerator.java b/src/main/java/model/ladder/LineGenerator.java
new file mode 100644
index 00000000..bdc3a8ac
--- /dev/null
+++ b/src/main/java/model/ladder/LineGenerator.java
@@ -0,0 +1,26 @@
+package model.ladder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LineGenerator {
+
+ public static HorizontalLine generateHorizontalLine(int width, LinkGenerator generator) {
+ Link prev = Link.UNLINKED;
+ List link = new ArrayList<>();
+
+ for (int i = 0; i < width - 1; i++) {
+ prev = addLink(prev, generator.generate(), link);
+ }
+ link.add(Link.UNLINKED);
+ return new HorizontalLine(link);
+ }
+
+ private static Link addLink(Link prev, Link now, List link) {
+ if (prev.isLinked()) {
+ now = Link.UNLINKED;
+ }
+ link.add(now);
+ return now;
+ }
+}
diff --git a/src/main/java/model/ladder/Link.java b/src/main/java/model/ladder/Link.java
new file mode 100644
index 00000000..269f2e2e
--- /dev/null
+++ b/src/main/java/model/ladder/Link.java
@@ -0,0 +1,17 @@
+package model.ladder;
+
+public enum Link {
+ LINKED,
+ UNLINKED;
+
+ public static Link from(boolean linked) {
+ if (linked) {
+ return LINKED;
+ }
+ return UNLINKED;
+ }
+
+ public boolean isLinked() {
+ return this == LINKED;
+ }
+}
diff --git a/src/main/java/model/ladder/LinkGenerator.java b/src/main/java/model/ladder/LinkGenerator.java
new file mode 100644
index 00000000..5c3d4d4c
--- /dev/null
+++ b/src/main/java/model/ladder/LinkGenerator.java
@@ -0,0 +1,5 @@
+package model.ladder;
+
+public interface LinkGenerator {
+ Link generate();
+}
diff --git a/src/main/java/model/ladder/RandomLinkGenerator.java b/src/main/java/model/ladder/RandomLinkGenerator.java
new file mode 100644
index 00000000..142dfdcd
--- /dev/null
+++ b/src/main/java/model/ladder/RandomLinkGenerator.java
@@ -0,0 +1,13 @@
+package model.ladder;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class RandomLinkGenerator implements LinkGenerator {
+
+ private final ThreadLocalRandom random = ThreadLocalRandom.current();
+
+ @Override
+ public Link generate() {
+ return Link.from(random.nextBoolean());
+ }
+}
diff --git a/src/main/java/model/user/User.java b/src/main/java/model/user/User.java
new file mode 100644
index 00000000..068ba438
--- /dev/null
+++ b/src/main/java/model/user/User.java
@@ -0,0 +1,41 @@
+package model.user;
+
+import static utils.Constants.FINAL_QUERY_KEYWORD;
+import static utils.Constants.MAX_USERNAME_LENGTH;
+
+public class User {
+
+ private final String name;
+ private final int position;
+
+ public User(String name, int position) {
+ validateUsername(name);
+ this.name = name;
+ this.position = position;
+ }
+
+ private static void validateUsername(String name) {
+ validateUsernameLength(name);
+ validateUsernameKeyword(name);
+ }
+
+ private static void validateUsernameLength(String name) {
+ if (name.length() > MAX_USERNAME_LENGTH) {
+ throw new IllegalArgumentException("유저 이름은 최대 " + MAX_USERNAME_LENGTH + "자 입니다!");
+ }
+ }
+
+ private static void validateUsernameKeyword(String name) {
+ if (name.equals(FINAL_QUERY_KEYWORD)) {
+ throw new IllegalArgumentException("유저 이름으로 \"" + FINAL_QUERY_KEYWORD + "\"는 사용할 수 없습니다!");
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+}
diff --git a/src/main/java/model/user/Users.java b/src/main/java/model/user/Users.java
new file mode 100644
index 00000000..fc5e7a40
--- /dev/null
+++ b/src/main/java/model/user/Users.java
@@ -0,0 +1,46 @@
+package model.user;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.IntStream;
+
+public class Users {
+
+ private final List users;
+
+ private Users(List users) {
+ this.users = users;
+ }
+
+ public static Users from(List usernames) {
+ validateDuplicatedUser(usernames);
+ return new Users(
+ IntStream.range(0, usernames.size())
+ .mapToObj(position -> new User(usernames.get(position), position))
+ .toList()
+ );
+ }
+
+ private static void validateDuplicatedUser(List usernames) {
+ HashSet usernameMap = new HashSet<>(usernames);
+ if (usernames.size() != usernameMap.size()) {
+ throw new IllegalArgumentException("게임 참가자의 이름은 중복이 되면 안됩니다!");
+ }
+ }
+
+ public User findByUsername(String findUsername) {
+ return users.stream()
+ .filter(user -> user.getName().equals(findUsername))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다!"));
+ }
+
+ public int size() {
+ return users.size();
+ }
+
+ public List getUsers() {
+ return Collections.unmodifiableList(users);
+ }
+}
diff --git a/src/main/java/utils/Constants.java b/src/main/java/utils/Constants.java
new file mode 100644
index 00000000..b717da4a
--- /dev/null
+++ b/src/main/java/utils/Constants.java
@@ -0,0 +1,6 @@
+package utils;
+
+public class Constants {
+ public static final int MAX_USERNAME_LENGTH = 5;
+ public static final String FINAL_QUERY_KEYWORD = "all";
+}
diff --git a/src/main/java/utils/StringSplitter.java b/src/main/java/utils/StringSplitter.java
new file mode 100644
index 00000000..16418310
--- /dev/null
+++ b/src/main/java/utils/StringSplitter.java
@@ -0,0 +1,11 @@
+package utils;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class StringSplitter {
+
+ public static List splitByComma(String inputString) {
+ return Arrays.asList(inputString.split(","));
+ }
+}
diff --git a/src/main/java/view/InputValidator.java b/src/main/java/view/InputValidator.java
new file mode 100644
index 00000000..a9b2c4be
--- /dev/null
+++ b/src/main/java/view/InputValidator.java
@@ -0,0 +1,12 @@
+package view;
+
+public class InputValidator {
+
+ private static final String USERNAME_NAMES_PATTERN = "^([a-zA-Z가-힣0-9]+)(,[a-zA-Z가-힣0-9]+)*$";
+
+ public void validateInputStringPattern(String input) {
+ if (!input.matches(USERNAME_NAMES_PATTERN)) {
+ throw new IllegalArgumentException("입력은 쉼표로 구분 되어야 합니다!");
+ }
+ }
+}
diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java
new file mode 100644
index 00000000..51ea21b1
--- /dev/null
+++ b/src/main/java/view/InputView.java
@@ -0,0 +1,20 @@
+package view;
+
+import java.util.Scanner;
+
+public class InputView {
+
+ private static final Scanner scanner = new Scanner(System.in);
+
+ public int getInt() {
+ int inputInt = scanner.nextInt();
+ System.out.println();
+ return inputInt;
+ }
+
+ public String getString() {
+ String inputString = scanner.next();
+ System.out.println();
+ return inputString;
+ }
+}
diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java
new file mode 100644
index 00000000..22ee7ff1
--- /dev/null
+++ b/src/main/java/view/OutputView.java
@@ -0,0 +1,83 @@
+package view;
+
+import model.*;
+import model.Prize.Prize;
+import model.Prize.Prizes;
+import model.ladder.HorizontalLine;
+import model.ladder.Ladder;
+import model.ladder.Link;
+import model.user.User;
+import model.user.Users;
+
+import java.util.List;
+import java.util.function.Function;
+
+public class OutputView {
+
+ public void printUsernameInputMessage() {
+ System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)");
+ }
+
+ public void printHeightInputMessage() {
+ System.out.println("최대 사다리 높이는 몇 개인가요?");
+ }
+
+ public void printPrizeInputMessage() {
+ System.out.println("실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)");
+ }
+
+ public void printQueryInputMessage() {
+ System.out.println("결과를 보고 싶은 사람은?");
+ }
+
+ public void printPrize(Prize prize) {
+ System.out.println("실행결과");
+ System.out.println(prize.getPrizeName());
+ System.out.println();
+ }
+
+ public void printErrorMessage(String errorMessage) {
+ System.out.println(errorMessage);
+ System.out.println();
+ }
+
+ public void printLadder(Ladder ladder, Users users, Prizes prizes) {
+ System.out.println("사다리 결과");
+ printAligned(users.getUsers(), User::getName);
+ ladder.getLines().forEach(OutputView::printHorizontalLine);
+ printAligned(prizes.getPrizes(), Prize::getPrizeName);
+ System.out.println();
+ }
+
+ public void printAllResults(LadderGameResult ladderGameResult) {
+ System.out.println("실행결과");
+ ladderGameResult.getGameResults().forEach((user, prize) ->
+ System.out.println(user.getName() + " : " + prize.getPrizeName())
+ );
+ System.out.println();
+ }
+
+ private static void printAligned(List items, Function mapper) {
+ items.forEach(item ->
+ System.out.printf("%-6s", mapper.apply(item)));
+ System.out.println();
+ }
+
+ private static void printHorizontalLine(HorizontalLine line) {
+ System.out.print(" ");
+ line.getPoints().forEach(point -> {
+ System.out.print("|");
+ printCrossing(point);
+ });
+ System.out.println();
+ }
+
+ private static void printCrossing(Link point) {
+ if (point == Link.LINKED) {
+ System.out.print("-----");
+ }
+ if (point == Link.UNLINKED) {
+ System.out.print(" ");
+ }
+ }
+}
diff --git a/src/test/java/model/HorizontalLineTest.java b/src/test/java/model/HorizontalLineTest.java
new file mode 100644
index 00000000..b12806eb
--- /dev/null
+++ b/src/test/java/model/HorizontalLineTest.java
@@ -0,0 +1,35 @@
+package model;
+
+import model.ladder.HorizontalLine;
+import model.ladder.Link;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+class HorizontalLineTest {
+
+ private final HorizontalLine horizontalLine = new HorizontalLine(List.of(Link.UNLINKED, Link.LINKED, Link.UNLINKED));
+
+ @Test
+ public void 오른쪽_사다리와_연결되면_오른쪽으로_움직여야_한다() {
+ int position = 1;
+ int afterMove = horizontalLine.move(position);
+ assertThat(afterMove).isEqualTo(position + 1);
+ }
+
+ @Test
+ public void 왼쪽_사다리와_연결되면_왼쪽으로_움직여야_한다() {
+ int position = 2;
+ int afterMove = horizontalLine.move(position);
+ assertThat(afterMove).isEqualTo(position - 1);
+ }
+
+ @Test
+ public void 사다리가_연결되지_않으면_제자리를_유지해햐_한다() {
+ int position = 0;
+ int afterMove = horizontalLine.move(position);
+ assertThat(afterMove).isEqualTo(position);
+ }
+}
diff --git a/src/test/java/model/LadderGameResultTest.java b/src/test/java/model/LadderGameResultTest.java
new file mode 100644
index 00000000..54a136de
--- /dev/null
+++ b/src/test/java/model/LadderGameResultTest.java
@@ -0,0 +1,51 @@
+package model;
+
+import model.Prize.Prize;
+import model.Prize.Prizes;
+import model.ladder.Ladder;
+import model.user.User;
+import model.user.Users;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.IntStream;
+
+import static org.assertj.core.api.Assertions.*;
+
+class LadderGameResultTest {
+
+ private Users users;
+ private Prizes prizes;
+ private Ladder ladder;
+
+ @BeforeEach
+ void setUp() {
+ int height = 10;
+ users = Users.from(List.of("짱구", "철수", "유리", "맹구", "훈이"));
+ prizes = Prizes.of(List.of("1등", "꽝","꽝","꽝","꽝"), users.size());
+ ladder = Ladder.of(height, users.size(), new UnLinkedGenerator());
+ }
+
+ @Test
+ public void 사다리가_연결되지_않는다면_입력한_순서대로_당첨됨다() {
+ LadderGameResult result = LadderGameResult.of(users, prizes, ladder);
+
+ Map gameResults = result.getGameResults();
+
+ IntStream.range(0, gameResults.size()).forEach(i ->
+ assertThat(result.findByUser(users.getUsers().get(i))).isEqualTo(prizes.getPrizeAtPosition(i))
+ );
+ }
+
+ @Test
+ public void 결과조회시_유저가_존재하지_않으면_예외가_발생해야_한다() {
+ LadderGameResult result = LadderGameResult.of(users, prizes, ladder);
+ User user = new User("수지", 5);
+
+ assertThatThrownBy(() -> result.findByUser(user))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("결과를 조회하려는 유저가 존재하지 않습니다!");
+ }
+}
diff --git a/src/test/java/model/LadderTest.java b/src/test/java/model/LadderTest.java
new file mode 100644
index 00000000..0ee81eb1
--- /dev/null
+++ b/src/test/java/model/LadderTest.java
@@ -0,0 +1,42 @@
+package model;
+
+import model.Prize.Prize;
+import model.Prize.Prizes;
+import model.ladder.Ladder;
+import model.user.User;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+
+class LadderTest {
+
+ private final int height = 10;
+ private final int participantCount = 5;
+ private User user;
+ private Prizes prizes;
+
+ @BeforeEach
+ void setUp() {
+ user = new User("이창희", 0);
+ prizes = Prizes.of(List.of("1등", "꽝","꽝","꽝","꽝"), participantCount);
+ }
+
+ @Test
+ public void 주어진_높이만큼_사다리가_생성되어야_한다() {
+ Ladder ladder = Ladder.of(height, participantCount, new LinkedGenerator());
+ int ladderHeight = ladder.getLines().size();
+ assertThat(ladderHeight).isEqualTo(height);
+ }
+
+ @Test
+ public void 사다라가_연결되어_있지_않다면_사다리를_탔을_때_유저와_같은_위치의_prize가_반환되어야_한다() {
+ Ladder ladder = Ladder.of(height, participantCount, new UnLinkedGenerator());
+ Prize result = ladder.ride(user, prizes);
+ Prize expectPrize = prizes.getPrizeAtPosition(user.getPosition());
+ assertThat(result).isEqualTo(expectPrize);
+ }
+}
diff --git a/src/test/java/model/LineGeneratorTest.java b/src/test/java/model/LineGeneratorTest.java
new file mode 100644
index 00000000..84d78d1e
--- /dev/null
+++ b/src/test/java/model/LineGeneratorTest.java
@@ -0,0 +1,46 @@
+package model;
+
+import model.ladder.HorizontalLine;
+import model.ladder.LineGenerator;
+import model.ladder.Link;
+import model.ladder.LinkGenerator;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.stream.IntStream;
+
+import static org.assertj.core.api.Assertions.*;
+
+class LineGeneratorTest {
+
+ private final int width = 5;
+
+ @Test
+ public void LinkGenerator가_항상_UNLINKED를_반환할_때_생성되는_모든_가로라인은_UNLINKED여야_한다() {
+ LinkGenerator unlinkedGenerator = new UnLinkedGenerator();
+ HorizontalLine generatedLine = LineGenerator.generateHorizontalLine(width, unlinkedGenerator);
+ generatedLine.getPoints().forEach(point ->
+ assertThat(point).isEqualTo(Link.UNLINKED)
+ );
+ }
+
+ @Test
+ public void LinkGenerator가_항상_LINKED를_반환해도_가로라인이_두_번_연속_LINKED이면_안_된다() {
+ LinkGenerator linkedGenerator = new LinkedGenerator();
+ HorizontalLine generatedLine = LineGenerator.generateHorizontalLine(width, linkedGenerator);
+
+ List links = generatedLine.getPoints();
+ boolean result = IntStream.range(0, links.size() - 1)
+ .anyMatch(i -> links.get(i).isLinked() && links.get(i + 1).isLinked());
+
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void 생성되는_가로_링크의_개수는_사다리의_넓이와_같아야한다() {
+ LinkGenerator unlinkedGenerator = new UnLinkedGenerator();
+ HorizontalLine generatedLine = LineGenerator.generateHorizontalLine(width, unlinkedGenerator);
+ assertThat(generatedLine.size()).isEqualTo(width);
+ }
+}
diff --git a/src/test/java/model/LinkTest.java b/src/test/java/model/LinkTest.java
new file mode 100644
index 00000000..b2217b5d
--- /dev/null
+++ b/src/test/java/model/LinkTest.java
@@ -0,0 +1,33 @@
+package model;
+
+import model.ladder.Link;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
+
+class LinkTest {
+
+ @Test
+ public void 연결상태가_false이면__UNLINKED가_생성_되어야_한다() {
+ Link link = Link.from(false);
+ assertThat(link).isEqualTo(Link.UNLINKED);
+ }
+
+ @Test
+ public void 연결상태가_true이면_LINKED가_생성_되어야_한다() {
+ Link link = Link.from(true);
+ assertThat(link).isEqualTo(Link.LINKED);
+ }
+
+ @Test
+ public void 현재_연결상태라면_true가_반환되어야_한다() {
+ Link link = Link.from(true);
+ assertThat(link.isLinked()).isTrue();
+ }
+
+ @Test
+ public void 현재_연결상태가_아니라면_false가_반환되어야_한다() {
+ Link link = Link.from(false);
+ assertThat(link.isLinked()).isFalse();
+ }
+}
diff --git a/src/test/java/model/LinkedGenerator.java b/src/test/java/model/LinkedGenerator.java
new file mode 100644
index 00000000..6c0eb037
--- /dev/null
+++ b/src/test/java/model/LinkedGenerator.java
@@ -0,0 +1,11 @@
+package model;
+
+import model.ladder.Link;
+import model.ladder.LinkGenerator;
+
+public class LinkedGenerator implements LinkGenerator {
+ @Override
+ public Link generate() {
+ return Link.from(true);
+ }
+}
diff --git a/src/test/java/model/MovingDirectionTest.java b/src/test/java/model/MovingDirectionTest.java
new file mode 100644
index 00000000..896ec02d
--- /dev/null
+++ b/src/test/java/model/MovingDirectionTest.java
@@ -0,0 +1,34 @@
+package model;
+
+import model.ladder.HorizontalLine;
+import model.ladder.LineGenerator;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
+
+class MovingDirectionTest {
+
+ @Test
+ public void 오른쪽_이동_전략() {
+ HorizontalLine line = LineGenerator.generateHorizontalLine(5, new LinkedGenerator());
+ int position = 0;
+
+ boolean canMove = MovingDirection.RIGHT.canMove(position, line);
+ int resultPosition = MovingDirection.RIGHT.move(position);
+
+ assertThat(canMove).isTrue();
+ assertThat(resultPosition).isEqualTo(position + 1);
+ }
+
+ @Test
+ public void 왼쪽_이동_전략() {
+ HorizontalLine line = LineGenerator.generateHorizontalLine(5, new LinkedGenerator());
+ int position = 1;
+
+ boolean canMove = MovingDirection.LEFT.canMove(position, line);
+ int resultPosition = MovingDirection.LEFT.move(position);
+
+ assertThat(canMove).isTrue();
+ assertThat(resultPosition).isEqualTo(position - 1);
+ }
+}
diff --git a/src/test/java/model/PrizesTest.java b/src/test/java/model/PrizesTest.java
new file mode 100644
index 00000000..d0b0fbc4
--- /dev/null
+++ b/src/test/java/model/PrizesTest.java
@@ -0,0 +1,29 @@
+package model;
+
+import model.Prize.Prizes;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+class PrizesTest {
+
+ private List prizeList = List.of("1등", "꽝","꽝","꽝","꽝");
+ private final int participantCount = 5;
+
+ @Test
+ public void 참가자_인원보다_Prize가_적으면_예외가_발생해야_한다() {
+ assertThatThrownBy(() -> Prizes.of(prizeList, participantCount - 1))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("참가자 인원만큼 실행결과를 입력해야 합니다!");
+ }
+
+ @Test
+ public void 찾으려는_위치에_Prize가_존재하지_않으면_예외가_발생해야_한다() {
+ Prizes prizes = Prizes.of(prizeList, participantCount);
+ assertThatThrownBy(() -> prizes.getPrizeAtPosition(90))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("해당하는 위치의 결과가 존재하지 않습니다!");
+ }
+}
diff --git a/src/test/java/model/UnLinkedGenerator.java b/src/test/java/model/UnLinkedGenerator.java
new file mode 100644
index 00000000..1c89b793
--- /dev/null
+++ b/src/test/java/model/UnLinkedGenerator.java
@@ -0,0 +1,11 @@
+package model;
+
+import model.ladder.Link;
+import model.ladder.LinkGenerator;
+
+public class UnLinkedGenerator implements LinkGenerator {
+ @Override
+ public Link generate() {
+ return Link.from(false);
+ }
+}
diff --git a/src/test/java/model/UserTest.java b/src/test/java/model/UserTest.java
new file mode 100644
index 00000000..117424bf
--- /dev/null
+++ b/src/test/java/model/UserTest.java
@@ -0,0 +1,17 @@
+package model;
+
+import model.user.User;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
+
+class UserTest {
+
+ @Test
+ public void 유저의_이름이_5자_초과이면_예외가_발생해야_한다() {
+ String username = "사다리게임장인";
+ assertThatThrownBy(() -> new User(username, 0))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("유저 이름은 최대 5자 입니다!");
+ }
+}
diff --git a/src/test/java/model/UsersTest.java b/src/test/java/model/UsersTest.java
new file mode 100644
index 00000000..e51eac01
--- /dev/null
+++ b/src/test/java/model/UsersTest.java
@@ -0,0 +1,35 @@
+package model;
+
+import model.user.Users;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+class UsersTest {
+
+ @Test
+ public void 참가자의_이름이_중복되면_예외가_발생해야_한다() {
+ List usernames = List.of("짱구","철수","유리","맹구","짱구");
+ assertThatThrownBy(() -> Users.from(usernames))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("게임 참가자의 이름은 중복이 되면 안됩니다!");
+ }
+
+ @Test
+ public void 참가자_이름_수_만큼_User가_생성되어야_한다() {
+ List usernames = List.of("짱구","철수","유리","맹구","훈이");
+ Users users = Users.from(usernames);
+ assertThat(users.size()).isEqualTo(5);
+ }
+
+ @Test
+ public void 찾으려는_유저의_이름이_리스트에_존재하지_않으면_예외가_발생해야_한다() throws Exception {
+ List usernames = List.of("짱구","철수","유리","맹구","훈이");
+ Users users = Users.from(usernames);
+ assertThatThrownBy(() -> users.findByUsername("수지"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("해당 유저가 존재하지 않습니다!");
+ }
+}
diff --git a/src/test/java/utils/StringSplitterTest.java b/src/test/java/utils/StringSplitterTest.java
new file mode 100644
index 00000000..6d5ccc58
--- /dev/null
+++ b/src/test/java/utils/StringSplitterTest.java
@@ -0,0 +1,17 @@
+package utils;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+class StringSplitterTest {
+
+ @Test
+ public void 문자열을_쉼표로_구분하여_리스트로_반환되어야_한다() {
+ String inputString = "짱구, 철수,유리,맹구,훈이";
+ List strings = StringSplitter.splitByComma(inputString);
+ assertThat(strings).hasSize(5);
+ }
+}
diff --git a/src/test/java/view/InputValidatorTest.java b/src/test/java/view/InputValidatorTest.java
new file mode 100644
index 00000000..5298b021
--- /dev/null
+++ b/src/test/java/view/InputValidatorTest.java
@@ -0,0 +1,16 @@
+package view;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
+
+class InputValidatorTest {
+
+ @Test
+ public void 입력_문자열의_구분자가_쉼표가_아니면_예외가_발생해야_한다() {
+ String inputString = "짱구&철수&유리&맹구&훈이";
+ assertThatThrownBy(() -> InputValidator.validateInputStringPattern(inputString))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("입력은 쉼표로 구분 되어야 합니다!");
+ }
+}