From d8da7b8cf59e2c01b9aba5296dbe80f1e0d13a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A2=85=EB=AF=B8=28=EB=AF=B8=EC=95=84=29?= <101439796+jongmee@users.noreply.github.com> Date: Tue, 20 Aug 2024 22:43:25 +0900 Subject: [PATCH] =?UTF-8?q?[BE-FEAT]=20=ED=8F=AC=EC=BC=93=EB=AA=AC=20?= =?UTF-8?q?=EB=B0=B0=ED=8B=80=20=EA=B2=B0=EA=B3=BC=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?API=20=20(#261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 배틀에 사용할 포켓몬, 타입 상성 데이터 세팅 * feat: 배틀 계산 api * refactor: 타입 enum으로 변경 * refactor: weather enum으로 변경 * feat: 배틀 계산에서 강풍 배수 적용 * refactor: 배틀 관련 데이터들 id 영어이름으로 변경 * fix: 날씨 비교 로직 오류 수정 * chore: 불필요한 데이터 삭제 * refactor: 불필요한 코드 제거 * refactor: api 명세에 따라 응답값 변경 * fix: 문법 오류 * style: 개행 추가 * refactor: BattleService 리팩토링 * refactor: MoveCategory 리팩토링 --- .../helper/battle/BattleController.java | 9 ++ .../pokerogue/helper/battle/BattleMove.java | 4 +- .../helper/battle/BattlePokemon.java | 11 ++ .../battle/BattlePokemonRepository.java | 27 +++++ .../battle/BattlePokemonTypeRepository.java | 27 ----- .../helper/battle/BattleResultResponse.java | 12 ++ .../helper/battle/BattleService.java | 105 ++++++++++++++++-- .../helper/battle/DataInitializer.java | 87 +++++++++------ .../pokerogue/helper/battle/MoveCategory.java | 26 +++-- .../pokerogue/helper/battle/PokemonType.java | 4 - .../com/pokerogue/helper/battle/Type.java | 65 +++++++++++ .../pokerogue/helper/battle/TypeMatching.java | 4 + .../helper/battle/TypeMatchingRepository.java | 30 +++++ .../com/pokerogue/helper/battle/Weather.java | 44 +++++++- .../helper/battle/WeatherRepository.java | 22 ---- .../helper/battle/WeatherResponse.java | 11 +- .../helper/global/exception/ErrorMessage.java | 4 + backend/pokerogue/src/main/resources | 2 +- .../helper/battle/BattleServiceTest.java | 18 +++ .../helper/battle/DataInitializerTest.java | 18 ++- 20 files changed, 404 insertions(+), 126 deletions(-) create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemon.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemonRepository.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemonTypeRepository.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleResultResponse.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/PokemonType.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/Type.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/TypeMatching.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/TypeMatchingRepository.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/battle/WeatherRepository.java diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleController.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleController.java index 2f513985a..43ea7a5c7 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleController.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleController.java @@ -24,4 +24,13 @@ public ApiResponse> weatherList() { public ApiResponse> moveByPokemonList(@RequestParam("pokedex-number") Integer pokedexNumber) { return new ApiResponse<>("포켓몬의 기술 리스트 불러오기에 성공했습니다.", battleService.findMovesByPokemon(pokedexNumber)); } + + @GetMapping("/api/v1/battle") + public ApiResponse 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)); + } } diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleMove.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleMove.java index 3c80dc4ed..da3d7fc63 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleMove.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleMove.java @@ -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, diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemon.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemon.java new file mode 100644 index 000000000..b3cd52532 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemon.java @@ -0,0 +1,11 @@ +package com.pokerogue.helper.battle; + +import java.util.List; + +public record BattlePokemon(String id, List pokemonTypes) { + + public boolean hasSameType(Type type) { + return this.pokemonTypes.stream() + .anyMatch(pokemonType -> pokemonType.equals(type)); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemonRepository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemonRepository.java new file mode 100644 index 000000000..5950af987 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemonRepository.java @@ -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 battlePokemons = new HashMap<>(); + + public void save(BattlePokemon battlePokemon) { + battlePokemons.put(battlePokemon.id(), battlePokemon); + } + + public List findAll() { + return battlePokemons.values() + .stream() + .toList(); + } + + public Optional findById(String id) { + return Optional.ofNullable(battlePokemons.get(id)); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemonTypeRepository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemonTypeRepository.java deleted file mode 100644 index ae9768638..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattlePokemonTypeRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -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 BattlePokemonTypeRepository { - - private final Map pokemonTypes = new HashMap<>(); - - public void save(PokemonType pokemonType) { - pokemonTypes.put(pokemonType.name(), pokemonType); - } - - public List findAll() { - return pokemonTypes.values() - .stream() - .toList(); - } - - public Optional findByName(String name) { - return Optional.ofNullable(pokemonTypes.get(name)); - } -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleResultResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleResultResponse.java new file mode 100644 index 000000000..01bc9f59b --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleResultResponse.java @@ -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 +) { +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleService.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleService.java index a983404aa..7f78e21fa 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleService.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/BattleService.java @@ -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; @@ -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 findWeathers() { - return weatherRepository.findAll().stream() + return Arrays.stream(Weather.values()) .map(WeatherResponse::from) .toList(); } @@ -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 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 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(); + } } diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/DataInitializer.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/DataInitializer.java index 23071685c..d91d33223 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/DataInitializer.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/DataInitializer.java @@ -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; @@ -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); @@ -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); }); } @@ -81,26 +80,20 @@ private List splitFields(String line) { .toList(); } - private Weather createWeather(List fields) { - String id = fields.get(0); - String name = fields.get(1); - String description = fields.get(2); - List effects = Arrays.stream(fields.get(3).split(LIST_DELIMITER)) - .map(String::trim) - .toList(); - - return new Weather(id, name, description, effects); - } - private BattleMove createMove(List 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)), @@ -124,7 +117,7 @@ private PokemonMovesByMachine createPokemonMovesByMachine(List fields) { private PokemonMovesBySelf createPokemonMovesBySelf(List fields) { Integer pokedexNumber = convertToInteger(fields.get(0)); - List moveIds = Arrays.stream(fields.get(19).split(LIST_DELIMITER)) + List moveIds = Arrays.stream(fields.get(5).split(LIST_DELIMITER)) .map(String::trim) .filter(s -> !s.isEmpty()) .toList(); @@ -134,7 +127,7 @@ private PokemonMovesBySelf createPokemonMovesBySelf(List fields) { private PokemonMovesByEgg createPokemonMovesByEgg(List fields) { Integer pokedexNumber = convertToInteger(fields.get(0)); - List moveIds = Arrays.stream(fields.get(18).split(LIST_DELIMITER)) + List moveIds = Arrays.stream(fields.get(4).split(LIST_DELIMITER)) .map(String::trim) .filter(s -> !s.isEmpty()) .toList(); @@ -142,14 +135,44 @@ private PokemonMovesByEgg createPokemonMovesByEgg(List fields) { return new PokemonMovesByEgg(pokedexNumber, moveIds); } - private PokemonType createPokemonType(List fields) { - String name = fields.get(0); - String engName = fields.get(1); - String image = s3Service.getPokerogueTypeImageFromS3(engName); + private BattlePokemon createBattlePokemon(List fields) { + String id = fields.get(1); + List 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 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 { diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/MoveCategory.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/MoveCategory.java index cc880f16f..e1c4f6aa7 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/MoveCategory.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/MoveCategory.java @@ -1,34 +1,36 @@ package com.pokerogue.helper.battle; -import com.pokerogue.helper.global.exception.ErrorMessage; -import com.pokerogue.helper.global.exception.GlobalCustomException; import java.util.Arrays; +import java.util.Optional; import lombok.Getter; @Getter public enum MoveCategory { - STATUS("status", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/move-category/status.png"), - SPECIAL("special", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/move-category/special.png"), - PHYSICAL("physical", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/move-category/physical.png"), + STATUS("변화", "status", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/move-category/status.png"), + SPECIAL("특수", "special", + "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/move-category/special.png"), + PHYSICAL("물리", "physical", + "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/move-category/physical.png"), ; private final String name; + private final String engName; private final String image; - MoveCategory(String name, String image) { + MoveCategory(String name, String engName, String image) { this.name = name; + this.engName = engName; this.image = image; } - public static MoveCategory findByName(String name) { + public static Optional findByEngName(String name) { return Arrays.stream(values()) - .filter(category -> category.hasSameName(name)) - .findAny() - .orElseThrow(() -> new GlobalCustomException(ErrorMessage.MOVE_CATEGORY_NOT_FOUND)); + .filter(category -> category.hasSameEngName(name)) + .findAny(); } - private boolean hasSameName(String name) { - return this.name.equals(name); + private boolean hasSameEngName(String name) { + return this.engName.equals(name); } } diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/PokemonType.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/PokemonType.java deleted file mode 100644 index 0b5905041..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/PokemonType.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.pokerogue.helper.battle; - -public record PokemonType(String name, String engName, String image) { -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/Type.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/Type.java new file mode 100644 index 000000000..9686ee162 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/Type.java @@ -0,0 +1,65 @@ +package com.pokerogue.helper.battle; + +import com.pokerogue.helper.global.exception.ErrorMessage; +import com.pokerogue.helper.global.exception.GlobalCustomException; +import java.util.Arrays; +import java.util.Optional; + +public enum Type { + + GRASS("grass", "풀", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/grass-1"), + POISON("poison", "독", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/poison-1"), + FIRE("fire", "불꽃", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/fire-1"), + WATER("water", "물", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/water-1"), + ELECTRIC("electric", "전기", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/electric-1"), + NORMAL("normal", "노말", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/normal-1"), + FAIRY("fairy", "페어리", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/fairy-1"), + BUG("bug", "벌레", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/bug-1"), + DARK("dark", "악", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/dark-1"), + DRAGON("dragon", "드래곤", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/dragon-1"), + FIGHTING("fighting", "격투", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/fighting-1"), + FLYING("flying", "비행", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/flying-1"), + GHOST("ghost", "고스트", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/ghost-1"), + GROUND("ground", "땅", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/ground-1"), + ICE("ice", "얼음", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/ice-1"), + ROCK("rock", "바위", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/rock-1"), + PSYCHIC("psychic", "에스퍼", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/psychic-1"), + STEEL("steel", "강철", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/steel-1"), + STELLAR("stellar", "스텔라", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/stellar-1"), + UNKNOWN("unknown", "언노운", "https://dl70s9ccojnge.cloudfront.net/pokerogue-helper/pokerogue/type/unknown-1"), + ; + + private final String engName; + private final String name; + private final String image; + + Type(String engName, String name, String image) { + this.engName = engName; + this.name = name; + this.image = image; + } + + public static Optional findByName(String name) { + return Arrays.stream(values()) + .filter(type -> type.name.equals(name)) + .findAny(); + } + + public static Optional findByEngName(String engName) { + return Arrays.stream(values()) + .filter(type -> type.engName.equals(engName)) + .findAny(); + } + + public String getImage() { + return image + ".png"; + } + + public String getName() { + return name; + } + + public String getEngName() { + return engName; + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/TypeMatching.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/TypeMatching.java new file mode 100644 index 000000000..a7634bfd2 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/TypeMatching.java @@ -0,0 +1,4 @@ +package com.pokerogue.helper.battle; + +public record TypeMatching(Type fromType, Type toType, double result) { +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/TypeMatchingRepository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/TypeMatchingRepository.java new file mode 100644 index 000000000..ddd1ee64d --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/TypeMatchingRepository.java @@ -0,0 +1,30 @@ +package com.pokerogue.helper.battle; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.springframework.stereotype.Repository; + +@Repository +public class TypeMatchingRepository { + + private final Map typeMatchings = new HashMap<>(); + + public void save(TypeMatching typeMatching) { + int id = Objects.hash(typeMatching.fromType(), typeMatching.toType()); + typeMatchings.put(id, typeMatching); + } + + public List findAll() { + return typeMatchings.values() + .stream() + .toList(); + } + + public Optional findByFromTypeAndToType(Type fromType, Type toType) { + int id = Objects.hash(fromType, toType); + return Optional.ofNullable(typeMatchings.get(id)); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/Weather.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/Weather.java index 5aa1320ad..c438aa713 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/Weather.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/Weather.java @@ -1,11 +1,43 @@ package com.pokerogue.helper.battle; +import java.util.Arrays; import java.util.List; +import java.util.Optional; +import lombok.Getter; -public record Weather( - String id, - String name, - String description, - List effects -) { +@Getter +public enum Weather { + + NONE("none", "없음", "없음", List.of("없음")), + SUNNY("sunny", "쾌청", "햇살이 강하다", List.of("불꽃 타입 기술의 위력이 1.5배가 된다", "물 타입 기술의 위력이 0.5배가 된다")), + RAIN("rain", "비", "비가 계속 내리고 있다", List.of("물 타입 기술의 위력이 1.5배가 된다", "불꽃 타입 기술의 위력이 0.5배가 된다")), + SANDSTORM("sandstorm", "모래바람", "모래바람이 세차게 분다", + List.of("바위 또는 땅 또는 강철 타입 포켓몬이 아니면 매 턴마다 체력의 1/16의 데미지를 받는다", "바위 타입 포켓몬의 특수방어가 1.5배가 된다")), + HAIL("hail", "싸라기눈", "싸라기눈이 계속 내리고 있다", List.of("얼음 타입 포켓몬이 아니면 매 턴마다 체력의 1/16의 데미지를 받는다")), + SNOW("snow", "눈", "눈이 계속 내리고 있다", List.of("얼음 타입 포켓몬의 방어가 1.5배 올라간다")), + FOG("fog", "안개", "발밑이 안개로 자욱하다", List.of("모든 기술의 명중률이 0.9배가 된다")), + HEAVY_RAIN("heavy_rain", "강한 비", "강한 비가 계속 내리고 있다", + List.of("불타입 기술은 모두 실패한다", "불꽃 타입 기술의 위력이 1.5배가 된다", "물 타입 기술의 위력이 1.5배가 된다")), + HARSH_SUN("harsh_sun", "강한 쾌청", "햇살이 아주 강하다", + List.of("물타입 기술은 모두 실패한다", "불꽃 타입 기술의 위력이 1.5배가 된다", "물 타입 기술의 위력이 0.5배가 된다")), + STRONG_WINDS("strong_winds", "난기류", "수수께끼의 난기류가 강렬하게 불고 있다", List.of("비행 타입의 약점을 없애준다")), + ; + + private final String id; + private final String name; + private final String description; + private final List effects; + + Weather(String id, String name, String description, List effects) { + this.id = id; + this.name = name; + this.description = description; + this.effects = effects; + } + + public static Optional findById(String id) { + return Arrays.stream(values()) + .filter(weather -> weather.id.equals(id)) + .findAny(); + } } diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/WeatherRepository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/WeatherRepository.java deleted file mode 100644 index 02ff5b514..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/WeatherRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.pokerogue.helper.battle; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.springframework.stereotype.Repository; - -@Repository -public class WeatherRepository { - - private final Map weathers = new HashMap<>(); - - public void save(Weather weather) { - weathers.put(weather.id(), weather); - } - - public List findAll() { - return weathers.values() - .stream() - .toList(); - } -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/WeatherResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/WeatherResponse.java index d201d108b..fdb1fa1b9 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/WeatherResponse.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/battle/WeatherResponse.java @@ -1,8 +1,15 @@ package com.pokerogue.helper.battle; -public record WeatherResponse(String id, String name) { +import java.util.List; + +public record WeatherResponse( + String id, + String name, + String description, + List effects +) { public static WeatherResponse from(Weather weather) { - return new WeatherResponse(weather.id(), weather.name()); + return new WeatherResponse(weather.getId(), weather.getName(), weather.getDescription(), weather.getEffects()); } } diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/global/exception/ErrorMessage.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/global/exception/ErrorMessage.java index 9a73cef6f..28baa6da1 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/global/exception/ErrorMessage.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/global/exception/ErrorMessage.java @@ -20,6 +20,10 @@ public enum ErrorMessage { MOVE_BY_SELF_NOT_FOUND(HttpStatus.NOT_FOUND, "도감 번호에 해당하는 자력 기술을 찾지 못했습니다."), MOVE_NOT_FOUND(HttpStatus.NOT_FOUND, "id에 해당하는 기술을 찾지 못했습니다."), MOVE_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "기술 카테고리를 찾지 못했습니다."), + WEATHER_NOT_FOUND(HttpStatus.NOT_FOUND, "id에 해당하는 날씨를 찾지 못했습니다."), + + PARSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "파싱에 실패했습니다."), + TYPE_MATCHING_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "타입 상성 찾기에 실패했습니다."), FILE_ACCESS_FAILED(HttpStatus.BAD_REQUEST, "파일 정보 접근에 실패했습니다."), FILE_EXTENSION_NOT_APPLY(HttpStatus.BAD_REQUEST, "지원하지 않는 파일 형식입니다."), diff --git a/backend/pokerogue/src/main/resources b/backend/pokerogue/src/main/resources index ccbd1a11e..1a2997093 160000 --- a/backend/pokerogue/src/main/resources +++ b/backend/pokerogue/src/main/resources @@ -1 +1 @@ -Subproject commit ccbd1a11e6a4aeaef6003a9389626fd0193a4999 +Subproject commit 1a2997093f1f81512fb5c6d19f23dfc36e28b484 diff --git a/backend/pokerogue/src/test/java/com/pokerogue/helper/battle/BattleServiceTest.java b/backend/pokerogue/src/test/java/com/pokerogue/helper/battle/BattleServiceTest.java index 8f58e3c42..abc83d37f 100644 --- a/backend/pokerogue/src/test/java/com/pokerogue/helper/battle/BattleServiceTest.java +++ b/backend/pokerogue/src/test/java/com/pokerogue/helper/battle/BattleServiceTest.java @@ -1,6 +1,7 @@ package com.pokerogue.helper.battle; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import com.pokerogue.environment.service.ServiceTest; import java.util.List; @@ -29,4 +30,21 @@ void findMovesByPokemon() { assertThat(moveResponses).isNotEmpty(); }); } + + @Test + @DisplayName("배틀 예상 결과를 계산한다.") + void calculateBattleResult() { + String weatherId = "sunny"; + String myPokemonId = "charmander"; + String rivalPokemonId = "bulbasaur"; + String myMoveId = "ember"; + + BattleResultResponse battleResultResponse = battleService.calculateBattleResult(weatherId, myPokemonId, + rivalPokemonId, myMoveId); + + assertAll(() -> { + assertThat(battleResultResponse.multiplier()).isEqualTo(4.5); + assertThat(battleResultResponse.accuracy()).isEqualTo(100); + }); + } } diff --git a/backend/pokerogue/src/test/java/com/pokerogue/helper/battle/DataInitializerTest.java b/backend/pokerogue/src/test/java/com/pokerogue/helper/battle/DataInitializerTest.java index bf1926012..fb2735c3a 100644 --- a/backend/pokerogue/src/test/java/com/pokerogue/helper/battle/DataInitializerTest.java +++ b/backend/pokerogue/src/test/java/com/pokerogue/helper/battle/DataInitializerTest.java @@ -3,8 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -import com.pokerogue.environment.client.FakeS3ImageClient; -import com.pokerogue.external.s3.service.S3Service; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.DefaultApplicationArguments; @@ -14,31 +12,29 @@ class DataInitializerTest { @Test @DisplayName("날씨, 기술 데이터를 세팅한다.") void setWeathersData() { - WeatherRepository weatherRepository = new WeatherRepository(); BattleMoveRepository battleMoveRepository = new BattleMoveRepository(); PokemonMovesByMachineRepository pokemonMovesByMachineRepository = new PokemonMovesByMachineRepository(); PokemonMovesBySelfRepository pokemonMovesBySelfRepository = new PokemonMovesBySelfRepository(); PokemonMovesByEggRepository pokemonMovesByEggRepository = new PokemonMovesByEggRepository(); - BattlePokemonTypeRepository battlePokemonTypeRepository = new BattlePokemonTypeRepository(); - S3Service s3Service = new S3Service(new FakeS3ImageClient()); + BattlePokemonRepository battlePokemonRepository = new BattlePokemonRepository(); + TypeMatchingRepository typeMatchingRepository = new TypeMatchingRepository(); DataInitializer dataInitializer = new DataInitializer( - weatherRepository, battleMoveRepository, pokemonMovesByMachineRepository, pokemonMovesBySelfRepository, pokemonMovesByEggRepository, - battlePokemonTypeRepository, - s3Service + battlePokemonRepository, + typeMatchingRepository ); dataInitializer.run(new DefaultApplicationArguments()); assertAll(() -> { - assertThat(weatherRepository.findAll()).hasSize(10); - assertThat(battleMoveRepository.findAll()).hasSize(902); + assertThat(battleMoveRepository.findAll()).hasSize(920); assertThat(pokemonMovesByMachineRepository.findAll()).hasSize(1082); assertThat(pokemonMovesBySelfRepository.findAll()).hasSize(1082); assertThat(pokemonMovesByEggRepository.findAll()).hasSize(1082); - assertThat(battlePokemonTypeRepository.findAll()).hasSize(20); + assertThat(battlePokemonRepository.findAll()).hasSize(1350); + assertThat(typeMatchingRepository.findAll()).hasSize(361); }); } }