Skip to content

Commit

Permalink
[1단계 - 사다리 생성] 몰리(김지민) 미션 제출합니다. (#314)
Browse files Browse the repository at this point in the history
* docs(README.md): 기능 요구사항 정리

* test(PersonTest): 이름 검증 로직 테스트 작성

* feat(Person): 이름 검증 로직 작성

* test(LineTest): 라인 검증 로직 테스트 작성

* feat(Line): 라인 검증 로직 작성

* test(PeopleTest): People 검증 테스트 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(People): People 검증 로직 작성

Co-authored-by: HaiSeong <[email protected]>

* test(LadderTest): Ladder 검증 테스트 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(Ladder): Ladder 검증 로직 작성

Co-authored-by: HaiSeong <[email protected]>

* test(LineTest): Line의 경로가 연달아 있지 않은지 검증하는 테스트 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(Line): 연달아 있지 않은 Line의 경로를 생성하는 로직 작성

Co-authored-by: HaiSeong <[email protected]>

* test(NameParserTest): 입력받은 이름을 파싱하는 테스트 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(NameParser): 입력받은 이름을 파싱하는 로직 작성

- InputView 에 입력받은 텍스트를 파싱해 전달하는 메서드 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(InputView): 높이를 입력받는 로직 작성

Co-authored-by: HaiSeong <[email protected]>

* test(NameFormatterTest): 형식에 맞춰 이름을 반환하는 테스트 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(NameFormatter): 형식에 맞춰 이름을 반환하는 로직 작성

Co-authored-by: HaiSeong <[email protected]>

* test(LineFormatterTest): 형식에 맞춰 라인을 반환하는 기능 테스트 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(LineFormatter): 형식에 맞춰 라인을 반환하는 기능 작성

Co-authored-by: HaiSeong <[email protected]>

* test(NamesFormatterTest): 형식에 맞춰 이름들을 반환하는 테스트 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(NamesFormatter): 형식에 맞춰 이름들을 반환하는 로직 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(OutputView): 형식에 맞춰 실행결과를 출력하는 로직 작성

Co-authored-by: HaiSeong <[email protected]>

* feat(InputView): 입력 문구 출력 추가

Co-authored-by: HaiSeong <[email protected]>

* feat(Ladder, LadderTest): 사다리의 정적 팩토리 메서드에 인원수 인자 추가

Co-authored-by: HaiSeong <[email protected]>

* refactor(LineTest): 연결 여부 변수명 교체

Co-authored-by: HaiSeong <[email protected]>

* feat(Result, LineDto): 출력에 필요한 Dto 추가

Co-authored-by: HaiSeong <[email protected]>

* feat(LadderGame, Application): 컨트롤러 작성 및 메인 메서드 추가

Co-authored-by: HaiSeong <[email protected]>

* refactor(LineTest): 경로 존재 여부 원시값 포장 테스트 작성

Co-authored-by: HaiSeong <[email protected]>

* refactor(Line, Path, LineInfo): 경로 존재 여부 원시값 포장

Co-authored-by: HaiSeong <[email protected]>

* refactor(LineTest): 연달아 있는 사다리 테스트 로직 수정

Co-authored-by: HaiSeong <[email protected]>

* style(PeopleTest, PersonTest, NamesFormatterTest): 필요없는 줄 제거

Co-authored-by: HaiSeong <[email protected]>

* refactor(LineTest): 라인의 생성자로 값 리스트를 받도록 변경

Co-authored-by: HaiSeong <[email protected]>

* refactor(Line): 라인의 생성자 변경, 검증로직 추가

Co-authored-by: HaiSeong <[email protected]>

* refactor(LadderTest): 사다리 정적 팩토리 메서드 인자 변경

Co-authored-by: HaiSeong <[email protected]>

* refactor(src): 메서드 및 변수에 final 키워드 추가

Co-authored-by: HaiSeong <[email protected]>

* style(NameParserTest): 파일 끝에 개행 추가

* refactor(formatter): formatter의 패키지 위치를 view 아래로 이동

* refactor(Ladder): pathCount 변수 추출

* refactor(Line): 인원 수 검증이 아닌 경로의 사이즈로 검증

* fix(Line): 경로 검증 시 depth를 1으로 줄이기 위해 메서드 추출

* refactor(parser): 패키지 위치를 view 아래로 이동

* test(LadderTest): 사다리 생성에 대한 테스트 추가

* feat(FixedPathGenerator): 테스트를 위한 PathGenerator 구현체 생성

* feat(Line): equals 메서드 구현

* feat(Application): 예외 처리 추가

* style(PersonTest): 파일 끝에 개행 추가

---------

Co-authored-by: HaiSeong <[email protected]>
  • Loading branch information
jminkkk and HaiSeong authored Feb 26, 2024
1 parent 6cd50d6 commit 8d5484d
Show file tree
Hide file tree
Showing 27 changed files with 654 additions and 0 deletions.
19 changes: 19 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### 기능 요구사항 정리
- [x] 사람의 이름을 갖는다.
- [x] 이름은 최소 1글자 최대 5글자다.
- [x] 라인은 사다리의 가로 한줄을 의미한다.
- [x] 한 라인은 사람수보다 하나 적은 폭을 갖는다.
- [x] 사다리의 경로(path:가로라인)은 연달아 있을 수 없다. (나란히 있을 수 없다.)
- [x] 참여할 사람의 이름은 쉼표로 구분해 받는다.
- [x] 사람은 최소 2명 참가해야한다.
- [x] 사다리의 높이를 입력받는다.
- [x] 최소 높이는 1 이상이다.
- [x] 실행 결과를 출력한다.
- [x] 사다리를 출력한다.
- [x] 라인의 첫 부분은 ` ` 4칸으로 출력한다.
- [x] 5칸과 가장 오른쪽에 `|`를 높이만큼 출력한다.
- [x] 라인에 경로가 있는 경우 `-`를 5칸을 출력한다.
- [x] 라인에 경로가 없는 경우 ` `를 5칸을 출력한다.
- [x] 이름을 출력한다.
- [x] 이름이 4글자 이하인 경우, 오른쪽에 하나의 공백을 두고 우측 정렬한다.
- [x] 이름이 5글자인 경우, 공백없이 우측 정렬한다.
14 changes: 14 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import controller.LadderGame;
import view.InputView;
import view.OutputView;

public class Application {
public static void main(String[] args) {
try {
final LadderGame ladderGame = new LadderGame(new InputView(), new OutputView());
ladderGame.play();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/controller/LadderGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package controller;

import dto.Result;
import java.util.List;
import model.Ladder;
import model.People;
import model.path.RandomPathGenerator;
import view.InputView;
import view.OutputView;

public class LadderGame {
private final InputView inputView;
private final OutputView outputView;

public LadderGame(final InputView inputView, final OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

public void play() {
final People people = initPeople();
final Ladder ladder = initLadder(people.getPersonCount());

final Result result = Result.from(people, ladder);
outputView.printResult(result);
}


private People initPeople() {
final List<String> names = inputView.inputNames();
return People.from(names);
}

private Ladder initLadder(final int personCount) {
final int height = inputView.inputHeight();
return Ladder.from(height, personCount, new RandomPathGenerator());
}
}
11 changes: 11 additions & 0 deletions src/main/java/dto/LineInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dto;

import java.util.List;
import model.Line;

public record LineInfo(List<Boolean> lineInfo) {
public static LineInfo from(final Line line) {
final List<Boolean> lineInfo = line.getExistFlags();
return new LineInfo(lineInfo);
}
}
17 changes: 17 additions & 0 deletions src/main/java/dto/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dto;

import java.util.List;
import model.Ladder;
import model.People;

public record Result(List<String> names, List<LineInfo> lines) {
public static Result from(final People people, final Ladder ladder) {
final List<String> names = people.getNames();
final List<LineInfo> lines = ladder.getLines()
.stream()
.map(LineInfo::from)
.toList();

return new Result(names, lines);
}
}
34 changes: 34 additions & 0 deletions src/main/java/model/Ladder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package model;

import java.util.ArrayList;
import java.util.List;
import model.path.PathGenerator;

public class Ladder {
private final List<Line> lines;

private Ladder(final List<Line> lines) {
this.lines = lines;
}

public static Ladder from(final int height, final int personCount, final PathGenerator pathGenerator) {
validateHeight(height);
final List<Line> lines = new ArrayList<>();
for (int i = 0; i < height; i++) {
int pathCount = personCount - 1;
final Line line = new Line(pathGenerator.generate(pathCount));
lines.add(line);
}
return new Ladder(lines);
}

private static void validateHeight(final int height) {
if (height < 1) {
throw new IllegalArgumentException("사다리의 높이는 1 이상이어야 합니다.");
}
}

public List<Line> getLines() {
return lines;
}
}
55 changes: 55 additions & 0 deletions src/main/java/model/Line.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package model;

import java.util.List;
import java.util.Objects;
import model.path.Path;

public class Line {
private static final int MIN_PATH_SIZE = 1;

private final List<Path> paths;

public Line(final List<Path> paths) {
validatePathCount(paths.size());
checkContinuousPaths(paths);
this.paths = paths;
}

private void validatePathCount(final int pathSize) {
if (pathSize < MIN_PATH_SIZE) {
throw new IllegalArgumentException("사다리의 경로는 비어있더라도 최소 1개 이상이여야 합니다.");
}
}

private void checkContinuousPaths(final List<Path> paths) {
for (int i = 0; i < paths.size() - 1; i++) {
final Path left = paths.get(i);
final Path right = paths.get(i + 1);
validateContinuousPaths(left, right);
}
}

private void validateContinuousPaths(final Path left, final Path right) {
if (left == Path.EXIST && right == Path.EXIST) {
throw new IllegalArgumentException("사다리의 경로는 연달아 있을 수 없습니다.");
}
}

public List<Boolean> getExistFlags() {
return paths.stream()
.map(Path::isExist)
.toList();
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Line line = (Line) o;
return Objects.equals(paths, line.paths);
}
}
37 changes: 37 additions & 0 deletions src/main/java/model/People.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package model;

import java.util.List;

public class People {
private static final int MIN_PERSON_COUNT = 2;

private final List<Person> personGroup;

private People(final List<Person> personGroup) {
validatePersonCount(personGroup.size());
this.personGroup = personGroup;
}

private void validatePersonCount(final int personCount) {
if (personCount < MIN_PERSON_COUNT) {
throw new IllegalArgumentException("사람은 최소 2명 참가해야 합니다.");
}
}

public static People from(final List<String> names) {
final List<Person> personGroup = names.stream()
.map(Person::new)
.toList();
return new People(personGroup);
}

public List<String> getNames() {
return personGroup.stream()
.map(Person::getName)
.toList();
}

public int getPersonCount() {
return personGroup.size();
}
}
24 changes: 24 additions & 0 deletions src/main/java/model/Person.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package model;

public class Person {
private static final int MIN_NAME_LENGTH = 1;
private static final int MAX_NAME_LENGTH = 5;

private final String name;

public Person(final String name) {
validateNameLength(name);
this.name = name;
}

private void validateNameLength(final String name) {
final int length = name.length();
if (length < MIN_NAME_LENGTH || length > MAX_NAME_LENGTH) {
throw new IllegalArgumentException("이름은 최소 1글자 최대 5글자여야 합니다.");
}
}

public String getName() {
return name;
}
}
16 changes: 16 additions & 0 deletions src/main/java/model/path/FixedPathGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package model.path;

import java.util.List;

public class FixedPathGenerator implements PathGenerator {
private final List<Path> lines;

public FixedPathGenerator(final List<Path> lines) {
this.lines = lines;
}

@Override
public List<Path> generate(final int count) {
return this.lines;
}
}
16 changes: 16 additions & 0 deletions src/main/java/model/path/Path.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package model.path;

public enum Path {
EXIST(true),
NOT_EXIST(false);

private final boolean isExist;

Path(final boolean isExist) {
this.isExist = isExist;
}

public boolean isExist() {
return isExist;
}
}
7 changes: 7 additions & 0 deletions src/main/java/model/path/PathGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package model.path;

import java.util.List;

public interface PathGenerator {
List<Path> generate(final int count);
}
34 changes: 34 additions & 0 deletions src/main/java/model/path/RandomPathGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package model.path;

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

public class RandomPathGenerator implements PathGenerator {
private final Random random = new Random();

@Override
public List<Path> generate(final int count) {
final List<Path> paths = new ArrayList<>();
while (paths.size() < count) {
Path randomPath = getNextPath(paths);
paths.add(randomPath);
}
return paths;
}

private Path getNextPath(final List<Path> paths) {
if (!paths.isEmpty() && getLastPath(paths) == Path.EXIST) {
return Path.NOT_EXIST;
}
if (random.nextBoolean()) {
return Path.EXIST;
}
return Path.NOT_EXIST;
}

private Path getLastPath(final List<Path> paths) {
return paths.get(paths.size() - 1);
}

}
21 changes: 21 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package view;

import java.util.List;
import java.util.Scanner;
import view.parser.NameParser;

public class InputView {
private final Scanner scanner = new Scanner(System.in);

public List<String> inputNames() {
System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)");
final String text = scanner.next();
return NameParser.parse(text);
}

public int inputHeight() {
System.out.println();
System.out.println("최대 사다리 높이는 몇 개인가요?");
return scanner.nextInt();
}
}
32 changes: 32 additions & 0 deletions src/main/java/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package view;

import dto.LineInfo;
import dto.Result;
import view.formatter.LineFormatter;
import view.formatter.NamesFormatter;
import java.util.List;

public class OutputView {

public void printResult(final Result result) {
System.out.println();
System.out.println("실행결과");
System.out.println();
final List<String> names = result.names();
printNames(names);
printLines(result.lines());
}

public void printNames(final List<String> names) {
System.out.println(NamesFormatter.format(names));
}

public void printLines(final List<LineInfo> lines) {
lines.forEach(this::printLine);
}

private void printLine(final LineInfo line) {
final List<Boolean> paths = line.lineInfo();
System.out.println(LineFormatter.format(paths));
}
}
Loading

0 comments on commit 8d5484d

Please sign in to comment.