Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[사다리 타기] 김동균 미션 제출합니다. #4

Open
wants to merge 31 commits into
base: dongkyun0713
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2ed04d7
docs: 구현할 기능 목록 작성
YehyeokBang May 8, 2024
c4f63be
test: 참여자 관련 테스트 작성
dongkyun0713 May 8, 2024
e5c8d33
feat: 참여자 구현
dongkyun0713 May 8, 2024
ce5e44a
test: 참여자들 관련 테스트 작성
YehyeokBang May 8, 2024
105c223
feat: 참여자의 이름을 반환하는 기능 추가
YehyeokBang May 8, 2024
3afef84
feat: 참여자들 구현
YehyeokBang May 8, 2024
b149802
test: 고정된 값을 가지는 생성전략 추가
dongkyun0713 May 9, 2024
291c80b
feat: 생성전략 인터페이스 추가
dongkyun0713 May 9, 2024
d9deef5
test: 랜덤 생성 전략 테스트 추가
dongkyun0713 May 9, 2024
a3a68d9
feat: 생성전략을 상속받는 랜덤 생성 전략 추가
dongkyun0713 May 9, 2024
489e113
feat: 존재 여부에 대한 필드를 가지는 다리 추가
dongkyun0713 May 9, 2024
5a312a5
test: 행 생성이 올바르게 되는지 테스트
dongkyun0713 May 9, 2024
b4091ba
feat: 행을 생성하는 행 클래스 추가
dongkyun0713 May 9, 2024
a586326
fix: 변수명 및 테스트 수정
YehyeokBang May 10, 2024
0393ada
test: 사다리 관련 테스트 작성
YehyeokBang May 10, 2024
468d016
feat: 사다리 구현
YehyeokBang May 10, 2024
d97ec88
refactor: 패키지 구조 변경
YehyeokBang May 10, 2024
4eb46b4
feat: 사다리 정보를 반환하는 기능 구현
YehyeokBang May 10, 2024
39a89bb
feat: 입력 및 출력 담당 구현
YehyeokBang May 10, 2024
a9a4c0f
feat: 사다리 게임 구현
YehyeokBang May 10, 2024
c50ea9a
docs: 리드미 수정
YehyeokBang May 11, 2024
89ad7c2
fix: 사용자 검증을 한번에 하도록 사용자 검증 메소드 추가
dongkyun0713 May 20, 2024
73a9881
중복된 플레이어 검사 로직 수정
dongkyun0713 May 20, 2024
58bd155
refactor: 들여쓰기가 1이 되도록 수정
dongkyun0713 May 20, 2024
9894ad3
refactor: 사다리가 정상적으로 생성되는지 테스트 로직 수정
dongkyun0713 May 20, 2024
59de099
refactor: 메소드 이름 수정
dongkyun0713 May 20, 2024
93374cc
refactor:static 제거
dongkyun0713 May 20, 2024
e9a3a39
refactor:reader.close 추가
dongkyun0713 May 22, 2024
8ec798b
refacor:StringBuilder를 통한 성능 개선
dongkyun0713 May 22, 2024
b0f375a
refactor: 매개변수명 더 직관적으로 수정
dongkyun0713 May 22, 2024
2f0bf29
refactor:StringBuilder를 StringBuffer로 변경
dongkyun0713 May 23, 2024
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
35 changes: 35 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## 구현할 기능 목록

- [x] 참여할 사람 이름을 입력
- [x] 참여할 사람 입력을 위한 안내 출력
- [x] 참여할 사람 이름을 쉼표(,)를 기준으로 분리
- [x] 참여할 사람 이름을 입력받아 유효성 검사

- [x] 사다리 높이 입력
- [x] 사다리 높이 입력을 위한 안내 출력
- [x] 사다리 높이를 입력받아 유효성 검사

- [x] 결과 출력
- [x] 참여한 사람의 이름 출력
- [x] 사다리 결과 출력

## 우리가 생각한 사다리

- 하나의 행은 여러 개의 연결로 이루어져 있고, 사다리는 여러 개의 행으로 이루어져 있다.

## 우리가 필요하다고 생각하는 객체

- 참여자
- 해야할 일: 참여할 사람 이름을 입력받아 유효성 검사
- 참여자들
- 해야할 일: 참여할 사람들을 생성하고 중복 여부를 검사
- 기준
- 해야할 일: 사다리 생성 전략을 담당하고 기준에 맞게 연결 여부를 생성
- 행
- 해야할 일: 사다리의 가로줄을 담당하고 기준에 맞게 무작위로 연결
- 연결
- 해야할 일: 연결 여부를 판단하고 연결된 결과를 반환
- 사다리
- 해야할 일: 사다리 높이에 맞게 행을 생성
- 입출력 뷰
- 해야할 일: 사용자 입력을 받고 결과를 출력
Comment on lines +1 to +35

Choose a reason for hiding this comment

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

