Skip to content

Commit

Permalink
[BE-FEAT] 포켓몬 배틀 결과 계산 API (#261)
Browse files Browse the repository at this point in the history
* feat: 배틀에 사용할 포켓몬, 타입 상성 데이터 세팅

* feat: 배틀 계산 api

* refactor: 타입 enum으로 변경

* refactor: weather enum으로 변경

* feat: 배틀 계산에서 강풍 배수 적용

* refactor: 배틀 관련 데이터들 id 영어이름으로 변경

* fix: 날씨 비교 로직 오류 수정

* chore: 불필요한 데이터 삭제

* refactor: 불필요한 코드 제거

* refactor: api 명세에 따라 응답값 변경

* fix: 문법 오류

* style: 개행 추가

* refactor: BattleService 리팩토링

* refactor: MoveCategory 리팩토링
  • Loading branch information
jongmee authored Aug 20, 2024
1 parent 36cf9a6 commit d8da7b8
Show file tree
Hide file tree
Showing 20 changed files with 404 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,13 @@ public ApiResponse<List<WeatherResponse>> weatherList() {
public ApiResponse<List<MoveResponse>> moveByPokemonList(@RequestParam("pokedex-number") Integer pokedexNumber) {
return new ApiResponse<>("포켓몬의 기술 리스트 불러오기에 성공했습니다.", battleService.findMovesByPokemon(pokedexNumber));
}

@GetMapping("/api/v1/battle")
public ApiResponse<BattleResultResponse> battleResult(@RequestParam("weather-id") String weatherId,
@RequestParam("my-pokemon-id") String myPokemonId,
@RequestParam("rival-pokemon-id") String rivalPokemonId,
@RequestParam("my-move-id") String myMoveId) {
return new ApiResponse<>("배틀 예측 결과 불러오기에 성공했습니다.",
battleService.calculateBattleResult(weatherId, myPokemonId, rivalPokemonId, myMoveId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ public record BattleMove(
String name,
String nameAppend,
String effect,
String type,
Type type,
String defaultTypeId,
String category,
MoveCategory category,
String moveTarget,
Integer power,
Integer accuracy,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.pokerogue.helper.battle;

import java.util.List;

public record BattlePokemon(String id, List<Type> pokemonTypes) {

public boolean hasSameType(Type type) {
return this.pokemonTypes.stream()
.anyMatch(pokemonType -> pokemonType.equals(type));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.pokerogue.helper.battle;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.stereotype.Repository;

@Repository
public class BattlePokemonRepository {

private final Map<String, BattlePokemon> battlePokemons = new HashMap<>();

public void save(BattlePokemon battlePokemon) {
battlePokemons.put(battlePokemon.id(), battlePokemon);
}

public List<BattlePokemon> findAll() {
return battlePokemons.values()
.stream()
.toList();
}

public Optional<BattlePokemon> findById(String id) {
return Optional.ofNullable(battlePokemons.get(id));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.pokerogue.helper.battle;

public record BattleResultResponse(
int power,
double multiplier,
double accuracy,
String moveName,
String moveDescription,
String moveType,
String moveCategory
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.pokerogue.helper.global.exception.ErrorMessage;
import com.pokerogue.helper.global.exception.GlobalCustomException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -11,15 +12,15 @@
@RequiredArgsConstructor
public class BattleService {

private final WeatherRepository weatherRepository;
private final BattleMoveRepository battleMoveRepository;
private final PokemonMovesByEggRepository pokemonMovesByEggRepository;
private final PokemonMovesBySelfRepository pokemonMovesBySelfRepository;
private final PokemonMovesByMachineRepository pokemonMovesByMachineRepository;
private final BattlePokemonTypeRepository battlePokemonTypeRepository;
private final BattlePokemonRepository battlePokemonRepository;
private final TypeMatchingRepository typeMatchingRepository;

public List<WeatherResponse> findWeathers() {
return weatherRepository.findAll().stream()
return Arrays.stream(Weather.values())
.map(WeatherResponse::from)
.toList();
}
Expand Down Expand Up @@ -51,12 +52,102 @@ private BattleMove findMoveById(String id) {
}

private MoveResponse toMoveResponseWithLogo(BattleMove battleMove) {
PokemonType pokemonType = battlePokemonTypeRepository.findByName(battleMove.type())
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
String typeLogo = pokemonType.image();
MoveCategory moveCategory = MoveCategory.findByName(battleMove.category().toLowerCase());
Type moveType = battleMove.type();
String typeLogo = moveType.getImage();
MoveCategory moveCategory = battleMove.category();
String categoryLogo = moveCategory.getImage();

return MoveResponse.of(battleMove, typeLogo, categoryLogo);
}

public BattleResultResponse calculateBattleResult(
String weatherId,
String myPokemonId,
String rivalPokemonId,
String myMoveId) {
Weather weather = Weather.findById(weatherId)
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.WEATHER_NOT_FOUND));
BattlePokemon myPokemon = battlePokemonRepository.findById(myPokemonId)
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_NOT_FOUND));
BattlePokemon rivalPokemon = battlePokemonRepository.findById(rivalPokemonId)
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_NOT_FOUND));
BattleMove move = battleMoveRepository.findById(myMoveId)
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.MOVE_CATEGORY_NOT_FOUND));
Type moveType = move.type();

double weatherMultiplier = getWeatherMultiplier(moveType, weather);
double typeMatchingMultiplier = getTypeMatchingMultiplier(moveType, rivalPokemon.pokemonTypes());
double sameTypeBonusMultiplier = getSameTypeAttackBonusMultiplier(moveType, myPokemon);
double stringWindMultiplier = getStringWindMultiplier(moveType, rivalPokemon.pokemonTypes(), weather);

double totalMultiplier =
weatherMultiplier * typeMatchingMultiplier * sameTypeBonusMultiplier * stringWindMultiplier;
double finalAccuracy = calculateAccuracy(move, weather);

return new BattleResultResponse(
move.power(),
totalMultiplier,
finalAccuracy,
move.name(),
move.effect(),
moveType.getName(),
move.category().getName()
);
}

private double getWeatherMultiplier(Type moveType, Weather weather) {
if (weather == Weather.SUNNY || weather == Weather.HARSH_SUN) {
if (moveType == Type.FIRE) {
return 1.5;
}
if (moveType == Type.WATER) {
return 0.5;
}
}
if (weather == Weather.RAIN || weather == Weather.HEAVY_RAIN) {
if (moveType == Type.FIRE) {
return 0.5;
}
if (moveType == Type.WATER) {
return 1.5;
}
}

return 1;
}

private double getTypeMatchingMultiplier(Type moveType, List<Type> rivalPokemonTypes) {
return rivalPokemonTypes.stream()
.map(toType -> typeMatchingRepository.findByFromTypeAndToType(moveType, toType)
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.TYPE_MATCHING_ERROR)))
.map(TypeMatching::result)
.reduce(1d, (a, b) -> a * b);
}

private double getSameTypeAttackBonusMultiplier(Type moveType, BattlePokemon rivalPokemon) {
if (rivalPokemon.hasSameType(moveType)) {
return 1.5;
}

return 1;
}

private double getStringWindMultiplier(Type moveType, List<Type> rivalPokemonTypes, Weather weather) {
TypeMatching typeMatching = typeMatchingRepository.findByFromTypeAndToType(moveType, Type.FLYING)
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.TYPE_MATCHING_ERROR));

if (weather == Weather.STRONG_WINDS && rivalPokemonTypes.contains(Type.FLYING) && typeMatching.result() == 2) {
return 0.5;
}

return 1;
}

