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

3단계 - 레이싱게임 #5739

Open
wants to merge 28 commits into
base: jongminch0i
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5933407
[chore] 네이밍 규칙 설정에 따른 패키지 이동
JongMinCh0i Sep 24, 2024
7ddf5fb
[delete] Rebase 에 의한 파일 중복 제거
JongMinCh0i Sep 27, 2024
9cf1712
[feature] 구분자 컴마(,)와 콜론(:) 그리고 숫자를 포함하는 문자열을 전달 받았을 경우 구분자를 기준으로 분리한 …
JongMinCh0i Sep 27, 2024
67ee6fb
[test] 테스트 케이스 추가
JongMinCh0i Sep 28, 2024
ee37ab5
[feature] 추상화 레이어 Calculate 추가
JongMinCh0i Sep 29, 2024
3461777
[refactor] 매직 넘버 및 리터럴 제거
JongMinCh0i Sep 29, 2024
41abb21
[feature] 예외 처리 기능 구현
JongMinCh0i Sep 29, 2024
51a2284
[feature] 문자열 덧셈 기능 구현
JongMinCh0i Sep 29, 2024
8c89ec7
[docs] 구현할 기능 목록 작성
JongMinCh0i Oct 3, 2024
d610ab1
[chore] 패키지 네이밍 수정
JongMinCh0i Oct 3, 2024
374c343
[docs] 기능할 기능 목록 수정
JongMinCh0i Oct 3, 2024
dfe8dda
[feat] 주어진 random 값이 조건 값 이상인지 판별하고 이를 True, False 로 반환하는 기능
JongMinCh0i Oct 3, 2024
94ee89a
[feat] 주어진 random 값이 조건 값 이상인지 판별하고 이를 True일 경우 distance 값이 1 증가하거나, …
JongMinCh0i Oct 3, 2024
f093e98
[refactor] 매직 리터럴 ENUM 처리
JongMinCh0i Oct 3, 2024
68fbe54
[feature] 게임 플레이의 Input, OutPut 기능 추가
JongMinCh0i Oct 3, 2024
215a2f4
[feature] 게임 플레이 기능 구현
JongMinCh0i Oct 3, 2024
5ec6324
[refactor] 뜻을 명확히 할 수 있는 변수명 변경
JongMinCh0i Oct 3, 2024
a9d166e
Merge branch 'jongminch0i' into step3
JongMinCh0i Oct 3, 2024
2f33e8d
Merge pull request #2 from JongMinCh0i/step3
JongMinCh0i Oct 3, 2024
c61d298
[docs] 4단계 미션을 위한 구현할 기능 목록 추가
JongMinCh0i Oct 3, 2024
3d07667
[docs] 3단계 구현할 기능 목록 작성 재작성, 4단계 구현할 기능 목록 이동
JongMinCh0i Oct 3, 2024
7a60ee1
[feat] 자동차 이름이 5글자를 초과할 경우 예외를 반환하는 기능 구현
JongMinCh0i Oct 3, 2024
c5a72f4
[feat] RacingCar 에 대한 유닛테스트 수행
JongMinCh0i Oct 3, 2024
d662b38
[feat] 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력하는 기능 추가
JongMinCh0i Oct 3, 2024
d3d6551
[feat] 게임을 시작하기 전 List 에 자동차를 저장(이름, 거리)하고 반환하는 기능
JongMinCh0i Oct 3, 2024
514ced0
[feat] 랜덤 값이 조건 값 이상일 경우 출력하는 기능 추가
JongMinCh0i Oct 3, 2024
c93ef74
[feat] 최종 우승자가 있을 경우 해당 우승자를 출력하는 기능
JongMinCh0i Oct 3, 2024
d6b3471
Merge pull request #3 from JongMinCh0i/step4
JongMinCh0i Oct 3, 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
17 changes: 17 additions & 0 deletions src/main/java/study/step_2/constant/error/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package study.step_2.constant.error;

