Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a8bb2bb
feat: 1단계 구현
chemistryx Oct 4, 2025
4f5f242
feat: 2단계 구현
chemistryx Oct 4, 2025
43cdd44
refactor: SRP를 보다 잘 준수하도록 수정
chemistryx Oct 8, 2025
b3f2321
feat: 3단계 구현
chemistryx Oct 8, 2025
d228d82
feat: 4단계 구현
chemistryx Oct 9, 2025
2994967
refactor: Ladder 도메인 로직 패키지 분리
chemistryx Oct 9, 2025
bd78398
refactor: 사용하지 않는 메소드 삭제
chemistryx Oct 9, 2025
d06f91d
refactor: LadderController::run 책임 분리
chemistryx Oct 9, 2025
784e390
feat: 5단계 구현
chemistryx Oct 9, 2025
5a0159e
test: 테스트 코드 작성
chemistryx Oct 30, 2025
f1244e4
refactor: VO에 대해서 record 도입
chemistryx Nov 1, 2025
5ef1716
refactor: scanner.nextLine() 사용
chemistryx Nov 1, 2025
2573545
refactor: Arrays.asList()로 입력 문자열 맵핑
chemistryx Nov 1, 2025
d3d50f7
refactor: 참가자 이름 공백 입력받지 않도록 제한
chemistryx Nov 1, 2025
82d4c5c
refactor: createConnections()에 대해 early return 적용
chemistryx Nov 1, 2025
854f13a
refactor: handleOutcomeQuery 책임 분리
chemistryx Nov 1, 2025
9648d42
feat: 결과 값에 대한 검증 추가
chemistryx Nov 1, 2025
7650568
refactor: \n 문자 println으로 대체
chemistryx Nov 4, 2025
bf3978b
refactor: stream method chain 분리
chemistryx Nov 4, 2025
a16fcf5
refactor: 사다리 선분 구성 문자열 상수화
chemistryx Nov 4, 2025
99a4d60
refactor: centerAlign width 주입받도록 변경
chemistryx Nov 4, 2025
e04642a
fix: 실행 결과가 올바르게 출력되지 않는 문제 수정
chemistryx Nov 4, 2025
1814a51
refactor: printLadderResult 호출 LadderController로 이관
chemistryx Nov 4, 2025
33d7096
docs: README.md 추가
chemistryx Nov 4, 2025
7933714
refactor: 검증 로직 세부 구분
chemistryx Nov 5, 2025
4f1af17
test: 검증 로직 관련 테스트 추가
chemistryx Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# java-ladder-func-playground

사다리 미션

## 개요
사다리 미션은 간단한 콘솔 애플리케이션으로, 사용자로부터 참여할 사람의 이름과 실행 결과, 최대 사다리 높이를 입력 받습니다.\
그리고 생성된 사다리에 대해 사용자는 참가자의 이름을 입력하여 해당 참가자에 대한 실행 결과를 확인할 수 있고, `all`을 입력하여 전체 결과를 확인할 수 있습니다.

## 프로젝트 구조
```
controller/
- LadderController.java : 전반적인 게임 Flow(입력, 실행, 출력)를 담당하는 클래스
model/
- ladder/
- Connection.java : 사다리 가로 줄의 지점 간 연결을 정의하는 클래스
- Ladder.java : 전체 사다리를 정의하는 클래스
- LadderFactory.java : 사다리 생성을 담당하는 클래스
- Line.java : 사다리의 가로 줄을 정의하는 클래스
- Game.java : 게임 실행을 담당하는 클래스
- GameConfiguration.java : 게임 설정을 정의하는 클래스
- GameConfigurationBuilder.java : 게임 설정 Builder 클래스
- GameResult.java : 게임 결과를 정의하는 클래스
- Participant.java : 각 참가자를 정의하는 클래스
view/
- InputView.java : 사용자 입력 처리 기능 수행
- OutputView.java : 사용자에게 정보 출력 기능 수행
Main.java : Main entrypoint
```
Empty file removed src/main/java/.gitkeep
Empty file.
10 changes: 10 additions & 0 deletions src/main/java/io/suhan/ladder/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.suhan.ladder;