private double calculateAccuracy(BattleMove move, Weather weather) {
if (weather == Weather.FOG) {
return (double) move.accuracy() * 0.9;
}

return (double) move.accuracy();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.pokerogue.helper.battle;

import com.pokerogue.external.s3.service.S3Service;
import com.pokerogue.helper.global.exception.ErrorMessage;
import com.pokerogue.helper.global.exception.GlobalCustomException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
Expand All @@ -23,20 +25,15 @@ public class DataInitializer implements ApplicationRunner {
private static final String FIELD_DELIMITER = "/";
private static final String LIST_DELIMITER = ",";

private final WeatherRepository weatherRepository;
private final BattleMoveRepository battleMoveRepository;
private final PokemonMovesByMachineRepository pokemonMovesByMachineRepository;
private final PokemonMovesBySelfRepository pokemonMovesBySelfRepository;
private final PokemonMovesByEggRepository pokemonMovesByEggRepository;
private final BattlePokemonTypeRepository battlePokemonTypeRepository;
private final S3Service s3Service;
private final BattlePokemonRepository battlePokemonRepository;
private final TypeMatchingRepository typeMatchingRepository;

@Override
public void run(ApplicationArguments args) {
saveData("weather.txt", fields -> {
Weather weather = createWeather(fields);
weatherRepository.save(weather);
});
saveData("battle-move.txt", fields -> {
BattleMove battleMove = createMove(fields);
battleMoveRepository.save(battleMove);
Expand All @@ -50,10 +47,12 @@ public void run(ApplicationArguments args) {
pokemonMovesBySelfRepository.save(pokemonMovesBySelf);
PokemonMovesByEgg pokemonMovesByEgg = createPokemonMovesByEgg(fields);
pokemonMovesByEggRepository.save(pokemonMovesByEgg);
BattlePokemon battlePokemon = createBattlePokemon(fields);
battlePokemonRepository.save(battlePokemon);
});
saveData("type.txt", fields -> {
PokemonType pokemonType = createPokemonType(fields);
battlePokemonTypeRepository.save(pokemonType);
saveData("type-matching.txt", fields -> {
TypeMatching typeMatching = createTypeMatching(fields);
typeMatchingRepository.save(typeMatching);
});
}

Expand Down Expand Up @@ -81,26 +80,20 @@ private List<String> splitFields(String line) {
.toList();
}

private Weather createWeather(List<String> fields) {
String id = fields.get(0);
String name = fields.get(1);
String description = fields.get(2);
List<String> effects = Arrays.stream(fields.get(3).split(LIST_DELIMITER))
.map(String::trim)
.toList();

return new Weather(id, name, description, effects);
}

private BattleMove createMove(List<String> fields) {
Type moveType = Type.findByName(fields.get(4))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
MoveCategory moveCategory = MoveCategory.findByEngName(fields.get(6).toLowerCase())
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.MOVE_CATEGORY_NOT_FOUND));

return new BattleMove(
fields.get(1), /* 우선은 한글 이름을 id로 설정, 추후 숫자로 변경 */
fields.get(0),
fields.get(1),
fields.get(2),
fields.get(3),
fields.get(4),
moveType,
fields.get(5),
fields.get(6),
moveCategory,
fields.get(7),
convertToInteger(fields.get(8)),
convertToInteger(fields.get(9)),
Expand All @@ -124,7 +117,7 @@ private PokemonMovesByMachine createPokemonMovesByMachine(List<String> fields) {

private PokemonMovesBySelf createPokemonMovesBySelf(List<String> fields) {
Integer pokedexNumber = convertToInteger(fields.get(0));
List<String> moveIds = Arrays.stream(fields.get(19).split(LIST_DELIMITER))
List<String> moveIds = Arrays.stream(fields.get(5).split(LIST_DELIMITER))
.map(String::trim)
.filter(s -> !s.isEmpty())
.toList();
Expand All @@ -134,22 +127,52 @@ private PokemonMovesBySelf createPokemonMovesBySelf(List<String> fields) {

private PokemonMovesByEgg createPokemonMovesByEgg(List<String> fields) {
Integer pokedexNumber = convertToInteger(fields.get(0));
List<String> moveIds = Arrays.stream(fields.get(18).split(LIST_DELIMITER))
List<String> moveIds = Arrays.stream(fields.get(4).split(LIST_DELIMITER))
.map(String::trim)
.filter(s -> !s.isEmpty())
.toList();

return new PokemonMovesByEgg(pokedexNumber, moveIds);
}

private PokemonType createPokemonType(List<String> fields) {
String name = fields.get(0);
String engName = fields.get(1);
String image = s3Service.getPokerogueTypeImageFromS3(engName);
private BattlePokemon createBattlePokemon(List<String> fields) {
String id = fields.get(1);
List<Type> types = new ArrayList<>();
if (existTypeName(fields.get(2))) {
Type type = Type.findByName(fields.get(2))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
types.add(type);
}
if (existTypeName(fields.get(3))) {
Type type = Type.findByName(fields.get(3))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
types.add(type);
}

return new BattlePokemon(id, types);
}

private TypeMatching createTypeMatching(List<String> fields) {
Type fromType = Type.findByEngName(fields.get(0))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
Type toType = Type.findByEngName(fields.get(1))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
double result = convertToDouble(fields.get(2));

return new TypeMatching(fromType, toType, result);
}

return new PokemonType(name, engName, image);
private boolean existTypeName(String data) {
return !data.equals("Type.undefined");
}

private double convertToDouble(String data) {
try {
return Double.parseDouble(data);
} catch (NumberFormatException e) {
throw new GlobalCustomException(ErrorMessage.PARSE_ERROR);
}
}

private Integer convertToInteger(String data) {
try {
Expand Down
Loading

0 comments on commit d8da7b8

Please sign in to comment.