public enum ErrorMessage {
INVALID_NEGATIVE_NUMBER("음수 값은 허용되지 않습니다."),
INVALID_NUMBER_TYPE("숫자 이외의 값은 허용되지 않습니다."),
INVALID_DELIMITER_TYPE("구분자는 숫자를 사용할 수 없습니다.");

private final String message;

ErrorMessage(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}
5 changes: 5 additions & 0 deletions src/main/java/study/step_2/domain/Calculate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package study.step_2.domain;

public interface Calculate {
int splitAndSum(String express);
}
154 changes: 154 additions & 0 deletions src/main/java/study/step_2/domain/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package study.step_2.domain;

import static study.step_2.constant.error.ErrorMessage.*;
import static study.step_2.domain.DelimConstant.*;

public class Calculator implements Calculate {

public static final int INVALID_INPUT_VALUE = 0;
public static final String USING_PREFIX = "//";
public Character customDelimiter;

public String expression;

public Calculator(String expression) {
if (BlankAndNull(expression)) return;
extractCustomDelim(expression);
validExpression(expression);
this.expression = expression;
}

private boolean BlankAndNull(String expression) {
if (checkBlankAndNull(expression)) {
this.expression = "0";
return true;
}
return false;
}

private boolean checkBlankAndNull(String expression) {
return expression == null || expression.trim().isEmpty();
}

private void extractCustomDelim(String expression) {
if (expression.startsWith("//")) {
customDelimiter = expression.substring(2, 3).charAt(0);
validCustomDelimiterDigit(customDelimiter);
}
}

private void validCustomDelimiterDigit(char c) {
if ((Character.isDigit(c))) {
throw new RuntimeException(INVALID_DELIMITER_TYPE.getMessage());
}
}

private void validExpression(String expression) {
validNegativeAndDelimiter(expression);
}

private void validNegativeAndDelimiter(String expression) {

if (startWithCustomDelimiter(expression)) {
validateInvalidCharacterWithCustomDelimiter(expression);
} else {
validateInvalidCharacter(expression);
}
}

private void validateInvalidCharacter(String expression) {
for (Character c : expression.toCharArray()) {
validateNegativeNumber(c);
validateInvalidCharacter(c);
}
}

private void validateNegativeNumber(Character c) {
if (c.equals('-') && customDelimiter != '-') {
throw new RuntimeException(INVALID_NEGATIVE_NUMBER.getMessage());
}
}

public boolean startWithCustomDelimiter(String expression) {
return expression.startsWith(USING_PREFIX);
}

private void validateInvalidCharacter(Character c) {
if (c != DELIMITER_COMMA.getValue() && c != DELIMITER_COLON.getValue()
&& !Character.isDigit(c)) {
throw new RuntimeException(INVALID_NUMBER_TYPE.getMessage());
}
}

private void validateInvalidCharacterWithCustomDelimiter(String expression) {
for(int i = 4; i < expression.length(); i++) {
char c = expression.charAt(i);
validateNumberType(c);
validateNegative(c);
}
}

private void validateNegative(char c) {
if (c == ('-') && customDelimiter != '-') {
throw new RuntimeException(INVALID_NEGATIVE_NUMBER.getMessage());
}
}

private void validateNumberType(char c) {
if (c != customDelimiter && !Character.isDigit(c) && c != '\n') {
throw new RuntimeException(INVALID_NUMBER_TYPE.getMessage());
}
}

@Override
public int splitAndSum(String express) {
int sumValue = 0;

if (expression.equals("0")) {
return 0;
}

if (customDelimiterUsing(customDelimiter)) {
return executeWithCustomDelimiter(express);
}

int i = executeWithDefaultDelimiter(express);
return (i >= 0) ? i : sumValue;
}

private int executeWithDefaultDelimiter(String express) {
int sumValue = 0;

for (int i = 0; i < express.length(); i++) {
sumValue += getValue(express, i);
}
return sumValue;
}

private static int getValue(String express, int i) {
if (express.charAt(i) != DELIMITER_COMMA.getValue() && express.charAt(i) != DELIMITER_COLON.getValue()) {
return Integer.parseInt(String.valueOf(express.charAt(i)));
}
return 0;
}

private boolean customDelimiterUsing(Character customDelimiter) {
return customDelimiter != null;
}

private int executeWithCustomDelimiter(String express) {
int sumValue = 0;

for (int i = 4; i < express.length(); i++) {
sumValue = getSumValue(express, i, sumValue);
}
return sumValue;
}

private int getSumValue(String express, int i, int sumValue) {
if (express.charAt(i) != DELIMITER_COMMA.getValue() && express.charAt(i) != DELIMITER_COLON.getValue() && express.charAt(i) != customDelimiter) {
sumValue += Integer.parseInt(String.valueOf(express.charAt(i)));
}
return sumValue;
}
}
17 changes: 17 additions & 0 deletions src/main/java/study/step_2/domain/DelimConstant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package study.step_2.domain;

public enum DelimConstant {

DELIMITER_COMMA(','),
DELIMITER_COLON(':');

private final char value;

DelimConstant(char value) {
this.value = value;
}

public char getValue() {
return value;
}
}
16 changes: 16 additions & 0 deletions src/main/java/study/step_2/service/AddCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package study.step_2.service;

import study.step_2.domain.Calculate;

public class AddCalculator {

private final Calculate calculate;

public AddCalculator(Calculate calculate) {
this.calculate = calculate;
}

public int executeAdd(String express) {
return calculate.splitAndSum(express);
}
}
31 changes: 31 additions & 0 deletions src/main/java/study/step_3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 🚀 3단계 - 자동차 경주
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 사용자는 몇 대의 자동차로 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4이상일 경우이다.
- 자동차의 상태를 화면에 출력한다. 어느 시점에 출력할 것인지에 대한 제약은 없다.

### play

---

- 전진하는 조건
- [ ] 주어진 random 값이 조건 값 이상인지 판별하고 결과에 따라서 이상일 경우 True, 이하일 경우 False 로 반환하는 기능
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- [ ] 주어진 random 값이 조건 값 이상인지 판별하고 결과에 따라서 이상일 경우 True, 이하일 경우 False 로 반환하는 기능
- [x] 주어진 random 값이 조건 값 이상인지 판별하고 결과에 따라서 이상일 경우 True, 이하일 경우 False 로 반환하는 기능

미리 메모해둔 기능을 모두 완성하셨다면 체크도 해주세요.


### input

---

- 게임 플레이를 위한 자동차 대수와 횟수를 입력
- [ ] 자동차 대수를 정수로 입력받는 기능
- [ ] 자동차 횟수를 정수로 입력받는 기능

### output

---

- 게임에 대한 안내 문구를 출력
- [ ] 자동차 대수가 몇 대 인지 안내문구를 출력하는 기능
- [ ] 시도할 횟수가 몇 회 인지를 안내문구를 출력하는 기능
- [ ] 실행결과 안내 문구를 출력하는 기능
- 각 시도 횟수 당 이동한 자동차에 대해서 출력
- [ ] 이동한 자동차에 대해서는 ‘-’ 를 출력하는 기능
61 changes: 61 additions & 0 deletions src/main/java/study/step_3/RacingCarGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package study.step_3;

import study.step_3.service.RacingCar;
import study.step_3.ui.UiController;

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

public class RacingCarGame {

private final UiController uiController;

public RacingCarGame(UiController uiController) {
this.uiController = uiController;
}

public List<RacingCar> setUpRacingCar(int numberOfCars) {
ArrayList<RacingCar> garage = new ArrayList<>();
for (int i = 0; i < numberOfCars; i++) {
garage.add(new RacingCar());
}
return garage;
}

public void gamePlay() {
int numberOfCars = uiController.welcomeMessage();
int attempt = uiController.askAttemptMessage();
List<RacingCar> garage = setUpRacingCar(numberOfCars);
startGame(attempt, numberOfCars, garage);
}

private void startGame(int attempt, int numberOfCars, List<RacingCar> garage) {
uiController.endGameMessage();

for (int i = 0; i < attempt; i++) {
startRacing(numberOfCars, garage);
System.out.println();
}
}

private void startRacing(int numberOfCars, List<RacingCar> garage) {
for (int j = 0; j < numberOfCars; j++) {
racingCarMoving(garage, j);
}
}

private void racingCarMoving(List<RacingCar> garage, int numberOfCar) {
int distance = garage.get(numberOfCar).drive(10, 4);

if ((distance > 0)) {
uiController.SkidMark(distance);
}

uiController.cantDrive(distance);
}

public static void main(String[] args) {
RacingCarGame game = new RacingCarGame(new UiController());
game.gamePlay();
}
}
28 changes: 28 additions & 0 deletions src/main/java/study/step_3/service/RacingCar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package study.step_3.service;

import java.util.Random;

public class RacingCar {

private int distance = 0;
Random random = new Random();
Copy link
Member

Choose a reason for hiding this comment

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

