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

[2단계 - 웹 자동차 경주] 제이(이재윤) 미션 제출합니다. #103

Merged
merged 32 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3f32c55
feat: Level 1 코드 복구 (1단계 미션 때 삭제해서 복구했습니다.)
sosow0212 Apr 15, 2023
3e5a7b7
feat: 게임 플레이 이력 조회 API 구현
sosow0212 Apr 15, 2023
51ff720
refactor: 게임 플레이 이력 조회 API 메서드 분리 및 서비스는 비즈니스 로직만 담당하도록 변경
sosow0212 Apr 15, 2023
94431bc
docs: 기능 구현 목록 수정
sosow0212 Apr 15, 2023
9ddcf23
test: 서비스 레이어 테스트 코드 작성
sosow0212 Apr 15, 2023
fad9124
test: 게임 이력 조회 테스트 작성
sosow0212 Apr 15, 2023
f4ca361
refactor: 우승자를 구하는 기능을 도메인 메서드로 변경
sosow0212 Apr 15, 2023
290166e
refactor: 게임 저장 후 결과를 반환하도록 변경
sosow0212 Apr 15, 2023
7f42ead
fix: 자동차 이름을 List로 나누게 리팩토링 후 실패하는 테스트 수정
sosow0212 Apr 15, 2023
1c20cec
refactor: Cars 정적 팩토리 메서드를 도메인 내부에서 가능하도록 변경
sosow0212 Apr 15, 2023
7b6e1df
refactor: Console 자동차 경주 게임에서 View의 파라미터를 Dto로 변경
sosow0212 Apr 15, 2023
968e0e4
docs: 기능 구현 목록 수정
sosow0212 Apr 15, 2023
abb5763
style: 불필요한 개행 삭제
sosow0212 Apr 15, 2023
4366e3e
refactor: 메서드명 통일 및 final 키워드 통일
sosow0212 Apr 15, 2023
b8b9d97
refactor: for에서 forEach로 변경
sosow0212 Apr 15, 2023
197ad1a
refactor: ResponseEntity 사용할 때 생성자로 반환하는 방식에서 빌더 패턴으로 변경
sosow0212 Apr 18, 2023
54d2e80
refactor: 불필요한 메서드 제거
sosow0212 Apr 18, 2023
8ef2ef4
style: 테스트 데이터 셋을 더 보기 좋게 개행 수정
sosow0212 Apr 18, 2023
a0260f5
refactor: 모호한 메서드명 변경
sosow0212 Apr 18, 2023
5f7f343
refactor: 외부에서 호출 안 하는 메서드의 접근 제어자를 private로 변경
sosow0212 Apr 18, 2023
53fbec3
test: GameResultDao 테스트 작성
sosow0212 Apr 18, 2023
08e605f
refactor: 컨트롤러 테스트 반환 값도 나오도록 리팩토링 진행
sosow0212 Apr 18, 2023
beca51d
test: 서비스 통합테스트 작성
sosow0212 Apr 18, 2023
c2e063e
refactor: 메서드명과 @DisplayName 변경
sosow0212 Apr 18, 2023
39e5cd1
fix: 테스트 개별 실행시에는 성공하지만, 전체 실행시 실패하는 문제 해결 (profile 설정)
sosow0212 Apr 18, 2023
0dd122b
chore: validation 종속성 추가
sosow0212 Apr 19, 2023
8f340b9
style: 디렉토리 이동
sosow0212 Apr 19, 2023
583b3fb
refactor: 잘못된 입력 값에 대해 예외처리 추가
sosow0212 Apr 19, 2023
8c0736e
test: 사용자 입력에 대한 값 검증 테스트 작성
sosow0212 Apr 19, 2023
b7d055c
refactor: 내부에서만 호출할 수 있도록 메서드의 접근제어자 변경
sosow0212 Apr 19, 2023
a2125d5
refactor: 상태코드가 중복된 의미이므로, 반환시에 상태코드를 반환 안 하도록 변경
sosow0212 Apr 20, 2023
6782237
refactor: 테스트 경우 추가
sosow0212 Apr 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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(HttpStatus.BAD_REQUEST.value(), errorMessage));
begaonnuri marked this conversation as resolved.
Show resolved Hide resolved
}
}
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 {
begaonnuri marked this conversation as resolved.
Show resolved Hide resolved

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.