final corrections before submission

This commit is contained in:
Edoardo Vergerio
2026-09-06 17:42:44 +02:00
parent 268fda0a6b
commit f046d06e21
15 changed files with 487 additions and 248 deletions
+3
View File
@@ -1,5 +1,8 @@
HW3/results/*
HW3/results HW3/results
HW3/plots
HW4/results HW4/results
HW4/plots
HW5/results HW5/results
HW5/plots HW5/plots
HW5/plots/* HW5/plots/*
+13 -34
View File
@@ -19,11 +19,8 @@ class Pokemon:
raise ValueError(f"{move} is not an available move.") raise ValueError(f"{move} is not an available move.")
else: else:
self.moves_dict[move] = copy.deepcopy(gd.available_moves[move]) 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'] self.curr_hp = self.baseStats['hp']
def _get_baseStats(self): def _get_baseStats(self):
return self.baseStats return self.baseStats
@@ -49,6 +46,7 @@ class Pokemon:
self.moves_dict[move] = gd.available_moves[move] self.moves_dict[move] = gd.available_moves[move]
def _useMove(self, move, opponent): def _useMove(self, move, opponent):
win_flag = False
if move['PP'] > 0: if move['PP'] > 0:
if random.random() < move['accuracy']: if random.random() < move['accuracy']:
if move['category'] == 'physical': if move['category'] == 'physical':
@@ -64,29 +62,32 @@ class Pokemon:
modifier = stability * effect * critical * luck modifier = stability * effect * critical * luck
damage = math.floor((( 2 * self.level + 10) / 250 * (attack / defense) * move['power'] + 2) * modifier) damage = math.floor((( 2 * self.level + 10) / 250 * (attack / defense) * move['power'] + 2) * modifier)
opponent.curr_hp -= damage opponent.curr_hp -= damage
if opponent.curr_hp < 0: if opponent.curr_hp < 0 or opponent.curr_hp == 0:
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(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 move['PP'] = move['PP'] - 1
else: else:
print(f"{self.name}'s attack missed!") print(f"{self.name}'s attack missed!")
move['PP'] = move['PP'] - 1 move['PP'] = move['PP'] - 1
else: else:
print(f"{self.name} has no PP left for {move['name']}!") print(f"{self.name} has no PP left for {move['name']}!")
pass return win_flag
def useMove(self, move_name, opponent): def useMove(self, move_name, opponent):
if move_name not in self.moves_dict: 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: else:
self._useMove(self.moves_dict[move_name], opponent) return self._useMove(self.moves_dict[move_name], opponent)
def print_move(self,move): def print_move(self,move):
print(f"Move: {self.moves_dict[move]['name']}") print(f"Move: {self.moves_dict[move]['name']}")
print(f"Type: {self.moves_dict[move]['type']}") #print(f"Type: {self.moves_dict[move]['type']}")
print(f"Category: {self.moves_dict[move]['category']}") #print(f"Category: {self.moves_dict[move]['category']}")
print(f"Power: {self.moves_dict[move]['power']}") 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']}") print(f"PP: {self.moves_dict[move]['PP']}")
def get_moves(self): def get_moves(self):
@@ -111,7 +112,7 @@ class Pokemon:
class Trainer: 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 self.name = name
if is_CPU: if is_CPU:
if starter_name not in gd.available_wild_pokemons: if starter_name not in gd.available_wild_pokemons:
@@ -171,28 +172,6 @@ class Trainer:
except ValueError as e: except ValueError as e:
raise 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): def update_pokemon_list(self, pokemon):
pass pass
@@ -203,7 +182,7 @@ class Trainer:
print(f"Pokemon List: {[pok.name for pok in self.pokemon_list]}") print(f"Pokemon List: {[pok.name for pok in self.pokemon_list]}")
print("Active Pokemon info:") print("Active Pokemon info:")
self.active_pokemon.print_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) print("-"*30)
for pokemon in self.pokemon_list: for pokemon in self.pokemon_list:
pokemon.print_pokemon_info() pokemon.print_pokemon_info()
+17 -18
View File
@@ -1,8 +1,5 @@
import random import random
import math
import game_data as gd import game_data as gd
from core import Trainer from core import Trainer
def Main(): def Main():
@@ -19,11 +16,11 @@ def Main():
print(e) print(e)
print("Please try again...") print("Please try again...")
continue 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!") 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 continue_playing = True
while continue_playing: while continue_playing:
selected_pokemon_combat_flag = False 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()) 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:") player_move_name = input("Choose:")
try: try:
#print the move selected by the player
print("Your selected move: ") print("Your selected move: ")
player_pokemon.checkout_move(player_move_name) player_pokemon.checkout_move(player_move_name)
selected_combat_move_flag = True selected_combat_move_flag = True
@@ -50,21 +46,24 @@ def Main():
print(e) print(e)
print("Please try again...") print("Please try again...")
#make a combat #combat
print('='*30) print('='*30)
print("BATTLE RESULTS:") print("BATTLE RESULTS:")
print("Your move:") print("Your move:")
player_pokemon.useMove(player_move_name, cpu_trainer.get_active_pokemon()) opponent_fainted = player_pokemon.useMove(player_move_name, cpu_trainer.get_active_pokemon())
player_pokemon.checkout_move(player_move_name) #player_pokemon.checkout_move(player_move_name)
print('-'*30) #print('-'*30)
print("CPU's pokemon") #print("CPU's pokemon")
cpu_trainer.get_active_pokemon().print_pokemon_info() #cpu_trainer.get_active_pokemon().print_pokemon_info()
print('='*30) #print('='*30)
if opponent_fainted:
print("Continue playing? (yes/no)") print("You won the battle!")
response = input().strip().lower()
if response != "yes":
continue_playing = False continue_playing = False
else:
print("Continue playing? (yes/no)")
response = input().strip().lower()
if response != "yes":
continue_playing = False
if __name__ == "__main__": if __name__ == "__main__":
+1 -1
View File
@@ -178,7 +178,7 @@ class Trainer:
raise ValueError(f"{item_name} is not an available item.") raise ValueError(f"{item_name} is not an available item.")
else: else:
if self.items[item_name] > 0: 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]}") print(f"Used {item_name}. Remaining: {self.items[item_name]}")
if item_name == "potions": if item_name == "potions":
self.active_pokemon.curr_hp += 20 self.active_pokemon.curr_hp += 20
+3 -4
View File
@@ -160,7 +160,7 @@ def Main():
print("Adding potions and pokeballs to your inventory...") print("Adding potions and pokeballs to your inventory...")
trainer.items = {"potions": 10, "pokeball": 10} trainer.items = {"potions": 10, "pokeball": 10}
print("Your character is ready!") print("Your character is ready!")
trainer.print_trainer_info() #trainer.print_trainer_info()
player_created_flag = True player_created_flag = True
print(f"Welcome {trainer.name}! Your adventure begins now!") print(f"Welcome {trainer.name}! Your adventure begins now!")
except ValueError as e: except ValueError as e:
@@ -201,9 +201,9 @@ def Main():
battle_prob = random.uniform(0, 1) battle_prob = random.uniform(0, 1)
print(f"Battle probability: {battle_prob:.2f}") print(f"Battle probability: {battle_prob:.2f}")
if battle_prob < 0.8: 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) 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' game_status = 'battle'
Battle(trainer,cpu_trainer) Battle(trainer,cpu_trainer)
else: else:
@@ -224,7 +224,6 @@ def Main():
else: else:
print("You exited the Pokemon Center.") print("You exited the Pokemon Center.")
game_status = 'story' game_status = 'story'
# Add Pokemon Center logic here
elif game_status == 'pokemon_store': elif game_status == 'pokemon_store':
print("You are in the Pokemon Store. You can buy items here.") print("You are in the Pokemon Store. You can buy items here.")
restore_items_flag = input("Type 'yes' to restore your items: ").strip().lower() restore_items_flag = input("Type 'yes' to restore your items: ").strip().lower()
-4
View File
@@ -21,7 +21,6 @@ class Pokemon:
self.moves_dict[move] = copy.deepcopy(gd.available_moves[move]) self.moves_dict[move] = copy.deepcopy(gd.available_moves[move])
self.curr_hp = self.baseStats['hp'] self.curr_hp = self.baseStats['hp']
def _get_baseStats(self): def _get_baseStats(self):
return self.baseStats return self.baseStats
@@ -51,9 +50,6 @@ class Pokemon:
self.moves_dict[move] = copy.deepcopy(gd.available_moves[move]) self.moves_dict[move] = copy.deepcopy(gd.available_moves[move])
def _compute_effect(self, move, opponent): 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'] attack_move_type = move['type']
defending_types = opponent.get_types() defending_types = opponent.get_types()
+4 -4
View File
@@ -1,9 +1,9 @@
import json import json
import random import random
#load moves from json file
#eclude moves with power equal to null, #exclude moves with power equal to null,
# do not consider the effect,effects or changes fields #do not consider the effect,effects or changes fields
available_moves = dict() available_moves = dict()
keys_to_skip = ['effect','effects','changes'] keys_to_skip = ['effect','effects','changes']
with open('moves.json', 'r') as file: with open('moves.json', 'r') as file:
@@ -33,7 +33,7 @@ with open('pokemons.json', 'r') as file:
for m in available_moves.keys(): for m in available_moves.keys():
if available_moves[m]['type'] in current_pokemon_types: if available_moves[m]['type'] in current_pokemon_types:
compatible_moves.append(m) compatible_moves.append(m)
#select two random moves #select two (compatible) random moves
selected_moves = [random.choice(compatible_moves) for _ in range(2)] selected_moves = [random.choice(compatible_moves) for _ in range(2)]
pokemons_dict[p['name']]['moves'] = selected_moves pokemons_dict[p['name']]['moves'] = selected_moves
+7 -4
View File
@@ -1,3 +1,4 @@
import os
from core import Trainer from core import Trainer
import random import random
@@ -68,11 +69,7 @@ def Main(starter_choice,Nbattles):
try: try:
starter_name = starter_choice starter_name = starter_choice
trainer = Trainer(name='edo', starter_name=starter_name) 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} trainer.items = {"potions": 10, "pokeball": 10}
#print("Your character is ready!")
#print(f"Welcome {trainer.name}! Your adventure begins now!")
except ValueError as e: except ValueError as e:
print(e) print(e)
print("Something went wrong!") print("Something went wrong!")
@@ -98,6 +95,8 @@ def Main(starter_choice,Nbattles):
def make_plots(Ngames,Nbattles): def make_plots(Ngames,Nbattles):
#SIMPLE PLOT #SIMPLE PLOT
if os.path.exists('plots') == False:
os.makedirs('plots')
cumulative_victories = np.zeros((Ngames,Nbattles)) cumulative_victories = np.zeros((Ngames,Nbattles))
plt.figure(figsize=(10, 6)) plt.figure(figsize=(10, 6))
for starter_choice in gd.available_starters: for starter_choice in gd.available_starters:
@@ -171,6 +170,10 @@ def make_plots(Ngames,Nbattles):
if __name__ == "__main__": if __name__ == "__main__":
Ngames = 500 Ngames = 500
Nbattles = 100 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 starter_choice in gd.available_starters:
for i in range(Ngames): for i in range(Ngames):
print(f"Generating data for {starter_choice}, game {i}...") print(f"Generating data for {starter_choice}, game {i}...")
+2 -3
View File
@@ -56,6 +56,5 @@ with open('type_effectiveness.json', 'r') as file:
type_effectiveness.append(json.loads(line)) type_effectiveness.append(json.loads(line))
type_effectiveness_df = pd.DataFrame.from_dict(type_effectiveness) type_effectiveness_df = pd.DataFrame.from_dict(type_effectiveness)
#NOTE: from a practical point of view, since type_effectiveness is accessed many times #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 #in the main code I decided to keep using dictionaries
#for the type effectiveness. The justification arises especially if we consider #for the type effectiveness
#how long data generation will take.
+4 -3
View File
@@ -7,7 +7,6 @@ import pickle
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import os import os
np.seterr(divide='raise', invalid='raise')
#if the results folder does not exist, create it #if the results folder does not exist, create it
if not os.path.exists('results'): if not os.path.exists('results'):
@@ -220,17 +219,19 @@ def make_plots(Ngames,Nbattles):
plt.savefig('plots/pokemon_types_distribution.pdf') plt.savefig('plots/pokemon_types_distribution.pdf')
if __name__ == "__main__": if __name__ == "__main__":
Ngames = 1000 Ngames = 1000
Nbattles = 150 Nbattles = 150
print("Generating data") print("Generating data")
for i in range(Ngames): for i in range(Ngames):
#automatic player creation (choose a random starter pokemon)
starter_choice = random.choice(gd.available_starters) 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) results = Main(starter_choice = starter_choice, Nbattles=Nbattles)
pickle_out = open(f"results/{starter_choice}_game_{i}_{Nbattles}_battles.pickle",'wb') pickle_out = open(f"results/{starter_choice}_game_{i}_{Nbattles}_battles.pickle",'wb')
pickle.dump(results,pickle_out) pickle.dump(results,pickle_out)
pickle_out.close() pickle_out.close()
print("Generating plots") print("Generating plots")
make_plots(Ngames, Nbattles) make_plots(Ngames, Nbattles)
print("Finished!")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+431 -171
View File
@@ -1,22 +1,199 @@
from core import Trainer from core import Trainer
import game_data as gd
import random import random
import copy import copy
import game_data as gd import numpy as np
import pickle import pickle
import joblib import joblib
import matplotlib.pyplot as plt
import numpy as np
import os import os
import pandas as pd 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 def recommend(player_pokemon, opponent_pokemon):
if not os.path.exists('results'): global recommender_sys_avail
os.makedirs('results') if recommender_sys_avail:
if not os.path.exists('plots'): model = joblib.load('trained_model.joblib')
os.makedirs('plots')
# 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): def RandomBattle(trainer,cpu_trainer):
#print(f"You are in battle against {cpu_trainer.name}!") #print(f"You are in battle against {cpu_trainer.name}!")
@@ -34,13 +211,20 @@ def RandomBattle(trainer,cpu_trainer):
cpu_hp_evolution = [] cpu_hp_evolution = []
cpu_selected_moves = [] cpu_selected_moves = []
cpu_damage_done = [] cpu_damage_done = []
zero_damage_counter = 0
zero_damage_max_turns = 10
while battle_active: while battle_active:
#print(f"Starting battle turn {battle_turns_num + 1}")
player_move_name = random.choice(active_pokemon.get_moves()) player_move_name = random.choice(active_pokemon.get_moves())
player_selected_moves.append(player_move_name) player_selected_moves.append(player_move_name)
#print(f"{active_pokemon.name} selected {player_move_name} to use against {cpu_active_pokemon.name}:\n") #print(f"{active_pokemon.name} selected {player_move_name} to use against {cpu_active_pokemon.name}:\n")
try: 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_damage_done.append(player_damage_done_value)
player_hp_evolution.append(active_pokemon.get_current_hp()) player_hp_evolution.append(active_pokemon.get_current_hp())
cpu_hp_evolution.append(cpu_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}!") #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_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) cpu_damage_done.append(cpu_damage_done_value)
player_hp_evolution.append(active_pokemon.get_current_hp()) player_hp_evolution.append(active_pokemon.get_current_hp())
@@ -67,7 +255,7 @@ def RandomBattle(trainer,cpu_trainer):
#update the player pokemon list #update the player pokemon list
trainer.update_pokemon_list(active_pokemon) trainer.update_pokemon_list(active_pokemon)
#check if the player has been defeated
if not trainer.active_pokemon_can_battle(): if not trainer.active_pokemon_can_battle():
#print(f"Your {active_pokemon.name} has been defeated!") #print(f"Your {active_pokemon.name} has been defeated!")
if not trainer.can_battle(): if not trainer.can_battle():
@@ -76,12 +264,16 @@ def RandomBattle(trainer,cpu_trainer):
battle_active = False battle_active = False
break break
else: else:
#print("You have other Pokemons that can battle!") print("You have other Pokemons that can battle!")
continue 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: except ValueError as e:
print(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 #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: if len(player_damage_done) == 0:
player_damage_done = [0] player_damage_done = [0]
@@ -116,112 +308,6 @@ def RandomBattle(trainer,cpu_trainer):
} }
return battle_results 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(): def build_dataset():
all_pokemon_types = list(gd.available_types) all_pokemon_types = list(gd.available_types)
player_ActStats_labels = ["player_hp", "player_attack", "player_defense", "player_speed", "player_special"] 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_ActStats_labels = ["opponent_hp", "opponent_attack", "opponent_defense", "opponent_speed", "opponent_special"]
opponent_type_label = ['opponent_type_' + t for t in all_pokemon_types] 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'] 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) dataset = pd.DataFrame(columns=dataset_column_labels)
files_to_analyze = os.listdir('results/') files_to_analyze = os.listdir('results/')
dataset_row = [] dataset_rows = []
for file_name in files_to_analyze: num_of_files = len(files_to_analyze)
pickle_in = open(f"results/{file_name}",'rb') for i, file_name in enumerate(files_to_analyze):
all_battle_results = pickle.load(pickle_in) print(f"Processing file {i + 1}/{num_of_files}: {file_name}")
pickle_in.close()
with open(f"results/{file_name}", "rb") as pickle_in:
all_battle_results = pickle.load(pickle_in)
for battle_result in all_battle_results: for battle_result in all_battle_results:
player_pokemon_ActStats = list(battle_result['player_pokemon'].ActStats.values()) player_pokemon = battle_result["player_pokemon"]
player_pokemon_type = battle_result['player_pokemon'].types opponent_pokemon = battle_result["encountered_pokemon"]
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()) player_stats = list(player_pokemon.ActStats.values())
opponent_pokemon_type = battle_result['encountered_pokemon'].types player_types = [
opponent_pokemon_type_encoded = [1 if t in opponent_pokemon_type else 0 for t in all_pokemon_types] int(pokemon_type in player_pokemon.types)
battle_result_integer = 1 if battle_result['result'] else 0 for pokemon_type in all_pokemon_types
dataset_row = player_pokemon_ActStats + player_pokemon_type_encoded + opponent_pokemon + opponent_pokemon_type_encoded + [battle_result_integer] ]
#append the row to the dataset opponent_stats = list(opponent_pokemon.ActStats.values())
dataset.loc[len(dataset)] = dataset_row 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 return dataset_column_labels
def train_model(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] dataset_labels = dataset_column_labels[:-1]
# Load the dataset # Load the dataset
dataset = pd.read_csv('dataset.csv') dataset = pd.read_csv('dataset.csv')
@@ -276,13 +371,14 @@ def train_model(dataset_column_labels):
# Create a Random Forest Classifier # Create a Random Forest Classifier
model = RandomForestClassifier() model = RandomForestClassifier()
param_dist = { param_dist = {
'n_estimators': randint(50, 500), 'n_estimators': randint(30, 300),
'max_depth': randint(1, 20), 'max_depth': randint(2, 30),
} }
#RandomizedSearchCV is used for parameter tuning.
rand_search = RandomizedSearchCV(model, rand_search = RandomizedSearchCV(model,
param_distributions=param_dist, param_distributions=param_dist,
n_iter=5, cv=5).fit(X_train, y_train) n_iter=7, cv=5, n_jobs=3).fit(X_train, y_train)
# Train the model # Train the model
best_model = rand_search.best_estimator_ best_model = rand_search.best_estimator_
@@ -299,8 +395,6 @@ def train_model(dataset_column_labels):
print("Confusion Matrix:") print("Confusion Matrix:")
print(confusion_matrix(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))
from sklearn.tree import export_graphviz
import graphviz
# Visualize the first three estimators # Visualize the first three estimators
for i in range(3): for i in range(3):
tree = best_model.estimators_[i] tree = best_model.estimators_[i]
@@ -313,23 +407,189 @@ def train_model(dataset_column_labels):
graph = graphviz.Source(dot_data) graph = graphviz.Source(dot_data)
graph.render(outfile=f"plots/tree_{i}.pdf", format='pdf') 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__": if __name__ == "__main__":
Ngames = 10 if not os.path.exists('results'):
Nbattles = 5 os.makedirs('results')
print("Generating data") if not os.path.exists('plots'):
for i in range(Ngames): os.makedirs('plots')
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") Main()
dataset_column_labels = build_dataset()
print("Training model")
best_model = train_model(dataset_column_labels)
joblib.dump(best_model, 'trained_model.joblib')
print("Ended")