diff --git a/build.gradle b/build.gradle index 239f9e7..43c8651 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,9 @@ dependencies { testImplementation platform('org.assertj:assertj-bom:3.25.1') testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.assertj:assertj-core') + implementation 'org.slf4j:slf4j-api:2.0.9' + implementation 'org.slf4j:slf4j-simple:2.0.9' + testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' } test { diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 0000000..ff54458 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,16 @@ +import controller.LadderController; +import dto.LadderDto; +import utils.exception.ExceptionHandler; +import view.LadderView; + +public class Application { + public static void main(String[] args) { + try { + LadderController ladderController = new LadderController(); + LadderDto ladderDto = ladderController.getLadderData(); + LadderView.printLadder(ladderDto); + } catch (Exception e) { + ExceptionHandler.handleException(e); + } + } +} diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java new file mode 100644 index 0000000..db5f42a --- /dev/null +++ b/src/main/java/controller/LadderController.java @@ -0,0 +1,18 @@ +package controller; + +import domain.Ladder; +import dto.LadderDto; +import view.InputView; + +public class LadderController { + private final Ladder ladder; + private final InputView inputView = new InputView(); + + public LadderController() { + this.ladder = Ladder.of(inputView.getWidth(), inputView.getHeight()); + } + + public LadderDto getLadderData() { + return LadderDto.from(ladder); + } +} diff --git a/src/main/java/domain/Ladder.java b/src/main/java/domain/Ladder.java new file mode 100644 index 0000000..c63b549 --- /dev/null +++ b/src/main/java/domain/Ladder.java @@ -0,0 +1,53 @@ +package domain; + +import utils.RandomUtil; +import utils.generator.LadderGenerator; +import java.util.List; +import java.util.stream.IntStream; + +public class Ladder { + private final Lines lines; + + private Ladder(Lines lines) { + this.lines = lines; + } + + public static Ladder of(int width, int height) { + return new Ladder(LadderGenerator.generate(width, height)); + } + + public Lines getLines() { + return lines; + } + + public static void validate(List lines, int width) { + IntStream.rangeClosed(0, width) + .forEach(i -> validateColumn(lines, i, width)); + } + + private static void validateColumn(List lines, int col, int width) { + boolean emptyColumn = lines.stream().noneMatch(line -> hasBridgeAt(line, col, width)); + if (emptyColumn) { + int randomIndex = RandomUtil.nextInt(lines.size()); + connect(lines, randomIndex, col, width); + } + } + + private static boolean hasBridgeAt(Line line, int col, int width) { + if (col == 0) return line.hasBridgeAt(col); + if (col == width) return line.hasBridgeAt(col - 1); + return line.hasBridgeAt(col - 1) || line.hasBridgeAt(col); + } + + private static void connect(List lines, int randomIndex, int col, int width) { + Line oldLine = lines.get(randomIndex); + + int index = col; + if (col == width) { + index = col - 1; + } + + Line updatedLine = oldLine.setBridgeAt(index); + lines.set(randomIndex, updatedLine); + } +} diff --git a/src/main/java/domain/Line.java b/src/main/java/domain/Line.java new file mode 100644 index 0000000..f8e4466 --- /dev/null +++ b/src/main/java/domain/Line.java @@ -0,0 +1,56 @@ +package domain; + +import utils.RandomUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.IntStream; + +public class Line { + private final List points; + + public Line(List points) { + this.points = List.copyOf(points); + } + + public List getPoints() { + return List.copyOf(points); + } + + public boolean hasBridgeAt(int index) { + return points.get(index); + } + + public Line setBridgeAt(int index) { + List newPoints = new ArrayList<>(points); + newPoints.set(index, true); + return new Line(newPoints); + } + + public static void applyBridges(List points, Set reserved, Line prev, boolean isReserved) { + IntStream.range(0, points.size()).forEach(i -> { + boolean shouldAddBridge = (isReserved && reserved.contains(i)) || (!isReserved && RandomUtil.nextBoolean()); + + if (shouldAddBridge && isValidBridgePosition(points, prev, i)) { + points.set(i, true); + } + }); + } + + public static void ensureOneBridge(List points, Line prev) { + if (points.contains(true)) return; + + IntStream.range(0, points.size()) + .filter(i -> isValidBridgePosition(points, prev, i)) + .findFirst() + .ifPresent(i -> points.set(i, true)); + } + + private static boolean isValidBridgePosition(List points, Line prev, int index) { + if (prev != null && prev.hasBridgeAt(index)) return false; + if (index > 0 && points.get(index - 1)) return false; + if (index < points.size() - 1 && points.get(index + 1)) return false; + return true; + } +} diff --git a/src/main/java/domain/Lines.java b/src/main/java/domain/Lines.java new file mode 100644 index 0000000..c912bd3 --- /dev/null +++ b/src/main/java/domain/Lines.java @@ -0,0 +1,15 @@ +package domain; + +import java.util.List; + +public class Lines { + private final List lines; + + public Lines(List lines) { + this.lines = List.copyOf(lines); + } + + public List getLines() { + return lines; + } +} diff --git a/src/main/java/dto/LadderDto.java b/src/main/java/dto/LadderDto.java new file mode 100644 index 0000000..902f159 --- /dev/null +++ b/src/main/java/dto/LadderDto.java @@ -0,0 +1,26 @@ +package dto; + +import domain.Ladder; +import domain.Line; +import java.util.List; +import java.util.stream.Collectors; + +public class LadderDto { + private final List> ladderData; + + public LadderDto(List> ladderData) { + this.ladderData = ladderData; + } + + public static LadderDto from(Ladder ladder) { + return new LadderDto( + ladder.getLines().getLines().stream() + .map(Line::getPoints) + .collect(Collectors.toList()) + ); + } + + public List> getLadderData() { + return ladderData; + } +} diff --git a/src/main/java/utils/RandomUtil.java b/src/main/java/utils/RandomUtil.java new file mode 100644 index 0000000..b45582a --- /dev/null +++ b/src/main/java/utils/RandomUtil.java @@ -0,0 +1,15 @@ +package utils; + +import java.util.Random; + +public class RandomUtil { + public static final Random random = new Random(); + + public static boolean nextBoolean() { + return random.nextBoolean(); + } + + public static int nextInt(int bound) { + return random.nextInt(bound); + } +} diff --git a/src/main/java/utils/exception/ExceptionHandler.java b/src/main/java/utils/exception/ExceptionHandler.java new file mode 100644 index 0000000..a14ca3d --- /dev/null +++ b/src/main/java/utils/exception/ExceptionHandler.java @@ -0,0 +1,13 @@ +package utils.exception; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExceptionHandler { + private static final Logger logger = LoggerFactory.getLogger(ExceptionHandler.class); + + public static void handleException(Exception e) { + System.err.println("시스템 오류 : " + e.getMessage()); + logger.error("시스템 오류 : ", e); + } +} diff --git a/src/main/java/utils/generator/LadderGenerator.java b/src/main/java/utils/generator/LadderGenerator.java new file mode 100644 index 0000000..70295c3 --- /dev/null +++ b/src/main/java/utils/generator/LadderGenerator.java @@ -0,0 +1,9 @@ +package utils.generator; + +import domain.Lines; + +public final class LadderGenerator { + public static Lines generate(int width, int height) { + return LineGenerator.generate(width, height); + } +} diff --git a/src/main/java/utils/generator/LineGenerator.java b/src/main/java/utils/generator/LineGenerator.java new file mode 100644 index 0000000..1dc8b70 --- /dev/null +++ b/src/main/java/utils/generator/LineGenerator.java @@ -0,0 +1,34 @@ +package utils.generator; + +import domain.Ladder; +import domain.Line; +import domain.Lines; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public final class LineGenerator { + public static Lines generate(int width, int height) { + List> reserved = ReservedPositionGenerator.generate(width - 1, height); + List lines = generateLines(width, height, reserved); + Ladder.validate(lines, width - 1); + return new Lines(lines); + } + + private static List generateLines(int width, int height, List> reserved) { + List lines = new ArrayList<>(); + for (int row = 0; row < height; row++) { + Line previous = getPreviousLine(lines, row); + Line line = SingleLineGenerator.generate(width - 1, reserved.get(row), previous); + lines.add(line); + } + return lines; + } + + private static Line getPreviousLine(List lines, int row) { + if (row == 0) { + return null; + } + return lines.get(row - 1); + } +} diff --git a/src/main/java/utils/generator/ReservedPositionGenerator.java b/src/main/java/utils/generator/ReservedPositionGenerator.java new file mode 100644 index 0000000..697ba6f --- /dev/null +++ b/src/main/java/utils/generator/ReservedPositionGenerator.java @@ -0,0 +1,27 @@ +package utils.generator; + +import utils.RandomUtil; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public final class ReservedPositionGenerator { + public static List> generate(int width, int height) { + List> reserved = initializeReservedPositions(height); + applyRandomReservations(reserved, width, height); + return reserved; + } + + private static List> initializeReservedPositions(int height) { + return IntStream.range(0, height) + .mapToObj(i -> new HashSet()) + .collect(Collectors.toList()); + } + + private static void applyRandomReservations(List> reserved, int width, int height) { + IntStream.range(0, width) + .forEach(i -> reserved.get(RandomUtil.nextInt(height)).add(i)); + } +} diff --git a/src/main/java/utils/generator/SingleLineGenerator.java b/src/main/java/utils/generator/SingleLineGenerator.java new file mode 100644 index 0000000..4f841df --- /dev/null +++ b/src/main/java/utils/generator/SingleLineGenerator.java @@ -0,0 +1,20 @@ +package utils.generator; + +import domain.Line; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class SingleLineGenerator { + public static Line generate(int width, Set reserved, Line prev) { + List points = new ArrayList<>(Collections.nCopies(width, false)); + + Line.applyBridges(points, reserved, prev, true); + Line.applyBridges(points, null, prev, false); + Line.ensureOneBridge(points, prev); + + return new Line(points); + } + +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 0000000..a924d03 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,18 @@ +package view; + +import java.util.Scanner; + +public class InputView { + private final Scanner scanner = new Scanner(System.in); + + public int getWidth() { + System.out.println("사다리의 넓이는 몇 개인가요?"); + return scanner.nextInt(); + } + + public int getHeight() { + System.out.println(); + System.out.println("사다리의 높이는 몇 개인가요?"); + return scanner.nextInt(); + } +} diff --git a/src/main/java/view/LadderView.java b/src/main/java/view/LadderView.java new file mode 100644 index 0000000..cee69d2 --- /dev/null +++ b/src/main/java/view/LadderView.java @@ -0,0 +1,32 @@ +package view; + +import dto.LadderDto; + +import java.util.List; + +public class LadderView { + private static final String BRIDGE = "-----|"; + private static final String SPACE = " |"; + private static final String BAR = "|"; + + public static void printLadder(LadderDto ladderDto) { + System.out.println(); + System.out.println("실행결과"); + System.out.println(); + ladderDto.getLadderData().forEach(LadderView::printLine); + } + + private static void printLine(List line) { + System.out.print(BAR); + line.forEach(LadderView::printPoint); + System.out.println(); + } + + private static void printPoint(Boolean point) { + if (point) { + System.out.print(BRIDGE); + return; + } + System.out.print(SPACE); + } +}