diff --git a/src/main/java/nextstep/ladder/GenerationStrategy.java b/src/main/java/nextstep/ladder/GenerationStrategy.java new file mode 100644 index 0000000000..12d009a0e4 --- /dev/null +++ b/src/main/java/nextstep/ladder/GenerationStrategy.java @@ -0,0 +1,5 @@ +package nextstep.ladder; + +public interface GenerationStrategy { + boolean shouldPlace(); +} diff --git a/src/main/java/nextstep/ladder/Ladder.java b/src/main/java/nextstep/ladder/Ladder.java new file mode 100644 index 0000000000..56a94ffbd0 --- /dev/null +++ b/src/main/java/nextstep/ladder/Ladder.java @@ -0,0 +1,13 @@ +package nextstep.ladder; + +public class Ladder { + public final Natural height; + public final Users users; + public final Legs legs; + + public Ladder(Users users, Natural height, GenerationStrategy strategy) { + this.height = height; + this.users = users; + legs = new Legs(height, users.size(), strategy); + } +} diff --git a/src/main/java/nextstep/ladder/LadderController.java b/src/main/java/nextstep/ladder/LadderController.java new file mode 100644 index 0000000000..ca12db9ca2 --- /dev/null +++ b/src/main/java/nextstep/ladder/LadderController.java @@ -0,0 +1,12 @@ +package nextstep.ladder; + +public class LadderController { + private static final LadderView view = new LadderView(); + public static void main(String[] args) { + var users = view.getUsers(); + var height = view.getHeight(); + + var ladder = new Ladder(users, height, new RandomGenerationStrategy()); + view.printResults(ladder); + } +} diff --git a/src/main/java/nextstep/ladder/LadderView.java b/src/main/java/nextstep/ladder/LadderView.java new file mode 100644 index 0000000000..7ad022ac7a --- /dev/null +++ b/src/main/java/nextstep/ladder/LadderView.java @@ -0,0 +1,65 @@ +package nextstep.ladder; + +import java.util.Scanner; + +public class LadderView { + public Users getUsers() { + System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); + var userInput = new UsersInput(getConsoleInput()); + return userInput.toUsers(); + } + + public Natural getHeight() { + System.out.println("최대 사다리 높이는 몇 개인가요?"); + var naturalInput = new NaturalInput(getConsoleInput()); + return naturalInput.toNatural(); + } + + public void printResults(Ladder ladder) { + System.out.println("실행결과"); + + ladder.users.forEach(user -> System.out.printf("%s\t", user)); + printNewLine(); + for (var level = ladder.height.value() - 1; level >= 0; level--) { + printLevel(level, ladder); + } + } + + private static void printLevel(long level, Ladder ladder) { + for (var place = 0; place < ladder.users.size().value() - 1; place++) { + printColumn(); + printLegIfExistsInLadder(new Position(level, place), ladder); + } + printColumn(); + printNewLine(); + } + + private static void printNewLine() { + System.out.println(); + } + + private static void printColumn() { + System.out.print("|"); + } + + private static void printLegIfExistsInLadder(Position position, Ladder ladder) { + if (ladder.legs.hasLegOnRightSideOf(position)) { + printFilledLeg(); + return; + } + printEmptyLeg(); + } + + private static void printEmptyLeg() { + System.out.print(" "); + } + + private static void printFilledLeg() { + System.out.print("-----"); + } + + private String getConsoleInput() { + Scanner scanner = new Scanner(System.in); + return scanner.nextLine(); + } +} diff --git a/src/main/java/nextstep/ladder/Legs.java b/src/main/java/nextstep/ladder/Legs.java new file mode 100644 index 0000000000..3c99244145 --- /dev/null +++ b/src/main/java/nextstep/ladder/Legs.java @@ -0,0 +1,45 @@ +package nextstep.ladder; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.LongStream; + +public class Legs { + private final Set legLeftPositions; + + public Legs(Natural height, Natural width, GenerationStrategy strategy) { + legLeftPositions = new HashSet<>(); + placeLegs(height, width, strategy); + } + + private void placeLegs(Natural height, Natural width, GenerationStrategy strategy) { + LongStream.range(0, height.value()) + .mapToObj(Natural::new) + .forEach(level -> placeLegsInLevel(level, width, strategy)); + } + + private void placeLegsInLevel(Natural level, Natural width, GenerationStrategy strategy) { + LongStream.range(0, width.value() - 1) + .mapToObj(Natural::new) + .map(place -> new Position(level, place)) + .forEach(position -> placeLegWithStrategy(strategy, position)); + } + + private void placeLegWithStrategy(GenerationStrategy strategy, Position position) { + if (isLegPlaceableOnPosition(position) && strategy.shouldPlace()) { + legLeftPositions.add(position); + } + } + + private boolean isLegPlaceableOnPosition(Position position) { + return isOnTheLeftEdge(position) || !hasLegOnRightSideOf(position.getLeftPosition()); + } + + private boolean isOnTheLeftEdge(Position position) { + return position.place.value() == 0; + } + + public boolean hasLegOnRightSideOf(Position position) { + return legLeftPositions.contains(position); + } +} diff --git a/src/main/java/nextstep/ladder/Natural.java b/src/main/java/nextstep/ladder/Natural.java new file mode 100644 index 0000000000..43bc5c7a91 --- /dev/null +++ b/src/main/java/nextstep/ladder/Natural.java @@ -0,0 +1,39 @@ +package nextstep.ladder; + +public class Natural implements Comparable { + private final Long value; + + public Natural(long value) { + if (value < 0) { + throw new IllegalArgumentException("Negative value given for natural number"); + } + this.value = value; + } + + public Long value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Natural) { + return value.equals(((Natural) o).value); + } + return false; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public int compareTo(Natural o) { + return value.compareTo(o.value); + } +} diff --git a/src/main/java/nextstep/ladder/NaturalInput.java b/src/main/java/nextstep/ladder/NaturalInput.java new file mode 100644 index 0000000000..3cfa096726 --- /dev/null +++ b/src/main/java/nextstep/ladder/NaturalInput.java @@ -0,0 +1,20 @@ +package nextstep.ladder; + +public class NaturalInput { + private final String input; + + public NaturalInput(String input) { + this.input = input; + checkInputIsNaturalNumber(); + } + + public Natural toNatural() { + return new Natural(Long.parseLong(input)); + } + + private void checkInputIsNaturalNumber() { + if (!input.matches("\\d+")) { + throw new IllegalArgumentException("Input is not a natural number"); + } + } +} diff --git a/src/main/java/nextstep/ladder/Position.java b/src/main/java/nextstep/ladder/Position.java new file mode 100644 index 0000000000..9925cebf5a --- /dev/null +++ b/src/main/java/nextstep/ladder/Position.java @@ -0,0 +1,36 @@ +package nextstep.ladder; + +import java.util.Objects; + +public class Position { + public final Natural level; + public final Natural place; + + public Position(Natural level, Natural place) { + this.level = level; + this.place = place; + } + + public Position(long level, long place) { + this.level = new Natural(level); + this.place = new Natural(place); + } + + public Position getLeftPosition() { + return new Position(level.value(), place.value() - 1); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Position) { + var that = (Position) o; + return level.equals(that.level) && place.equals(that.place); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(level, place); + } +} diff --git a/src/main/java/nextstep/ladder/RandomGenerationStrategy.java b/src/main/java/nextstep/ladder/RandomGenerationStrategy.java new file mode 100644 index 0000000000..ae0fedbd9a --- /dev/null +++ b/src/main/java/nextstep/ladder/RandomGenerationStrategy.java @@ -0,0 +1,11 @@ +package nextstep.ladder; + +import java.util.Random; + +public class RandomGenerationStrategy implements GenerationStrategy { + private final Random random = new Random(); + @Override + public boolean shouldPlace() { + return random.nextBoolean(); + } +} diff --git a/src/main/java/nextstep/ladder/Users.java b/src/main/java/nextstep/ladder/Users.java new file mode 100644 index 0000000000..8efa830ea2 --- /dev/null +++ b/src/main/java/nextstep/ladder/Users.java @@ -0,0 +1,21 @@ +package nextstep.ladder; + +import java.util.Iterator; +import java.util.List; + +public class Users implements Iterable { + private final List users; + + public Users(List users) { + this.users = users; + } + + @Override + public Iterator iterator() { + return users.iterator(); + } + + public Natural size() { + return new Natural(users.size()); + } +} diff --git a/src/main/java/nextstep/ladder/UsersInput.java b/src/main/java/nextstep/ladder/UsersInput.java new file mode 100644 index 0000000000..824150957c --- /dev/null +++ b/src/main/java/nextstep/ladder/UsersInput.java @@ -0,0 +1,15 @@ +package nextstep.ladder; + +import java.util.Arrays; + +public class UsersInput { + private final String input; + + public UsersInput(String input) { + this.input = input; + } + + public Users toUsers() { + return new Users(Arrays.asList(input.replace(" ", "").split(","))); + } +} diff --git a/src/test/java/nextstep/ladder/LadderTest.java b/src/test/java/nextstep/ladder/LadderTest.java new file mode 100644 index 0000000000..f0266cf09e --- /dev/null +++ b/src/test/java/nextstep/ladder/LadderTest.java @@ -0,0 +1,52 @@ +package nextstep.ladder; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +public class LadderTest { + @Test + public void levelAndUsersAreSet() { + var levels = new Natural(5); + var users = new Users(List.of("erik", "jake", "david")); + + var ladder = new Ladder(users, levels, () -> true); + + assertThat(ladder.height).isEqualTo(levels); + assertThat(ladder.users).containsExactly("erik", "jake", "david"); + } + + @Test + public void throwErrorOnNegativeInputs() { + assertThatThrownBy(() -> { + var levels = new Natural(-1); + var users = new Users(List.of()); + new Ladder(users, levels, () -> true); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessage("Negative value given for natural number"); + } + + @Test + public void legsAreNotPlacedConsecutively() { + var height = new Natural(5); + var users = new Users(List.of("erik", "jake", "david")); + + var ladder = new Ladder(users, height, () -> true); + + for (int level = 0; level < height.value(); level++) { + assertThatLegsAreNotPlacedConsecutivelyInLevel(ladder, level, users.size().value()); + } + } + + private static void assertThatLegsAreNotPlacedConsecutivelyInLevel(Ladder ladder, long level, long width) { + for (int place = 1; place < width; place++) { + var currentPosition = new Position(level, place); + var currentLeg = ladder.legs.hasLegOnRightSideOf(currentPosition); + var leftLeg = ladder.legs.hasLegOnRightSideOf(currentPosition.getLeftPosition()); + assert !(leftLeg && currentLeg); + } + } +} diff --git a/src/test/java/nextstep/ladder/NaturalInputTest.java b/src/test/java/nextstep/ladder/NaturalInputTest.java new file mode 100644 index 0000000000..628b19be1b --- /dev/null +++ b/src/test/java/nextstep/ladder/NaturalInputTest.java @@ -0,0 +1,27 @@ +package nextstep.ladder; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +public class NaturalInputTest { + @ParameterizedTest + @ValueSource(strings = {"12wr23", " "}) + public void throwErrorOnInvalidInputs(String input) { + assertThatThrownBy(() -> new NaturalInput(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Input is not a natural number"); + } + + @Test + public void convertsInputToNatural() { + var input = "5"; + + var natural = new NaturalInput(input).toNatural(); + + assertThat(natural.value()).isEqualTo(5L); + } +} diff --git a/src/test/java/nextstep/ladder/UsersInputTest.java b/src/test/java/nextstep/ladder/UsersInputTest.java new file mode 100644 index 0000000000..e36f69fe41 --- /dev/null +++ b/src/test/java/nextstep/ladder/UsersInputTest.java @@ -0,0 +1,16 @@ +package nextstep.ladder; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +public class UsersInputTest { + @Test + public void parseCommaSplicedUserInput() { + var input = "pobi, honux, crong, jk"; + + var userInput = new UsersInput(input); + + assertThat(userInput.toUsers()).containsExactly("pobi", "honux", "crong", "jk"); + } +}