from core import Trainer import game_data as gd import random import copy import numpy as np import pickle import joblib 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 game_status = 'story' game_active_flag = True if os.path.exists('trained_model.joblib'): recommender_sys_avail = True else: recommender_sys_avail = False 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}!") battle_active = True active_pokemon = trainer.get_active_pokemon() cpu_active_pokemon = cpu_trainer.get_active_pokemon() #print(f"Your active Pokemon is {active_pokemon.name} and the CPU's active Pokemon is {cpu_active_pokemon.name}.") did_player_won = False battle_turns_num = 0 player_hp_evolution = [] player_selected_moves = [] player_damage_done = [] 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,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()) battle_turns_num += 1 #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! You win!") did_player_won = True battle_active = False 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_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,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()) cpu_hp_evolution.append(cpu_active_pokemon.get_current_hp()) battle_turns_num += 1 #update the player pokemon list trainer.update_pokemon_list(active_pokemon) 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 lose!") did_player_won=False battle_active = False break else: 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] if len(cpu_damage_done) == 0: cpu_damage_done = [0] player_avg_damage_done = np.mean(player_damage_done) #average damage done per turn cpu_avg_damage_done = np.mean(cpu_damage_done) #average damage done per turn player_residual_hp_percent = trainer.get_active_pokemon().get_current_hp_percent() player_history = {'player_hp_evolution':player_hp_evolution, 'player_selected_moves':player_selected_moves, 'player_damage_done':player_damage_done, 'player_avg_damage_done':player_avg_damage_done } cpu_history = {'cpu_hp_evolution':cpu_hp_evolution, 'cpu_selected_moves':cpu_selected_moves, 'cpu_damage_done':cpu_damage_done, 'cpu_avg_damage_done':cpu_avg_damage_done } turn_history = {'player_history':player_history, 'cpu_history':cpu_history } battle_results = { 'player_pokemon':trainer.get_active_pokemon(), 'encountered_pokemon':cpu_trainer.get_active_pokemon(), 'result':did_player_won, 'battle_turns_num':battle_turns_num, 'player_residual_hp_percent':player_residual_hp_percent, 'turn_history':turn_history } return battle_results def build_dataset(): all_pokemon_types = list(gd.available_types) player_ActStats_labels = ["player_hp", "player_attack", "player_defense", "player_speed", "player_special"] player_type_label = ['player_type_' + t for t in all_pokemon_types] 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 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_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 = 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) return dataset_column_labels def train_model(dataset_column_labels): dataset_labels = dataset_column_labels[:-1] # Load the dataset dataset = pd.read_csv('dataset.csv') # Split the dataset into features and target variable X = dataset.drop('battle_result', axis=1) y = dataset['battle_result'] # Split the data into training and testing sets X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # Create a Random Forest Classifier model = RandomForestClassifier() param_dist = { '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=7, cv=5, n_jobs=3).fit(X_train, y_train) # Train the model best_model = rand_search.best_estimator_ print(f"Best parameters found: {rand_search.best_params_}") # Make predictions on the test set y_pred = best_model.predict(X_test) # Evaluate the model accuracy = accuracy_score(y_test, y_pred) print(f"Model Accuracy: {accuracy:.2f}") print("Classification Report:") print(classification_report(y_test, y_pred)) print("Confusion Matrix:") print(confusion_matrix(y_test, y_pred)) # Visualize the first three estimators for i in range(3): tree = best_model.estimators_[i] dot_data = export_graphviz(tree, feature_names=dataset_labels, filled = True, max_depth = 2, impurity = False, proportion = True) graph = graphviz.Source(dot_data) graph.render(outfile=f"plots/tree_{i}.pdf", format='pdf') # 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__": if not os.path.exists('results'): os.makedirs('results') if not os.path.exists('plots'): os.makedirs('plots') Main()