diff --git a/README.md b/README.md new file mode 100644 index 00000000..cc469ea2 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# 자동차 경주 + +## 1단계 - 움직이는 자동차 + - [ ] 자동차는 이름을 가지고 있다. + - [x] 자동차는 움직일 수 있다. + - [X] 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. + +## 2단계 - 우승 자동차 구하기 + - [X] n대의 자동차가 참여할 수 있다. + - [X] 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. + - [X] 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. + - [X] 자동차 경주 게임을 완료한 후 누가 우승했는지를 구할 수 있다. 우승자는 한 명 이상일 수 있다. + +## 3단계 - 게임 실행 + - [ ] 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. + - [ ] 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. + - [ ] 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. + - [ ] 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. + - [ ] 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. + - [ ] 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. + + +## 바꾸자 + 선언할때 값을 할당 안해도 final를 넣을 수 있다..! + +## 막히는 부분? + - Input시 예외처리.. + InputView에서 하는가? Serive에서 하는가. + 에러 핸들링은 어디서 하는가.. + 입력을 받자마자 바로 처리하는게 좋은 것인지, 코드상에서 에러가 나기 시작하는 지점에서 예외처리를 하는게 좋은 것인지.. + + - Dto를 제작해야 할까? + 현재 코드에선 car.move가 public임 + 하지만 outView에서는 이 메소드에 접근하지 않으므로 CarList를 넘기는 것보다 RoundResultDto를 넘기는 것이 맞다 판단.. + + - 게임의 N번 실행은 Controller의 역할? RacingGame의 역할? + 뭔가 RacingGame에서 N번 라운드를 실행해야 할 것 같다. + 그러면 OutputView가 RacingGame의 라운드 결과를 출력하는 과정이 매끄럽지 않아진다. + * OutputView가 RacingGame 안에서 동작해야 함 -> contoller의 역할이 애매해진다. + + - model(Domain)은 메소드를 안 가지는 것이 좋은가? + 뭔가 도메인이 해야하는 일들은 서비스에서 처리하는 것이 더욱 깔끔할 것 같다.. + + - vaildator를 넣으면 특정 객체에 대한 탐색을 두번하게 된다 -> 비효율적인가? + + - 출력되는 메세지는 어떻게 테스트하지? + 애초에 테스트를 해야하나?? \ No newline at end of file diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..c6ce52b6 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,11 @@ +import controller.GameController; + +public class Application { + + + public static void main(String[] args) { + final GameController gameController = new GameController(); + + gameController.start(); + } +} diff --git a/src/main/java/Dto/GameResultDto.java b/src/main/java/Dto/GameResultDto.java new file mode 100644 index 00000000..12ab9ab4 --- /dev/null +++ b/src/main/java/Dto/GameResultDto.java @@ -0,0 +1,38 @@ +package Dto; + +import java.util.List; + +public class GameResultDto { + + private List nameList; + + public GameResultDto() { + } + + public GameResultDto(Builder builder) { + this.nameList = builder.nameList; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private List nameList; + + public Builder name(List nameList) { + this.nameList = nameList; + return this; + } + + public GameResultDto build() { + return new GameResultDto(this); + } + + } + + public List getNameList() { + return this.nameList; + } +} diff --git a/src/main/java/Dto/RoundResultDto.java b/src/main/java/Dto/RoundResultDto.java new file mode 100644 index 00000000..b5e0e9f7 --- /dev/null +++ b/src/main/java/Dto/RoundResultDto.java @@ -0,0 +1,49 @@ +package Dto; + +import java.util.List; + +public class RoundResultDto { + + private String name; + private int pos; + + public RoundResultDto() { + } + + public RoundResultDto(Builder builder) { + this.name = builder.name; + this.pos = builder.pos; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String name; + private int pos; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder pos(int pos) { + this.pos = pos; + return this; + } + + public RoundResultDto build() { + return new RoundResultDto(this); + } + + } + + public String getName() { + return this.name; + } + + public int getPos() { + return this.pos; + } +} diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java new file mode 100644 index 00000000..03c824d9 --- /dev/null +++ b/src/main/java/controller/GameController.java @@ -0,0 +1,35 @@ +package controller; + +import model.RacingGame; +import validator.Validator; +import view.InputView; +import view.OutputView; +import java.util.List; + + +public class GameController { + private final RacingGame racingGame= new RacingGame(); + + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + private final Validator validator = new Validator(); + + public void start() { + + List nameList = inputView.inputNameList(); + validator.validName(nameList); + + racingGame.createCarList(nameList); + + int round = inputView.inputAttempt(); + validator.validRound(round); + + outputView.printRoundResultInfo(); + for (int i=1; i<=round; i++) { + racingGame.run(); + outputView.printRoundResult( racingGame.getRoundResult() ); + } + + outputView.printWinner( racingGame.getWinnerList() ); + } +} diff --git a/src/main/java/model/Car.java b/src/main/java/model/Car.java new file mode 100644 index 00000000..2402fcf8 --- /dev/null +++ b/src/main/java/model/Car.java @@ -0,0 +1,45 @@ +package model; + +import service.NumGenerator; +import service.RandomNumGenerator; + + +public class Car { + private int pos=0; + private String name; + private NumGenerator numGenerator = new RandomNumGenerator(); + + public int getPos() { + return this.pos; + } + + public void setPos(int pos) { + this.pos = pos; + } + + public String getName() { + return this.name; + } + + public Car (String name) { + numGenerator = new RandomNumGenerator(); + this.name = name; + } + + public Car (String name, NumGenerator numGenerator) { + this.numGenerator = numGenerator; + this.name = name; + } + + public void move() { + int num = numGenerator.createNumber(); + if (num > 9 || num < 0) { + throw new RuntimeException("생성된 숫자가 정해진 범위를 초과합니다."); + } + + else if (num >= 4) { + pos += 1; + } + } + +} diff --git a/src/main/java/model/RacingGame.java b/src/main/java/model/RacingGame.java new file mode 100644 index 00000000..fa97b77b --- /dev/null +++ b/src/main/java/model/RacingGame.java @@ -0,0 +1,80 @@ +package model; + +import Dto.GameResultDto; +import Dto.RoundResultDto; +import java.util.ArrayList; +import java.util.List; + +public class RacingGame { + + private int maxPos = 0; + + private final List carList = new ArrayList<>(); + + + public void createCarList(List nameList) { + for (String name : nameList) { + carList.add(new Car(name)); + } + } + + private void getMaxPos() { + int maxPos = 0; + for (Car car : carList) { + maxPos = Math.max( maxPos, car.getPos() ); + } + + this.maxPos = maxPos; + } + + private boolean isWinner(Car car) { + if (maxPos == car.getPos()) { + return true; + } + else { + return false; + } + } + + public void run() { + for (Car car : carList) { + car.move(); + } + } + + public GameResultDto getWinnerList() { + List winnerList = new ArrayList<>(); + + getMaxPos(); + + winnerList = carList.stream() + .filter( car -> isWinner(car) ) + .map(Car::getName) + .toList(); + + return new GameResultDto().builder() + .name(winnerList) + .build(); + } + + public List getCarList() { + return carList; + } + + public List getRoundResult() { + + List roundResultDtoList = new ArrayList<>(); + + for(Car car : carList){ + RoundResultDto roundResultDto = new RoundResultDto().builder() + .name(car.getName()) + .pos(car.getPos()) + .build(); + + roundResultDtoList.add(roundResultDto); + } + + return roundResultDtoList; + } + +} diff --git a/src/main/java/service/FixedNumGenerator.java b/src/main/java/service/FixedNumGenerator.java new file mode 100644 index 00000000..2a8a1186 --- /dev/null +++ b/src/main/java/service/FixedNumGenerator.java @@ -0,0 +1,14 @@ +package service; + +public class FixedNumGenerator implements NumGenerator{ + + private int num; + + public FixedNumGenerator (final int num) { + this.num = num; + } + @Override + public int createNumber() { + return num; + } +} diff --git a/src/main/java/service/NumGenerator.java b/src/main/java/service/NumGenerator.java new file mode 100644 index 00000000..6b9c89de --- /dev/null +++ b/src/main/java/service/NumGenerator.java @@ -0,0 +1,5 @@ +package service; + +public interface NumGenerator { + int createNumber(); +} diff --git a/src/main/java/service/RandomNumGenerator.java b/src/main/java/service/RandomNumGenerator.java new file mode 100644 index 00000000..1fcf5ad4 --- /dev/null +++ b/src/main/java/service/RandomNumGenerator.java @@ -0,0 +1,18 @@ +package service; + +import java.util.Random; + +public class RandomNumGenerator implements NumGenerator { + + private final int RAND_MAX = 10; + private final int RAND_MIN = 0; + + @Override + public int createNumber() { + Random random = new Random(); + return random.nextInt(0, 10); + } + + + +} diff --git a/src/main/java/validator/ErrorStatus.java b/src/main/java/validator/ErrorStatus.java new file mode 100644 index 00000000..a4775a86 --- /dev/null +++ b/src/main/java/validator/ErrorStatus.java @@ -0,0 +1,17 @@ +package validator; + +public enum ErrorStatus { + INVAILD_ROUND_ERROR("시도할 횟수가 유효하지 않습니다."), + EMPTY_NAME_ERROR("이름에 빈값이 포함되어 있습니다."), + DUPLICATE_NAME_ERROR("중복된 이름이 존재합니다."), + NOT_FOUND_NAME_ERROR ("참가자가 존재하지 않습니다."); + private final String reason; + + private ErrorStatus(String reason) { + this.reason = reason; + } + + public String getReason() { + return reason; + } +} diff --git a/src/main/java/validator/Validator.java b/src/main/java/validator/Validator.java new file mode 100644 index 00000000..cf727797 --- /dev/null +++ b/src/main/java/validator/Validator.java @@ -0,0 +1,36 @@ +package validator; + +import java.util.List; +import java.util.Set; + +public class Validator { + + public void validRound(int round) { + if (round <= 0 ) { + throw new RuntimeException(ErrorStatus.INVAILD_ROUND_ERROR.getReason()); + } + } + + public void validName(List nameList) { + for (String name : nameList) { + vaildEmpty(name); + } + + if ( nameList.isEmpty() ) { + throw new RuntimeException(ErrorStatus.NOT_FOUND_NAME_ERROR.getReason()); + } + + Set set = Set.copyOf(nameList); + if ( set.size() != nameList.size() ) { + throw new RuntimeException(ErrorStatus.DUPLICATE_NAME_ERROR.getReason()); + } + + + } + + private void vaildEmpty(String str) { + if ( str.isBlank() ) + throw new RuntimeException(ErrorStatus.EMPTY_NAME_ERROR.getReason()); + } + +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..8e5f817f --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,42 @@ +package view; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +public class InputView { + private final String INPUT_NAME_MESSAGE = "경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."; + private final String INPUT_MAX_ATTEMPT_MESSAGE = "시도할 회수는 몇회인가요?"; + + public List inputNameList() { + + // 스캐너 선언 + Scanner scanner = new Scanner(System.in); + + // 자동차 이름 입력받기 + System.out.println(INPUT_NAME_MESSAGE); + String str = scanner.nextLine(); + + // nameList 반환 (공백제거 및 split) + List nameList = new ArrayList<>(); + nameList = Arrays.stream(str.replaceAll(" ", "").split(",")).toList(); + + return nameList; + } + + public int inputAttempt() { + + // 스캐너 선언 + Scanner scanner = new Scanner(System.in); + + // 시도횟수 입력받기 + System.out.println(INPUT_MAX_ATTEMPT_MESSAGE); + int attempt = scanner.nextInt(); + + // 시도횟수 반환 + return attempt; + + } + +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..aa9f6f03 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,53 @@ +package view; + +import Dto.GameResultDto; +import Dto.RoundResultDto; +import model.Car; +import java.util.List; + +public class OutputView { + + private final String ROUND_RESULT_INFO_MESSAGE = "실행결과"; + private final String WINNER_RESULT_MESSAGE = "가 최종 우승했습니다."; + + private void print(String string) { + System.out.println(string); + } + + public void printRoundResultInfo() { + print(ROUND_RESULT_INFO_MESSAGE); + } + public void printRoundResult(List results) { + for (RoundResultDto result : results) { + String Result = result.getName() + " : " + posBar( result.getPos() ); + print(Result); + } + print(""); + } + + + + private String posBar(int pos) { + String str = ""; + for (int i=0; i winnerList = result.getNameList(); + String str = ""; + + str += winnerList.get(0); + + for (int i=1; i car.move()) + .isInstanceOf(RuntimeException.class); + + } + } +} diff --git a/src/test/java/GameTest.java b/src/test/java/GameTest.java new file mode 100644 index 00000000..88501496 --- /dev/null +++ b/src/test/java/GameTest.java @@ -0,0 +1,110 @@ +import static org.assertj.core.api.Assertions.*; + +import Dto.GameResultDto; +import Dto.RoundResultDto; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import model.Car; +import model.RacingGame; +import org.junit.jupiter.api.Test; +import validator.Validator; +import java.io.ByteArrayInputStream; +import view.InputView; +import view.OutputView; + +public class GameTest { + + @Test + public void 차량을_원하는_수만큼_생성시킨다() { + final RacingGame racingGame = new RacingGame(); + + List nameList = List.of("Steve", "Alex", "James"); + + racingGame.createCarList(nameList); + List actual = racingGame.getCarList().stream().map(Car::getName).toList(); + + assertThat(actual).containsExactlyInAnyOrderElementsOf( nameList ); + } + + @Test + public void 라운드_결과를_출력한다() { + final OutputView outputView = new OutputView(); + final List result = new ArrayList<>(); + + result.add( + new RoundResultDto().builder() + .name("Steve") + .pos(5) + .build() + ); + + String expected = "Steve : -----\n\n"; + + OutputStream out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + + outputView.printRoundResult( result ); + + assertThat(out.toString()).isEqualTo(expected); + } + + @Test + public void 승자를_출력한다() { + final OutputView outputView = new OutputView(); + final GameResultDto result = new GameResultDto().builder() + .name( List.of("Steve", "Alex") ) + .build(); + + String expected = "Steve, Alex가 최종 우승했습니다.\n"; + + OutputStream out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + + outputView.printWinner( result ); + + assertThat(out.toString()).isEqualTo(expected); + } + + @Test + public void 참가하는_차량이_없다면_예외를_던진다() { + final Validator validator = new Validator(); + + List nameList = List.of(); + + assertThatThrownBy( () -> validator.validName(nameList) ) + .isInstanceOf(RuntimeException.class); + } + + @Test + public void 차량이름이_비었다면_예외를_던진다() { + final Validator validator = new Validator(); + + List nameList = List.of("Steve", ""); + + assertThatThrownBy( () -> validator.validName(nameList) ) + .isInstanceOf(RuntimeException.class); + } + + @Test + public void 차량이름이_중복이라면_예외를_던진다() { + final Validator validator = new Validator(); + + List nameList = List.of("Steve", "Alex", "Steve"); + + assertThatThrownBy( () -> validator.validName(nameList) ) + .isInstanceOf(RuntimeException.class); + } + + @Test + public void 시도횟수가_음수라면_예외를_던진다() { + final Validator validator = new Validator(); + int round = -5; + + assertThatThrownBy( () -> validator.validRound(round)) + .isInstanceOf(RuntimeException.class); + } + +}