Skip to content

Commit

Permalink
[1단계 - 웹 자동차 경주] 히이로(문제웅) 미션 제출합니다. (#53)
Browse files Browse the repository at this point in the history
* chore: 기존 콘솔 자동차 경주 프로젝트 코드 마이그레이션

* refactor: 콘솔 기반에서 Web 기반으로 게임 로직 마이그레이션

* feat: 게임 결과 저장 기능 구현

세부사항
- h2 데이터베이스 연동

* refactor: Layered Architecture 적용

* docs: 새로 추가된 기능들 README 추가

* style: 코드 포맷팅 확인 및 수정

* refactor: controller 반환 타입 변경
  • Loading branch information
MoonJeWoong authored Apr 13, 2023
1 parent 07ad425 commit 1e66520
Show file tree
Hide file tree
Showing 35 changed files with 1,019 additions and 7 deletions.
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
# jwp-racingcar

# 자동차 경주 구현

## 💡프로젝트 개요
- 자동차 경주 프로젝트는 경주할 자동차 이름과 시도할 횟수를 입력받은 후 전진 과정을 통해 가장 많이 전진한 자동차가 우승을 하는 프로젝트이다. 이 때 자동차 경주 게임을 진행하고 최종 우승 결과를 출력해야한다.
- 진행한 게임 결과는 DB에 저장된다.
---


## 📋 구현 기능 목록

## 프로그램 전체 로직
- ✅자동차 이름에 대한 사용자 입력을 받는다.
- ✅총 시도 횟수에 대한 사용자 입력을 받는다.
- ✅자동차 경주를 진행한다.
- ✅DB에 결과 값들을 저장한다.
- ✅경주 결과를 출력한다.


## domain(model)
- ✅자동차 정보를 저장하고 관리한다.
- ✅각 자동차 이름은 1자 이상 5자 이하여야 한다.
- ✅자동차의 위치를 증가 시킬 수 있다.
- ✅자동차의 전진 여부를 판단한다.
- ✅random 값이 4 이상일 경우 전진한다.
- ✅random 값이 3 이하일 경우 정지한다.
- ✅생성된 자동차들의 정보를 저장하고 관리한다.
- ✅자동차 대수는 1대 이상이어야 한다.
- ✅우승한 자동차를 제공한다.
- ✅모든 자동차들이 전진을 1회 시도한다.
- ✅random 하게 0~9 범위 내에 한 자리 숫자를 반환한다.
- ✅자동차 경주를 진행한다.
- ✅몇 라운드를 진행할 지 저장한다.
- ✅라운드가 1 이상의 값으로 주어지는지 검증한다.
- ✅현재 게임진행 현황을 통해 게임 종료 여부를 판단한다.


## view
### 입력
- ✅자동차 이름 입력받기
- ✅총 시도 횟수 입력받기
- ✅시도 횟수 입력값은 숫자여야 한다.
- ✅총 시도 횟수는 1 이상의 양의 정수 값이어야 한다.

### 출력
- ✅프로그램 실행 결과
- ✅최종 결과 문구 출력
- ✅공동 우승자 발생 경우 고려


## 예외 처리
- ✅자동차 이름이 0자인 경우 고려하기
- ✅총 시도 횟수 입력값이 양의 정수가 아닌 경우 고려하기
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'

runtimeOnly 'com.h2database:h2'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Expand Down
19 changes: 19 additions & 0 deletions src/main/java/racingcar/ConsoleRacingCarApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package racingcar;

import racingcar.controller.ConsoleRacingCarController;
import racingcar.domain.RandomNumberGenerator;
import racingcar.view.InputView;
import racingcar.view.OutputView;

import java.util.Scanner;

public class ConsoleRacingCarApplication {

public static void main(String[] args) {
ConsoleRacingCarController controller = new ConsoleRacingCarController(new InputView(new Scanner(System.in)),
new OutputView(),
new RandomNumberGenerator());

controller.run();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RacingCarApplication {
public class WebRacingCarApplication {

public static void main(String[] args) {
SpringApplication.run(RacingCarApplication.class, args);
SpringApplication.run(WebRacingCarApplication.class, args);
}

}
47 changes: 47 additions & 0 deletions src/main/java/racingcar/controller/ConsoleRacingCarController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package racingcar.controller;

import racingcar.domain.Car;
import racingcar.domain.Cars;
import racingcar.domain.NumberGenerator;
import racingcar.domain.RacingGame;
import racingcar.view.InputView;
import racingcar.view.OutputView;

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

public class ConsoleRacingCarController {

private final InputView inputView;
private final OutputView outputView;
private final NumberGenerator numberGenerator;

public ConsoleRacingCarController(InputView inputView, OutputView outputView, NumberGenerator numberGenerator) {
this.inputView = inputView;
this.outputView = outputView;
this.numberGenerator = numberGenerator;
}

public void run() {
Cars cars = generateCars(inputView.readCarNames());
int round = inputView.readRacingRound();
playGame(cars, round);
}

private Cars generateCars(List<String> carNames) {
List<Car> carInstances = new ArrayList<>();
for (String name : carNames) {
carInstances.add(new Car(name, numberGenerator));
}
return new Cars(carInstances);
}

private void playGame(Cars cars, int round) {
RacingGame racingGame = new RacingGame(cars, round);
outputView.printResultMessage();
while (!racingGame.isGameEnded()) {
outputView.printRoundResult(racingGame.playOneRound());
}
outputView.printFinalResult(racingGame.findWinnerCars());
}
}
24 changes: 24 additions & 0 deletions src/main/java/racingcar/controller/WebRacingCarController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package racingcar.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import racingcar.dto.request.RacingStartRequest;
import racingcar.dto.response.RacingResultResponse;
import racingcar.service.RacingCarService;

@RestController
public class WebRacingCarController {

private final RacingCarService racingCarService;

public WebRacingCarController(RacingCarService racingCarService) {
this.racingCarService = racingCarService;
}

@PostMapping("/plays")
public ResponseEntity<RacingResultResponse> play(@RequestBody RacingStartRequest racingStartRequest) {
return ResponseEntity.ok(racingCarService.play(racingStartRequest));
}
}
10 changes: 10 additions & 0 deletions src/main/java/racingcar/dao/CarDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package racingcar.dao;

import racingcar.dto.CarDto;

import java.util.List;

public interface CarDao {

void save(int gameId, List<CarDto> carDtos);
}
35 changes: 35 additions & 0 deletions src/main/java/racingcar/dao/H2CarDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package racingcar.dao;

import org.springframework.stereotype.Repository;
import racingcar.dto.CarDto;
import racingcar.utils.ConnectionProvider;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

@Repository
public class H2CarDao implements CarDao {

@Override
public void save(int gameId, List<CarDto> carDtos) {
String sql = "INSERT INTO CAR(game_id, name, position, is_win) VALUES (?,?,?,?)";

try (Connection connection = ConnectionProvider.getConnection()) {
for (CarDto carDto : carDtos) {
PreparedStatement ps = connection.prepareStatement(sql);

ps.setInt(1, gameId);
ps.setString(2, carDto.getName());
ps.setInt(3, carDto.getPosition());
ps.setBoolean(4, carDto.isWin());
ps.executeUpdate();

ps.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/racingcar/dao/H2RacingGameDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package racingcar.dao;

import org.springframework.stereotype.Repository;
import racingcar.utils.ConnectionProvider;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

@Repository
public class H2RacingGameDao implements RacingGameDao {

@Override
public int save(int count) {
String sql = "INSERT INTO RACING_GAME(count) VALUES (?)";

try (Connection connection = ConnectionProvider.getConnection();
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setInt(1, count);
ps.executeUpdate();

ResultSet rs = ps.getGeneratedKeys();

if (rs.next()) {
return rs.getInt(1);
}

throw new IllegalStateException();

} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
6 changes: 6 additions & 0 deletions src/main/java/racingcar/dao/RacingGameDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingcar.dao;

public interface RacingGameDao {

int save(int count);
}
66 changes: 66 additions & 0 deletions src/main/java/racingcar/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package racingcar.domain;

import java.util.Objects;

public class Car implements Comparable<Car> {

private static final int NAME_MIN_LENGTH = 1;
private static final int NAME_MAX_LENGTH = 5;
private static final int MOVABLE_MIN_NUMBER = 4;
private static final String NAME_LENGTH_ERROR = "[ERROR] 자동차 이름은 1자 이상 5자 이하여야 합니다.";

private String name;
private int position;
private NumberGenerator numberGenerator;

public Car(String name, NumberGenerator numberGenerator) {
name = name.trim();
validateName(name);
this.name = name;
this.position = 0;
this.numberGenerator = numberGenerator;
}

public void goForward() {
if (numberGenerator.generate() >= MOVABLE_MIN_NUMBER) {
position++;
}
}

public boolean isSamePosition(Car otherCar) {
return this.position == otherCar.position;
}

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

public String getName() {
return this.name;
}

@Override
public int compareTo(Car otherCar) {
return otherCar.position - this.position;
}

private void validateName(String name) {
if (name.length() < NAME_MIN_LENGTH || name.length() > NAME_MAX_LENGTH) {
throw new IllegalArgumentException(NAME_LENGTH_ERROR);
}
}

@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Car car = (Car) other;
return position == car.position && Objects.equals(name, car.name);
}

@Override
public int hashCode() {
return Objects.hash(name, position);
}
}

48 changes: 48 additions & 0 deletions src/main/java/racingcar/domain/Cars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package racingcar.domain;

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

public class Cars {

private static final int CARS_MIN_SIZE = 1;
private static final String CARS_SIZE_ERROR = "[ERROR] 자동차 대수는 1이상이어야 합니다.";

private List<Car> cars;

public Cars(List<Car> cars) {
validateCarsSize(cars);
this.cars = new ArrayList<>(cars);
}

public List<Car> findAllWinner() {
Car maxPositionCar = findMaxPositionCar();
return cars.stream()
.filter(car -> car.isSamePosition(maxPositionCar))
.collect(Collectors.toList());
}

public List<Car> moveEachCar() {
for (Car car : cars) {
car.goForward();
}
return new ArrayList<>(cars);
}

// Todo : 나중에 중복되는 이름은 입력되면 예외처리 되도록 구현해야 한다.
private void validateCarsSize(List<Car> cars) {
if (cars.size() < CARS_MIN_SIZE) {
throw new IllegalArgumentException(CARS_SIZE_ERROR);
}
}

private Car findMaxPositionCar() {
cars.sort(Car::compareTo);
return cars.get(0);
}

public List<Car> getCars() {
return cars;
}
}
7 changes: 7 additions & 0 deletions src/main/java/racingcar/domain/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package racingcar.domain;

@FunctionalInterface
public interface NumberGenerator {

int generate();
}
Loading

0 comments on commit 1e66520

Please sign in to comment.