  1. Random의 접근제한자를 지정하지 않고 default로 설정한 이유가 있을까요?
  2. RacingCar 에서 직접 Random 을 사용한다면 테스트 작성이 어려우셨을 것 같습니다. 이 부분에 대해서는 어떤 점을 느끼셨는지 궁금해요.

Copy link
Author

Choose a reason for hiding this comment

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

  1. default 로 설정한 이유보다는 제가 신경쓰지 못 했습니다.
  2. RacingCar 에서 getRandomValue 를 통해서 랜덤값을 호출하여 테스트를 작성했었습니다. 이 부분이 어렵다고 생각은 들지 않았습니다만, 아래 리뷰를 통해 RacingCar가 랜덤값을 생성하고 있는 것이 옳지 않다고 생각했습니다.


public int drive(int bound, int condition) {
Copy link
Member

Choose a reason for hiding this comment

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

bound는 무작위의 최대값을 정하기 위해 받는 매개변수입니다.
RacingCar 에서 bound를 주입받을 필요가 있을까요?

힌트를 몇 가지 드려보겠습니다.

  1. 0 ~ 9까지 무작위로 점수를 생성하는건 무작위 의 역할입니다. RacingCar의 역할은 아닙니다.
  2. condition 으로 특정 값 이상이면 전진하도록 하고 있습니다. condition은 4로 고정되어 있구요. condition은 누구의 역할일까요? 주입받는 방향으로 설계를 하셨다면 누구의 역할에 더 가까운지 고려해보시면 좋습니다.

if (canMove(bound, condition)) {
Move();
}
return distance;
}

public int getRandomValue(int bound) {
return random.nextInt(bound);
}

private boolean canMove(int bound, int condition) {
return getRandomValue(bound) >= condition;
}

private void Move() {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private void Move() {
private void move() {

소문자로 바꿔주세요

distance += 1;
}
}
19 changes: 19 additions & 0 deletions src/main/java/study/step_3/ui/InfoMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package study.step_3.ui;

public enum InfoMessage {
CAR_COUNT_QUESTION("자동차 대수는 몇 대 인가요?"),
ATTEMPT_COUNT_QUESTION("시도할 회수는 몇 회 인가요?"),
EXECUTION_RESULT("실행 결과"),
WHEEL_LOSS("바퀴가 빠졌습니다..."),
SKID_MARK("-");

private final String message;

InfoMessage(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}
Loading