import re import json from typing import Dict, Any, List, Optional import os # Resolve data paths robustly CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(CURRENT_DIR)), 'data') DATASETS_DIR = os.path.join(DATA_DIR, 'datasets') class DataLoader: def __init__(self): self.moves_data = {} self.moves_desc_data = {} self.learnsets_data = {} self.typechart_data = {} self.abilities_data = {} self.items_data = {} self._load_all_data() def _load_all_data(self): self._load_moves() self._load_moves_descriptions() self._load_learnsets() self._load_typechart() self._load_abilities() self._load_items() def _load_moves(self): try: moves_path = os.path.join(DATASETS_DIR, 'moves.json') with open(moves_path, 'r', encoding='utf-8') as f: moves_data = json.load(f) for move_key, move_data in moves_data.items(): move_name = move_data.get('name', '').lower() if not move_name: continue self.moves_data[move_key.lower()] = move_data clean_name = move_name.lower().replace(' ', '').replace('-', '') if move_key.lower() != clean_name: self.moves_data[clean_name] = move_data pass except FileNotFoundError: print("Warning: moves.json file not found") except Exception as e: print(f"Error loading moves data: {e}") raise def _load_moves_descriptions(self): try: desc_path = os.path.join(DATASETS_DIR, 'moves_desc.json') with open(desc_path, 'r', encoding='utf-8') as f: content = f.read() content = content.strip() if content.startswith('{') and content.endswith('}'): content = content[1:-1] move_pattern = r'(\w+):\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}' matches = re.findall(move_pattern, content, re.DOTALL) for move_key, move_content in matches: try: move_desc = {} name_match = re.search(r'name:\s*"([^"]+)"', move_content) if name_match: move_desc['name'] = name_match.group(1) desc_match = re.search(r'desc:\s*"([^"]+)"', move_content) if desc_match: move_desc['desc'] = desc_match.group(1) short_desc_match = re.search(r'shortDesc:\s*"([^"]+)"', move_content) if short_desc_match: move_desc['shortDesc'] = short_desc_match.group(1) boost_match = re.search(r'boost:\s*"([^"]+)"', move_content) if boost_match: move_desc['boost'] = boost_match.group(1) self.moves_desc_data[move_key.lower()] = move_desc except Exception as e: print(f"Warning: Failed to parse move description for {move_key}: {e}") continue except FileNotFoundError: print("Warning: moves_desc.json file not found") except Exception as e: print(f"Error loading move descriptions: {e}") def _load_learnsets(self): try: learnsets_path = os.path.join(DATASETS_DIR, 'learnsets.json') with open(learnsets_path, 'r', encoding='utf-8') as f: learnsets_data = json.load(f) for pokemon_name, data in learnsets_data.items(): if 'learnset' in data: moves = [move.lower() for move in data['learnset'].keys()] self.learnsets_data[pokemon_name.lower()] = moves except FileNotFoundError: print("Warning: learnsets.json file not found") except Exception as e: print(f"Error loading learnset data: {e}") raise def _load_typechart(self): try: typechart_path = os.path.join(DATASETS_DIR, 'typechart.json') with open(typechart_path, 'r', encoding='utf-8') as f: self.typechart_data = json.load(f) except FileNotFoundError: print("Warning: typechart.json file not found") except json.JSONDecodeError as e: print(f"Error parsing type chart data: {e}") except Exception as e: print(f"Error loading type chart data: {e}") raise def _load_abilities(self): try: abilities_path = os.path.join(DATASETS_DIR, 'abilities_logic.json') with open(abilities_path, 'r', encoding='utf-8') as f: self.abilities_data = json.load(f) pass except FileNotFoundError: print("Warning: abilities_logic.json file not found") except Exception as e: print(f"Error loading abilities data: {e}") raise def _load_items(self): try: items_path = os.path.join(DATASETS_DIR, 'items_logic.json') with open(items_path, 'r', encoding='utf-8') as f: self.items_data = json.load(f) pass except FileNotFoundError: print("Warning: items_logic.json file not found") except Exception as e: print(f"Error loading items data: {e}") raise def get_item(self, item_name: str) -> Optional[Dict[str, Any]]: if not item_name: return None item_key = item_name.lower().replace(' ', '').replace('-', '') return self.items_data.get(item_key) def get_move(self, move_name): if not move_name: return None move_key = move_name.lower().replace(' ', '').replace('-', '') move_data = self.moves_data.get(move_key) return move_data def get_pokemon_moves(self, pokemon_name: str, limit: Optional[int] = None) -> List[str]: name_lower = pokemon_name.lower() normalized = name_lower.replace("-", "") all_move_ids = set() data = self.learnsets_data.get(normalized) if data: all_move_ids.update(data) data_hyphen = self.learnsets_data.get(name_lower) if data_hyphen: all_move_ids.update(data_hyphen) if "-" in name_lower: base_name = name_lower.split("-")[0] base_data = self.learnsets_data.get(base_name) if base_data: all_move_ids.update(base_data) base_norm = base_name.replace("-", "") if base_norm != base_name: base_data_norm = self.learnsets_data.get(base_norm) if base_data_norm: all_move_ids.update(base_data_norm) # 3. Fuzzy matching as a last resort, but more strictly if not all_move_ids: for key in self.learnsets_data: # Only match if one is a subset of the other and they are clearly related # e.g., "ninetales" in "ninetalesalola" if key == normalized or key == name_lower: all_move_ids.update(self.learnsets_data[key]) elif normalized in key and (key.startswith(normalized) or key.endswith(normalized)): all_move_ids.update(self.learnsets_data[key]) # If we found a good match, we can stop early if we have enough moves if len(all_move_ids) > 20: break move_names = [] for move_id in all_move_ids: move_data = self.moves_data.get(move_id.lower()) if move_data: move_names.append(move_data.get('name', move_id)) else: move_names.append(move_id.replace('-', ' ').title()) results = sorted(list(set(move_names))) if limit: return results[:limit] return results def get_type_effectiveness(self, attacking_type: str, defending_type: str) -> float: if not attacking_type or not defending_type: return 1.0 attacking_type = attacking_type.title() defending_type = defending_type.title() defending_data = self.typechart_data.get(defending_type, {}) damage_taken = defending_data.get('damageTaken', {}) effectiveness_code = damage_taken.get(attacking_type, 0) multiplier = 1.0 if effectiveness_code == 0: multiplier = 1.0 elif effectiveness_code == 1: multiplier = 2.0 elif effectiveness_code == 2: multiplier = 0.5 elif effectiveness_code == 3: multiplier = 0.0 return multiplier def get_move_power(self, move_name: str) -> int: move_data = self.get_move_data(move_name) power = move_data.get('basePower', 50) if move_data else 50 return power def get_effectiveness_message(self, effectiveness: float) -> str: if effectiveness == 0: return "It had no effect..." elif effectiveness < 1: return "It's not very effective..." elif effectiveness > 1: return "It's super effective!" return "" def calculate_effectiveness(self, move_type: str, target_types: list) -> tuple[float, str]: if not move_type or not target_types: return 1.0, "" effectiveness = 1.0 for target_type in target_types: effectiveness *= self.get_type_effectiveness(move_type, target_type) effectiveness = round(effectiveness, 2) if effectiveness == 0: message = "It had no effect..." elif effectiveness < 1: message = "It's not very effective..." elif effectiveness > 1: message = "It's super effective!" else: message = "" return effectiveness, message def get_move_type(self, move_name: str) -> str: move_data = self.get_move_data(move_name) return move_data.get('type', 'normal').lower() if move_data else 'normal' def get_move_pp(self, move_name: str) -> int: move_data = self.get_move_data(move_name) return move_data.get('pp', 15) if move_data else 15 def get_move_data(self, move_name: str) -> Optional[Dict[str, Any]]: if not move_name: return None # Try to find the move by exact key first move_key = move_name.lower().replace(' ', '').replace('-', '') if move_key in self.moves_data: return self.moves_data[move_key] for key, data in self.moves_data.items(): if data.get('name', '').lower() == move_name.lower(): return data for key, data in self.moves_data.items(): if move_name.lower() in key.lower() or move_name.lower() in data.get('name', '').lower(): return data return None def get_ability(self, ability_name: str) -> Optional[Dict[str, Any]]: if not ability_name: return None ability_key = ability_name.lower().replace(' ', '').replace('-', '') return self.abilities_data.get(ability_key) def get_move_description(self, move_name: str) -> Optional[Dict[str, Any]]: if not move_name: return None # Try to find the move by exact key first move_key = move_name.lower().replace(' ', '').replace('-', '') if move_key in self.moves_desc_data: return self.moves_desc_data[move_key] for key, data in self.moves_desc_data.items(): if data.get('name', '').lower() == move_name.lower(): return data return None # Global instance data_loader = DataLoader()