Skip to content

Commit

Permalink
[2단계 - 웹 자동차 경주] 제이(이재윤) 미션 제출합니다. (#103)
Browse files Browse the repository at this point in the history
* feat: Level 1 코드 복구 (1단계 미션 때 삭제해서 복구했습니다.)

* feat: 게임 플레이 이력 조회 API 구현

* refactor: 게임 플레이 이력 조회 API 메서드 분리 및 서비스는 비즈니스 로직만 담당하도록 변경

* docs: 기능 구현 목록 수정

* test: 서비스 레이어 테스트 코드 작성

* test: 게임 이력 조회 테스트 작성

* refactor: 우승자를 구하는 기능을 도메인 메서드로 변경

* refactor: 게임 저장 후 결과를 반환하도록 변경

* fix: 자동차 이름을 List로 나누게 리팩토링 후 실패하는 테스트 수정

* refactor: Cars 정적 팩토리 메서드를 도메인 내부에서 가능하도록 변경

* refactor: Console 자동차 경주 게임에서 View의 파라미터를 Dto로 변경

* docs: 기능 구현 목록 수정

* style: 불필요한 개행 삭제

* refactor: 메서드명 통일 및 final 키워드 통일

* refactor: for에서 forEach로 변경

* refactor: ResponseEntity 사용할 때 생성자로 반환하는 방식에서 빌더 패턴으로 변경

* refactor: 불필요한 메서드 제거

* style: 테스트 데이터 셋을 더 보기 좋게 개행 수정

* refactor: 모호한 메서드명 변경

* refactor: 외부에서 호출 안 하는 메서드의 접근 제어자를 private로 변경

* test: GameResultDao 테스트 작성

* refactor: 컨트롤러 테스트 반환 값도 나오도록 리팩토링 진행

* test: 서비스 통합테스트 작성

* refactor: 메서드명과 @DisplayName 변경

* fix: 테스트 개별 실행시에는 성공하지만, 전체 실행시 실패하는 문제 해결 (profile 설정)

* chore: validation 종속성 추가

* style: 디렉토리 이동

* refactor: 잘못된 입력 값에 대해 예외처리 추가

* test: 사용자 입력에 대한 값 검증 테스트 작성

* refactor: 내부에서만 호출할 수 있도록 메서드의 접근제어자 변경

* refactor: 상태코드가 중복된 의미이므로, 반환시에 상태코드를 반환 안 하도록 변경

* refactor: 테스트 경우 추가
  • Loading branch information
sosow0212 committed Apr 20, 2023
1 parent c323676 commit f1170b5
Show file tree
Hide file tree
Showing 29 changed files with 847 additions and 196 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
## 기능 구현 목록

- [x] 자동차 경주 코드 가져오기

- [x] 웹 요청 구현하기
- [x] 플레이어의 이름 요청 받기
- [x] 주행 횟수 요청 받기
- [x] 웹 응답 구현하기
- [x] 우승자 출력하기
- [x] 플레이어의 좌표 값 출력
- [x] 자동차 경주 게임 API 구현
- [x] 게임 이력 조회 API 구현

- [x] DB 연동하기
- [x] H2 DB 연동하기
- [x] 테이블 만들기
- [x] 게임에 관한 정보 저장하기

- [x] console application 기존 기능 수정하기
- [x] 플레이 중간 과정을 출력하는 로직 제거하기
- [x] 우승자와 player 별 최종 이동거리 출력하도록 리팩토링

- [x] 중복 코드 제거하기
- [x] 중복 코드 제거하기
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation'
runtimeOnly 'com.h2database:h2'
}