구현 기능 목록 작성 👍

다만, "우리가"나 "해야할 일:" 같은 표현은 해당 README가 기능 명세라기보단 메모에 가까운 느낌을 줄 수 있습니다.

8 changes: 8 additions & 0 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import controller.LadderGameController;

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

import model.ladder.Ladder;
import model.ladder.strategy.RandomGenerateStrategy;
import model.players.Players;
import view.InputView;
import view.OutputView;

public class LadderGameController {
public void run() {
Players players = initPlayers();
int countOfPlayers = players.countOfPlayers();
int height = InputView.readHeight();
Ladder ladder = initLadder(countOfPlayers, height);
OutputView.printResult(players.getPlayerNames(), ladder.getLadderInformation());
}

private Players initPlayers() {
return new Players(InputView.readPlayerNames());
}

private Ladder initLadder(int countOfPlayers, int height) {
Ladder ladder = initLadderStrategy();
ladder.init(countOfPlayers, height);
return ladder;
}

private Ladder initLadderStrategy() {
return new Ladder(new RandomGenerateStrategy());
}
}
16 changes: 16 additions & 0 deletions src/main/java/model/ladder/Bridge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package model.ladder;

public enum Bridge {
EMPTY, EXIST;

public static Bridge of(boolean exist) {
if (exist) {
return EXIST;
}
return EMPTY;
}

public boolean isExist() {
return this == EXIST;
}
}
29 changes: 29 additions & 0 deletions src/main/java/model/ladder/Ladder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package model.ladder;

import model.ladder.strategy.GenerateStrategy;

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

public class Ladder {
private final GenerateStrategy generateStrategy;
private final List<Row> rows = new ArrayList<>();

public Ladder(GenerateStrategy generateStrategy) {
this.generateStrategy = generateStrategy;
}

public void init(int countOfPlayer, int countOfHeight) {
for (int i = 0; i < countOfHeight; i++) {
rows.add(new Row(countOfPlayer, generateStrategy));
}
}

public List<List<Boolean>> getLadder() {
return rows.stream()
.map(row -> row.getBridges().stream()
.map(Bridge::isExist)
.toList())
.toList();
}
}
25 changes: 25 additions & 0 deletions src/main/java/model/ladder/Row.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package model.ladder;

import model.ladder.strategy.GenerateStrategy;

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

public class Row {
private final GenerateStrategy generateStrategy;
private final List<Bridge> bridges = new ArrayList<>();

public Row(int countOfPlayer, GenerateStrategy generateStrategy) {
this.generateStrategy = generateStrategy;
generateRow(countOfPlayer);
}

private void generateRow(int countOfPlayer) {
generateStrategy.generate(countOfPlayer)
.forEach(bridge -> bridges.add(Bridge.of(bridge)));
}
Comment on lines +12 to +20

Choose a reason for hiding this comment

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

전략 패턴을 활용한 것이 눈에 띄네요.

혹시 전략 패턴에 대해서 학습한 것이 있다면 말씀해주실 수 있나요?

Copy link
Author

Choose a reason for hiding this comment

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

이번에 전략 패턴을 처음으로 사용해보면서 공부해봤습니다!
전략 패턴은 어떤 일을 수행하는 알고리즘이 여러 개일 때 적합한 패턴으로, 이번 미션에서는 랜덤으로 값을 생성하는 객체와 고정된 값을 생성하는 객체 2개가 있으므로 전략 패턴을 활용했습니다.


public List<Bridge> getBridges() {
return bridges;
}
}
7 changes: 7 additions & 0 deletions src/main/java/model/ladder/strategy/GenerateStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package model.ladder.strategy;

import java.util.List;

public interface GenerateStrategy {
List<Boolean> generate(int countOfPlayer);
}
Comment on lines +5 to +7

Choose a reason for hiding this comment

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

추상화 활용 👍

47 changes: 47 additions & 0 deletions src/main/java/model/ladder/strategy/RandomGenerateStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package model.ladder.strategy;

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

public class RandomGenerateStrategy implements GenerateStrategy {
private static final int BRIDGE_UNCONNECTED = 0;
private static final int MINIMUM_PLAYER_COUNT = 2;

@Override
public List<Boolean> generate(int countOfPlayer) {
validatePlayerCount(countOfPlayer);
return createBridges(countOfPlayer - 1);
}

private void validatePlayerCount(int countOfPlayer) {
if (countOfPlayer < MINIMUM_PLAYER_COUNT) {
throw new IllegalArgumentException("플레이어는 최소 2명 이상이어야 합니다.");
}
}

private List<Boolean> createBridges(int countOfBridge) {
List<Boolean> bridges = new ArrayList<>();
for (int i = 0; i < countOfBridge; i++) {
bridges.add(decideBridgeConnection(bridges, i));
}
return bridges;
}

private boolean decideBridgeConnection(List<Boolean> bridges, int currentIndex) {
if (wasPreviousBridgeConnected(bridges, currentIndex)) {
return false;
}
return decideBridgeConnection();
}

private boolean wasPreviousBridgeConnected(List<Boolean> bridges, int currentIndex) {
if (currentIndex == 0) {
return false;
}
return bridges.get(currentIndex - 1);
}

private boolean decideBridgeConnection() {
return (int) (Math.random() * 2) != BRIDGE_UNCONNECTED;
}
}
61 changes: 61 additions & 0 deletions src/main/java/model/players/Player.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package model.players;

