diff --git a/.gitignore b/.gitignore index 00048f7..d357198 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ +HW3/results/* HW3/results +HW3/plots HW4/results +HW4/plots HW5/results HW5/plots HW5/plots/* HW5/dataset.csv -HW5/trained_model.joblib +HW5/trained_model.joblib \ No newline at end of file diff --git a/HW1/core.py b/HW1/core.py index 7bc0191..b20f237 100644 --- a/HW1/core.py +++ b/HW1/core.py @@ -19,11 +19,8 @@ class Pokemon: raise ValueError(f"{move} is not an available move.") else: self.moves_dict[move] = copy.deepcopy(gd.available_moves[move]) - print(gd.available_moves[move]['PP']) - print("TYPE", type(gd.available_moves[move])) self.curr_hp = self.baseStats['hp'] - def _get_baseStats(self): return self.baseStats @@ -49,6 +46,7 @@ class Pokemon: self.moves_dict[move] = gd.available_moves[move] def _useMove(self, move, opponent): + win_flag = False if move['PP'] > 0: if random.random() < move['accuracy']: if move['category'] == 'physical': @@ -64,29 +62,32 @@ class Pokemon: modifier = stability * effect * critical * luck damage = math.floor((( 2 * self.level + 10) / 250 * (attack / defense) * move['power'] + 2) * modifier) opponent.curr_hp -= damage - if opponent.curr_hp < 0: + if opponent.curr_hp < 0 or opponent.curr_hp == 0: opponent.curr_hp = 0 + win_flag = True + print(f"{opponent.name} has fainted!") print(f"{self.name} used {move['name']} and dealt {damage} damage to {opponent.name}!") + print("CPU's Pokemon current HP: ", opponent.curr_hp) move['PP'] = move['PP'] - 1 else: print(f"{self.name}'s attack missed!") move['PP'] = move['PP'] - 1 else: print(f"{self.name} has no PP left for {move['name']}!") - pass + return win_flag def useMove(self, move_name, opponent): if move_name not in self.moves_dict: - raise ValueError(f"{self.name} does not know the move {move_name}.") + raise ValueError(f"{self.name} does not know the move {move_name}.") else: - self._useMove(self.moves_dict[move_name], opponent) + return self._useMove(self.moves_dict[move_name], opponent) def print_move(self,move): print(f"Move: {self.moves_dict[move]['name']}") - print(f"Type: {self.moves_dict[move]['type']}") - print(f"Category: {self.moves_dict[move]['category']}") + #print(f"Type: {self.moves_dict[move]['type']}") + #print(f"Category: {self.moves_dict[move]['category']}") print(f"Power: {self.moves_dict[move]['power']}") - print(f"Accuracy: {self.moves_dict[move]['accuracy']}") + #print(f"Accuracy: {self.moves_dict[move]['accuracy']}") print(f"PP: {self.moves_dict[move]['PP']}") def get_moves(self): @@ -111,7 +112,7 @@ class Pokemon: class Trainer: - def __init__(self, name, starter_name, items = {"potions": 0, "pokeball": 0}, is_CPU = False): + def __init__(self, name, starter_name, items = {}, is_CPU = False): self.name = name if is_CPU: if starter_name not in gd.available_wild_pokemons: @@ -171,28 +172,6 @@ class Trainer: except ValueError as e: raise e - def print_items(self): - print(f"1) Potions: {self.items['potions']}") - print(f"2) Pokeballs: {self.items['pokeball']}") - - def use_item(self, item_name): - if item_name not in self.items: - raise ValueError(f"{item_name} is not an available item.") - else: - if self.items[item_name] > 0: - self.items[item_name] -= 1 #use the item - print(f"Used {item_name}. Remaining: {self.items[item_name]}") - if item_name == "potions": - self.active_pokemon.curr_hp += 20 - if self.active_pokemon.curr_hp > self.active_pokemon.baseStats['hp']: - self.active_pokemon.curr_hp = self.active_pokemon.baseStats['hp'] - print(f"{self.active_pokemon.name} has been healed by 20 HP!") - return False - elif item_name == "pokeball": - return True - else: - raise ValueError(f"You have no {item_name} left.") - def update_pokemon_list(self, pokemon): pass @@ -203,7 +182,7 @@ class Trainer: print(f"Pokemon List: {[pok.name for pok in self.pokemon_list]}") print("Active Pokemon info:") self.active_pokemon.print_pokemon_info() - print("Detailed info of Pokemons in your list:") + print("Detailed info of Pokemons in the list:") print("-"*30) for pokemon in self.pokemon_list: pokemon.print_pokemon_info() diff --git a/HW1/hw1.py b/HW1/hw1.py index 2f6df94..d1494bd 100644 --- a/HW1/hw1.py +++ b/HW1/hw1.py @@ -1,8 +1,5 @@ import random -import math - import game_data as gd - from core import Trainer def Main(): @@ -19,11 +16,11 @@ def Main(): print(e) print("Please try again...") continue - trainer.print_trainer_info() + #trainer.print_trainer_info() - cpu_trainer = Trainer(name="CPU", starter_name=random.choice(gd.available_starter_names)) + cpu_trainer = Trainer(name="CPU", starter_name=random.choice(gd.available_wild_pokemon_names), is_CPU=True) print(f"CPU has chosen {cpu_trainer.get_active_pokemon().name} as their starter Pokemon!") - cpu_trainer.print_trainer_info() + #cpu_trainer.print_trainer_info() continue_playing = True while continue_playing: selected_pokemon_combat_flag = False @@ -42,7 +39,6 @@ def Main(): print(f"Choose a move for {player_pokemon.name} to use against {cpu_trainer.get_active_pokemon().name}:\n", player_pokemon.get_moves()) player_move_name = input("Choose:") try: - #print the move selected by the player print("Your selected move: ") player_pokemon.checkout_move(player_move_name) selected_combat_move_flag = True @@ -50,21 +46,24 @@ def Main(): print(e) print("Please try again...") - #make a combat + #combat print('='*30) print("BATTLE RESULTS:") print("Your move:") - player_pokemon.useMove(player_move_name, cpu_trainer.get_active_pokemon()) - player_pokemon.checkout_move(player_move_name) - print('-'*30) - print("CPU's pokemon") - cpu_trainer.get_active_pokemon().print_pokemon_info() - print('='*30) - - print("Continue playing? (yes/no)") - response = input().strip().lower() - if response != "yes": + opponent_fainted = player_pokemon.useMove(player_move_name, cpu_trainer.get_active_pokemon()) + #player_pokemon.checkout_move(player_move_name) + #print('-'*30) + #print("CPU's pokemon") + #cpu_trainer.get_active_pokemon().print_pokemon_info() + #print('='*30) + if opponent_fainted: + print("You won the battle!") continue_playing = False + else: + print("Continue playing? (yes/no)") + response = input().strip().lower() + if response != "yes": + continue_playing = False if __name__ == "__main__": diff --git a/HW2/core.py b/HW2/core.py index d263b82..ea38a82 100644 --- a/HW2/core.py +++ b/HW2/core.py @@ -178,7 +178,7 @@ class Trainer: raise ValueError(f"{item_name} is not an available item.") else: if self.items[item_name] > 0: - self.items[item_name] -= 1 #use the item + self.items[item_name] -= 1 print(f"Used {item_name}. Remaining: {self.items[item_name]}") if item_name == "potions": self.active_pokemon.curr_hp += 20 diff --git a/HW2/hw2.py b/HW2/hw2.py index 4a0aced..08b2c66 100644 --- a/HW2/hw2.py +++ b/HW2/hw2.py @@ -160,7 +160,7 @@ def Main(): print("Adding potions and pokeballs to your inventory...") trainer.items = {"potions": 10, "pokeball": 10} print("Your character is ready!") - trainer.print_trainer_info() + #trainer.print_trainer_info() player_created_flag = True print(f"Welcome {trainer.name}! Your adventure begins now!") except ValueError as e: @@ -201,9 +201,9 @@ def Main(): battle_prob = random.uniform(0, 1) print(f"Battle probability: {battle_prob:.2f}") if battle_prob < 0.8: - print("A wild Pokemon appeared!") cpu_trainer = Trainer(name='CPU',starter_name=gd.available_wild_pokemon_names[random.randint(0,len(gd.available_wild_pokemon_names)-1)],is_CPU=True) - print(cpu_trainer.get_active_pokemon().print_pokemon_info()) + print(f"A wild {cpu_trainer.get_active_pokemon().name} appeared!") + #print(cpu_trainer.get_active_pokemon().print_pokemon_info()) game_status = 'battle' Battle(trainer,cpu_trainer) else: @@ -224,7 +224,6 @@ def Main(): else: print("You exited the Pokemon Center.") game_status = 'story' - # Add Pokemon Center logic here elif game_status == 'pokemon_store': print("You are in the Pokemon Store. You can buy items here.") restore_items_flag = input("Type 'yes' to restore your items: ").strip().lower() diff --git a/HW3/core.py b/HW3/core.py index 6ed7e09..56865be 100644 --- a/HW3/core.py +++ b/HW3/core.py @@ -21,7 +21,6 @@ class Pokemon: self.moves_dict[move] = copy.deepcopy(gd.available_moves[move]) self.curr_hp = self.baseStats['hp'] - def _get_baseStats(self): return self.baseStats @@ -51,9 +50,6 @@ class Pokemon: self.moves_dict[move] = copy.deepcopy(gd.available_moves[move]) def _compute_effect(self, move, opponent): - #check the move type (attack) and the defending - #pokemon type (defend) and look at effectiveness list. - #this should be done for all type of the defending pokemon attack_move_type = move['type'] defending_types = opponent.get_types() diff --git a/HW3/game_data.py b/HW3/game_data.py index 9984353..80bd04c 100644 --- a/HW3/game_data.py +++ b/HW3/game_data.py @@ -1,9 +1,9 @@ import json import random -#load moves from json file -#eclude moves with power equal to null, -# do not consider the effect,effects or changes fields + +#exclude moves with power equal to null, +#do not consider the effect,effects or changes fields available_moves = dict() keys_to_skip = ['effect','effects','changes'] with open('moves.json', 'r') as file: @@ -33,7 +33,7 @@ with open('pokemons.json', 'r') as file: for m in available_moves.keys(): if available_moves[m]['type'] in current_pokemon_types: compatible_moves.append(m) - #select two random moves + #select two (compatible) random moves selected_moves = [random.choice(compatible_moves) for _ in range(2)] pokemons_dict[p['name']]['moves'] = selected_moves diff --git a/HW3/hw3.py b/HW3/hw3.py index af69bb9..ffbce59 100644 --- a/HW3/hw3.py +++ b/HW3/hw3.py @@ -1,3 +1,4 @@ +import os from core import Trainer import random @@ -68,11 +69,7 @@ def Main(starter_choice,Nbattles): try: starter_name = starter_choice trainer = Trainer(name='edo', starter_name=starter_name) - #print(f"{trainer.name} has chosen {trainer.starter} as their starter Pokemon!") - #print("Adding potions and pokeballs to your inventory...") trainer.items = {"potions": 10, "pokeball": 10} - #print("Your character is ready!") - #print(f"Welcome {trainer.name}! Your adventure begins now!") except ValueError as e: print(e) print("Something went wrong!") @@ -98,6 +95,8 @@ def Main(starter_choice,Nbattles): def make_plots(Ngames,Nbattles): #SIMPLE PLOT + if os.path.exists('plots') == False: + os.makedirs('plots') cumulative_victories = np.zeros((Ngames,Nbattles)) plt.figure(figsize=(10, 6)) for starter_choice in gd.available_starters: @@ -171,6 +170,10 @@ def make_plots(Ngames,Nbattles): if __name__ == "__main__": Ngames = 500 Nbattles = 100 + if os.path.exists('results') == False: + os.makedirs('results') + if os.path.exists('plots') == False: + os.makedirs('plots') for starter_choice in gd.available_starters: for i in range(Ngames): print(f"Generating data for {starter_choice}, game {i}...") diff --git a/HW4/game_data.py b/HW4/game_data.py index 3393341..01d6935 100644 --- a/HW4/game_data.py +++ b/HW4/game_data.py @@ -56,6 +56,5 @@ with open('type_effectiveness.json', 'r') as file: type_effectiveness.append(json.loads(line)) type_effectiveness_df = pd.DataFrame.from_dict(type_effectiveness) #NOTE: from a practical point of view, since type_effectiveness is accessed many times -#in the main code, using pandas DataFrame is not efficient. I decided to keep using dictionaries -#for the type effectiveness. The justification arises especially if we consider -#how long data generation will take. \ No newline at end of file +#in the main code I decided to keep using dictionaries +#for the type effectiveness \ No newline at end of file diff --git a/HW4/hw4.py b/HW4/hw4.py index 017e8e2..c615879 100644 --- a/HW4/hw4.py +++ b/HW4/hw4.py @@ -7,7 +7,6 @@ import pickle import matplotlib.pyplot as plt import numpy as np import os -np.seterr(divide='raise', invalid='raise') #if the results folder does not exist, create it if not os.path.exists('results'): @@ -220,17 +219,19 @@ def make_plots(Ngames,Nbattles): plt.savefig('plots/pokemon_types_distribution.pdf') - if __name__ == "__main__": Ngames = 1000 Nbattles = 150 + print("Generating data") for i in range(Ngames): + #automatic player creation (choose a random starter pokemon) starter_choice = random.choice(gd.available_starters) - #print(f"Generating data for {starter_choice}, game {i}...") + print(f"Generating data for {starter_choice}, game {i}...") results = Main(starter_choice = starter_choice, Nbattles=Nbattles) pickle_out = open(f"results/{starter_choice}_game_{i}_{Nbattles}_battles.pickle",'wb') pickle.dump(results,pickle_out) pickle_out.close() print("Generating plots") - make_plots(Ngames, Nbattles) \ No newline at end of file + make_plots(Ngames, Nbattles) + print("Finished!") \ No newline at end of file diff --git a/HW4/plots/average_damage_per_turn_bulbasaur.pdf b/HW4/plots/average_damage_per_turn_bulbasaur.pdf index ca04325..1ae1b2f 100644 Binary files a/HW4/plots/average_damage_per_turn_bulbasaur.pdf and b/HW4/plots/average_damage_per_turn_bulbasaur.pdf differ diff --git a/HW4/plots/average_damage_per_turn_charmander.pdf b/HW4/plots/average_damage_per_turn_charmander.pdf index 44b2a1f..ce389d3 100644 Binary files a/HW4/plots/average_damage_per_turn_charmander.pdf and b/HW4/plots/average_damage_per_turn_charmander.pdf differ diff --git a/HW4/plots/average_damage_per_turn_squirtle.pdf b/HW4/plots/average_damage_per_turn_squirtle.pdf index d3515bc..f7a6b68 100644 Binary files a/HW4/plots/average_damage_per_turn_squirtle.pdf and b/HW4/plots/average_damage_per_turn_squirtle.pdf differ diff --git a/HW4/plots/pokemon_types_distribution.pdf b/HW4/plots/pokemon_types_distribution.pdf index e9fbad2..8e458d7 100644 Binary files a/HW4/plots/pokemon_types_distribution.pdf and b/HW4/plots/pokemon_types_distribution.pdf differ diff --git a/HW5/hw5.py b/HW5/hw5.py index 94c3761..51a3707 100644 --- a/HW5/hw5.py +++ b/HW5/hw5.py @@ -1,22 +1,199 @@ from core import Trainer - +import game_data as gd import random import copy -import game_data as gd +import numpy as np import pickle import joblib -import matplotlib.pyplot as plt -import numpy as np import os import pandas as pd +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix +from sklearn.model_selection import RandomizedSearchCV +from scipy.stats import randint +from sklearn.tree import export_graphviz +import graphviz -np.seterr(divide='raise', invalid='raise') +game_status = 'story' +game_active_flag = True +if os.path.exists('trained_model.joblib'): + recommender_sys_avail = True +else: + recommender_sys_avail = False -#if the results folder does not exist, create it -if not os.path.exists('results'): - os.makedirs('results') -if not os.path.exists('plots'): - os.makedirs('plots') +def recommend(player_pokemon, opponent_pokemon): + global recommender_sys_avail + if recommender_sys_avail: + model = joblib.load('trained_model.joblib') + + # Prepare input data + all_pokemon_types = list(gd.available_types) + player_ActStats = list(player_pokemon.ActStats.values()) + player_pokemon_type = player_pokemon.types + player_pokemon_type_encoded = [1 if t in player_pokemon_type else 0 for t in all_pokemon_types] + opponent_ActStats = list(opponent_pokemon.ActStats.values()) + opponent_pokemon_type = opponent_pokemon.types + opponent_pokemon_type_encoded = [1 if t in opponent_pokemon_type else 0 for t in all_pokemon_types] + + input_data = player_ActStats + player_pokemon_type_encoded + opponent_ActStats + opponent_pokemon_type_encoded + labels = build_dataset()[:-1] # Exclude the last label which is 'battle_result' + df_to_eval = pd.DataFrame([input_data], columns=labels) + + prediction_proba = model.predict_proba(df_to_eval) + return prediction_proba[0][1] # Returns 1 if prediction is a win, 0 otherwise, and the probability of winning + else: + raise ValueError("Recommender system is not available. Play NewRandomBattle once!") +def Battle(trainer,cpu_trainer): + global game_status + global game_active_flag + print(f"You are in battle against CPU's {cpu_trainer.get_active_pokemon().name}!") + battle_active = True + + while battle_active: + player_battle_action_selected = False + while not player_battle_action_selected: + print("Type the number of one of the following actions:") + print(f"1) Attack - [Your active Pokemon - {trainer.get_active_pokemon().name}]") + print("2) Change pokemon") + print("3) Use item") + print("4) Consult the Oracle") + print("5) Run away") + print('6) Check status') + + try: + action = int(input("Choose an action (1-6): ").strip()) + player_battle_action_selected = True + except ValueError: + print("Invalid input. Please enter a number between 1 and 6.") + continue + + if action == 1: + #check if the player can sustain a battle: first check the active pokemon, + #then check the pokemons in the list + if not trainer.active_pokemon_can_battle(): + print("Your active pokemon cannot battle!") + if not trainer.can_battle(): + print("You have no more pokemon that can battle! You lost the battle!") + battle_active = False + game_status = 'pokemon_center' + break + else: + print("You have other pokemon that can battle! Please change your active pokemon!") + continue + + selected_combat_move_flag = False + while not selected_combat_move_flag: + active_pokemon = trainer.get_active_pokemon() + cpu_active_pokemon = cpu_trainer.get_active_pokemon() + print(f"Choose a move for {active_pokemon.name} to use against {cpu_active_pokemon.name}:\n", active_pokemon.get_moves()) + player_move_name = input("Choose:") + try: + active_pokemon.useMove(player_move_name,cpu_active_pokemon) + selected_combat_move_flag = True + #check if the opponent has been defeated + if not cpu_trainer.active_pokemon_can_battle(): + print(f"{cpu_trainer.get_active_pokemon().name} has been defeated!") + battle_active = False + game_status = 'story' + break + + #if the opponent has not been defeated, then its the CPU turn to play + cpu_active_pokemon_selected_move_name = random.choice(cpu_active_pokemon.get_moves()) + print(f"CPU's {cpu_active_pokemon.name} is using {cpu_active_pokemon_selected_move_name} against your {active_pokemon.name}!") + cpu_active_pokemon.useMove(cpu_active_pokemon_selected_move_name, active_pokemon) + + #update the player pokemon list + trainer.update_pokemon_list(active_pokemon) + #check if the player has been defeated + if not trainer.active_pokemon_can_battle(): + print(f"Your {active_pokemon.name} has been defeated!") + if not trainer.can_battle(): + print("You have no more Pokemons that can battle! You lost!") + battle_active = False + game_status = 'pokemon_center' + break + else: + print("You have other Pokemons that can battle!") + continue + except ValueError as e: + print(e) + print("Please try again...") + + elif action == 2: + print('='*30) + print("Change active pokemon") + print('-'*30) + change_pokemon_success = False + while not change_pokemon_success: + try: + print("Select the name of a pokemon in your list:") + print(trainer.get_pokemon_list()) + chosen_new_pokemon_name = input('Choose...') + trainer.change_active_pokemon(chosen_new_pokemon_name) + change_pokemon_success = True + except ValueError as e: + print(e) + + elif action == 3: + print("Select the item index to use:") + trainer.print_items() + item_selected_flag = False + while not item_selected_flag: + try: + avail_items = list(trainer.items.keys()) + item_index = int(input("Choose an item index (1-2): ").strip()) + item_selected_flag = True + is_pokeball = trainer.use_item(item_name=avail_items[item_index - 1]) + if is_pokeball: + catch_prob = 1 - (cpu_trainer.get_active_pokemon().get_current_hp() / cpu_trainer.get_active_pokemon().baseStats['hp']) + random_value = random.uniform(0, 1) + if random_value < catch_prob: + print(f"You caught {cpu_trainer.get_active_pokemon().name}!") + trainer.add_pokemon(cpu_trainer.get_active_pokemon()) + battle_active = False + game_status = 'story' + else: + print(f"{cpu_trainer.get_active_pokemon().name} escaped your pokeball!") + except ValueError: + print("Please enter a number between 1 and 2.") + continue + + elif action == 4: + global recommender_sys_avail + if recommender_sys_avail: + try: + for pokemon in trainer.pokemon_list: + win_prob = recommend(player_pokemon=pokemon, opponent_pokemon=cpu_trainer.get_active_pokemon()) + print(f"The Oracle predicts {pokemon.name} to have {win_prob:.2f} of chances to win against {cpu_trainer.get_active_pokemon().name}.") + except ValueError as e: + print(e) + + elif action == 5: + runaway_prob = 0.6 + random_value = random.uniform(0, 1) + if random_value < runaway_prob: + print("You successfully ran away from the battle!") + battle_active = False + game_status = 'story' + else: + print("You could not run away from the battle!") + + elif action == 6: + print('='*30) + print("BATTLE INFO") + print('-'*30) + print("Your items:") + trainer.print_items() + print("Your pokemon list info:") + for pokemon in trainer.pokemon_list: + pokemon.print_pokemon_info() + print("-"*30) + print("CPU active pokemon info:") + cpu_trainer.get_active_pokemon().print_pokemon_info() + print('='*30) + else: + print("Invalid action. Please choose again.") def RandomBattle(trainer,cpu_trainer): #print(f"You are in battle against {cpu_trainer.name}!") @@ -34,13 +211,20 @@ def RandomBattle(trainer,cpu_trainer): cpu_hp_evolution = [] cpu_selected_moves = [] cpu_damage_done = [] + zero_damage_counter = 0 + zero_damage_max_turns = 10 while battle_active: + #print(f"Starting battle turn {battle_turns_num + 1}") player_move_name = random.choice(active_pokemon.get_moves()) player_selected_moves.append(player_move_name) #print(f"{active_pokemon.name} selected {player_move_name} to use against {cpu_active_pokemon.name}:\n") try: - player_damage_done_value = active_pokemon.useMove(player_move_name,cpu_active_pokemon) - + player_damage_done_value = active_pokemon.useMove(player_move_name,cpu_active_pokemon,game_status='new_random_battle') + if player_damage_done_value == 0: + zero_damage_counter += 1 + else: + zero_damage_counter = 0 + #print(f"Player did {player_damage_done_value} damage with {player_move_name}") player_damage_done.append(player_damage_done_value) player_hp_evolution.append(active_pokemon.get_current_hp()) cpu_hp_evolution.append(cpu_active_pokemon.get_current_hp()) @@ -58,7 +242,11 @@ def RandomBattle(trainer,cpu_trainer): #print(f"CPU's {cpu_active_pokemon.name} is using {cpu_active_pokemon_selected_move_name} against your {active_pokemon.name}!") cpu_selected_moves.append(cpu_active_pokemon_selected_move_name) - cpu_damage_done_value = cpu_active_pokemon.useMove(cpu_active_pokemon_selected_move_name, active_pokemon) + cpu_damage_done_value = cpu_active_pokemon.useMove(cpu_active_pokemon_selected_move_name, active_pokemon,game_status='new_random_battle') + if cpu_damage_done_value == 0: + zero_damage_counter += 1 + else: + zero_damage_counter = 0 cpu_damage_done.append(cpu_damage_done_value) player_hp_evolution.append(active_pokemon.get_current_hp()) @@ -67,7 +255,7 @@ def RandomBattle(trainer,cpu_trainer): #update the player pokemon list trainer.update_pokemon_list(active_pokemon) - #check if the player has been defeated + if not trainer.active_pokemon_can_battle(): #print(f"Your {active_pokemon.name} has been defeated!") if not trainer.can_battle(): @@ -76,12 +264,16 @@ def RandomBattle(trainer,cpu_trainer): battle_active = False break else: - #print("You have other Pokemons that can battle!") + print("You have other Pokemons that can battle!") continue - + if zero_damage_counter >= zero_damage_max_turns: + print("Zero damage detected for too many consecutive turns. Ending battle.") + battle_active = False + did_player_won = False + break except ValueError as e: print(e) - + flag_1 = True #manage those cases for which the player wins in the first turn and the cpu does not play any turn if len(player_damage_done) == 0: player_damage_done = [0] @@ -116,112 +308,6 @@ def RandomBattle(trainer,cpu_trainer): } return battle_results -def Main(starter_choice,Nbattles): - try: - starter_name = starter_choice - trainer = Trainer(name='edo', starter_name=starter_name) - #print(f"{trainer.name} has chosen {trainer.starter} as their starter Pokemon!") - #print("Adding potions and pokeballs to your inventory...") - trainer.items = {"potions": 10, "pokeball": 10} - #print("Your character is ready!") - #print(f"Welcome {trainer.name}! Your adventure begins now!") - except ValueError as e: - print(e) - print("Something went wrong!") - - game_active_flag = True - while game_active_flag: - all_battle_results = [] - for i in range(0,Nbattles): - #print("You entered the Random Battle mode") - #print("Battle #",i) - cpu_trainer = Trainer(name='CPU',starter_name=gd.pokemons_df.index[random.randint(0,len(gd.pokemons_df.index)-1)],is_CPU=True) - battle_results = RandomBattle(trainer,cpu_trainer) - all_battle_results.append(battle_results) - heal_flag = 'yes' - if heal_flag == 'yes': - for pokemon in trainer.pokemon_list: - pokemon.curr_hp = pokemon.baseStats['hp'] - #print("All your Pokemon have been healed!") - - game_active_flag = False - #save all battle results - return all_battle_results - -def make_plots(Ngames,Nbattles): - files_to_analyze = os.listdir('results/') - - avg_damage_per_level = dict() - for file_name in files_to_analyze: - pickle_in = open(f"results/{file_name}",'rb') - all_battle_results = pickle.load(pickle_in) - pickle_in.close() - for battle_result in all_battle_results: - player_pokemon = battle_result['player_pokemon'] - player_pokemon_name = player_pokemon.name - player_pokemon_level = player_pokemon.level - player_avg_damage_done = battle_result['turn_history']['player_history']['player_avg_damage_done'] - - if player_pokemon_name not in avg_damage_per_level: - avg_damage_per_level[player_pokemon_name] = {} - if player_pokemon_level not in avg_damage_per_level[player_pokemon_name]: - avg_damage_per_level[player_pokemon_name][player_pokemon_level] = [] - avg_damage_per_level[player_pokemon_name][player_pokemon_level].append(player_avg_damage_done) - for pokemon_name in avg_damage_per_level: - for level in avg_damage_per_level[pokemon_name]: - avg_damage_per_level[pokemon_name][level] = np.mean(avg_damage_per_level[pokemon_name][level]) - - for pokemon_name in avg_damage_per_level: - levels = list(avg_damage_per_level[pokemon_name].keys()) - avg_damages = list(avg_damage_per_level[pokemon_name].values()) - plt.figure(figsize=(12, 6)) - plt.bar(levels, avg_damages) - plt.xlabel('Pokemon Level') - plt.ylabel('Average Damage Done') - plt.title(f'Average Damage Done per battle turn for {pokemon_name}') - plt.xticks(rotation=45) - plt.tight_layout() - plt.savefig(f'plots/average_damage_per_turn_{pokemon_name}.pdf') - - #pie chart - available_pokemon_type_distribution = {} - for pokemon_name in gd.pokemons_dict: - pokemon_types = gd.pokemons_dict[pokemon_name]['types'] - for pokemon_type in pokemon_types: - if pokemon_type not in available_pokemon_type_distribution: - available_pokemon_type_distribution[pokemon_type] = 0 - available_pokemon_type_distribution[pokemon_type] += 1 - - encountered_pokemon_types = {} - for file_name in files_to_analyze: - pickle_in = open(f"results/{file_name}",'rb') - all_battle_results = pickle.load(pickle_in) - pickle_in.close() - for battle_result in all_battle_results: - encountered_pokemon = battle_result['encountered_pokemon'] - encountered_pokemon_types_list = encountered_pokemon.types - for pokemon_type in encountered_pokemon_types_list: - if pokemon_type not in encountered_pokemon_types: - encountered_pokemon_types[pokemon_type] = 0 - encountered_pokemon_types[pokemon_type] += 1 - - quantities = list(encountered_pokemon_types.values()) - labels = list(encountered_pokemon_types.keys()) - plt.figure(figsize=(12, 6)) - plt.subplot(1, 2, 1) - plt.pie(quantities, labels=labels, autopct='%1.1f%%', startangle=140) - plt.title('Distribution of Encountered Pokemon Types') - plt.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle - - plt.subplot(1, 2, 2) - quantities_base = list(available_pokemon_type_distribution.values()) - labels_base = list(available_pokemon_type_distribution.keys()) - plt.pie(quantities_base, labels=labels_base, autopct='%1.1f%%', startangle=140) - plt.title('Distribution of Available Pokemon Types') - plt.axis('equal') - - plt.savefig('plots/pokemon_types_distribution.pdf') - def build_dataset(): all_pokemon_types = list(gd.available_types) player_ActStats_labels = ["player_hp", "player_attack", "player_defense", "player_speed", "player_special"] @@ -229,39 +315,48 @@ def build_dataset(): opponent_ActStats_labels = ["opponent_hp", "opponent_attack", "opponent_defense", "opponent_speed", "opponent_special"] opponent_type_label = ['opponent_type_' + t for t in all_pokemon_types] - #define a padas dataframe to store the dataset + #define a pandas dataframe to store the dataset dataset_column_labels = player_ActStats_labels + player_type_label + opponent_ActStats_labels + opponent_type_label + ['battle_result'] + + if os.path.exists('dataset.csv'): + return dataset_column_labels + dataset = pd.DataFrame(columns=dataset_column_labels) - files_to_analyze = os.listdir('results/') - dataset_row = [] - for file_name in files_to_analyze: - pickle_in = open(f"results/{file_name}",'rb') - all_battle_results = pickle.load(pickle_in) - pickle_in.close() + dataset_rows = [] + num_of_files = len(files_to_analyze) + for i, file_name in enumerate(files_to_analyze): + print(f"Processing file {i + 1}/{num_of_files}: {file_name}") + + with open(f"results/{file_name}", "rb") as pickle_in: + all_battle_results = pickle.load(pickle_in) + for battle_result in all_battle_results: - player_pokemon_ActStats = list(battle_result['player_pokemon'].ActStats.values()) - player_pokemon_type = battle_result['player_pokemon'].types - player_pokemon_type_encoded = [1 if t in player_pokemon_type else 0 for t in all_pokemon_types] - opponent_pokemon = list(battle_result['encountered_pokemon'].ActStats.values()) - opponent_pokemon_type = battle_result['encountered_pokemon'].types - opponent_pokemon_type_encoded = [1 if t in opponent_pokemon_type else 0 for t in all_pokemon_types] - battle_result_integer = 1 if battle_result['result'] else 0 - dataset_row = player_pokemon_ActStats + player_pokemon_type_encoded + opponent_pokemon + opponent_pokemon_type_encoded + [battle_result_integer] - #append the row to the dataset - dataset.loc[len(dataset)] = dataset_row + player_pokemon = battle_result["player_pokemon"] + opponent_pokemon = battle_result["encountered_pokemon"] + + player_stats = list(player_pokemon.ActStats.values()) + player_types = [ + int(pokemon_type in player_pokemon.types) + for pokemon_type in all_pokemon_types + ] + opponent_stats = list(opponent_pokemon.ActStats.values()) + opponent_types = [ + int(pokemon_type in opponent_pokemon.types) + for pokemon_type in all_pokemon_types + ] + result = int(battle_result["result"]) + + dataset_rows.append( + player_stats + player_types + opponent_stats + opponent_types + [result] + ) + + dataset = pd.DataFrame(dataset_rows, columns=dataset_column_labels) + dataset.to_csv("dataset.csv", index=False) - #save the dataset to a csv file - dataset.to_csv('dataset.csv', index=False) return dataset_column_labels def train_model(dataset_column_labels): - from sklearn.model_selection import train_test_split - from sklearn.ensemble import RandomForestClassifier - from sklearn.metrics import accuracy_score, classification_report, confusion_matrix - from sklearn.model_selection import RandomizedSearchCV - from scipy.stats import randint - dataset_labels = dataset_column_labels[:-1] # Load the dataset dataset = pd.read_csv('dataset.csv') @@ -276,13 +371,14 @@ def train_model(dataset_column_labels): # Create a Random Forest Classifier model = RandomForestClassifier() param_dist = { - 'n_estimators': randint(50, 500), - 'max_depth': randint(1, 20), + 'n_estimators': randint(30, 300), + 'max_depth': randint(2, 30), } + #RandomizedSearchCV is used for parameter tuning. rand_search = RandomizedSearchCV(model, - param_distributions=param_dist, - n_iter=5, cv=5).fit(X_train, y_train) + param_distributions=param_dist, + n_iter=7, cv=5, n_jobs=3).fit(X_train, y_train) # Train the model best_model = rand_search.best_estimator_ @@ -299,8 +395,6 @@ def train_model(dataset_column_labels): print("Confusion Matrix:") print(confusion_matrix(y_test, y_pred)) - from sklearn.tree import export_graphviz - import graphviz # Visualize the first three estimators for i in range(3): tree = best_model.estimators_[i] @@ -313,23 +407,189 @@ def train_model(dataset_column_labels): graph = graphviz.Source(dot_data) graph.render(outfile=f"plots/tree_{i}.pdf", format='pdf') - return best_model + # Save the trained model + joblib.dump(best_model, 'trained_model.joblib') + +def MainRandomBattle(starter_choice,Nbattles): + try: + starter_name = starter_choice + trainer = Trainer(name='trainer', starter_name=starter_name) + trainer.items = {"potions": 10, "pokeball": 10} + except ValueError as e: + print(e) + print("Something went wrong!") + + game_active_flag = True + while game_active_flag: + all_battle_results = [] + for i in range(0,Nbattles): + cpu_trainer = Trainer(name='CPU',starter_name=gd.pokemons_df.index[random.randint(0,len(gd.pokemons_df.index)-1)],is_CPU=True) + battle_results = RandomBattle(trainer,cpu_trainer) + all_battle_results.append(battle_results) + heal_flag = 'yes' + if heal_flag == 'yes': + for pokemon in trainer.pokemon_list: + pokemon.curr_hp = pokemon.baseStats['hp'] + + game_active_flag = False + #save all battle results + return all_battle_results + +def Main(): + global game_status + global game_active_flag + player_created_flag = False + while not player_created_flag: + try: + trainer_name = input("Enter your trainer name: ") + print("Available starter Pokemon: ", gd.available_starter_names) + starter_name = input("Choose your starter Pokemon: ") + trainer = Trainer(name=trainer_name, starter_name=starter_name) + print(f"{trainer.name} has chosen {trainer.starter} as their starter Pokemon!") + print("Adding potions and pokeballs to your inventory...") + trainer.items = {"potions": 10, "pokeball": 10} + print("Your character is ready!") + #trainer.print_trainer_info() + player_created_flag = True + print(f"Welcome {trainer.name}! Your adventure begins now!") + except ValueError as e: + print(e) + print("Please try again...") + continue + + while game_active_flag: + if game_status == 'story': + print("Type the number of one of the following actions:") + print("1) Explore ") + print("2) Pokemon Center") + print("3) Pokemon Store") + print("4) Show trainer data") + print("5) NewRandomBattle") + print("6) Exit") + + try: + action = int(input("Choose an action (1-6): ").strip()) + except ValueError: + print("Invalid input. Please enter a number between 1 and 6.") + continue + if action == 1: + game_status = 'explore' + elif action == 2: + game_status = 'pokemon_center' + elif action == 3: + game_status = 'pokemon_store' + elif action == 4: + print("Showing trainer data:") + trainer.print_trainer_info() + elif action == 5: + confirm=input("Are you sure you want to start a new random battle? This will generate data and train the model. Type 'yes' to continue: ") + if confirm.lower() == 'yes': + print("Starting a new random battle!") + game_status = 'new_random_battle' + else: + print("Cancelled starting a new random battle.") + elif action == 6: + print("Exiting the game. Goodbye!") + game_active_flag = False + else: + print("Invalid action. Please choose again.") + + elif game_status == 'explore': + print("You are exploring") + battle_prob = random.uniform(0, 1) + #print(f"Battle probability: {battle_prob:.2f}") + if battle_prob < 0.8: + cpu_trainer = Trainer(name='CPU',starter_name=gd.pokemons_df.index[random.randint(0,len(gd.pokemons_df.index)-1)],is_CPU=True) + print(f"A wild {cpu_trainer.get_active_pokemon().name} appeared!") + + #print(cpu_trainer.get_active_pokemon().print_pokemon_info()) + game_status = 'battle' + #before entering the battle, we predict the win probability of + #each of the player's pokemon against the opponent's pokemon + try: + win_probs = [] + for pokemon in trainer.pokemon_list: + win_prob = recommend(player_pokemon=pokemon, opponent_pokemon=cpu_trainer.get_active_pokemon()) + win_probs.append(win_prob) + print(f"The Oracle predicts {pokemon.name} to have {win_prob:.2f} of chances to win.") + #if all the win probabilities are below 0.5, we suggest the player to run away + if all(prob < 0.5 for prob in win_probs): + print("The Oracle suggests to RUN AWAY!") + else: + if len(win_probs) > 1: + #find the pokemon with the highest win probability + best_pokemon_index = win_probs.index(max(win_probs)) + best_pokemon = trainer.pokemon_list[best_pokemon_index] + print(f"The Oracle suggests to use {best_pokemon.name}!") + except ValueError as e: + print("HINT: Unlock access to the Oracle by playing NewRandomBattle!") + + Battle(trainer,cpu_trainer) + else: + print('-'*30) + print("No wild Pokemon appeared.") + game_status = 'story' + + elif game_status == 'pokemon_center': + print("You are at the Pokemon Center. You can heal your Pokemon here.") + heal_flag = input("Type 'yes' to heal your Pokemons or any other key to exit: ").strip().lower() + if heal_flag == 'yes': + for pokemon in trainer.pokemon_list: + pokemon.curr_hp = pokemon.baseStats['hp'] + for move in pokemon.moves_dict.keys(): + pokemon.moves_dict[move]['pp'] = copy.deepcopy(gd.available_moves_df.loc[move]['pp']) + print("All your Pokemon have been healed!") + game_status = 'story' + else: + print("You exited the Pokemon Center.") + game_status = 'story' + # Add Pokemon Center logic here + + elif game_status == 'pokemon_store': + print("You are in the Pokemon Store. You can buy items here.") + restore_items_flag = input("Type 'yes' to restore your items: ").strip().lower() + if restore_items_flag == 'yes': + trainer.items = {"potions": 10, "pokeball": 10} + print("Your items have been restored!") + game_status = 'story' + else: + print("You exited the Pokemon Store.") + game_status = 'story' + + elif game_status == 'new_random_battle': + Ngames = 1000 + Nbattles = 500 + print("Generating data...") + #generate battle data only if the dataset.csv does not exist + if not os.path.exists('dataset.csv'): + for i in range(Ngames): + starter_choice = gd.pokemons_df.index[random.randint(0,len(gd.pokemons_df.index)-1)] + print(f"Generating data for {starter_choice}, game {i}...") + results = MainRandomBattle(starter_choice=starter_choice, Nbattles=Nbattles) + pickle_out = open(f"results/{starter_choice}_game_{i}_{Nbattles}_battles.pickle",'wb') + pickle.dump(results,pickle_out) + pickle_out.close() + + print("Setting up the dataset...") + dataset_column_labels = build_dataset() + try: + print("Training model...") + train_model(dataset_column_labels) + print("Model has been trained! Ending NewRandomBattle") + global recommender_sys_avail + recommender_sys_avail = True + except Exception as e: + print(f"An error occurred during model training: {e}") + print("Ending NewRandomBattle without training the model.") + game_status = 'story' + + else: + print("Unknown game status. Try again.") if __name__ == "__main__": - Ngames = 10 - Nbattles = 5 - print("Generating data") - for i in range(Ngames): - starter_choice = gd.pokemons_df.index[random.randint(0,len(gd.pokemons_df.index)-1)] - #print(f"Generating data for {starter_choice}, game {i}...") - results = Main(starter_choice = starter_choice, Nbattles=Nbattles) - pickle_out = open(f"results/{starter_choice}_game_{i}_{Nbattles}_battles.pickle",'wb') - pickle.dump(results,pickle_out) - pickle_out.close() + if not os.path.exists('results'): + os.makedirs('results') + if not os.path.exists('plots'): + os.makedirs('plots') - print("Generating dataset") - dataset_column_labels = build_dataset() - print("Training model") - best_model = train_model(dataset_column_labels) - joblib.dump(best_model, 'trained_model.joblib') - print("Ended") + Main() \ No newline at end of file