From 0b1b12b0d6a23d674f8cac84a7278ca5ac5802aa Mon Sep 17 00:00:00 2001 From: woojong Date: Wed, 21 Aug 2024 16:06:25 +0900 Subject: [PATCH] =?UTF-8?q?[BE-REFACTOR]=20=ED=8F=AC=EC=BC=93=EB=AA=AC=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20API=20(#263)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * reafactor: 단일 포켓몬 조회 api명세서대로 주소 변경 * test: 포켓몬 정보가 지정된 개수만큼 저장된다 * test: 랜덤 포켓몬 이름 검 * refactor: 파일 위치 이동 및 repsonse 구현 * refactor: 더미 데이터로 단일 조회 리펙터링 * refactor: 응답 관련 dto 생 * refactor: 서비스단에서 응답 객체 생성 * refactor: 포켓몬 데이터 저장 객체 생성 * feat: 바이옴, 타입, 특성 이넘 구 * feat: 데이터 추가 * feat: 데이터 추가 * feat: 간이 데이터 추가 * refactor: 전체 포켓몬 덱스 번호 기준으로 반환 * feat: 폼 이름 추가 * chore: 커밋 시점 변경 * chore: 커밋 시점 변경 * chore: 커밋 시점 변경 * refactor: s3 이미지 URL 적용 * chore: 커밋 시점 변경 * feat: 진화체 응답 구현 * refactor: repository optinal 적용 * refactor: 포켓몬 데이터 저장할 때 스탯 파싱으로 변경 * refactor: 진화 응답에 depth추가 * chore:커밋 시점 변경 * chore: 서브모듈 커밋 시점 변경 * test --- .gitmodules | 2 +- backend/.DS_Store | Bin 0 -> 6148 bytes .../pokerogue/helper/global/config/move.txt | 920 ----------- .../helper/global/config/pokemon.txt | 1350 ----------------- .../exception/PokemonExceptionHandler.java | 2 +- .../helper/pokemon2/BiomeResponse.java | 4 - .../helper/pokemon2/EggMoveResponse.java | 4 - .../helper/pokemon2/EvolutionResponse.java | 5 - .../helper/pokemon2/MoveRepository.java | 22 - .../helper/pokemon2/MoveResponse.java | 11 - .../pokemon2/Pokemon2DatabaseInitializer.java | 92 -- .../helper/pokemon2/Pokemon2Repository.java | 20 - .../helper/pokemon2/Pokemon2Service.java | 168 -- .../pokemon2/PokemonAbilityResponse.java | 5 - .../config/Pokemon2DatabaseInitializer.java | 332 ++++ .../{ => controller}/Pokemon2Controller.java | 12 +- .../helper/pokemon2/data/Ability.java | 352 +++++ .../pokerogue/helper/pokemon2/data/Biome.java | 65 + .../helper/pokemon2/data/Evolution.java | 5 + .../helper/pokemon2/data/EvolutionChain.java | 40 + .../helper/pokemon2/data/EvolutionItem.java | 33 + .../pokerogue/helper/pokemon2/data/Move.java | 4 + .../helper/pokemon2/data/MoveCategory.java | 33 + .../helper/pokemon2/data/Pokemon.java | 37 + .../pokerogue/helper/pokemon2/data/Type.java | 50 + .../helper/pokemon2/dto/BiomeResponse.java | 5 + .../pokemon2/dto/EvolutionResponse.java | 31 + .../pokemon2/dto/EvolutionResponses.java | 7 + .../helper/pokemon2/dto/MoveResponse.java | 36 + .../pokemon2/dto/Pokemon2DetailResponse.java | 31 + .../helper/pokemon2/dto/Pokemon2Response.java | 49 + .../pokemon2/dto/PokemonAbilityResponse.java | 8 + .../repository/EvolutionRepository.java | 38 + .../pokemon2/repository/MoveRepository.java | 22 + .../repository/Pokemon2Repository.java | 26 + .../pokemon2/service/Pokemon2Service.java | 234 +++ .../helper/type/dto/PokemonTypeResponse.java | 1 + .../data/Pokemon2DatabaseInitializerTest.java | 48 + 38 files changed, 1496 insertions(+), 2608 deletions(-) create mode 100644 backend/.DS_Store delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/global/config/move.txt delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/global/config/pokemon.txt delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/BiomeResponse.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/EggMoveResponse.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/EvolutionResponse.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/MoveRepository.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/MoveResponse.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2DatabaseInitializer.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Repository.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Service.java delete mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/PokemonAbilityResponse.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/config/Pokemon2DatabaseInitializer.java rename backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/{ => controller}/Pokemon2Controller.java (62%) create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Ability.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Biome.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Evolution.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/EvolutionChain.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/EvolutionItem.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Move.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/MoveCategory.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Pokemon.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Type.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/BiomeResponse.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/EvolutionResponse.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/EvolutionResponses.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/MoveResponse.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/Pokemon2DetailResponse.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/Pokemon2Response.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/PokemonAbilityResponse.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/EvolutionRepository.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/MoveRepository.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/Pokemon2Repository.java create mode 100644 backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/service/Pokemon2Service.java create mode 100644 backend/pokerogue/src/test/java/com/pokerogue/helper/pokemon2/data/Pokemon2DatabaseInitializerTest.java diff --git a/.gitmodules b/.gitmodules index 5a5f4400c..30e64ffd3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "backend/pokerogue/src/main/resources"] path = backend/pokerogue/src/main/resources - url = https://github.com/team-timmyroom/be-config.git + url = https://github.com/team-timmyroom/be-config diff --git a/backend/.DS_Store b/backend/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9f107a5269db38eaec78fb13cb38991700ea5344 GIT binary patch literal 6148 zcmeHKJ8nWj3>+sEL^PBs_X@ee3c(4u07Vc*5JX6%epSwuqh4Ifrdwwh2Zp3eJQl*4+Wq7;w< z*9tu5cJBRuMgL{~zb0uX1*E`TDPWW3db!{$Rc{@=ocG#B-_X70i|)pCP#B^e6Qdn- g handleDataAccessException(DataAccessException e) { @ExceptionHandler(NullPointerException.class) public ResponseEntity handleNullPointerException(NullPointerException e) { - log.error("error message : {}", e.getStackTrace()[0]); + log.error("error message : {}", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Null Pointer 에러가 발생했습니다."); diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/BiomeResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/BiomeResponse.java deleted file mode 100644 index f5c45a04c..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/BiomeResponse.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.pokerogue.helper.pokemon2; - -public record BiomeResponse(String id, String name) { -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/EggMoveResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/EggMoveResponse.java deleted file mode 100644 index 1476b61fe..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/EggMoveResponse.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.pokerogue.helper.pokemon2; - -public record EggMoveResponse(String id, String name) { -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/EvolutionResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/EvolutionResponse.java deleted file mode 100644 index 9730a3557..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/EvolutionResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.pokerogue.helper.pokemon2; - -public record EvolutionResponse(String pokemonName, Integer level) { - -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/MoveRepository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/MoveRepository.java deleted file mode 100644 index e4bf1e0f8..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/MoveRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.pokerogue.helper.pokemon2; - - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; -import org.springframework.stereotype.Component; -import org.springframework.stereotype.Repository; - -@Component -public class MoveRepository { - private final Map> data = new TreeMap<>(); - - public Map> findAll() { - return Collections.unmodifiableMap(data); - } - - public void save(String key, Map value) { - data.put(key, value); - } -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/MoveResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/MoveResponse.java deleted file mode 100644 index 5336986fb..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/MoveResponse.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.pokerogue.helper.pokemon2; - -record MoveResponse( - String name, - Integer level, - Integer power, - Integer accuracy, - String type, - String category) { - -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2DatabaseInitializer.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2DatabaseInitializer.java deleted file mode 100644 index 5b15f49b9..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2DatabaseInitializer.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.pokerogue.helper.pokemon2; - -import com.pokerogue.helper.pokemon2.MoveRepository; -import com.pokerogue.helper.pokemon2.Pokemon2Repository; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.StringTokenizer; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import lombok.RequiredArgsConstructor; -import org.apache.catalina.Store; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - - -@Component -@Transactional -@Profile("local-mysql") -@RequiredArgsConstructor -public class Pokemon2DatabaseInitializer implements ApplicationRunner { - - private final Pokemon2Repository pokemon2Repository; - private final MoveRepository moveRepository; - - List moveKeys = List.of("id", "name", "nameAppend", "effect", "type", "defaultType", "category", - "moveTarget", "power", "accuracy", "pp", "chance", "priority", "generation", "flags"); - List pokemonKeys = List.of("speciesId", "name", "type1", "type2", "ability1", "ability2", "abilityHidden", - "abilityPassive", "generation", "legendary", "subLegendary", "mythical", "getEvolutionLevels", "baseTotal", - "baseStats", "height", "weight", "canChangeForm", "eggMoves", "moves", "biomes"); - - @Override - public void run(ApplicationArguments args) { - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("pokemon.txt"); - BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { - - String line; - while ((line = br.readLine()) != null) { - List values = new ArrayList<>(); - StringTokenizer stringTokenizer = new StringTokenizer(line, "/"); - - while (stringTokenizer.hasMoreTokens()) { - values.add(stringTokenizer.nextToken().strip()); - } - - if (values.size() != pokemonKeys.size()) { - values.add("EMPTY"); - } - Map map = IntStream.range(0, pokemonKeys.size()) - .boxed() - .collect(Collectors.toMap(pokemonKeys::get, values::get)); - - pokemon2Repository.save(map.get("name"), map); - } - saveMove(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void saveMove() { - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("move.txt"); - BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { - - String line; - while ((line = br.readLine()) != null) { - List values = new ArrayList<>(); - StringTokenizer stringTokenizer = new StringTokenizer(line, "/"); - - while (stringTokenizer.hasMoreTokens()) { - values.add(stringTokenizer.nextToken().strip()); - } - - Map map = IntStream.range(0, values.size()) - .boxed() - .collect(Collectors.toMap(moveKeys::get, values::get)); - - moveRepository.save(map.get("name"), map); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Repository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Repository.java deleted file mode 100644 index 540f6797a..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Repository.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.pokerogue.helper.pokemon2; - - -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; -import org.springframework.stereotype.Component; - -@Component -public class Pokemon2Repository { - private final Map> data = new TreeMap<>(); - - public Map> findAll() { - return Collections.unmodifiableMap(data); - } - - public void save(String key, Map value) { - data.put(key, value); - } -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Service.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Service.java deleted file mode 100644 index 942443551..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Service.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.pokerogue.helper.pokemon2; - - -import com.pokerogue.helper.type.dto.PokemonTypeResponse; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class Pokemon2Service { - - private final Pokemon2Repository pokemon2Repository; - private final MoveRepository moveRepository; - - private List> findAllCache = new ArrayList<>(); - private Map findByIdCache = new HashMap<>(); - - public List> findAll() { - if (findAllCache.isEmpty()) { - findAllCache = initPokemons(); - } - return findAllCache; - } - - private List> initPokemons() { - List> ret = new ArrayList<>(); - Map> all = pokemon2Repository.findAll(); - for (var entry : all.entrySet()) { - Map cur = new HashMap<>(); - - String name = entry.getKey(); - Map value = entry.getValue(); - - cur.put("id", name); - cur.put("pokedexNumber", Integer.parseInt(value.get("speciesId"))); - cur.put("name", name); - cur.put("image", "todo:imageLink"); - List pokemonTypeResponses = List.of( - new PokemonTypeResponse(value.get("type1"), "todo:logo"), - new PokemonTypeResponse(value.get("type2"), "todo:logo") - ); - cur.put("pokemonTypeResponse", pokemonTypeResponses); - - cur.put("generation", Integer.parseInt(value.get("generation"))); - cur.put("totalStats", Integer.parseInt(value.get("baseTotal"))); - - List baseStats = Arrays.stream(value.get("baseStats").split(",")) - .mapToInt(Integer::parseInt) - .boxed() - .toList(); - cur.put("hp", baseStats.get(0)); - cur.put("attack", baseStats.get(1)); - cur.put("defense", baseStats.get(2)); - cur.put("specialAttack", baseStats.get(3)); - cur.put("specialDefense", baseStats.get(4)); - cur.put("speed", baseStats.get(5)); - - ret.add(cur); - } - ret.sort(Comparator.comparingInt(a -> (Integer) a.get("pokedexNumber"))); - return ret; - } - - public Map findById(String id) { - if (findByIdCache.isEmpty()) { - initFindByIdCache(); - return (Map) findByIdCache.get(id); - } - return (Map) findByIdCache.get(id); - } - - private void initFindByIdCache() { - Map> all = pokemon2Repository.findAll(); - - for (Entry> entry : all.entrySet()) { - Map tmp = new HashMap<>(); - - String name = entry.getKey(); - Map value = entry.getValue(); - - tmp.put("id", name); - tmp.put("pokeDexNumber", Integer.parseInt(value.get("speciesId"))); - tmp.put("name", value.get("name")); - tmp.put("pokemonImage", "dummy-image"); - - String ability1 = value.get("ability1"); - String ability2 = value.get("ability2"); - String abilityPassive = value.get("abilityPassive"); - String abilityHidden = value.get("abilityHidden"); - - tmp.put("pokemonAbilityResponses", List.of( - new PokemonAbilityResponse(abilityPassive, abilityPassive, "description", true, false), - new PokemonAbilityResponse(ability1, ability1, "description", false, false), - new PokemonAbilityResponse(ability2, ability2, "description", false, false), - new PokemonAbilityResponse(abilityHidden, abilityHidden, "description", false, true) - )); - - String type1 = value.get("type1"); - String type2 = value.get("type2"); - tmp.put("pokemonTypeResponses", List.of( - new PokemonTypeResponse(type1, "dummy-logo") - , new PokemonTypeResponse(type2, "dummy-logo") - )); - - tmp.put("totalStats", Integer.parseInt(value.get("baseTotal"))); - String[] baseStats = value.get("baseStats").split(","); - tmp.put("hp", Integer.parseInt(baseStats[0])); - tmp.put("attack", Integer.parseInt(baseStats[1])); - tmp.put("defense", Integer.parseInt(baseStats[2])); - tmp.put("specialAttack", Integer.parseInt(baseStats[3])); - tmp.put("specialDefense", Integer.parseInt(baseStats[4])); - tmp.put("speed", Integer.parseInt(baseStats[5])); - tmp.put("legendary", Boolean.valueOf(value.get("legendary"))); - tmp.put("subLegendary", Boolean.valueOf(value.get("subLegendary"))); - tmp.put("mythical", Boolean.valueOf(value.get("mythical"))); - tmp.put("canChangeForm", Boolean.valueOf(value.get("canChangeForm"))); - tmp.put("weight", Double.valueOf(value.get("weight"))); - tmp.put("height", Double.valueOf(value.get("height"))); - - String[] evolutionLevels = value.get("getEvolutionLevels").split(","); - var evolutions = new ArrayList<>(); - if (evolutionLevels.length % 2 == 0) { - for (int i = 0; i < evolutionLevels.length; i += 2) { - String evovleName = evolutionLevels[i]; - Integer level = Integer.parseInt(evolutionLevels[i + 1]); - evolutions.add(new EvolutionResponse(evovleName, level)); - } - tmp.put("evolutions", evolutions); - } - String[] moves = value.get("moves").split(","); - - List eggMoves = Arrays.stream(value.get("eggMoves").split(",")).toList(); - List biomes = Arrays.stream(value.get("biomes").split(",")).toList(); - - tmp.put("eggMoves", eggMoves.stream() - .map(r -> new EggMoveResponse(r, r)) - .toList() - ); - - tmp.put("biomes", biomes.stream() - .map(r -> new BiomeResponse(r, r)) - .toList() - ); - var moveAll = moveRepository.findAll(); - List moveResponses = new ArrayList<>(); - - for (int i = 0; i < moves.length; i += 2) { - String moveName = moves[i].strip(); - Integer moveLevel = Integer.parseInt(moves[i + 1]); - var moveValues = moveAll.get(moveName); - Integer power = Integer.parseInt(moveValues.get("power")); - Integer accuracy = Integer.parseInt(moveValues.get("accuracy")); - String type = moveValues.get("type"); - String category = moveValues.get("category"); - moveResponses.add(new MoveResponse(moveName, moveLevel, power, accuracy, type, category)); - } - tmp.put("moves", moveResponses); - findByIdCache.put(name, tmp); - } - } -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/PokemonAbilityResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/PokemonAbilityResponse.java deleted file mode 100644 index cab1708c2..000000000 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/PokemonAbilityResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.pokerogue.helper.pokemon2; - -public record PokemonAbilityResponse(String id, String name, String description, Boolean passive, Boolean hidden) { - -} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/config/Pokemon2DatabaseInitializer.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/config/Pokemon2DatabaseInitializer.java new file mode 100644 index 000000000..effd37e6d --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/config/Pokemon2DatabaseInitializer.java @@ -0,0 +1,332 @@ +package com.pokerogue.helper.pokemon2.config; + +import com.pokerogue.helper.pokemon2.data.EvolutionChain; +import com.pokerogue.helper.pokemon2.data.Move; +import com.pokerogue.helper.pokemon2.data.Pokemon; +import com.pokerogue.helper.pokemon2.data.Evolution; +import com.pokerogue.helper.pokemon2.repository.EvolutionRepository; +import com.pokerogue.helper.pokemon2.repository.MoveRepository; +import com.pokerogue.helper.pokemon2.repository.Pokemon2Repository; +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.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + + +@Component +@RequiredArgsConstructor +public class Pokemon2DatabaseInitializer implements ApplicationRunner { + + private final Pokemon2Repository pokemon2Repository; + private final MoveRepository moveRepository; + private final EvolutionRepository evolutionRepository; + + List pokemonKeys = List.of( + "id", + "speciesId", + "speciesName", + "formName", + "nameKo", + "type1", + "type2", + "ability1", + "ability2", + "abilityHidden", + "abilityPassive", + "generation", + "legendary", + "subLegendary", + "mythical", + "canChangeForm", + "evolutionLevel", + "baseTotal", + "baseStats", + "height", + "weight", + "eggMoves", + "moves", + "biomes" + ); + List moveKeys = List.of( + "id", + "name", + "effect", + "power", + "accuracy", + "type", + "category" + ); + + List evolutionChainKeys = List.of( + "from", + "to", + "level", + "item", + "condition" + ); + + @Override + public void run(ApplicationArguments args) { + try ( + InputStream inputStream = getClass() + .getClassLoader() + .getResourceAsStream("pokemon.txt"); + BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) + ) { + savePokemon(br); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try (InputStream inputStream = getClass() + .getClassLoader() + .getResourceAsStream("move-for-pokemon-response.txt"); + BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) + ) { + saveMove(br); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try (InputStream inputStream = getClass() + .getClassLoader() + .getResourceAsStream("evolution-for-pokemon-response.txt"); + BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) + ) { + saveEvolution(br); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void saveEvolution(BufferedReader br) throws IOException { + String line; + while ((line = br.readLine()) != null) { + List tokens = parseToken(line); + String speciesName = regularize(tokens.get(0)); + + for (int i = 1; i < tokens.size(); i++) { + Evolution evolution = createEvolution(speciesName, tokens.get(i)); + evolutionRepository.saveEdge(speciesName, evolution); + } + } + + for (Pokemon pokemon : pokemon2Repository.findAll().values()) { + List normalForms = List.of("mega", "mega_x", "mega_y", "primal", "g_max", "e_max"); + + if (normalForms.contains(regularize(pokemon.formName()))) { + + evolutionRepository.saveEdge(pokemon.id(), + new Evolution(pokemon.speciesName(), pokemon.id(), + "EMPTY", "EMPTY", "EMPTY")); + } + } + + createEvolutionChains(); + } + + private void createEvolutionChains() { + + List evolutions = evolutionRepository.findAll() + .keySet() + .stream() + .sorted(Comparator.comparing(r -> pokemon2Repository.findById(r) + .orElseThrow(() -> new IllegalArgumentException("")) + .speciesId() + )) + .toList(); + + Set vis = new HashSet<>(); + for (String from : evolutions) { + + if (vis.contains(from)) { + continue; + } + + EvolutionChain chain = new EvolutionChain(); + createChain(vis, from, chain); + evolutionRepository.saveChain(from, chain); + } + + for (String from : pokemon2Repository.findAll().keySet()) { + if ( + evolutionRepository.findEvolutionChainById(from).isEmpty() || + evolutionRepository.findEvolutionChainById(from).get().getChain().isEmpty() + ) { + evolutionRepository.saveChain(from, new EvolutionChain(List.of(List.of(from)))); + } + } + + } + + private void createChain(Set vis, String from, EvolutionChain chain) { + Queue q = new LinkedList<>(); + vis.add(from); + q.add(from); + int depth = 0; + while (!q.isEmpty()) { + int qs = q.size(); + while (qs-- > 0) { + String cur = q.poll(); + Optional> edges = evolutionRepository.findEdgeById(cur); + if (edges.isEmpty()) { + continue; + } + for (Evolution edge : edges.get()) { + if (vis.contains(edge.to())) { + continue; + } + q.add(edge.to()); + vis.add(edge.to()); + chain.push(edge, depth); + } + } + depth++; + } + + chain.getAllIds().forEach(id -> evolutionRepository.saveChain(id, chain)); + } + + private Evolution createEvolution(String from, String values) { + List evolveCondtions = Arrays.stream(values.split("@")).toList(); + + return new Evolution( + regularize(from), + regularize(evolveCondtions.get(0)), + regularize(evolveCondtions.get(1)), + regularize(evolveCondtions.get(2)), + evolveCondtions.get(3) + ); + } + + private List parseToken(String line) { + StringTokenizer stringTokenizer = new StringTokenizer(line, "/"); + + List values = new ArrayList<>(); + while (stringTokenizer.hasMoreTokens()) { + String token = stringTokenizer.nextToken().strip(); + + if (token.contains("undefined")) { + token = "empty"; + } + + values.add(token); + } + + return values; + } + + private String regularize(String str) { + return str.strip() + .replace(" ", "_") + .replace("-", "_") + .toLowerCase(); + } + + private void savePokemon(BufferedReader br) throws IOException { + String line; + while ((line = br.readLine()) != null) { + List tokens = parseToken(line); + + if (pokemonKeys.size() != tokens.size()) { + throw new IllegalArgumentException(pokemonKeys.size() + " " + tokens.size() + "포켓몬 데이터가 잘못 되었습니다."); + } + + Pokemon pokemon = createPokemon(tokens); + pokemon2Repository.save(pokemon.id(), pokemon); + } + } + + private Pokemon createPokemon(List values) { + List moves = Arrays.stream(values.get(22).split(",")) + .collect(Collectors.toList()); + for (int i = 0; i < moves.size(); i += 2) { + moves.set(i, regularize(moves.get(i))); + } + + List stats = Arrays.stream(regularize(values.get(18)).split(",")) + .mapToInt(Integer::parseInt) + .boxed() + .toList(); + + + return new Pokemon( + regularize(values.get(0)), + regularize(values.get(1)), + regularize(values.get(2)), + regularize(values.get(3)), + regularize(values.get(4)), + regularize(values.get(5)), + regularize(values.get(6)), + regularize(values.get(7)), + regularize(values.get(8)), + regularize(values.get(9)), + regularize(values.get(10)), + Integer.valueOf(regularize(values.get(11))), + Boolean.valueOf(regularize(values.get(12))), + Boolean.valueOf(regularize(values.get(13))), + Boolean.valueOf(regularize(values.get(14))), + Boolean.valueOf(regularize(values.get(15))), + Arrays.stream(regularize(values.get(16)).split(",")).toList(), + Integer.parseInt(values.get(17)), + stats.get(0), + stats.get(1), + stats.get(2), + stats.get(3), + stats.get(4), + stats.get(5), + Double.parseDouble(values.get(19)), + Double.parseDouble(values.get(20)), + Arrays.stream(regularize(values.get(21)).split(",")).toList(), + moves, + Arrays.stream(regularize(values.get(23)).split(",")).toList() + ); + } + + + private void saveMove(BufferedReader br) throws IOException { + String line; + while ((line = br.readLine()) != null) { + List tokens = parseToken(line); + + if (moveKeys.size() != tokens.size()) { + throw new IllegalArgumentException(moveKeys.size() + " " + tokens.size() + "기술 데이터가 잘못 되었습니다."); + } + + Move move = createMove(tokens); + + moveRepository.save(move.id(), move); + } + } + + private Move createMove(List tokens) { + return new Move( + regularize(tokens.get(0)), + regularize(tokens.get(1)), + tokens.get(2), + tokens.get(3), + tokens.get(4), + regularize(tokens.get(5)), + regularize(tokens.get(6)) + ); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Controller.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/controller/Pokemon2Controller.java similarity index 62% rename from backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Controller.java rename to backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/controller/Pokemon2Controller.java index dd46f3204..b26895d0b 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/Pokemon2Controller.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/controller/Pokemon2Controller.java @@ -1,9 +1,11 @@ -package com.pokerogue.helper.pokemon2; +package com.pokerogue.helper.pokemon2.controller; +import com.pokerogue.helper.pokemon2.dto.Pokemon2Response; +import com.pokerogue.helper.pokemon2.dto.Pokemon2DetailResponse; +import com.pokerogue.helper.pokemon2.service.Pokemon2Service; import com.pokerogue.helper.util.dto.ApiResponse; import java.util.List; -import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -16,12 +18,12 @@ public class Pokemon2Controller { private final Pokemon2Service pokemon2Service; @GetMapping("/api/v1/pokemons2") - public ApiResponse>> findAll() { + public ApiResponse> findAll() { return new ApiResponse<>("포켓몬 리스트 불러오기에 성공했습니다.", pokemon2Service.findAll()); } - @GetMapping("/api/v1/pokemon2/{stringId}") - public ApiResponse> findAll(@PathVariable("stringId") String id) { + @GetMapping("/api/v1/pokemon2/{id}") + public ApiResponse findAll(@PathVariable("id") String id) { return new ApiResponse<>("포켓몬 정보 불러오기에 성공했습니다.", pokemon2Service.findById(id)); } } diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Ability.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Ability.java new file mode 100644 index 000000000..779cf41f1 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Ability.java @@ -0,0 +1,352 @@ +package com.pokerogue.helper.pokemon2.data; + + +import java.util.Arrays; + +public enum Ability { + STENCH("Stench", "악취", "악취를 풍겨서 공격했을 때 상대가 풀죽을 때가 있다."), + DRIZZLE("Drizzle", "잔비", "등장했을 때 날씨를 비로 만든다."), + SPEEDBOOST("Speed Boost", "가속", "매 턴 스피드가 올라간다."), + BATTLEARMOR("Battle Armor", "전투무장", "단단한 껍질에 보호받아 상대의 공격이 급소에 맞지 않는다."), + STURDY("Sturdy", "옹골참", "상대 기술을 받아도 일격으로 쓰러지지 않는다. 일격필살 기술도 효과 없다."), + DAMP("Damp", "습기", "주변을 습하게 함으로써 자폭 등 폭발하는 기술을 아무도 못 쓰게 한다."), + LIMBER("Limber", "유연", "유연한 몸으로 인해 마비 상태가 되지 않는다."), + SANDVEIL("Sand Veil", "모래숨기", "모래바람일 때 회피율이 올라간다."), + STATIC("Static", "정전기", "정전기를 몸에 둘러 접촉한 상대를 마비시킬 때가 있다."), + VOLTABSORB("Volt Absorb", "축전", "전기타입의 기술을 받으면 데미지를 받지 않고 회복한다."), + WATERABSORB("Water Absorb", "저수", "물타입의 기술을 받으면 데미지를 받지 않고 회복한다."), + OBLIVIOUS("Oblivious", "둔감", "둔감해서 헤롱헤롱이나 도발 상태가 되지 않는다."), + CLOUDNINE("Cloud Nine", "날씨부정", "모든 날씨의 영향이 없어진다."), + COMPOUNDEYES("Compound Eyes", "복안", "복안을 가지고 있어 기술의 명중률이 올라간다."), + INSOMNIA("Insomnia", "불면", "잠들지 못하는 체질이라 잠듦 상태가 되지 않는다."), + COLORCHANGE("Color Change", "변색", "상대에게 받은 기술의 타입으로 자신의 타입이 변화한다."), + IMMUNITY("Immunity", "면역", "체내에 면역을 가지고 있어 독 상태가 되지 않는다."), + FLASHFIRE("Flash Fire", "타오르는불꽃", "불꽃타입의 기술을 받으면 불꽃을 받아서 자신이 사용하는 불꽃타입의 기술이 강해진다."), + SHIELDDUST("Shield Dust", "인분", "인분에 보호받아 기술의 추가 효과를 받지 않게 된다."), + OWNTEMPO("Own Tempo", "마이페이스", "마이페이스라서 혼란 상태가 되지 않는다."), + SUCTIONCUPS("Suction Cups", "흡반", "흡반으로 지면에 달라붙어 포켓몬을 교체시키는 기술이나 도구의 효과를 발휘하지 못하게 한다."), + INTIMIDATE("Intimidate", "위협", "등장했을 때 위협해서 상대를 위축시켜 상대의 공격을 떨어뜨린다."), + SHADOWTAG("Shadow Tag", "그림자밟기", "상대의 그림자를 밟아 도망치거나 교체할 수 없게 한다."), + ROUGHSKIN("Rough Skin", "까칠한피부", "공격을 받았을 때 자신에게 접촉한 상대를 까칠까칠한 피부로 상처를 입힌다."), + WONDERGUARD("Wonder Guard", "불가사의부적", "효과가 굉장한 기술만 맞는 불가사의한 힘."), + LEVITATE("Levitate", "부유", "땅에서 뜨는 것으로 땅타입의 기술을 받지 않는다."), + EFFECTSPORE("Effect Spore", "포자", "공격으로 자신에게 접촉한 상대를 독이나 마비, 잠듦 상태로 만들 때가 있다."), + SYNCHRONIZE("Synchronize", "싱크로", "자신이 걸린 독이나 마비, 화상을 상대에게 옮긴다."), + CLEARBODY("Clear Body", "클리어바디", "상대 기술이나 특성으로 능력이 떨어지지 않는다."), + NATURALCURE("Natural Cure", "자연회복", "지닌 포켓몬으로 돌아오면 상태 이상이 회복된다."), + LIGHTNINGROD("Lightning Rod", "피뢰침", "전기타입의 기술을 자신에게 끌어모아 데미지를 받지 않고 특수공격을 올린다."), + SERENEGRACE("Serene Grace", "하늘의은총", "하늘의 은총 덕분에 기술의 추가 효과가 나오기 쉽다."), + SWIFTSWIM("Swift Swim", "쓱쓱", "비가 오는 날씨일 때 스피드가 올라간다."), + CHLOROPHYLL("Chlorophyll", "엽록소", "날씨가 맑을 때 스피드가 올라간다."), + ILLUMINATE("Illuminate", "발광", "주변을 밝게 하여 명중률이 떨어지지 않는다."), + TRACE("Trace", "트레이스", "등장했을 때 상대의 특성을 트레이스해서 같은 특성이 된다."), + HUGEPOWER("Huge Power", "천하장사", "물리공격의 위력이 2배가 된다."), + POISONPOINT("Poison Point", "독가시", "자신과 접촉한 상대를 독 상태로 만들 때가 있다."), + INNERFOCUS("Inner Focus", "정신력", "단련한 정신으로 인하여 상대의 공격에 풀죽지 않는다."), + MAGMAARMOR("Magma Armor", "마그마의무장", "뜨거운 마그마를 몸에 둘러서 얼음 상태가 되지 않는다."), + WATERVEIL("Water Veil", "수의베일", "물의 베일을 몸에 둘러서 화상 상태가 되지 않는다."), + MAGNETPULL("Magnet Pull", "자력", "강철타입의 포켓몬을 자력으로 끌어모아 도망칠 수 없게 한다."), + SOUNDPROOF("Soundproof", "방음", "소리를 차단하는 것으로 소리 공격을 받지 않는다."), + RAINDISH("Rain Dish", "젖은접시", "비가 오는 날씨일 때 조금씩 HP를 회복한다."), + SANDSTREAM("Sand Stream", "모래날림", "등장했을 때 날씨를 모래바람으로 만든다."), + PRESSURE("Pressure", "프레셔", "프레셔를 줘서 상대가 쓰는 기술의 PP를 많이 줄인다."), + THICKFAT("Thick Fat", "두꺼운지방", "두꺼운 지방으로 보호되고 있어 불꽃타입과 얼음타입의 기술의 데미지를 반감시킨다."), + EARLYBIRD("Early Bird", "일찍기상", "잠듦 상태가 되어도 2배 스피드로 깨어날 수 있다."), + FLAMEBODY("Flame Body", "불꽃몸", "자신과 접촉한 상대를 화상 상태로 만들 때가 있다."), + RUNAWAY("Run Away", "도주", "야생 포켓몬으로부터 반드시 도망칠 수 있다."), + KEENEYE("Keen Eye", "날카로운눈", "날카로운 눈 덕분에 명중률이 떨어지지 않는다."), + HYPERCUTTER("Hyper Cutter", "괴력집게", "힘이 자랑인 집게를 가지고 있어 상대가 공격을 떨어뜨리지 못한다."), + PICKUP("Pickup", "픽업", "상대가 지닌 도구를 주워올 때가 있다."), + TRUANT("Truant", "게으름", "기술을 사용하면 다음 턴은 쉰다."), + HUSTLE("Hustle", "의욕", "자신의 공격이 높아지지만 명중률이 떨어진다."), + CUTECHARM("Cute Charm", "헤롱헤롱바디", "자신과 접촉한 상대를 헤롱헤롱 상태로 만들 때가 있다."), + PLUS("Plus", "플러스", "플러스나 마이너스의 특성을 가진 포켓몬이 동료에 있으면 자신의 특수공격이 올라간다."), + MINUS("Minus", "마이너스", "플러스나 마이너스의 특성을 가진 포켓몬이 동료에 있으면 자신의 특수공격이 올라간다."), + FORECAST("Forecast", "기분파", "날씨의 영향을 받아 물타입, 불꽃타입, 얼음타입 중 하나로 변화한다."), + STICKYHOLD("Sticky Hold", "점착", "점착질의 몸에 도구가 달라붙어 있어 상대에게 도구를 뺏기지 않는다."), + SHEDSKIN("Shed Skin", "탈피", "몸의 껍질을 벗어 던져 상태 이상을 회복할 때가 있다."), + GUTS("Guts", "근성", "상태 이상이 되면 근성을 보여서 공격이 올라간다."), + MARVELSCALE("Marvel Scale", "이상한비늘", "상태 이상이 되면 이상한 비늘이 반응해서 방어가 올라간다."), + LIQUIDOOZE("Liquid Ooze", "해감액", "해감액을 흡수한 상대는 강렬한 악취로 데미지를 받아 HP가 줄어든다."), + OVERGROW("Overgrow", "심록", "HP가 줄었을 때 풀타입 기술의 위력이 올라간다."), + BLAZE("Blaze", "맹화", "HP가 줄었을 때 불꽃타입 기술의 위력이 올라간다."), + TORRENT("Torrent", "급류", "HP가 줄었을 때 물타입 기술의 위력이 올라간다."), + SWARM("Swarm", "벌레의알림", "HP가 줄었을 때 벌레타입 기술의 위력이 올라간다."), + ROCKHEAD("Rock Head", "돌머리", "반동을 받는 기술을 사용해도 HP가 줄지 않는다."), + DROUGHT("Drought", "가뭄", "등장했을 때 날씨를 맑음으로 만든다."), + ARENATRAP("Arena Trap", "개미지옥", "배틀에서 상대를 도망칠 수 없게 한다."), + VITALSPIRIT("Vital Spirit", "의기양양", "의기양양해져서 잠듦 상태가 되지 않는다."), + WHITESMOKE("White Smoke", "하얀연기", "하얀 연기의 보호를 받아 상대가 능력을 떨어뜨릴 수 없다."), + PUREPOWER("Pure Power", "순수한힘", "요가의 힘으로 물리공격의 위력이 2배가 된다."), + SHELLARMOR("Shell Armor", "조가비갑옷", "단단한 껍질의 보호를 받아 상대의 공격이 급소에 맞지 않는다."), + AIRLOCK("Air Lock", "에어록", "모든 날씨의 영향이 없어진다."), + TANGLEDFEET("Tangled Feet", "갈지자걸음", "혼란 상태일 때는 회피율이 올라간다."), + MOTORDRIVE("Motor Drive", "전기엔진", "전기타입의 기술을 받으면 데미지를 받지 않고 스피드가 올라간다."), + RIVALRY("Rivalry", "투쟁심", "성별이 같으면 투쟁심을 불태워 강해진다. 성별이 다르면 약해진다."), + STEADFAST("Steadfast", "불굴의마음", "풀죽을 때마다 불굴의 마음을 불태워 스피드가 올라간다."), + SNOWCLOAK("Snow Cloak", "눈숨기", "날씨가 눈일 때 회피율이 올라간다."), + GLUTTONY("Gluttony", "먹보", "HP가 줄어들면 먹을 나무열매를 HP가 절반일 때 먹어버린다."), + ANGERPOINT("Anger Point", "분노의경혈", "급소에 공격이 맞으면 크게 분노해 공격력이 최대가 된다."), + UNBURDEN("Unburden", "곡예", "지니던 도구가 없어지면 스피드가 올라간다."), + HEATPROOF("Heatproof", "내열", "내열인 몸으로 인해 불꽃타입 공격의 데미지를 반감한다."), + SIMPLE("Simple", "단순", "능력 변화가 평소의 2배가 된다."), + DRYSKIN("Dry Skin", "건조피부", "비가 오는 날씨나 물타입의 기술로 HP가 회복되고 맑을 때나 불꽃타입의 기술로는 줄어든다."), + DOWNLOAD("Download", "다운로드", "상대의 방어와 특수방어를 비교해서 낮은 쪽 능력에 맞춰서 자신의 공격이나 특수공격을 올린다."), + IRONFIST("Iron Fist", "철주먹", "펀치를 사용하는 기술의 위력이 올라간다."), + POISONHEAL("Poison Heal", "포이즌힐", "독 상태가 되면 HP가 줄지 않고 증가한다."), + ADAPTABILITY("Adaptability", "적응력", "자신과 같은 타입의 기술 위력이 올라간다."), + SKILLLINK("Skill Link", "스킬링크", "연속 기술을 사용하면 항상 최고 횟수를 사용할 수 있다."), + HYDRATION("Hydration", "촉촉바디", "비가 오는 날씨일 때 상태 이상이 회복된다."), + SOLARPOWER("Solar Power", "선파워", "날씨가 맑으면 특수공격이 올라가지만 매 턴 HP가 줄어든다."), + QUICKFEET("Quick Feet", "속보", "상태 이상이 되면 스피드가 올라간다."), + NORMALIZE("Normalize", "노말스킨", "어떤 타입의 기술도 모두 노말타입이 된다. 위력이 조금 올라간다."), + SNIPER("Sniper", "스나이퍼", "공격을 급소에 맞혔을 때 위력이 더욱 올라간다."), + MAGICGUARD("Magic Guard", "매직가드", "공격 이외에는 데미지를 입지 않는다."), + NOGUARD("No Guard", "노가드", "노가드전법에 따라 서로가 사용하는 기술이 반드시 맞게 된다."), + STALL("Stall", "시간벌기", "기술을 사용하는 순서가 반드시 마지막이 된다."), + TECHNICIAN("Technician", "테크니션", "위력이 약한 기술의 위력을 올려서 공격할 수 있다."), + LEAFGUARD("Leaf Guard", "리프가드", "날씨가 맑을 때는 상태 이상이 되지 않는다."), + KLUTZ("Klutz", "서투름", "지니고 있는 도구를 쓸 수 없다."), + MOLDBREAKER("Mold Breaker", "틀깨기", "상대 특성에 방해받지 않고 상대에게 기술을 쓸 수 있다."), + SUPERLUCK("Super Luck", "대운", "대운을 가지고 있어 상대의 급소에 공격이 맞기 쉽다."), + AFTERMATH("Aftermath", "유폭", "기절했을 때 접촉한 상대에게 데미지를 준다."), + ANTICIPATION("Anticipation", "위험예지", "상대가 지닌 위험한 기술을 감지할 수 있다."), + FOREWARN("Forewarn", "예지몽", "등장했을 때 상대가 지닌 기술을 하나만 꿰뚫어본다."), + UNAWARE("Unaware", "천진", "상대의 능력 변화를 무시하고 공격할 수 있다."), + TINTEDLENS("Tinted Lens", "색안경", "효과가 별로인 기술을 통상의 위력으로 쓸 수 있다."), + FILTER("Filter", "필터", "효과가 굉장한 공격의 위력을 약하게 만든다."), + SLOWSTART("Slow Start", "슬로스타트", "5턴 동안 공격과 스피드가 절반이 된다."), + SCRAPPY("Scrappy", "배짱", "고스트타입 포켓몬에게 노말타입과 격투타입의 기술을 맞게 한다."), + STORMDRAIN("Storm Drain", "마중물", "물타입의 기술을 자신에게 끌어모아 데미지는 받지 않고 특수공격이 올라간다."), + ICEBODY("Ice Body", "아이스바디", "날씨가 눈일 때 HP를 조금씩 회복한다."), + SOLIDROCK("Solid Rock", "하드록", "효과가 굉장한 공격의 위력을 약하게 만든다."), + SNOWWARNING("Snow Warning", "눈퍼뜨리기", "등장했을 때 날씨를 눈으로 만든다."), + HONEYGATHER("Honey Gather", "꿀모으기", "배틀이 끝났을 때 달콤한꿀을 주울 때가 있다. 배틀 후에 꿀을 팔아 돈을 받을 수 있다."), + FRISK("Frisk", "통찰", "등장했을 때 상대의 특성을 통찰할 수 있다."), + RECKLESS("Reckless", "이판사판", "반동 데미지를 받는 기술의 위력이 올라간다."), + MULTITYPE("Multitype", "멀티타입", "지니고 있는 플레이트나 Z크리스탈 타입에 따라 자신의 타입이 바뀐다."), + FLOWERGIFT("Flower Gift", "플라워기프트", "날씨가 맑을 때 자신과 같은 편의 공격과 특수방어의 능력이 올라간다."), + BADDREAMS("Bad Dreams", "나이트메어", "잠듦 상태의 상대에게 데미지를 준다."), + PICKPOCKET("Pickpocket", "나쁜손버릇", "접촉한 상대의 도구를 훔친다."), + SHEERFORCE("Sheer Force", "우격다짐", "기술의 추가 효과가 없어지지만 그만큼 높은 위력으로 기술을 사용할 수 있다."), + CONTRARY("Contrary", "심술꾸러기", "능력의 변화가 역전해서 올라갈 때 떨어지고 떨어질 때 올라간다."), + UNNERVE("Unnerve", "긴장감", "상대를 긴장시켜 나무열매를 먹지 못하게 한다."), + DEFIANT("Defiant", "오기", "능력이 떨어지면 공격이 크게 올라간다."), + DEFEATIST("Defeatist", "무기력", "HP가 절반이 되면 무기력해져서 공격과 특수공격이 반감된다."), + CURSEDBODY("Cursed Body", "저주받은바디", "공격을 받으면 상대의 기술을 사슬묶기 상태로 만들 때가 있다."), + HEALER("Healer", "치유의마음", "같은 편의 상태 이상을 가끔 회복시킨다."), + FRIENDGUARD("Friend Guard", "프렌드가드", "같은 편의 데미지를 줄일 수 있다."), + WEAKARMOR("Weak Armor", "깨어진갑옷", "물리 기술로 데미지를 받으면 방어가 떨어지고 스피드가 크게 올라간다."), + HEAVYMETAL("Heavy Metal", "헤비메탈", "자신의 무게가 2배가 된다."), + LIGHTMETAL("Light Metal", "라이트메탈", "자신의 무게가 절반이 된다."), + MULTISCALE("Multiscale", "멀티스케일", "HP가 꽉 찼을 때 받는 데미지가 줄어든다."), + TOXICBOOST("Toxic Boost", "독폭주", "독 상태가 되었을 때 물리 기술의 위력이 올라간다."), + FLAREBOOST("Flare Boost", "열폭주", "화상 상태가 되었을 때 특수 기술의 위력이 올라간다."), + HARVEST("Harvest", "수확", "사용한 나무열매를 몇 번이고 만들어 낸다."), + TELEPATHY("Telepathy", "텔레파시", "같은 편의 공격의 낌새를 읽고 기술을 회피한다."), + MOODY("Moody", "변덕쟁이", "매 턴 능력 중 하나가 크게 오르고 하나가 떨어진다."), + OVERCOAT("Overcoat", "방진", "모래바람이나 싸라기눈 등의 데미지를 입지 않는다. 가루의 기술을 받지 않는다."), + POISONTOUCH("Poison Touch", "독수", "접촉하기만 해도 상대를 독 상태로 만들 때가 있다."), + REGENERATOR("Regenerator", "재생력", "지닌 포켓몬으로 돌아오면 HP를 조금 회복한다."), + BIGPECKS("Big Pecks", "부풀린가슴", "방어를 떨어뜨리는 효과를 받지 않는다."), + SANDRUSH("Sand Rush", "모래헤치기", "날씨가 모래바람일 때 스피드가 올라간다."), + WONDERSKIN("Wonder Skin", "미라클스킨", "변화 기술을 받기 어려운 몸으로 되어 있다."), + ANALYTIC("Analytic", "애널라이즈", "제일 마지막에 기술을 쓰면 기술의 위력이 올라간다."), + ILLUSION("Illusion", "일루전", "지닌 포켓몬 중 제일 뒤에 있는 포켓몬으로 둔갑하여 나와서 상대를 속인다."), + IMPOSTER("Imposter", "괴짜", "눈앞의 포켓몬으로 변신해버린다."), + INFILTRATOR("Infiltrator", "틈새포착", "상대의 벽이나 대타출동을 뚫고 공격할 수 있다."), + MUMMY("Mummy", "미라", "상대가 접촉하면 상대를 미라로 만들어버린다."), + MOXIE("Moxie", "자기과신", "상대를 쓰러뜨리면 자신감이 붙어서 공격이 올라간다."), + JUSTIFIED("Justified", "정의의마음", "악타입 공격을 받으면 정의감으로 공격이 올라간다."), + RATTLED("Rattled", "주눅", "위협이나 악타입과 고스트타입과 벌레타입의 기술에 주눅이 들어 스피드가 올라간다."), + MAGICBOUNCE("Magic Bounce", "매직미러", "상대가 쓴 변화 기술을 받지 않고 그대로 되받아칠 수 있다."), + SAPSIPPER("Sap Sipper", "초식", "풀타입 기술을 받으면 데미지를 입지 않고 공격이 올라간다."), + PRANKSTER("Prankster", "짓궂은마음", "변화 기술을 먼저 쓸 수 있다."), + SANDFORCE("Sand Force", "모래의힘", "날씨가 모래바람일 때 바위타입과 땅타입과 강철타입의 위력이 올라간다."), + IRONBARBS("Iron Barbs", "철가시", "자신과 접촉한 상대에게 철가시로 데미지를 준다."), + ZENMODE("Zen Mode", "달마모드", "HP가 절반 이하가 되면 모습이 변화한다."), + VICTORYSTAR("Victory Star", "승리의별", "자신과 같은 편의 명중률이 올라간다."), + TURBOBLAZE("Turboblaze", "터보블레이즈", "상대 특성에 방해받지 않고 상대에게 기술을 쓸 수 있다."), + TERAVOLT("Teravolt", "테라볼티지", "상대 특성에 방해받지 않고 상대에게 기술을 쓸 수 있다."), + AROMAVEIL("Aroma Veil", "아로마베일", "자신과 같은 편으로 향하는 멘탈 공격을 막을 수 있다."), + FLOWERVEIL("Flower Veil", "플라워베일", "같은 편의 풀타입 포켓몬은 능력이 떨어지지 않고 상태 이상도 되지 않는다."), + CHEEKPOUCH("Cheek Pouch", "볼주머니", "어떤 나무열매라도 먹으면 HP도 회복한다."), + PROTEAN("Protean", "변환자재", "자신이 사용한 기술과 같은 타입으로 변화한다."), + FURCOAT("Fur Coat", "퍼코트", "상대로부터 받는 물리 기술의 데미지가 절반이 된다."), + MAGICIAN("Magician", "매지션", "기술을 맞은 상대의 도구를 빼앗아 버린다."), + BULLETPROOF("Bulletproof", "방탄", "상대 구슬이나 폭탄 등 기술을 막을 수 있다."), + COMPETITIVE("Competitive", "승기", "능력이 떨어지면 특수공격이 크게 올라간다."), + STRONGJAW("Strong Jaw", "옹골찬턱", "턱이 튼튼하여 무는 기술의 위력이 올라간다."), + REFRIGERATE("Refrigerate", "프리즈스킨", "노말타입의 기술이 얼음타입이 된다. 위력이 조금 올라간다."), + SWEETVEIL("Sweet Veil", "스위트베일", "같은 편의 포켓몬이 잠들지 않게 된다."), + STANCECHANGE("Stance Change", "배틀스위치", "공격 기술을 쓰면 블레이드폼으로 기술 킹실드를 쓰면 실드폼으로 변한다."), + GALEWINGS("Gale Wings", "질풍날개", "HP가 꽉 찼을 때 비행타입의 기술을 먼저 쓸 수 있다."), + MEGALAUNCHER("Mega Launcher", "메가런처", "파동 기술의 위력이 올라간다."), + GRASSPELT("Grass Pelt", "풀모피", "그래스필드일 때 방어가 올라간다."), + SYMBIOSIS("Symbiosis", "공생", "같은 편이 도구를 쓰면 자신이 지니고 있는 도구를 같은 편에게 건넨다."), + TOUGHCLAWS("Tough Claws", "단단한발톱", "상대에게 접촉하는 기술의 위력이 올라간다."), + PIXILATE("Pixilate", "페어리스킨", "노말타입의 기술이 페어리타입이 된다. 위력이 조금 올라간다."), + GOOEY("Gooey", "미끈미끈", "공격으로 자신과 접촉한 상대의 스피드를 떨어뜨린다."), + AERILATE("Aerilate", "스카이스킨", "노말타입의 기술이 비행타입이 된다. 위력이 조금 올라간다."), + PARENTALBOND("Parental Bond", "부자유친", "부모와 자식 2마리로 2번 공격할 수 있다."), + DARKAURA("Dark Aura", "다크오라", "전원의 악타입 기술이 강해진다."), + FAIRYAURA("Fairy Aura", "페어리오라", "전원의 페어리타입 기술이 강해진다."), + AURABREAK("Aura Break", "오라브레이크", "오라의 효과를 역전시켜 위력을 떨어뜨린다."), + PRIMORDIALSEA("Primordial Sea", "시작의바다", "불꽃타입의 공격을 받지 않는 날씨로 만든다."), + DESOLATELAND("Desolate Land", "끝의대지", "물타입의 공격을 받지 않는 날씨로 만든다."), + DELTASTREAM("Delta Stream", "델타스트림", "비행타입의 약점이 없어지는 날씨로 만든다."), + STAMINA("Stamina", "지구력", "공격을 받으면 방어가 올라간다."), + WIMPOUT("Wimp Out", "도망태세", "HP가 절반이 되면 황급히 도망쳐서 지닌 포켓몬으로 돌아간다."), + EMERGENCYEXIT("Emergency Exit", "위기회피", "HP가 절반이 되면 위험을 회피하기 위해 지닌 포켓몬으로 돌아간다."), + WATERCOMPACTION("Water Compaction", "꾸덕꾸덕굳기", "물타입의 기술을 받으면 방어가 크게 올라간다."), + MERCILESS("Merciless", "무도한행동", "독 상태의 상대를 공격하면 반드시 급소에 맞는다."), + SHIELDSDOWN("Shields Down", "리밋실드", "HP가 절반이 되면 껍질이 깨져 공격적으로 된다."), + STAKEOUT("Stakeout", "잠복", "교체로 나온 상대에게 2배 데미지로 공격할 수 있다."), + WATERBUBBLE("Water Bubble", "수포", "자신을 향하는 불꽃타입 기술의 위력을 떨어뜨린다. 화상을 입지 않는다."), + STEELWORKER("Steelworker", "강철술사", "강철타입 기술의 위력이 올라간다."), + BERSERK("Berserk", "발끈", "상대의 공격으로 HP가 절반이 되면 특수공격이 올라간다."), + SLUSHRUSH("Slush Rush", "눈치우기", "날씨가 눈일 때 스피드가 올라간다."), + LONGREACH("Long Reach", "원격", "모든 기술을 상대에게 접촉하지 않고 사용할 수 있다."), + LIQUIDVOICE("Liquid Voice", "촉촉보이스", "모든 소리 기술이 물타입이 된다."), + TRIAGE("Triage", "힐링시프트", "회복 기술을 먼저 사용할 수 있다."), + GALVANIZE("Galvanize", "일렉트릭스킨", "노말타입 기술이 전기타입이 된다. 위력이 조금 올라간다."), + SURGESURFER("Surge Surfer", "서핑테일", "일렉트릭필드일 때 스피드가 2배가 된다."), + SCHOOLING("Schooling", "어군", "HP가 많을 때 무리지어 강해진다. HP가 얼마 남지 않으면 무리는 뿔뿔이 흩어진다."), + DISGUISE("Disguise", "탈", "몸을 덮는 탈로 1번 공격을 막을 수 있다."), + BATTLEBOND("Battle Bond", "유대변화", "상대를 쓰러뜨리면 트레이너와의 유대감이 깊어져서 지우개굴닌자로 변한다. 물수리검이 강해진다."), + POWERCONSTRUCT("Power Construct", "스웜체인지", "HP가 절반이 되면 셀들이 응원하러 달려와 퍼펙트폼으로 모습이 변한다."), + CORROSION("Corrosion", "부식", "강철타입이나 독타입도 독 상태로 만들 수 있다."), + COMATOSE("Comatose", "절대안깸", "항상 비몽사몽 상태로 절대 깨지 않는다. 잠든 상태로 공격할 수 있다."), + QUEENLYMAJESTY("Queenly Majesty", "여왕의위엄", "상대에게 위압감을 줘서 이쪽을 향한 선제 기술을 사용할 수 없게 한다."), + INNARDSOUT("Innards Out", "내용물분출", "상대가 쓰러뜨렸을 때 HP의 남은 양만큼 상대에게 데미지를 준다."), + DANCER("Dancer", "무희", "누군가 춤 기술을 쓰면 자신도 이어서 춤 기술을 쓸 수 있다."), + BATTERY("Battery", "배터리", "같은 편 특수 기술의 위력을 올린다."), + FLUFFY("Fluffy", "복슬복슬", "상대로부터 받은 접촉하는 기술의 데미지를 반감시키지만 불꽃타입 기술의 데미지는 2배가 된다."), + DAZZLING("Dazzling", "비비드바디", "상대를 놀라게 해서 이쪽을 향한 선제 기술을 사용할 수 없게 한다."), + SOULHEART("Soul-Heart", "소울하트", "포켓몬이 기절할 때마다 특수공격이 올라간다."), + TANGLINGHAIR("Tangling Hair", "컬리헤어", "공격으로 자신에게 접촉한 상대의 스피드를 떨어뜨린다."), + RECEIVER("Receiver", "리시버", "쓰러진 같은 편의 특성을 이어받아 같은 특성으로 바뀐다."), + POWEROFALCHEMY("Power of Alchemy", "화학의힘", "쓰러진 같은 편의 특성을 이어받아 같은 특성으로 바뀐다."), + BEASTBOOST("Beast Boost", "비스트부스트", "상대를 기절시켰을 때 자신의 가장 높은 능력이 올라간다."), + RKSSYSTEM("RKS System", "AR시스템", "지니고 있는 메모리로 자신의 타입이 변한다."), + ELECTRICSURGE("Electric Surge", "일렉트릭메이커", "등장했을 때 일렉트릭필드를 사용한다."), + PSYCHICSURGE("Psychic Surge", "사이코메이커", "등장했을 때 사이코필드를 사용한다."), + MISTYSURGE("Misty Surge", "미스트메이커", "등장했을 때 미스트필드를 사용한다."), + GRASSYSURGE("Grassy Surge", "그래스메이커", "등장했을 때 그래스필드를 사용한다."), + FULLMETALBODY("Full Metal Body", "메탈프로텍트", "상대 기술이나 특성으로 능력이 떨어지지 않는다."), + SHADOWSHIELD("Shadow Shield", "스펙터가드", "HP가 꽉 찼을 때 받는 데미지가 줄어든다."), + PRISMARMOR("Prism Armor", "프리즘아머", "효과가 굉장한 공격의 위력을 약하게 만든다."), + NEUROFORCE("Neuroforce", "브레인포스", "효과가 굉장한 공격의 위력이 더욱 올라간다."), + INTREPIDSWORD("Intrepid Sword", "불요의검", "등장했을 때 공격이 올라간다."), + DAUNTLESSSHIELD("Dauntless Shield", "불굴의방패", "등장했을 때 방어가 올라간다."), + LIBERO("Libero", "리베로", "자신이 사용한 기술과 같은 타입으로 변화한다."), + BALLFETCH("Ball Fetch", "볼줍기", "첫 번째로 실패한 몬스터볼을 주워온다."), + COTTONDOWN("Cotton Down", "솜털", "공격을 받으면 솜털을 흩뿌려서 자신을 제외한 모든 포켓몬의 스피드를 떨어뜨린다."), + PROPELLERTAIL("Propeller Tail", "스크루지느러미", "상대의 기술을 끌어모으는 특성이나 기술의 영향을 받지 않는다."), + MIRRORARMOR("Mirror Armor", "미러아머", "자신이 받는 능력 다운 효과에 한해 되받아친다."), + GULPMISSILE("Gulp Missile", "그대로꿀꺽미사일", "파도타기나 다이빙을 쓰면 먹이를 물어온다. 데미지를 받으면 먹이를 토해내서 공격한다."), + STALWART("Stalwart", "굳건한신념", "상대의 기술을 끌어모으는 특성이나 기술의 영향을 받지 않는다."), + STEAMENGINE("Steam Engine", "증기기관", "물타입이나 불꽃타입 기술을 받으면 스피드가 매우 크게 올라간다."), + PUNKROCK("Punk Rock", "펑크록", "소리 기술의 위력이 올라간다. 상대로부터 받는 소리 기술의 데미지는 절반이 된다."), + SANDSPIT("Sand Spit", "모래뿜기", "공격을 받으면 모래바람을 일으킨다."), + ICESCALES("Ice Scales", "얼음인분", "얼음인분의 보호를 받아 특수공격으로 받는 데미지가 절반이 된다."), + RIPEN("Ripen", "숙성", "나무열매를 숙성시켜서 효과가 2배가 된다."), + ICEFACE("Ice Face", "아이스페이스", "물리공격을 머리의 얼음이 대신 맞아주지만 모습도 바뀐다. 얼음은 싸라기눈이 내리면 원래대로 돌아온다."), + POWERSPOT("Power Spot", "파워스폿", "옆에 있기만 해도 기술의 위력이 올라간다."), + MIMICRY("Mimicry", "의태", "필드의 상태에 따라 포켓몬의 타입이 바뀐다."), + SCREENCLEANER("Screen Cleaner", "배리어프리", "등장했을 때 상대와 같은 편의 빛의장막, 리플렉터, 오로라베일의 효과가 사라진다."), + STEELYSPIRIT("Steely Spirit", "강철정신", "같은 편의 강철타입 공격의 위력이 올라간다."), + PERISHBODY("Perish Body", "멸망의바디", "접촉하는 기술을 받으면 3턴 후에 양쪽 모두 기절한다. 교체되면 효과가 없어진다."), + WANDERINGSPIRIT("Wandering Spirit", "떠도는영혼", "접촉하는 기술로 공격해온 포켓몬과 특성을 바꾼다."), + GORILLATACTICS("Gorilla Tactics", "무아지경", "공격이 올라가지만 처음에 선택한 기술 외에는 쓸 수 없게 된다."), + NEUTRALIZINGGAS("Neutralizing Gas", "화학변화가스", "화학변화가스를 가진 포켓몬이 배틀에 나와 있으면 모든 포켓몬이 가진 특성의 효과가 사라지거나 발동하지 않게 된다."), + PASTELVEIL("Pastel Veil", "파스텔베일", "자신과 같은 편이 독의 상태 이상 효과를 받지 않게 된다."), + HUNGERSWITCH("Hunger Switch", "꼬르륵스위치", "턴이 끝날 때마다 배부른 모양, 배고픈 모양, 배부른 모양...으로 번갈아서 모습을 바꾼다."), + QUICKDRAW("Quick Draw", "퀵드로", "상대보다 먼저 행동할 수도 있다."), + UNSEENFIST("Unseen Fist", "보이지않는주먹", "상대에게 접촉하는 기술을 사용하면 방어의 효과를 무시하고 공격할 수 있다."), + CURIOUSMEDICINE("Curious Medicine", "기묘한약", "등장했을 때 조개껍질에서 약을 흩뿌려서 능력 변화를 원래대로 되돌린다."), + TRANSISTOR("Transistor", "트랜지스터", "전기타입 기술의 위력이 올라간다."), + DRAGONSMAW("Dragon's Maw", "용의턱", "드래곤타입 기술의 위력이 올라간다."), + CHILLINGNEIGH("Chilling Neigh", "백의울음", "상대를 쓰러뜨리면 차가운 울음소리를 내면서 공격이 올라간다."), + GRIMNEIGH("Grim Neigh", "흑의울음", "상대를 쓰러뜨리면 무서운 울음소리를 내면서 특수공격이 올라간다."), + ASONEGLASTRIER("As One", "혼연일체", "버드렉스의 긴장감과 블리자포스의 백의울음 두 가지 특성을 겸비한다."), + ASONESPECTRIER("As One", "혼연일체", "버드렉스의 긴장감과 레이스포스의 흑의울음 두 가지 특성을 겸비한다."), + LINGERINGAROMA("Lingering Aroma", "가시지않는향기", "상대가 접촉하면 가시지 않는 향기가 상대에게 배어 버린다."), + SEEDSOWER("Seed Sower", "넘치는씨", "공격을 받으면 필드를 그래스필드로 만든다."), + THERMALEXCHANGE("Thermal Exchange", "열교환", "불꽃타입 기술로 공격받으면 공격이 올라간다. 화상 상태가 되지 않는다."), + ANGERSHELL("Anger Shell", "분노의껍질", "상대의 공격에 의해 HP가 절반이 되면 화가 나서 방어와 특수방어가 떨어지지만 공격, 특수공격, 스피드가 올라간다."), + PURIFYINGSALT("Purifying Salt", "정화의소금", "깨끗한 소금에 의해 상태 이상이 되지 않는다. 고스트타입 기술의 데미지를 반감시킨다."), + WELLBAKEDBODY("Well-Baked Body", "노릇노릇바디", "불꽃타입 기술로 공격받으면 데미지를 입지 않고 방어가 크게 올라간다."), + WINDRIDER("Wind Rider", "바람타기", "순풍이 불거나 바람 기술로 공격받으면 데미지를 받지 않고 공격이 올라간다."), + GUARDDOG("Guard Dog", "파수견", "위협을 받으면 공격이 올라간다. 포켓몬을 교체시키는 기술이나 도구의 효과를 받지 않는다."), + ROCKYPAYLOAD("Rocky Payload", "바위나르기", "바위타입 기술의 위력이 올라간다."), + WINDPOWER("Wind Power", "풍력발전", "바람 기술로 공격받으면 충전 상태가 된다."), + ZEROTOHERO("Zero to Hero", "마이티체인지", "지닌 포켓몬으로 돌아오면 마이티폼으로 변한다."), + COMMANDER("Commander", "사령탑", "등장했을 때 같은 편에 어써러셔가 있으면 입속에 들어가 안에서 지시를 내린다."), + ELECTROMORPHOSIS("Electromorphosis", "전기로바꾸기", "데미지를 받으면 충전 상태가 된다."), + PROTOSYNTHESIS("Protosynthesis", "고대활성", "부스트에너지를 지니고 있거나 날씨가 맑을 때 가장 높은 능력이 올라간다."), + QUARKDRIVE("Quark Drive", "쿼크차지", "부스트에너지를 지니고 있거나 일렉트릭필드일 때 가장 높은 능력이 올라간다."), + GOODASGOLD("Good as Gold", "황금몸", "산화하지 않는 튼튼한 황금몸 덕분에 상대의 변화 기술의 영향을 받지 않는다."), + VESSELOFRUIN("Vessel of Ruin", "재앙의그릇", "재앙을 부르는 그릇의 힘으로 자신을 제외한 모든 포켓몬의 특수 공격을 약하게 만든다."), + SWORDOFRUIN("Sword of Ruin", "재앙의검", "재앙을 부르는 검의 힘으로 자신을 제외한 모든 포켓몬의 방어를 약하게 만든다."), + TABLETSOFRUIN("Tablets of Ruin", "재앙의목간", "재앙을 부르는 목간의 힘으로 자신을 제외한 모든 포켓몬의 공격을 약하게 만든다."), + BEADSOFRUIN("Beads of Ruin", "재앙의구슬", "재앙을 부르는 곡옥의 힘으로 자신을 제외한 모든 포켓몬의 특수방어를 약하게 만든다."), + ORICHALCUMPULSE("Orichalcum Pulse", "진홍빛고동", "등장했을 때 날씨를 맑음으로 만든다. 햇살이 강하면 고대의 고동에 의해 공격이 강화된다."), + HADRONENGINE("Hadron Engine", "하드론엔진", "등장했을 때 일렉트릭필드를 전개한다. 일렉트릭필드일 때 미래 기관에 의해 특수공격이 강화된다."), + OPPORTUNIST("Opportunist", "편승", "상대의 능력이 올라가면 자신도 편승해서 똑같이 자신도 올린다."), + CUDCHEW("Cud Chew", "되새김질", "한 번에 한하여 나무열매를 먹으면 다음 턴이 끝날 때 위에서 꺼내서 또 먹는다."), + SHARPNESS("Sharpness", "예리함", "상대를 베는 기술의 위력이 올라간다."), + SUPREMEOVERLORD("Supreme Overlord", "총대장", "등장했을 때 지금까지 쓰러진 같은 편의 수가 많을수록 조금씩 공격과 특수공격이 올라간다."), + COSTAR("Costar", "협연", "등장했을 때 같은 편의 능력 변화를 복사한다."), + TOXICDEBRIS("Toxic Debris", "독치장", "물리 기술로 데미지를 받으면 상대의 발밑에 독압정을 뿌린다."), + ARMORTAIL("Armor Tail", "테일아머", "머리를 감싸고 있는 수수께끼의 꼬리가 이쪽을 향한 선제 기술을 사용할 수 없게 한다."), + EARTHEATER("Earth Eater", "흙먹기", "땅타입의 기술로 공격받으면 데미지를 받지 않고 회복한다."), + MYCELIUMMIGHT("Mycelium Might", "균사의힘", "변화 기술을 사용할 때 반드시 행동이 느려지지만 상대 특성에 방해받지 않는다."), + MINDSEYE("Mind's Eye", "심안", "노말타입과 격투타입 기술을 고스트타입에게 맞힐 수 있다. 상대의 회피율 변화를 무시하고 명중률도 떨어지지 않는다."), + SUPERSWEETSYRUP("Supersweet Syrup", "감미로운꿀", "처음 등장했을 때 감미로운 꿀의 향기를 흩뿌려서 상대의 회피율을 떨어뜨린다."), + HOSPITALITY("Hospitality", "대접", "등장했을 때 같은 편을 대접해서 HP를 조금 회복시킨다."), + TOXICCHAIN("Toxic Chain", "독사슬", "독소를 머금은 사슬의 힘으로 기술에 맞은 상대를 맹독 상태로 만들 때가 있다."), + EMBODYASPECTTEAL("Embody Aspect", "초상투영", "마음속에 깃든 추억의 힘으로 벽록의가면을 빛나게 하여 자신의 스피드를 올린다."), + EMBODYASPECTWELLSPRING("Embody Aspect", "초상투영", "마음속에 깃든 추억의 힘으로 우물의가면을 빛나게 하여 자신의 특수방어를 올린다."), + EMBODYASPECTHEARTHFLAME("Embody Aspect", "초상투영", "마음속에 깃든 추억의 힘으로 화덕의가면을 빛나게 하여 자신의 공격력을 올린다."), + EMBODYASPECTCORNERSTONE("Embody Aspect", "초상투영", "마음속에 깃든 추억의 힘으로 주춧돌의가면을 빛나게 하여 자신의 방어력을 올린다."), + TERASHIFT("Tera Shift", "테라체인지", "등장했을 때 주위의 에너지를 흡수하여 테라스탈폼으로 변한다."), + TERASHELL("Tera Shell", "테라셸", "모든 타입의 힘이 담긴 등껍질이 HP가 꽉 찼을 때 받는 데미지를 모두 효과가 별로이게 만든다."), + TERAFORMZERO("Teraform Zero", "제로포밍", "테라파고스가 스텔라폼이 되었을 때 숨겨진 힘에 의해 날씨와 필드의 영향을 모두 무효로 만든다."), + POISONPUPPETEER("Poison Puppeteer", "독조종", "복숭악동의 기술에 의해 독 상태가 된 상대는 혼란 상태도 되어 버린다."), + EMPTY("empty", "empty", "empty"), + ; + + private final String id; + private final String name; + private final String description; + + Ability(String id, String name, String description) { + this.id = id; + this.name = name; + this.description = description; + } + + public static Ability findById(String id) { + return Arrays.stream(values()) + .filter(value -> value.getId() + .replace("-", "_") + .replace(" ", "_") + .toLowerCase() + .equals(id)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("잘못된 특성 아이디입니다.")); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Biome.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Biome.java new file mode 100644 index 000000000..14132b52e --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Biome.java @@ -0,0 +1,65 @@ +package com.pokerogue.helper.pokemon2.data; + +import java.util.Arrays; +import lombok.Getter; + +@Getter +public enum Biome { + UNKNOWNLOCATION("unknownLocation", "기억할 수 없는 곳"), + TOWN("TOWN", "마을"), + PLAINS("PLAINS", "평야"), + GRASS("GRASS", "풀숲"), + TALL_GRASS("TALL_GRASS", "높은 풀숲"), + METROPOLIS("METROPOLIS", "대도시"), + FOREST("FOREST", "숲"), + SEA("SEA", "바다"), + SWAMP("SWAMP", "늪지"), + BEACH("BEACH", "해변"), + LAKE("LAKE", "호수"), + SEABED("SEABED", "해저"), + MOUNTAIN("MOUNTAIN", "산"), + BADLANDS("BADLANDS", "악지"), + CAVE("CAVE", "동굴"), + DESERT("DESERT", "사막"), + ICE_CAVE("ICE_CAVE", "얼음 동굴"), + MEADOW("MEADOW", "목초지"), + POWER_PLANT("POWER_PLANT", "발전소"), + VOLCANO("VOLCANO", "화산"), + GRAVEYARD("GRAVEYARD", "묘지"), + DOJO("DOJO", "도장"), + FACTORY("FACTORY", "공장"), + RUINS("RUINS", "고대 유적"), + WASTELAND("WASTELAND", "황무지"), + ABYSS("ABYSS", "심연"), + SPACE("SPACE", "우주"), + CONSTRUCTION_SITE("CONSTRUCTION_SITE", "공사장"), + JUNGLE("JUNGLE", "정글"), + FAIRY_CAVE("FAIRY_CAVE", "페어리 동굴"), + TEMPLE("TEMPLE", "사원"), + SLUM("SLUM", "슬럼"), + SNOWY_FOREST("SNOWY_FOREST", "눈덮인 숲"), + ISLAND("ISLAND", "섬"), + LABORATORY("LABORATORY", "연구소"), + END("END", "???"), + EMPTY("EMPTY", "EMPTY"), + ; + + private final String id; + private final String name; + + Biome(String id, String name) { + this.id = id; + this.name = name; + } + + public static Biome findById(String id) { + return Arrays.stream(values()) + .filter(value -> value.getId().toLowerCase() + .replace("-", "_") + .replace(" ", "_") + .equals(id) + ) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("잘못된 바이옴 아이디입니다.")); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Evolution.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Evolution.java new file mode 100644 index 000000000..eae748d54 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Evolution.java @@ -0,0 +1,5 @@ +package com.pokerogue.helper.pokemon2.data; + +public record Evolution(String from, String to, String level, String item, String condition) { + +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/EvolutionChain.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/EvolutionChain.java new file mode 100644 index 000000000..3d941c84f --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/EvolutionChain.java @@ -0,0 +1,40 @@ +package com.pokerogue.helper.pokemon2.data; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + + +public class EvolutionChain { + private List> chain = new ArrayList<>(); + + public EvolutionChain() { + } + + public EvolutionChain(List> newChain) { + this.chain = newChain; + } + + public void push(Evolution evolution, int depth) { + while (chain.size() <= depth + 1) { + chain.add(new ArrayList<>()); + } + + chain.get(depth).add(evolution.from()); + chain.get(depth + 1).add(evolution.to()); + + chain.set(depth, chain.get(depth).stream().distinct().collect(Collectors.toList())); + chain.set(depth + 1, chain.get(depth + 1).stream().distinct().collect(Collectors.toList())); + } + + public List getAllIds() { + return chain.stream() + .flatMap(Collection::stream) + .toList(); + } + + public List> getChain() { + return chain; + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/EvolutionItem.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/EvolutionItem.java new file mode 100644 index 000000000..4c62a792e --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/EvolutionItem.java @@ -0,0 +1,33 @@ +package com.pokerogue.helper.pokemon2.data; + +public enum EvolutionItem { + NONE, + LINKING_CORD, + SUN_STONE, + MOON_STONE, + LEAF_STONE, + FIRE_STONE, + WATER_STONE, + THUNDER_STONE, + ICE_STONE, + DUSK_STONE, + DAWN_STONE, + SHINY_STONE, + CRACKED_POT, + SWEET_APPLE, + TART_APPLE, + STRAWBERRY_SWEET, + UNREMARKABLE_TEACUP, + CHIPPED_POT, + BLACK_AUGURITE, + GALARICA_CUFF, + GALARICA_WREATH, + PEAT_BLOCK, + AUSPICIOUS_ARMOR, + MALICIOUS_ARMOR, + MASTERPIECE_TEACUP, + METAL_ALLOY, + SCROLL_OF_DARKNESS, + SCROLL_OF_WATERS, + SYRUPY_APPLE +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Move.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Move.java new file mode 100644 index 000000000..a6e86bffa --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Move.java @@ -0,0 +1,4 @@ +package com.pokerogue.helper.pokemon2.data; + +public record Move(String id, String name, String effect, String power, String accuracy, String type, String category) { +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/MoveCategory.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/MoveCategory.java new file mode 100644 index 000000000..d4e48cfb3 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/MoveCategory.java @@ -0,0 +1,33 @@ +package com.pokerogue.helper.pokemon2.data; + +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"), + ; + + private final String id; + private final String name; + private final String image; + + MoveCategory(String id, String name, String image) { + this.id = id; + this.name = name; + this.image = image; + } + + public static MoveCategory findById(String id) { + return Arrays.stream(values()) + .filter(category -> category.id.equals(id)) + .findAny() + .orElseThrow(); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Pokemon.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Pokemon.java new file mode 100644 index 000000000..428a48aca --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Pokemon.java @@ -0,0 +1,37 @@ +package com.pokerogue.helper.pokemon2.data; + +import java.util.List; + +public record Pokemon( + String id, + String speciesId, + String speciesName, + String formName, + String koName, + String type1, + String type2, + String ability1, + String ability2, + String abilityHidden, + String abilityPassive, + Integer generation, + Boolean legendary, + Boolean subLegendary, + Boolean mythical, + Boolean canChangeForm, + List evolutionLevel, + Integer baseTotal, + Integer hp, + Integer attack, + Integer defense, + Integer specialAttack, + Integer specialDefense, + Integer speed, + Double height, + Double weight, + List eggMoves, + List moves, + List biomes +) { + +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Type.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Type.java new file mode 100644 index 000000000..1dabe0a02 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/data/Type.java @@ -0,0 +1,50 @@ +package com.pokerogue.helper.pokemon2.data; + +import java.util.Arrays; +import lombok.Getter; + +@Getter +public enum Type { + + UNKNOWN("UNKNOWN", "Unknown"), + NORMAL("NORMAL", "노말"), + FIGHTING("FIGHTING", "격투"), + FLYING("FLYING", "비행"), + POISON("POISON", "독"), + GROUND("GROUND", "땅"), + ROCK("ROCK", "바위"), + BUG("BUG", "벌레"), + GHOST("GHOST", "고스트"), + STEEL("STEEL", "강철"), + FIRE("FIRE", "불꽃"), + WATER("WATER", "물"), + GRASS("GRASS", "풀"), + ELECTRIC("ELECTRIC", "전기"), + PSYCHIC("PSYCHIC", "에스퍼"), + ICE("ICE", "얼음"), + DRAGON("DRAGON", "드래곤"), + DARK("DARK", "악"), + FAIRY("FAIRY", "페어리"), + STELLAR("STELLAR", "스텔라"), + EMPTY("EMPTY", "EMPTY"); + + private final String id; + private final String name; + + Type(String id, String name) { + this.id = id; + this.name = name; + } + + public static Type findById(String id) { + return Arrays.stream(values()) + .filter(value -> value.getId() + .replace("-", "_") + .replace(" ", "_") + .toLowerCase() + .equals(id) + ) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 타입 아이디입니다")); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/BiomeResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/BiomeResponse.java new file mode 100644 index 000000000..e64354896 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/BiomeResponse.java @@ -0,0 +1,5 @@ +package com.pokerogue.helper.pokemon2.dto; + +public record BiomeResponse(String id, String name, String image) { + +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/EvolutionResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/EvolutionResponse.java new file mode 100644 index 000000000..9204677bd --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/EvolutionResponse.java @@ -0,0 +1,31 @@ +package com.pokerogue.helper.pokemon2.dto; + + +import java.util.Objects; + +public record EvolutionResponse( + String name, + Integer level, + Integer depth, + String item, + String condition, + String image +) { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EvolutionResponse that = (EvolutionResponse) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/EvolutionResponses.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/EvolutionResponses.java new file mode 100644 index 000000000..119d62cdb --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/EvolutionResponses.java @@ -0,0 +1,7 @@ +package com.pokerogue.helper.pokemon2.dto; + +import java.util.List; + +public record EvolutionResponses(int currentDepth, List> stages) { + +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/MoveResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/MoveResponse.java new file mode 100644 index 000000000..fa22265fd --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/MoveResponse.java @@ -0,0 +1,36 @@ +package com.pokerogue.helper.pokemon2.dto; + +import com.pokerogue.helper.pokemon2.data.Move; +import com.pokerogue.helper.pokemon2.data.MoveCategory; +import com.pokerogue.helper.pokemon2.data.Type; + +public record MoveResponse( + String id, + String name, + Integer level, + Integer power, + Integer accuracy, + String type, + String typeLogo, + String category, + String categoryLogo +) { + + public static MoveResponse from(Move move, Integer level, String typeImageFromS3) { + MoveCategory moveCategory = MoveCategory.findById(move.category()); + Type type1 = Type.findById(move.type()); + + return new MoveResponse( + move.id(), + move.name(), + level, + Integer.parseInt(move.power()), + Integer.parseInt(move.accuracy()), + type1.getName(), + typeImageFromS3, + moveCategory.getName(), + moveCategory.getImage() + ); + } + +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/Pokemon2DetailResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/Pokemon2DetailResponse.java new file mode 100644 index 000000000..cb8224f81 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/Pokemon2DetailResponse.java @@ -0,0 +1,31 @@ +package com.pokerogue.helper.pokemon2.dto; + +import com.pokerogue.helper.type.dto.PokemonTypeResponse; +import java.util.List; + +public record Pokemon2DetailResponse( + String id, + Long pokedexNumber, + String name, + String pokemonImage, + List pokemonTypeResponses, + List pokemonAbilityResponses, + int totalStats, + int hp, + int attack, + int defense, + int specialAttack, + int specialDefense, + int speed, + Boolean legendary, + Boolean subLegendary, + Boolean mythical, + Boolean canChangeForm, + Double weight, + Double height, + EvolutionResponses evolutions, + List moves, + List eggMoveResponses, + List biomes +) { +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/Pokemon2Response.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/Pokemon2Response.java new file mode 100644 index 000000000..ec0d4a46f --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/Pokemon2Response.java @@ -0,0 +1,49 @@ +package com.pokerogue.helper.pokemon2.dto; + +import com.pokerogue.external.pokemon.dto.type.TypeResponse; +import com.pokerogue.helper.pokemon2.data.Pokemon; +import com.pokerogue.helper.pokemon2.data.Type; +import com.pokerogue.helper.type.dto.PokemonTypeResponse; +import jakarta.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + +public record Pokemon2Response( + String id, + Long pokedexNumber, + String name, + String formName, + String image, + List pokemonTypeResponses, + Integer generation, + Integer totalStats, + Integer hp, + Integer speed, + Integer attack, + Integer defense, + Integer specialAttack, + Integer specialDefense +) { + public static Pokemon2Response from(Pokemon pokemon, String image, String typeLogo1, String typeLogo2) { + + return new Pokemon2Response( + pokemon.id(), + Long.parseLong(pokemon.speciesId()), + pokemon.koName(), + pokemon.formName(), + image, + List.of( + new PokemonTypeResponse(Type.findById(pokemon.type1()).getName(), typeLogo1), + new PokemonTypeResponse(Type.findById(pokemon.type2()).getName(), typeLogo2) + ), + pokemon.generation(), + pokemon.baseTotal(), + pokemon.hp(), + pokemon.attack(), + pokemon.defense(), + pokemon.specialAttack(), + pokemon.specialDefense(), + pokemon.speed() + ); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/PokemonAbilityResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/PokemonAbilityResponse.java new file mode 100644 index 000000000..13fbcf6c4 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/dto/PokemonAbilityResponse.java @@ -0,0 +1,8 @@ +package com.pokerogue.helper.pokemon2.dto; + +public record PokemonAbilityResponse(String id, String name, String description, Boolean passive, Boolean hidden) { + + public static PokemonAbilityResponse from(String name, String description, boolean passive, boolean hidden) { + return new PokemonAbilityResponse(name, name, description, passive, hidden); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/EvolutionRepository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/EvolutionRepository.java new file mode 100644 index 000000000..eca2b4ae5 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/EvolutionRepository.java @@ -0,0 +1,38 @@ +package com.pokerogue.helper.pokemon2.repository; + +import com.pokerogue.helper.pokemon2.data.Evolution; +import com.pokerogue.helper.pokemon2.data.EvolutionChain; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.springframework.stereotype.Repository; + +@Repository +public class EvolutionRepository { + private final Map> data = new HashMap<>(); + private final Map chains = new HashMap<>(); + + public Map> findAll() { + return Collections.unmodifiableMap(data); + } + + public Optional> findEdgeById(String id) { + return Optional.ofNullable(data.get(id)); + } + + public Optional findEvolutionChainById(String id) { + return Optional.ofNullable(chains.get(id)); + } + + public void saveEdge(String key, Evolution value) { + data.putIfAbsent(key, new ArrayList<>()); + data.get(key).add(value); + } + + public void saveChain(String id, EvolutionChain value) { + chains.put(id, value); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/MoveRepository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/MoveRepository.java new file mode 100644 index 000000000..5be41afe6 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/MoveRepository.java @@ -0,0 +1,22 @@ +package com.pokerogue.helper.pokemon2.repository; + + +import com.pokerogue.helper.pokemon2.data.Move; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import org.springframework.stereotype.Repository; + +@Repository +public class MoveRepository { + private final Map data = new HashMap<>(); + + public Optional findById(String id) { + return Optional.ofNullable(data.get(id)); + } + + public void save(String key, Move value) { + data.put(key, value); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/Pokemon2Repository.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/Pokemon2Repository.java new file mode 100644 index 000000000..db5bd6f72 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/repository/Pokemon2Repository.java @@ -0,0 +1,26 @@ +package com.pokerogue.helper.pokemon2.repository; + + +import com.pokerogue.helper.pokemon2.data.Pokemon; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import org.springframework.stereotype.Repository; + +@Repository +public class Pokemon2Repository { + private final Map data = new TreeMap<>(); + + public Map findAll() { + return Collections.unmodifiableMap(data); + } + + public Optional findById(String id) { + return Optional.ofNullable(data.get(id)); + } + + public void save(String key, Pokemon pokemon) { + data.put(key, pokemon); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/service/Pokemon2Service.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/service/Pokemon2Service.java new file mode 100644 index 000000000..1aeebba54 --- /dev/null +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/pokemon2/service/Pokemon2Service.java @@ -0,0 +1,234 @@ +package com.pokerogue.helper.pokemon2.service; + + +import com.pokerogue.external.s3.service.S3Service; +import com.pokerogue.helper.pokemon2.data.Ability; +import com.pokerogue.helper.pokemon2.data.Biome; +import com.pokerogue.helper.pokemon2.data.Evolution; +import com.pokerogue.helper.pokemon2.data.EvolutionChain; +import com.pokerogue.helper.pokemon2.data.Pokemon; +import com.pokerogue.helper.pokemon2.data.Type; +import com.pokerogue.helper.pokemon2.dto.BiomeResponse; +import com.pokerogue.helper.pokemon2.dto.EvolutionResponse; +import com.pokerogue.helper.pokemon2.dto.EvolutionResponses; +import com.pokerogue.helper.pokemon2.dto.MoveResponse; +import com.pokerogue.helper.pokemon2.dto.Pokemon2DetailResponse; +import com.pokerogue.helper.pokemon2.dto.Pokemon2Response; +import com.pokerogue.helper.pokemon2.dto.PokemonAbilityResponse; +import com.pokerogue.helper.pokemon2.repository.EvolutionRepository; +import com.pokerogue.helper.pokemon2.repository.MoveRepository; +import com.pokerogue.helper.pokemon2.repository.Pokemon2Repository; +import com.pokerogue.helper.type.dto.PokemonTypeResponse; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class Pokemon2Service { + + private final S3Service s3Service; + private final Pokemon2Repository pokemon2Repository; + private final MoveRepository moveRepository; + private final EvolutionRepository evolutionRepository; + + private List findAllCache = List.of(); + private Map findByIdCache = new HashMap<>(); + + public List findAll() { + if (findAllCache.isEmpty()) { + initFindAllCache(); + } + return findAllCache; + } + + private void initFindAllCache() { + findAllCache = pokemon2Repository.findAll().values().stream() + .map(pokemon -> Pokemon2Response.from( + pokemon, + s3Service.getPokemonImageFromS3(pokemon.id()), + s3Service.getTypeImageFromS3(pokemon.type1()), + s3Service.getTypeImageFromS3(pokemon.type2()) + )) + .sorted(Comparator.comparingLong(Pokemon2Response::pokedexNumber)) + .toList(); + } + + public Pokemon2DetailResponse findById(String id) { + if (findByIdCache.isEmpty()) { + initFindByIdCache(); + } + return findByIdCache.get(id); + } + + private void initFindByIdCache() { + List pokemon2DetailResponses = pokemon2Repository.findAll().values() + .stream() + .map(this::toPokemon2DetailResponse) + .toList(); + + findByIdCache = pokemon2DetailResponses.stream().collect( + Collectors.toMap( + Pokemon2DetailResponse::id, + Function.identity() + )); + } + + private Pokemon2DetailResponse toPokemon2DetailResponse(Pokemon pokemon) { + List pokemonTypeResponses = createTypeResponse(pokemon); + List pokemonAbilityResponses = createAbilityResponse(pokemon); + EvolutionResponses evolutionResponses = createEvolutionResponse(pokemon); + List moveResponse = createMoveResponse(pokemon.moves()); + List eggMoveResponse = createEggMoveResponse(pokemon.eggMoves()); + List biomeResponse = createBiomeResponse(pokemon.biomes()); + + return new Pokemon2DetailResponse( + pokemon.id(), + Long.parseLong(pokemon.speciesId()), + pokemon.koName(), + s3Service.getPokemonImageFromS3(pokemon.id()), + pokemonTypeResponses, + pokemonAbilityResponses, + pokemon.baseTotal(), + pokemon.hp(), + pokemon.attack(), + pokemon.defense(), + pokemon.specialAttack(), + pokemon.specialDefense(), + pokemon.speed(), + pokemon.legendary(), + pokemon.subLegendary(), + pokemon.mythical(), + pokemon.canChangeForm(), + pokemon.weight(), + pokemon.height(), + evolutionResponses, + moveResponse, + eggMoveResponse, + biomeResponse + ); + } + + private EvolutionResponses createEvolutionResponse(Pokemon pokemon) { + EvolutionChain chain = evolutionRepository.findEvolutionChainById(pokemon.id()) + .orElseThrow(() -> new IllegalArgumentException()); + + int currentStage = IntStream.range(0, chain.getChain().size()) + .filter(i -> chain.getChain().get(i).stream().anyMatch(r -> r.equals(pokemon.id()))) + .sum(); + + return new EvolutionResponses( + currentStage, + createStages(chain) + ); + } + + private List> createStages(EvolutionChain evolutionChain) { + List> chain = evolutionChain.getChain(); + List> ret = new ArrayList<>(); + + Pokemon firstPokemon = pokemon2Repository.findById(chain.get(0).get(0)) + .orElseThrow(() -> new IllegalArgumentException()); + ret.add(List.of(new EvolutionResponse( + firstPokemon.koName(), + 1, + 0, + "EMPTY", + "EMPTY", + s3Service.getPokemonImageFromS3(firstPokemon.id()) + ))); + + for (int i = 0; i < chain.size() - 1; i++) { + List stage = chain.get(i); + List tmp = new ArrayList<>(); + for (String id : stage) { + List evolutions = evolutionRepository.findEdgeById(id) + .orElseThrow(() -> new IllegalArgumentException()); + for (Evolution evolution : evolutions) { + Pokemon pokemon = pokemon2Repository.findById(evolution.to()) + .orElseThrow(() -> new IllegalArgumentException("")); + tmp.add(new EvolutionResponse( + pokemon.koName(), + Integer.parseInt(evolution.level()), + i + 1, + evolution.item(), + evolution.condition(), + s3Service.getPokemonImageFromS3(pokemon.id()) + )); + } + } + ret.add(tmp); + } + return ret; + } + + private List createAbilityResponse(Pokemon pokemon) { + Ability passive = Ability.findById(pokemon.abilityPassive()); + Ability hidden = Ability.findById(pokemon.abilityHidden()); + Ability ability1 = Ability.findById(pokemon.ability1()); + Ability ability2 = Ability.findById(pokemon.ability2()); + + return List.of( + new PokemonAbilityResponse(pokemon.abilityPassive(), passive.getName(), passive.getDescription(), true, + false), + new PokemonAbilityResponse(pokemon.abilityHidden(), hidden.getName(), hidden.getDescription(), false, + true), + new PokemonAbilityResponse(pokemon.ability1(), ability1.getName(), ability1.getDescription(), false, + false), + new PokemonAbilityResponse(pokemon.ability2(), ability2.getName(), ability2.getDescription(), false, + false) + ); + } + + private List createTypeResponse(Pokemon pokemon) { + Type type1 = Type.findById(pokemon.type1()); + Type type2 = Type.findById(pokemon.type2()); + + return List.of( + new PokemonTypeResponse(type1.getName(), s3Service.getTypeImageFromS3(type1.getId())), + new PokemonTypeResponse(type2.getName(), s3Service.getTypeImageFromS3(type2.getId())) + ); + } + + private List createBiomeResponse(List biomes) { + return biomes.stream() + .map(id -> { + Biome biome = Biome.findById(id); + return new BiomeResponse( + id, + biome.getName(), + s3Service.getBiomeImageFromS3(biome.getId()) + ); + } + ) + .toList(); + } + + private List createEggMoveResponse(List moves) { + return moves.stream() + .map(r -> moveRepository.findById(r).orElseThrow(() -> new IllegalArgumentException())) + .map(move -> MoveResponse.from(move, 1, + s3Service.getTypeImageFromS3(moveRepository.findById(move.id()) + .orElseThrow(() -> new IllegalArgumentException()) + .type()))) + .toList(); + } + + private List createMoveResponse(List moves) { + return IntStream.iterate(0, index -> index + 2) + .limit(moves.size() / 2) + .mapToObj(index -> MoveResponse.from( + moveRepository.findById(moves.get(index)).orElseThrow(), + Integer.parseInt(moves.get(index + 1)), + s3Service.getTypeImageFromS3(moveRepository.findById(moves.get(index)).orElseThrow().type()) + )) + .toList(); + } +} diff --git a/backend/pokerogue/src/main/java/com/pokerogue/helper/type/dto/PokemonTypeResponse.java b/backend/pokerogue/src/main/java/com/pokerogue/helper/type/dto/PokemonTypeResponse.java index 2f675fad3..ca5996eee 100644 --- a/backend/pokerogue/src/main/java/com/pokerogue/helper/type/dto/PokemonTypeResponse.java +++ b/backend/pokerogue/src/main/java/com/pokerogue/helper/type/dto/PokemonTypeResponse.java @@ -1,5 +1,6 @@ package com.pokerogue.helper.type.dto; +import com.pokerogue.helper.pokemon2.data.Pokemon; import com.pokerogue.helper.type.domain.PokemonType; public record PokemonTypeResponse(String pokemonTypeName, String pokemonTypeLogo) { diff --git a/backend/pokerogue/src/test/java/com/pokerogue/helper/pokemon2/data/Pokemon2DatabaseInitializerTest.java b/backend/pokerogue/src/test/java/com/pokerogue/helper/pokemon2/data/Pokemon2DatabaseInitializerTest.java new file mode 100644 index 000000000..b54b42a80 --- /dev/null +++ b/backend/pokerogue/src/test/java/com/pokerogue/helper/pokemon2/data/Pokemon2DatabaseInitializerTest.java @@ -0,0 +1,48 @@ +package com.pokerogue.helper.pokemon2.data; + +import com.pokerogue.helper.pokemon2.config.Pokemon2DatabaseInitializer; +import com.pokerogue.helper.pokemon2.repository.EvolutionRepository; +import com.pokerogue.helper.pokemon2.repository.MoveRepository; +import com.pokerogue.helper.pokemon2.repository.Pokemon2Repository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.boot.DefaultApplicationArguments; + + +class Pokemon2DatabaseInitializerTest { + + @Test + @DisplayName("포켓몬 정보가 지정된 개수만큼 저장된다") + void savePokemons() { + Pokemon2Repository pokemon2Repository = new Pokemon2Repository(); + Pokemon2DatabaseInitializer pokemon2DatabaseInitializer = new Pokemon2DatabaseInitializer( + pokemon2Repository, + new MoveRepository(), + new EvolutionRepository() + ); + + pokemon2DatabaseInitializer.run(new DefaultApplicationArguments()); + + Assertions.assertThat(pokemon2Repository.findAll()).hasSize(1350); + } + + @ParameterizedTest + @ValueSource(strings = {"bulbasaur", "chikorita", "wailmer", "virizion", "golisopod", "melmetal", "spidops", + "hydrapple", "alola_exeggutor"}) + @DisplayName("포켓몬의 저장된 이름을 확인한다") + void savePokemons2(String name) { + Pokemon2Repository pokemon2Repository = new Pokemon2Repository(); + Pokemon2DatabaseInitializer pokemon2DatabaseInitializer = new Pokemon2DatabaseInitializer( + pokemon2Repository, + new MoveRepository(), + new EvolutionRepository() + ); + + pokemon2DatabaseInitializer.run(new DefaultApplicationArguments()); + + Assertions.assertThat(pokemon2Repository.findById(name)).isNotNull(); + } +}