336 lines
14 KiB
Python
336 lines
14 KiB
Python
from core import Trainer
|
|
|
|
import random
|
|
import copy
|
|
import game_data as gd
|
|
import pickle
|
|
import joblib
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
import os
|
|
import pandas as pd
|
|
|
|
np.seterr(divide='raise', invalid='raise')
|
|
|
|
#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 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 = []
|
|
while battle_active:
|
|
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.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)
|
|
|
|
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)
|
|
#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 lose!")
|
|
did_player_won=False
|
|
battle_active = False
|
|
break
|
|
else:
|
|
#print("You have other Pokemons that can battle!")
|
|
continue
|
|
|
|
except ValueError as e:
|
|
print(e)
|
|
|
|
#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 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"]
|
|
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 padas dataframe to store the dataset
|
|
dataset_column_labels = player_ActStats_labels + player_type_label + opponent_ActStats_labels + opponent_type_label + ['battle_result']
|
|
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()
|
|
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
|
|
|
|
#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')
|
|
|
|
# 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(50, 500),
|
|
'max_depth': randint(1, 20),
|
|
}
|
|
|
|
rand_search = RandomizedSearchCV(model,
|
|
param_distributions=param_dist,
|
|
n_iter=5, cv=5).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))
|
|
|
|
from sklearn.tree import export_graphviz
|
|
import graphviz
|
|
# 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')
|
|
|
|
return best_model
|
|
|
|
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()
|
|
|
|
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")
|