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단계 - 자동차 경주 #6062

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
417bc2f
feat: StringTest Task.1
jlee0505 Mar 10, 2025
00d600f
feat: StringTest Task.2
jlee0505 Mar 12, 2025
f0d569d
feat: StringTest Task.3
jlee0505 Mar 12, 2025
d50617f
feat: SetTest Task.1
jlee0505 Mar 12, 2025
2850c2b
feat: SetTest Task.2
jlee0505 Mar 12, 2025
4b4f543
feat: SetTest Task.2 using ParameterizedTest
jlee0505 Mar 12, 2025
6b85fc4
feat: SetTest Task.3
jlee0505 Mar 12, 2025
f3f5bc6
피드백 반영
idisdi Mar 13, 2025
c3aa562
gradle 버전 맞추기
idisdi Mar 16, 2025
b86c75d
폴더 구조 step 별로 정리
idisdi Mar 16, 2025
afd66a1
feat: 수도(pseudo) 테스트 작성
idisdi Mar 16, 2025
5e9061f
feat: Test 1 작성, 메소드 작성, 및 테스트 통과
idisdi Mar 16, 2025
4e1fe35
feat: Test 2 작성, 메소드 작성, 및 테스트 통과
idisdi Mar 16, 2025
8f3e4e6
feat: Test 3 작성, 메소드 작성, 및 테스트 통과
idisdi Mar 16, 2025
532c99c
feat: Test 4 작성, 메소드 작성, 및 테스트 통과
idisdi Mar 16, 2025
c133ec2
feat: Test 5 작성, 메소드 작성, 및 테스트 통과
idisdi Mar 16, 2025
1d46a14
feat: Test 6 작성, 메소드 작성, 및 테스트 통과
idisdi Mar 16, 2025
97b1b61
feat: Test 7 작성, 메소드 작성, 및 테스트 통과
idisdi Mar 16, 2025
f607ccc
init: 수도 코드 작성
idisdi Mar 19, 2025
17dd239
feat: RacingGame 전반적 로직 구현
idisdi Mar 20, 2025
01b1f81
feat: InputView 구현
idisdi Mar 20, 2025
9cd346f
feat: ResultView 구현
idisdi Mar 20, 2025
9e28d14
docs: 주석으로 작성했던 요구사항 분석 README 파일로 정리
idisdi Mar 20, 2025
aaed423
refactor: InputView 에서 Scanner 타입을 생성자로 주입 받도록 변경
idisdi Mar 20, 2025
9f931b3
test: ResultView 테스트 작성
idisdi Mar 20, 2025
aa408d2
test: Car 테스트
idisdi Mar 21, 2025
b6d19c2
chore: Car 테스트 후 README 반영
idisdi Mar 21, 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
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,19 @@
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

## 온라인 코드 리뷰 과정
* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)


## 요구사항 분석
### InputView
- [x] 유저에게서 차 갯수를 입력 받는다.
- [x] 유저에게서 게임 시도 횟수를 입력 받는다.
- [x] 값을 입력 받는 API는 Scanner를 이용한다.
### RacingGame
- [x] 시도 횟수 만큼 게임 반복:
- [x] 각 게임에서 자동차 수 만큼 반복:
- [x] 각 자동차는 전진한다. when 랜덤 숫자 >= 4
- [x] 한 게임 마무리 후 결과 저장
- [x] 최종 결과 return
### ResultView
- [x] 최종 결과를 화면에 출력한다.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

plugins {
id 'java'
}
Expand Down
3 changes: 2 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Sun Mar 16 16:29:57 KST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
115 changes: 115 additions & 0 deletions src/main/java/step2/StringAddCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package step2;

import java.util.Arrays;