import io.suhan.ladder.controller.LadderController;

public class Main {
public static void main(String[] args) {
LadderController controller = new LadderController();
controller.run();
}
}
71 changes: 71 additions & 0 deletions src/main/java/io/suhan/ladder/controller/LadderController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.suhan.ladder.controller;

import io.suhan.ladder.model.Game;
import io.suhan.ladder.model.GameConfiguration;
import io.suhan.ladder.model.GameConfigurationBuilder;
import io.suhan.ladder.model.GameResult;
import io.suhan.ladder.model.Outcome;
import io.suhan.ladder.model.Participant;
import io.suhan.ladder.view.InputView;
import io.suhan.ladder.view.OutputView;
import java.util.List;
import java.util.stream.Stream;

public class LadderController {
public void run() {
try {
GameConfiguration configuration = readConfiguration();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개별 값(participants, outcomes, width, height)을 바로 넘기지 않고, GameConfiguration 객체를 통해 입력값을 한 번에 전달하도록 설계하신 이유가 궁금합니다!

Copy link
Author

@chemistryx chemistryx Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 생각해보았을때 GameConfiguration객체를 도입한 이유는 다음과 같습니다:

  1. 입력 검증 로직과 게임 로직 분리
    현재 GameConfiguration객체의 경우 GameConfigurationBuilder를 통해 생성되도록 의도하였는데, 검증 로직의 경우 빌더가 담당함으로써 컨트롤러에서는 게임 실행 자체만을 담당할 수 있도록 구성하였습니다.

  2. 입력 값에 대한 순차적 검증
    사실 빌더 패턴을 도입하게된 이유라고 볼 수도 있을 것 같은데, 기존 validate()함수와 같은 방식으로 사용하게 되면, 아래와 같이 검증이 필요한 값들에 대하여 대체로 한 곳에서 검증을 수행합니다.

private static void validateParams(List<Participant> participants, List<String> outcomes, int width, int height) {
    if (participants.size() != outcomes.size()) {
        throw new IllegalArgumentException("참가자의 수와 실행 결과의 수는 같아야 합니다.");
    }

    if (width <= 0 || height <= 0) {
        throw new IllegalArgumentException("크기는 양수만 입력할 수 있습니다.");
    }
}

해당 방식의 경우 검증할 값이 많아질 수록 가독성이 떨어진다고 판단했고, 값이 많아짐에 따라 메소드 별로 분리하는 방법도 생각해볼 수 있으나 이 방식 또한 결국 각각의 검증 메소드를 호출해야 한다는 부분이 효율적이지 못하다고 판단했습니다.
따라서 빌더를 통해 값 할당과 동시에 검증 로직을 수행하는 방식이 위 두가지 단점을 해결할 수 있다고 판단하여 빌더 패턴 방식을 도입해보았습니다.

  1. Game 생성의 단순화
    만약 인자 수가 많아진다면 인자의 순서가 헷갈릴 수도 있고, 새로운 필드가 추가된다면 이에 따라 계속 수정해야 한다는 것이 번거롭다고 판단했습니다. 따라서 객체 하나만을 넘겨주는 방식을 통해 이러한 문제를 해결할 수 있었습니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로직 생성의 이유를 명확히 고민하고 코드로 잘 구현하신 점이 정말 좋네용👍

2번에 관해서는

해당 방식의 경우 검증할 값이 많아질 수록 가독성이 떨어진다고 판단했고, 값이 많아짐에 따라 메소드 별로 분리하는 방법도 생각해볼 수 있으나 이 방식 또한 결국 각각의 검증 메소드를 호출해야 한다는 부분이 효율적이지 못하다고 판단했습니다.

라는 의견에 동의합니다! builder로 검증 로직을 분리하여 현재는 도메인이 더욱 비즈니스 로직에 집중할 수 있도록 설계된 것 같아요.

다만 매우매우 개인적으로는 도메인이 유효성까지 스스로 보장하는 것도 DDD 관점에서 괜찮은 접근이라고 생각합니다! 그래도 지금은 설계 의도도 명확하고, 충분히 적절해 보여요👍

Game game = Game.of(configuration);

GameResult result = game.execute();

OutputView.printLadderResult(game);

handleOutcomeQuery(result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

private GameConfiguration readConfiguration() {
// method chaining으로 구성하려고 하였으나 Input 검증으로 인해 각각 따로 받음
GameConfigurationBuilder builder = new GameConfigurationBuilder();

List<Participant> participants = InputView.getParticipants().stream().map(Participant::new).toList();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

입력받은 String을 List로 변환하는 과정을 두 단계로 나누어
string을 List으로 변환하는 로직은 inputView에 위치하고,
List을 List와 같이 객체들의 리스트로 저장하는 로직은 컨트롤러에 작성하셨네요!

해당 로직을 모두 controller에서 진행하지 않고, 단계 별로 계층을 다르게 한 이유가 궁금합니다!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가로 participant 입력에 대한 검증 로직이 없어

  1. , 또는 ,,(,만으로 이루어진 문자열) 입력한 경우
  2. 공백 혹은 아무것도 입력하지 않은 경우

에 예외를 던지지 않고 다음 단계 입력 로직이 실행되네요! 적절한 위치에 검증 로직을 추가해 위와 같은 경우에도 예외문을 던지면 좋을 것 같은데, 어디에 위치시키면 좋을 것 같나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

입력받은 String을 List로 변환하는 과정을 두 단계로 나누어 string을 List으로 변환하는 로직은 inputView에 위치하고, List을 List와 같이 객체들의 리스트로 저장하는 로직은 컨트롤러에 작성하셨네요!

해당 로직을 모두 controller에서 진행하지 않고, 단계 별로 계층을 다르게 한 이유가 궁금합니다!

해당 방식의 경우 InputView에서는 정말 기본적인 입력값에 대한 처리(e.g., 목록의 경우 split 후 List로 변환)만 담당하고, 이후 해당 값들을 의미있는 객체로 변환하는 과정은 컨트롤러와 같이 관련 있는 로직에서 담당하는 것이 더 좋다고 판단하여 두 단계로 나누어서 이루어질 수 있도록 작성하였습니다!

Copy link
Author

@chemistryx chemistryx Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가로 participant 입력에 대한 검증 로직이 없어

  1. , 또는 ,,(,만으로 이루어진 문자열) 입력한 경우
  2. 공백 혹은 아무것도 입력하지 않은 경우

에 예외를 던지지 않고 다음 단계 입력 로직이 실행되네요! 적절한 위치에 검증 로직을 추가해 위와 같은 경우에도 예외문을 던지면 좋을 것 같은데, 어디에 위치시키면 좋을 것 같나요?

제가 테스트 해보았을때는 두 케이스(,, ,,) 모두 정상적으로 검증 로직(GameConfigurationBuilder::participants())이 실행되는데, 혹시 제가 놓친 부분이 있을까요?

image

공백 입력의 경우 #83 (comment) 에서 이어서 다루도록 하겠습니다!!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 테스트 해보았을때는 두 케이스(,, ,,) 모두 정상적으로 검증 로직(GameConfigurationBuilder::participants())이 실행되는데, 혹시 제가 놓친 부분이 있을까요?

다시 실행해보니 정상 작동하네요😂

builder.participants(participants);

List<Outcome> outcomes = InputView.getOutcomes().stream().map(Outcome::new).toList();
builder.outcomes(outcomes);

int height = InputView.getLadderHeight();
builder.height(height);

return builder.build();
}

private void handleOutcomeQuery(GameResult result) {
Stream.generate(InputView::getParticipantForResult)
.takeWhile((input) -> !input.equals("all"))
.forEach((input) -> handleSingleQuery(result, input));

OutputView.printGameResult(result);
}

private void handleSingleQuery(GameResult result, String input) {
Participant participant = findParticipantByName(result, input);

if (participant == null) {
System.out.println("존재하지 않는 참가자입니다.");
return;
}

OutputView.printGameResultOf(participant, result);
}

private Participant findParticipantByName(GameResult result, String name) {
return result.results().keySet().stream()
.filter((participant -> participant.name().equals(name)))
.findFirst()
.orElse(null);
}
}
81 changes: 81 additions & 0 deletions src/main/java/io/suhan/ladder/model/Game.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package io.suhan.ladder.model;

import io.suhan.ladder.model.ladder.Connection;
import io.suhan.ladder.model.ladder.Ladder;
import io.suhan.ladder.model.ladder.LadderFactory;
import io.suhan.ladder.model.ladder.Line;
import io.suhan.ladder.view.OutputView;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class Game {
private final Ladder ladder;
private final GameConfiguration configuration;

private Game(GameConfiguration configuration, Ladder ladder) {
this.configuration = configuration;
this.ladder = ladder;
}

public static Game of(GameConfiguration configuration) {
return new Game(configuration, LadderFactory.createLadder(configuration.width(), configuration.height()));
}

public static Game of(GameConfiguration configuration, Ladder ladder) {
return new Game(configuration, ladder);
}

public GameResult execute() {
List<Participant> participants = configuration.participants();
List<Outcome> outcomes = configuration.outcomes();
Map<Participant, Outcome> result = new LinkedHashMap<>();

for (int start = 0; start < configuration.width(); start++) {
int end = traverse(start);
Participant participant = participants.get(start);
Outcome outcome = outcomes.get(end);

result.put(participant, outcome);
}

return new GameResult(result);
}

private int traverse(int start) {
int col = start;

for (Line line : ladder.lines()) {
col = findNextColumn(line, col);
}

return col;
}

private int findNextColumn(Line line, int col) {
Optional<Connection> connected = line.connections().stream()
.filter((connection) -> connection.left() == col || connection.right() == col)
.findFirst();

return connected
.map((connection) -> getConnectedColumn(connection, col))
.orElse(col);
}

private int getConnectedColumn(Connection connection, int col) {
if (connection.left() == col) {
return connection.right();
}

return connection.left();
}

public Ladder getLadder() {
return ladder;
}

public GameConfiguration getConfiguration() {
return configuration;
}
}
16 changes: 16 additions & 0 deletions src/main/java/io/suhan/ladder/model/GameConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.suhan.ladder.model;

import java.util.Collections;
import java.util.List;

public record GameConfiguration(List<Participant> participants, List<Outcome> outcomes, int width, int height) {
@Override
public List<Participant> participants() {
return Collections.unmodifiableList(participants);
}

@Override
public List<Outcome> outcomes() {
return Collections.unmodifiableList(outcomes);
}
}
51 changes: 51 additions & 0 deletions src/main/java/io/suhan/ladder/model/GameConfigurationBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.suhan.ladder.model;

import java.util.List;

public class GameConfigurationBuilder {
private List<Participant> participants;
private List<Outcome> outcomes;
private int height;

public GameConfigurationBuilder participants(List<Participant> participants) {
if (participants == null) {
throw new IllegalStateException("참가자 목록이 설정되지 않았습니다.");
}

if (participants.isEmpty()) {
throw new IllegalArgumentException("참가자 목록은 비어 있을 수 없습니다.");
}

this.participants = participants;

return this;
}

public GameConfigurationBuilder outcomes(List<Outcome> outcomes) {
if (outcomes == null) {
throw new IllegalStateException("결과 목록이 설정되지 않았습니다.");
}

if (outcomes.size() != participants.size()) {
throw new IllegalArgumentException("참가자의 수와 실행 결과의 수는 같아야 합니다.");
}

this.outcomes = outcomes;

return this;
}

public GameConfigurationBuilder height(int height) {
if (height <= 0) {
throw new IllegalArgumentException("높이는 양수여야 합니다.");
}

this.height = height;

return this;
}

public GameConfiguration build() {
return new GameConfiguration(participants, outcomes, participants.size(), height);
}
}
15 changes: 15 additions & 0 deletions src/main/java/io/suhan/ladder/model/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.suhan.ladder.model;

import java.util.Collections;
import java.util.Map;

public record GameResult(Map<Participant, Outcome> results) {
@Override
public Map<Participant, Outcome> results() {
return Collections.unmodifiableMap(results);
}

public Outcome getOutcome(Participant participant) {
return results.get(participant);
}
}
9 changes: 9 additions & 0 deletions src/main/java/io/suhan/ladder/model/Outcome.java
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 클래스는 객체로 관리하기에는 이유가 부족해 보입니다.
검증 로직만을 담당하고 있다면, GameConfigurationBuilder::outcomes로 옮기는 것도 괜찮을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 Outcome 객체가 단순 검증 로직만을 담당하고 있는 것은 맞습니다! 하지만 만약 Outcome객체가 없었다면, 다음과 같은 방식으로 GameConfigurationBuilder에서 검증을 추가로 수행해야할 것으로 예상됩니다:

public GameConfigurationBuilder outcomes(List<String> outcomes) {
        if (outcomes == null || outcomes.size() != participants.size()) {
            throw new IllegalArgumentException("참가자의 수와 실행 결과의 수는 같아야 합니다.");
        }

        boolean isBlank = outcomes.stream()
                .map(String::trim)
                .anyMatch(String::isEmpty);

        if (isBlank) {
            throw new IllegalArgumentException("결과는 공백일 수 없습니다.");
        }

        this.outcomes = outcomes;

        return this;
    }

만약 Outcome 객체가 존재한다면 공백에 대한 검증은 이미 Outcome 객체 자체에서 수행되기에 추가 공백 검사 로직이 필요 없어져 코드를 더 간소화할 수 있는 이점이 존재한다고 생각합니다.
이러한 이점이 있기에 객체로 관리해보는 것도 고려해볼 수 있을 것 같은데, 이와 관련해서 해윤님 의견은 어떠신지 궁금합니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 수한님의 의견에 매우 동의합니다!
처음에는 단순 검증 로직만을 담당하는 VO가 과할 수도 있지 않을까 고민했는데,

측면에서 현재처럼 객체를 분리하는 구조를 유지하는 것이 좋을 것 같아요! 좋은 의견 감사합니다👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.suhan.ladder.model;

public record Outcome(String value) {
public Outcome {
if (value.isBlank()) {
throw new IllegalArgumentException("결과는 공백일 수 없습니다.");
}
}
}
15 changes: 15 additions & 0 deletions src/main/java/io/suhan/ladder/model/Participant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.suhan.ladder.model;

public record Participant(String name) {
public static final int PARTICIPANT_NAME_MAX_LENGTH = 5;

public Participant {
if (name.length() > PARTICIPANT_NAME_MAX_LENGTH) {
throw new IllegalArgumentException("참가자의 이름은 최대 " + PARTICIPANT_NAME_MAX_LENGTH + "자만 가능합니다.");
}

if (name.isBlank()) {
throw new IllegalArgumentException("참가자의 이름은 공백일 수 없습니다.");
}
Comment on lines +7 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수한님께서 어떤 검증 로직은 도메인 내부에 두고, 어떤 검증 로직은 GameConfigurationBuilder 내부에 두었는지 기준이 궁금합니다!

도메인 값 자체의 유효성 검증은 도메인 내부에서 이루어졌다고 한다면(Participant나 Outcome)
GameConfigurationBuilderparticipantsheight도 도메인 값 자체의 유효성 검증이라는 생각이 들어서요. 의견이 궁금합니다 💭

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구현 당시에는 객체 자체에 대한 검증(e.g., Participant 이름 최대 글자 수, Outcome 값 공백 여부)은 도메인 내부에서 수행하도록 의도하였고, 객체가 다른 객체로 wrapping된 경우(e.g., List<Participant>)에 대해서 검증이 필요한 경우에는 GameConfigurationBuilder에서 담당하도록 의도하였습니다.

하지만 말씀하신대로 height 검증 로직의 경우에는 도메인 값 자체의 유효성 검증을 수행하는것임에도 현재 GameConfigurationBuilder 내부에 존재하는데, 이 부분에 있어서는 heightOutcome간 기준이 모순되는 부분이 있는 것이 맞습니다. 😅

따라서 해당 부분에 대해서 생각을 해보았는데, 만약 제 구현 의도대로라면 height 역시 객체로 wrapping되어 그 내부에서 검증 로직을 수행하는 것이 타당하다고 생각합니다.
하지만 #83 (comment) 코멘트에서 말씀해주신 것 처럼 단순히 검증 로직 하나만을 위해 객체로 관리하는 것이 타당하지 않다고 한다면, Outcome 값 공백 유무에 대한 검증도 GameConfigurationBuilder에서 담당하는 것이 맞는 것 같습니다.

Outcome을 VO로 남길지 여부에 대한 의견은 #83 (comment) 에 이어서 남겨놓겠습니다!

}
}
3 changes: 3 additions & 0 deletions src/main/java/io/suhan/ladder/model/ladder/Connection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.suhan.ladder.model.ladder;

public record Connection(int left, int right) { }
11 changes: 11 additions & 0 deletions src/main/java/io/suhan/ladder/model/ladder/Ladder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.suhan.ladder.model.ladder;

import java.util.Collections;
import java.util.List;

public record Ladder(List<Line> lines) {
@Override
public List<Line> lines() {
return Collections.unmodifiableList(lines);
}
}
52 changes: 52 additions & 0 deletions src/main/java/io/suhan/ladder/model/ladder/LadderFactory.java
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드 호출 체인이 다소 깊은 것 같은데

indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.

위 요구사항 때문인가요? 저도 비슷한 로직 (createLine와 createConnections)은 합치려고 고민해보았지만 depth를 1까지만 허용한다는 조건때문에 쉽지 않은 것 같긴하네요🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞습니다! 사실 두 로직이 원래 하나의 메소드로 구성되어 있었는데, 해당 조건을 만족하기 위해서 지금과 같은 형태로 변경하게 되었습니다.

안그래도 로직을 나누면서 각 메소드 자체의 길이는 줄었지만, 메소드 내 호출되는 다른 메소드를 따라가면서 이해해야 하는 과정이 추가되었다는 점에서 좀 아쉽다는 생각이 들긴 했습니다 ㅠ

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.suhan.ladder.model.ladder;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class LadderFactory {
private static final Random DEFAULT_RANDOM = new Random();

public static Ladder createLadder(int width, int height) {
return createLadder(width, height, DEFAULT_RANDOM);
}

public static Ladder createLadder(int width, int height, Random random) {
List<Line> lines = createLines(width, height, random);

return new Ladder(lines);
}

private static List<Line> createLines(int width, int height, Random random) {
List<Line> lines = new ArrayList<>();

for (int i = 0; i < height; i++) {
lines.add(createLine(width, random));
}

return lines;
}

private static Line createLine(int width, Random random) {
List<Connection> connections = createConnections(width, random);

return new Line(connections);
}

private static List<Connection> createConnections(int width, Random random) {
List<Connection> connections = new ArrayList<>();

for (int i = 0; i < width - 1; i++) {
if (!shouldConnect(random)) continue;

connections.add(new Connection(i, i + 1));
i += 1; // skip the right next line
}

return connections;
}

private static boolean shouldConnect(Random random) {
return random.nextBoolean();
}
}
11 changes: 11 additions & 0 deletions src/main/java/io/suhan/ladder/model/ladder/Line.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.suhan.ladder.model.ladder;

import java.util.Collections;
import java.util.List;

public record Line(List<Connection> connections) {
@Override
public List<Connection> connections() {
return Collections.unmodifiableList(connections);
}
}
Loading