Expand Down
5 changes: 2 additions & 3 deletions src/main/java/racingcar/RacingCarApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
@SpringBootApplication
public class RacingCarApplication {

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

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

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import racingcar.dto.response.ErrorResponse;

import java.util.Objects;

@RestControllerAdvice
public class ControllerAdvice {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleException(final MethodArgumentNotValidException exception) {
String errorMessage = Objects.requireNonNull(exception.getBindingResult().getFieldError()).getDefaultMessage();

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse.send(errorMessage));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package racingcar.controller;

import racingcar.domain.Cars;
import racingcar.domain.TryCount;
import racingcar.dto.car.CarStatusResponseDto;
import racingcar.dto.car.GameHistoriesResponseDto;
import racingcar.utils.RandomPowerGenerator;
import racingcar.view.InputView;
import racingcar.view.OutputView;

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

public class RacingGameConsoleController {

private static final String CAR_NAME_DELIMITER = ",";

private final InputView inputView;
private final OutputView outputView;
private final RandomPowerGenerator randomPowerGenerator;

public RacingGameConsoleController(final InputView inputView,
final OutputView outputView,
final RandomPowerGenerator randomPowerGenerator) {
this.inputView = inputView;
this.outputView = outputView;
this.randomPowerGenerator = randomPowerGenerator;
}

public void startGame() {
final Cars cars = makeCars();
final TryCount tryCount = makeTryCount();

startRace(cars, tryCount);

GameHistoriesResponseDto gameHistoryDto = GameHistoriesResponseDto.toDto(getWinnerNames(cars), convertToCarStatuses(cars));

outputView.printWinners(gameHistoryDto);
outputView.printCurrentRacingStatus(gameHistoryDto);
}

private Cars makeCars() {
try {
List<String> carNames = inputView.inputCarNames();
return Cars.from(carNames);
} catch (IllegalArgumentException exception) {
System.out.println(exception.getMessage());
return makeCars();
}
}

private TryCount makeTryCount() {
try {
int tryCount = inputView.inputTryCount();
return new TryCount(tryCount);
} catch (IllegalArgumentException | InputMismatchException exception) {
System.out.println(exception.getMessage());
return makeTryCount();
}
}

private void startRace(final Cars cars, final TryCount tryCount) {
outputView.printResultMessage();

for (int i = 0; i < tryCount.getTryCount(); i++) {
cars.moveAll(randomPowerGenerator);
}
}

private String getWinnerNames(final Cars cars) {
return String.join(CAR_NAME_DELIMITER, cars.getWinnerNames());
}

private List<CarStatusResponseDto> convertToCarStatuses(final Cars cars) {
return cars.getCars().stream()
.map(CarStatusResponseDto::toDto)
.collect(Collectors.toList());
}
}
43 changes: 0 additions & 43 deletions src/main/java/racingcar/controller/RacingGameController.java

This file was deleted.

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

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import racingcar.domain.Cars;
import racingcar.domain.TryCount;
import racingcar.dto.car.GameHistoriesResponseDto;
import racingcar.dto.car.GameResultResponseDto;
import racingcar.dto.car.StartGameRequestDto;
import racingcar.service.RacingCarService;

import javax.validation.Valid;
import java.util.List;

@RequestMapping("/plays")
@RestController
public class RacingGameWebController {

private static final String CAR_NAME_DELIMITER = ",";

private final RacingCarService racingCarService;

public RacingGameWebController(final RacingCarService racingCarService) {
this.racingCarService = racingCarService;
}

@GetMapping
public ResponseEntity<List<GameHistoriesResponseDto>> findGameHistories() {
return ResponseEntity.ok()
.body(racingCarService.findAllGameHistories());
}

@PostMapping
public ResponseEntity<GameResultResponseDto> startGame(@Valid @RequestBody final StartGameRequestDto request) {
Cars cars = makeCars(request.getNames());
TryCount tryCount = makeTryCount(request.getCount());

return ResponseEntity.status(HttpStatus.CREATED)
.body(racingCarService.startRace(cars, tryCount));
}

private Cars makeCars(final String input) {
List<String> carNames = List.of(input.split(CAR_NAME_DELIMITER));
return Cars.from(carNames);
}

private TryCount makeTryCount(final int input) {
return new TryCount(input);
}
}
55 changes: 41 additions & 14 deletions src/main/java/racingcar/dao/GameResultDao.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package racingcar.dao;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Objects;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Component;
import racingcar.domain.Cars;
import racingcar.domain.TryCount;
import racingcar.dto.GameResultResponseDto;
import racingcar.dto.car.GameResultResponseDto;
import racingcar.dto.car.PlayerHistoryDto;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Component
public class GameResultDao {
Expand All @@ -21,12 +26,32 @@ public GameResultDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void saveGame(final TryCount tryCount, final GameResultResponseDto gameResult) {
public List<Long> findAllGamesId() {
String sql = "SELECT id FROM game";
return jdbcTemplate.queryForList(sql, Long.class);
}

public List<PlayerHistoryDto> findPlayerHistoriesByGameId(final Long gameId) {
String sql = "SELECT name, position, isWinner FROM player WHERE game_id = ?";

List<PlayerHistoryDto> playerHistories = new ArrayList<>();

jdbcTemplate.query(sql, preparedStatement -> preparedStatement.setLong(1, gameId), response -> {
String name = response.getString("name");
int position = response.getInt("position");
boolean isWinner = response.getBoolean("isWinner");
playerHistories.add(PlayerHistoryDto.toDto(name, position, isWinner));
});

return playerHistories;
}

public GameResultResponseDto saveGame(final Cars cars, final TryCount tryCount) {
long gameId = saveGameHistory(tryCount);
savePlayersStatus(gameResult, gameId);
return savePlayersStatus(cars, gameId);
}

public long saveGameHistory(final TryCount tryCount) {
private Long saveGameHistory(final TryCount tryCount) {
String sql = "INSERT INTO game (trialCount, dateTime) VALUES (?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();

Expand All @@ -40,16 +65,18 @@ public long saveGameHistory(final TryCount tryCount) {
return Objects.requireNonNull(keyHolder.getKey()).longValue();
}

private void savePlayersStatus(final GameResultResponseDto gameResult, final long gameId) {
private GameResultResponseDto savePlayersStatus(final Cars cars, final Long gameId) {
String sql = "INSERT INTO player (game_id, name, position, isWinner) VALUES (?, ?, ?, ?)";

gameResult.getRacingCars()
cars.getCars()
.forEach(racingCar -> {
jdbcTemplate.update(sql,
gameId,
racingCar.getName(),
racingCar.getPosition(),
gameResult.isWinner(racingCar.getName()));
racingCar.getCarName(),
racingCar.getDistance(),
cars.isWinner(racingCar.getCarName()));
});

return GameResultResponseDto.toDto(cars);
}
}
19 changes: 18 additions & 1 deletion src/main/java/racingcar/domain/Cars.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,26 @@

public class Cars {

private static final int DEFAULT_DISTANCE_VALUE = 0;

private final List<Car> cars;

public Cars(final List<Car> cars) {
private Cars(final List<Car> cars) {
validate(cars);
this.cars = cars;
}

public static Cars from(final List<String> carNames) {
List<Car> cars = makeCars(carNames);
return new Cars(cars);
}

private static List<Car> makeCars(final List<String> carNames) {
return carNames.stream()
.map(carName -> new Car(carName, DEFAULT_DISTANCE_VALUE))
.collect(Collectors.toList());
}

private void validate(List<Car> cars) {
List<String> carNames = cars.stream()
.map(Car::getCarName)
Expand Down Expand Up @@ -46,6 +59,10 @@ public List<String> getWinnerNames() {
return winnerNames;
}

public boolean isWinner(final String name) {
return getWinnerNames().contains(name);
}

private int findMaxCountOfDistance() {
int maxCountOfDistance = Integer.MIN_VALUE;

Expand Down
23 changes: 0 additions & 23 deletions src/main/java/racingcar/dto/StartGameRequestDto.java

This file was deleted.

0 comments on commit f1170b5

Please sign in to comment.