public class StringAddCalculator {

private static String createRegex(String separators) {
return "["+separators+"]";
}

public int calculate(String input) {
if(checkIfEmptyInput(input)) {
return 0;
}
if(!checkIfValidPattern(input)) {
throw new IllegalArgumentException("Invalid Pattern are not allowed");
}
if(!checkIfOnlyPositiveNumbers(input)) {
throw new IllegalArgumentException("Negative numbers or NaN are not allowed");
}

String numbersWithSeparators = extractNumbers(input); // "\\;\n1;2;3" -> "1;2;3"
String separators = filterSeparators(input); // SEPARATORS = ",:" -> ",:;"
int[] numbers = splitNumbers(numbersWithSeparators, separators); // e,g. "1,2;3" -> [1,2,3]
int result = addNumbersInArray(numbers); // [1,2,3] -> 6

return result;
};

public int[] splitNumbers(String numbers, String separators) {
String regex = createRegex(separators);

// [TODO] 강의에서 로또 미션 전까지는 stream 사용 없이 해보라는 조언
return Arrays.stream(numbers.split(regex))
.mapToInt(Integer::parseInt)
.toArray();
}

public int addNumbersInArray(int[] numbers) {
int sum = 0;

if (numbers == null) {
return sum;
}

for (int num:numbers) {
sum += num;
}

return sum;
}

public String[] checkIfOnlyNumbers(String numbersWithCustomSeparator) {
String[] arr = numbersWithCustomSeparator.split("\n");
return arr;
}

public String filterSeparators(String numbersWithCustomSeparator) {
String separators = ",:"; // default_separators

String[] arr = checkIfOnlyNumbers(numbersWithCustomSeparator);

if (arr.length > 1) {
separators += arr[0].substring(2);
}

return separators;
}

public String extractNumbers (String numbersWithCustomSeparator) {
String[] arr = checkIfOnlyNumbers(numbersWithCustomSeparator);
return arr[arr.length-1];
}

public boolean checkIfValidPattern(String input) {
// 커스텀 구분자 형식이 유효하지 않으면 오류
if (input.startsWith("//")) {
String[] parts = input.split("\n", 2);
if (parts.length < 2 || parts[0].length() < 3 || !parts[0].substring(2).matches("^[^\\d]+$")) {
return false; // 커스텀 구분자가 숫자나 빈 문자열이면 오류
}
} else if (!Character.isDigit(input.charAt(0)) && !input.startsWith("//")) {
return false; // "\n;\n1;2;3;" 과 같은 경우
}

return true; // 모든 검증을 통과하면 유효
}

public boolean checkIfOnlyPositiveNumbers(String input) {
// 숫자 확인: 입력 문자열에서 유효한 숫자 배열로 변환 시도
try {
String separators = filterSeparators(input);
String numbers = extractNumbers(input);
String[] tokens = numbers.split(createRegex(separators));

for (String token : tokens) {
int number = Integer.parseInt(token.trim());
if (number < 0) { // 음수일 경우 오류
return false;
}
}
} catch (NumberFormatException e) {
return false; // 숫자가 아닌 값이 포함되어 있으면 오류
}
return true;
}

public boolean checkIfEmptyInput (String input) {
if (input == null || input.isEmpty()) {
return true;
}
return false;
}

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

import java.util.Random;

public class Car {
private int position;
private Random random;

public Car(Random random) {
this.position = 1;
this.random = random;
}

public int getPosition () {
return this.position;
}

private boolean isMovable () {
return this.random.nextInt(10) >= 4;
}

public void move() {
if(!isMovable()) return;
position++;
}
public String draw(int position) {
return "-".repeat(this.position);
}
}
26 changes: 26 additions & 0 deletions src/main/java/step3/CarRacingGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package step3;

import java.util.List;
import java.util.Scanner;

/**
* 기능 요구사항
* 초간단 자동차 경주 게임을 구현한다.
* 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
* 사용자는 몇 대의 자동차로 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
* 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4이상일 경우이다.
* 자동차의 상태를 화면에 출력한다. 어느 시점에 출력할 것인지에 대한 제약은 없다.
*/
public class CarRacingGame {
public static void main(String[] args) {
InputView inputView = new InputView(new Scanner(System.in));
int carCount = inputView.getCarCountFromUser();
int roundCount = inputView.getRoundCountFromUser();

RacingGame racingGame = new RacingGame(carCount);
List<List<String>> raceResults = racingGame.play(roundCount);

ResultView resultView = new ResultView();
resultView.printResults(raceResults);
}
}
22 changes: 22 additions & 0 deletions src/main/java/step3/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package step3;

import java.util.Scanner;

public class InputView {
private final Scanner scanner;

public InputView(Scanner scanner) {
this.scanner = scanner;
}

public int getCarCountFromUser() {
System.out.println("자동차 대수는 몇 대 인가요?");
return scanner.nextInt();
}

public int getRoundCountFromUser() {
System.out.println("시도할 회수는 몇 회 인가요?");
return scanner.nextInt();
}

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

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

public class RacingGame {
private final List<Car> cars;

public RacingGame(int carCount) {
cars = new ArrayList<>();
for(int i = 0; i < carCount; i++) {
cars.add(new Car(new Random()));
}
}

public List<List<String>> play(int roundCount) {
List<List<String>> FinalResults = new ArrayList<>();

for(int i = 0; i < roundCount; i++) {
List<String> RoundResults = new ArrayList<>();
for(Car car : cars) {
car.move();
RoundResults.add(car.draw(car.getPosition()));
}
FinalResults.add(RoundResults);
}

return FinalResults;
}
}
14 changes: 14 additions & 0 deletions src/main/java/step3/ResultView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package step3;

import java.util.List;

public class ResultView {
public void printResults (List<List<String>> finalResults) {
for (List<String> round : finalResults) {
for (String s : round) {
System.out.println(s);
}
System.out.println();
}
}
}
84 changes: 84 additions & 0 deletions src/test/java/step1/SetTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package step1;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class SetTest {
private Set<Integer> numbers;

@BeforeEach
void setUp() {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(2);
numbers.add(3);
}

// Test Case 구현

/**
* 요구사항 1
* Set의 size() 메소드를 활용해 Set의 크기를 확인하는 학습테스트를 구현한다.
*/

@Test
void sizeTest (){
assertEquals(3, numbers.size()); // Hash라 중복값(1)은 저장안되므로 사이즈는 3.
}

/**
* 요구사항 2
* Set의 contains() 메소드를 활용해 1, 2, 3의 값이 존재하는지를 확인하는 학습테스트를 구현하려한다.
* 구현하고 보니 다음과 같이 중복 코드가 계속해서 발생한다.
* JUnit의 ParameterizedTest를 활용해 중복 코드를 제거해 본다.
*
* @return
*/

@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void containsTest (int number){
assertTrue(numbers.contains(number), "Set에 값 " + number + "이(가) 존재하지 않습니다.");
}

/**
* 요구사항 3
* 요구사항 2는 contains 메소드 결과 값이 true인 경우만 테스트 가능하다. 입력 값에 따라 결과 값이 다른 경우에 대한 테스트도 가능하도록 구현한다.
* 예를 들어 1, 2, 3 값은 contains 메소드 실행결과 true, 4, 5 값을 넣으면 false 가 반환되는 테스트를 하나의 Test Case로 구현한다.
* 힌트: Guide to JUnit 5 Parameterized Tests 문서에서 @CsvSource를 활용한다.
*/

private static Stream<Arguments> provideTestData() {
return Stream.of(
Arguments.of(1, true),
Arguments.of(2, true),
Arguments.of(3, true),
Arguments.of(4, false),
Arguments.of(5, false)
);
}

@ParameterizedTest
@CsvSource({"1,true","2,true","3,true","4,false","5,false"})
void conditionalTest (int input, boolean expected ){
boolean result = numbers.contains(input);
assertEquals(expected, result, "Set에 값 " + input + "이(가) 존재하는지 여부가 일치하지 않습니다.");
}




}
Loading