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("입력은 쉼표로 구분 되어야 합니다!"); + } +}