public class Player {
private static final int MAXIMUM_ALLOWED_LENGTH = 5;

private final String name;

public Player(String name) {
checkPlayerName(name);
this.name = name;
}

protected String getName() {
return name;
}

private void checkPlayerName(String name) {
checkNameIsNotNull(name);
checkNameIsNotEmpty(name);
checkNameLength(name);
checkNameIsNotNumber(name);
checkNameIsNotKorean(name);
checkNameIsNotSpecialCharacter(name);
Comment on lines +18 to +23

Choose a reason for hiding this comment

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

사용자 이름에 대한 메소드들을 잘 분리해두셨군요.

다만, 생성자에서는 validatePlayerName 메소드만 두고, 해당 메소드 안에 이 코드들을 넣어두는 것이 추상화 수준이 잘 맞을 것 같습니다. 개발자가 생성자를 읽었을 때, 어떤 검증을 하는지 상세하게 아는 것보단, '사용자의 이름은 검증을 거친다.' 수준의 정보만 알아도 충분합니다.

}

private void checkNameIsNotNull(String name) {
if (name == null) {
throw new IllegalArgumentException("참여자 이름은 null일 수 없습니다.");
}
}

private void checkNameIsNotEmpty(String name) {
if (name.isEmpty()) {
throw new IllegalArgumentException("참여자 이름은 빈 문자열일 수 없습니다.");
}
}

private void checkNameLength(String name) {
if (name.length() > MAXIMUM_ALLOWED_LENGTH) {
throw new IllegalArgumentException("참여자 이름은 5글자를 넘을 수 없습니다.");
}
}

private void checkNameIsNotNumber(String name) {
if (name.matches(".*\\d.*")) {
throw new IllegalArgumentException("참여자 이름에 숫자가 포함될 수 없습니다.");
}
}

private void checkNameIsNotKorean(String name) {
if (name.matches(".*[ㄱ-ㅎㅏ-ㅣ가-힣].*")) {
throw new IllegalArgumentException("참여자 이름에 한글이 포함될 수 없습니다.");
}
}

private void checkNameIsNotSpecialCharacter(String name) {
if (name.matches(".*[!@#$%^&*()].*")) {
throw new IllegalArgumentException("참여자 이름에 특수문자가 포함될 수 없습니다.");
}
}
}
44 changes: 44 additions & 0 deletions src/main/java/model/players/Players.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package model.players;

import java.util.List;
import java.util.stream.Collectors;

public class Players {
private static final int MINIMUM_ALLOWED_LENGTH = 2;

private final List<Player> players;

public Players(List<String> playerNames) {
checkPlayersLength(playerNames);
checkPlayersIsNotDuplicate(playerNames);
this.players = generatePlayers(playerNames);
}

public List<String> getPlayerNames() {
return players.stream()
.map(Player::getName)
.toList();
}

public int countOfPlayers() {
return players.size();
}

private void checkPlayersLength(List<String> playerNames) {
if (playerNames.size() < MINIMUM_ALLOWED_LENGTH) {
throw new IllegalArgumentException("플레이어는 최소 2명 이상이어야 합니다.");
}
}

private void checkPlayersIsNotDuplicate(List<String> playerNames) {
if (playerNames.size() != playerNames.stream().distinct().count()) {
throw new IllegalArgumentException("중복된 플레이어가 존재합니다.");
}
}

private List<Player> generatePlayers(List<String> playerNames) {
return playerNames.stream()
.map(Player::new)
.toList();
}
}
41 changes: 41 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package view;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;

public class InputView {
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

public static List<String> readPlayerNames() {
System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)");
try {
String input = reader.readLine();
String removedInput = removeBlank(input);
return split(removedInput);
} catch (IOException e) {
throw new IllegalArgumentException("비정상적인 입력입니다. 다시 입력해주세요.");
}
}

public static int readHeight() {
System.out.println("최대 사다리 높이는 몇 개인가요?");
try {
String input = reader.readLine();
reader.close();
return Integer.parseInt(input);
} catch (NumberFormatException | IOException e) {
throw new IllegalArgumentException("높이는 숫자로 입력해주세요.");
}
}

private static String removeBlank(String input) {
return input.replaceAll(" ", "");
}

private static List<String> split(String input) {
return Arrays.asList(input.split(","));
}
}
Loading