diff --git a/README.md b/README.md index e3e14da0ee..f5add406e4 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,19 @@ - Optional을 활용해 조건에 따른 반환 - Optional에서 값을 반환 - Optional에서 exception 처리 + +## 사다리 게임 기능 목록 + +1. 사용자 입력 처리 +- [x] 참여자 이름 입력 받기 (쉼표로 구분) +- [x] 이름 유효성 검사 (최대 5글자) +- [x] 사다리 높이 입력 받기 (숫자) +2. 사다리 생성 +- [x] 사다리 높이만큼 행 생성 +- [x] 참여자 수에 맞게 열 생성 +- [x] 랜덤으로 가로 연결선 생성 +- [x] 가로 연결선 겹치면 안됨 +3. 사다리 출력 +- [x] 참여자 이름 출력 (5자 기준 정렬) +- [x] 사다리 모양 출력 ("|", "-" 사용) + diff --git a/src/main/java/LadderGameApplication.java b/src/main/java/LadderGameApplication.java new file mode 100644 index 0000000000..4818c4a9a7 --- /dev/null +++ b/src/main/java/LadderGameApplication.java @@ -0,0 +1,16 @@ +import view.InputView; +import view.ResultView; +import domain.Ladder; +import domain.Players; + + +public class LadderGameApplication { + + public static void main(String[] args) { + Players players = InputView.inputPlayers(); + int ladderHeight = InputView.inputLadderHeight(); + + Ladder ladder = new Ladder(ladderHeight, players.size()); + ResultView.printResult(players, ladder); + } +} diff --git a/src/main/java/domain/Ladder.java b/src/main/java/domain/Ladder.java new file mode 100644 index 0000000000..2ed55eaf81 --- /dev/null +++ b/src/main/java/domain/Ladder.java @@ -0,0 +1,22 @@ +package domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.function.Consumer; +public class Ladder { + + private static final Random RANDOM = new Random(); + + private final List ladderRows = new ArrayList<>(); + + public Ladder(int height, int width) { + for (int i = 0; i < height; i++) { + ladderRows.add(new LadderRow(width, () -> RANDOM.nextBoolean())); + } + } + + public void forEach(Consumer consumer) { + ladderRows.forEach(consumer); + } +} diff --git a/src/main/java/domain/LadderRow.java b/src/main/java/domain/LadderRow.java new file mode 100644 index 0000000000..66a406dba6 --- /dev/null +++ b/src/main/java/domain/LadderRow.java @@ -0,0 +1,35 @@ +package domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class LadderRow { + + private final List columns = new ArrayList<>(); + + public LadderRow(int width, Supplier connectionFunction) { + initializeColumns(width, connectionFunction); + } + + private void initializeColumns(int width, Supplier connectionFunction) { + boolean prevPoint = false; + for (int i = 0; i < width - 1; i++) { + boolean isConnected = determineConnection(prevPoint, connectionFunction); + columns.add(isConnected); + prevPoint = isConnected; + } + } + + private boolean determineConnection(boolean prevPoint, Supplier connectionFunction) { + if (prevPoint) { + return false; + } + return connectionFunction.get(); + } + + public void forEach(Consumer consumer) { + columns.forEach(consumer); + } +} diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java new file mode 100644 index 0000000000..e48df1e6ff --- /dev/null +++ b/src/main/java/domain/Player.java @@ -0,0 +1,24 @@ +package domain; + +public class Player { + private final String name; + + public Player(String name) { + validate(name); + this.name = name; + } + + private void validate(String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("이름은 필수입니다."); + } + + if (name.length() > 5) { + throw new IllegalArgumentException("이름은 최대 5글자입니다."); + } + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java new file mode 100644 index 0000000000..6b35ceaf5d --- /dev/null +++ b/src/main/java/domain/Players.java @@ -0,0 +1,31 @@ +package domain; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class Players { + + private final List players; + + public Players(List playerNames) { + if (playerNames == null) { + throw new IllegalArgumentException("플레이어가 존재하지 않습니다."); + } + this.players = playerNames.stream() + .map(Player::new) + .collect(Collectors.toList()); + } + + public int size() { + return players.size(); + } + + public void forEach(Consumer consumer) { + players.forEach(consumer); + } + + public List getPlayers() { + return players; + } +} diff --git a/src/main/java/nextstep/fp/Conditional.java b/src/main/java/nextstep/fp/Conditional.java deleted file mode 100644 index 5a61a6c381..0000000000 --- a/src/main/java/nextstep/fp/Conditional.java +++ /dev/null @@ -1,6 +0,0 @@ -package nextstep.fp; - -@FunctionalInterface -public interface Conditional { - boolean test(Integer number); -} diff --git a/src/main/java/nextstep/fp/Lambda.java b/src/main/java/nextstep/fp/Lambda.java index 81ed2fb841..008f92fd50 100644 --- a/src/main/java/nextstep/fp/Lambda.java +++ b/src/main/java/nextstep/fp/Lambda.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.function.Function; +import java.util.function.Predicate; public class Lambda { public static void printAllOld(List numbers) { @@ -39,7 +40,7 @@ public static int sumAllOverThree(List numbers) { return sumAll(numbers, number -> number > 3); } - private static int sumAll(List numbers, Conditional condition) { + private static int sumAll(List numbers, Predicate condition) { int total = 0; for (int number : numbers) { if (condition.test(number)) { diff --git a/src/main/java/nextstep/optional/ComputerStore.java b/src/main/java/nextstep/optional/ComputerStore.java index 2695c967ac..e68a7f9cba 100644 --- a/src/main/java/nextstep/optional/ComputerStore.java +++ b/src/main/java/nextstep/optional/ComputerStore.java @@ -1,5 +1,6 @@ package nextstep.optional; +import java.util.Optional; import nextstep.optional.Computer.Soundcard; import nextstep.optional.Computer.USB; @@ -21,6 +22,10 @@ public static String getVersion(Computer computer) { } public static String getVersionOptional(Computer computer) { - return null; + return Optional.ofNullable(computer) + .map(Computer::getSoundcard) + .map(Soundcard::getUsb) + .map(USB::getVersion) + .orElse(UNKNOWN_VERSION); } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 0000000000..7b97e12b3f --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,35 @@ +package view; + +import java.util.Scanner; +import java.util.List; +import domain.Players; +public class InputView { + + private static final Scanner scanner = new Scanner(System.in); + + public static Players inputPlayers() { + System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); + return new Players(parseCommaSeparatedString(scanner.nextLine())); + } + + public static int inputLadderHeight() { + System.out.println("최대 사다리 높이는 몇 개 인가요?"); + return parseInt(scanner.nextLine()); + } + + private static int parseInt(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자를 입력해주세요."); + } + } + + private static List parseCommaSeparatedString(String input) { + try { + return List.of(input.split(",")); + } catch (NullPointerException e) { + throw new IllegalArgumentException("쉼표(,)로 구분된 문자열을 입력해주세요."); + } + } +} diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java new file mode 100644 index 0000000000..2490f6357e --- /dev/null +++ b/src/main/java/view/ResultView.java @@ -0,0 +1,18 @@ +package view; + +import domain.Ladder; +import domain.Players; + +public class ResultView { + public static void printResult(Players players, Ladder ladder) { + System.out.println("실행 결과"); + players.forEach(player -> System.out.printf("%-5s ", player.getName())); + System.out.println(); + + ladder.forEach(ladderRow -> { + System.out.print("|"); + ladderRow.forEach(column -> System.out.print(column ? "-----|" : " |")); + System.out.println(); + }); + } +} diff --git a/src/test/java/domain/LadderRowTest.java b/src/test/java/domain/LadderRowTest.java new file mode 100644 index 0000000000..9cb86bf946 --- /dev/null +++ b/src/test/java/domain/LadderRowTest.java @@ -0,0 +1,33 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class LadderRowTest { + + @Test + @DisplayName("사다리 행의 열은 전체 너비 - 1 만큼 생성된다.") + void createColumns() { + LadderRow ladderRow = new LadderRow(5, () -> true); + AtomicInteger count = new AtomicInteger(); + ladderRow.forEach(column -> count.getAndIncrement()); + assertThat(count.get()).isEqualTo(4); + } + + @Test + @DisplayName("사다리에서 연결된 다음 열은 연결되지 않는다.") + void adjacentConnectionsDoNotOverlap() { + LadderRow ladderRow = new LadderRow(5, () -> true); + List expected = List.of(true, false, true, false); + + AtomicInteger index = new AtomicInteger(); + ladderRow.forEach(column -> { + assertThat(column).isEqualTo(expected.get(index.getAndIncrement())); + }); + } +} diff --git a/src/test/java/domain/LadderTest.java b/src/test/java/domain/LadderTest.java new file mode 100644 index 0000000000..c82bcce67f --- /dev/null +++ b/src/test/java/domain/LadderTest.java @@ -0,0 +1,14 @@ +package domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class LadderTest { + @Test + @DisplayName("사다리 생성") + void createLadder() { + assertThat(new Ladder(3, 4)).isNotNull(); + } +} \ No newline at end of file diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java new file mode 100644 index 0000000000..fa66896f10 --- /dev/null +++ b/src/test/java/domain/PlayerTest.java @@ -0,0 +1,26 @@ +package domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class PlayerTest { + + @Test + @DisplayName("플레이어 생성") + void createPlayer() { + assertThat(new Player("홍길동")).isNotNull(); + } + + @Test + @DisplayName("플레이어 이름은 최대 5글자이다") + void playerNameMaxLength() { + assertThatThrownBy(() -> new Player("123456")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("이름은 최대 5글자입니다."); + } + + +}