Update rewared structure (fixed major bugs)

This commit is contained in:
Vasilis Valatsos 2023-11-23 12:44:23 +01:00
parent ce4a90ac43
commit 1a6ed25673
11 changed files with 106 additions and 58 deletions

BIN
assets/graphics/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

View file

@ -6,7 +6,7 @@ asset_path = os.path.join(
script_dir, '../..', 'assets') script_dir, '../..', 'assets')
monster_data = { monster_data = {
'squid': {'id': 1, 'health': 100, 'exp': 100, 'attack': 20, 'attack_type': 'slash', 'attack_sound': f'{asset_path}/audio/attack/slash.wav', 'speed': 3, 'knockback': 20, 'attack_radius': 80, 'notice_radius': 360}, 'squid': {'id': 1, 'health': .1, 'exp': 1, 'attack': .5, 'attack_type': 'slash', 'speed': 3, 'knockback': 20, 'attack_radius': 80, 'notice_radius': 360},
'raccoon': {'id': 2, 'health': 300, 'exp': 250, 'attack': 40, 'attack_type': 'claw', 'attack_sound': f'{asset_path}/audio/attack/claw.wav', 'speed': 2, 'knockback': 20, 'attack_radius': 120, 'notice_radius': 400}, 'raccoon': {'id': 2, 'health': .3, 'exp': 2.5, 'attack': .8, 'attack_type': 'claw', 'speed': 2, 'knockback': 20, 'attack_radius': 120, 'notice_radius': 400},
'spirit': {'id': 3, 'health': 100, 'exp': 110, 'attack': 8, 'attack_type': 'thunder', 'attack_sound': f'{asset_path}/audio/attack/fireball.wav', 'speed': 4, 'knockback': 20, 'attack_radius': 60, 'notice_radius': 350}, 'spirit': {'id': 3, 'health': .1, 'exp': 1.1, 'attack': .6, 'attack_type': 'thunder', 'speed': 4, 'knockback': 20, 'attack_radius': 60, 'notice_radius': 350},
'bamboo': {'id': 4, 'health': 70, 'exp': 120, 'attack': 6, 'attack_type': 'leaf_attack', 'attack_sound': f'{asset_path}/audio/attack/slash.wav', 'speed': 3, 'knockback': 20, 'attack_radius': 50, 'notice_radius': 300}} 'bamboo': {'id': 4, 'health': .07, 'exp': 1.2, 'attack': .4, 'attack_type': 'leaf_attack', 'speed': 3, 'knockback': 20, 'attack_radius': 50, 'notice_radius': 300}}

View file

@ -1,9 +1,9 @@
tank_stats = { tank_stats = {
'role_id': 1, 'role_id': 1,
'health': 150, 'health': 1.5,
'energy': 40, 'energy': .4,
'attack': 7, 'attack': .7,
'magic': 3, 'magic': .3,
'speed': 3 'speed': 3
} }

View file

@ -5,5 +5,5 @@ asset_path = os.path.join(
script_dir, '../..', 'assets') script_dir, '../..', 'assets')
magic_data = { magic_data = {
'flame': {'strength': 5, 'cost': 20, 'graphic': f"{asset_path}/graphics/particles/flame/fire.png"}, 'flame': {'strength': 5, 'cost': .020, 'graphic': f"{asset_path}/graphics/particles/flame/fire.png"},
'heal': {'strength': 20, 'cost': 10, 'graphic': f"{asset_path}/graphics/particles/heal/heal.png"}} 'heal': {'strength': 20, 'cost': .010, 'graphic': f"{asset_path}/graphics/particles/heal/heal.png"}}

View file

@ -42,6 +42,12 @@ class StatsHandler:
else: else:
self.energy = self.stats['energy'] self.energy = self.stats['energy']
def health_recovery(self):
if self.energy < self.stats['health']:
self.energy += 0.15
else:
self.energy = self.stats['energy']
def get_value_by_index(self, index): def get_value_by_index(self, index):
return list(self.stats.values())[index] return list(self.stats.values())[index]

View file

@ -16,7 +16,16 @@ from agents.ppo.agent import Agent
class Player(pygame.sprite.Sprite): class Player(pygame.sprite.Sprite):
def __init__(self, position, groups, obstacle_sprites, visible_sprites, attack_sprites, attackable_sprites, role, player_id): def __init__(self,
position,
groups,
obstacle_sprites,
visible_sprites,
attack_sprites,
attackable_sprites,
role,
player_id):
super().__init__(groups) super().__init__(groups)
# Setup Sprites # Setup Sprites
@ -79,7 +88,9 @@ class Player(pygame.sprite.Sprite):
offset = pygame.math.Vector2(0, 75) offset = pygame.math.Vector2(0, 75)
for leaf in range(randint(3, 6)): for leaf in range(randint(3, 6)):
self.animation_player.create_grass_particles( self.animation_player.create_grass_particles(
position=pos - offset, groups=[self.visible_sprites]) position=pos - offset,
groups=[self.visible_sprites])
target_sprite.kill() target_sprite.kill()
else: else:
target_sprite.get_damaged( target_sprite.get_damaged(
@ -109,13 +120,12 @@ class Player(pygame.sprite.Sprite):
self.reward_features = [ self.reward_features = [
self.stats.exp, self.stats.exp,
# nearest_dist, np.exp(-nearest_dist**2),
-nearest_enemy.stats.health, np.exp(-nearest_enemy.stats.health**2),
self.stats.health -np.exp(-self.stats.health)
] ]
self.state_features = [ self.state_features = [
# TODO: Find a way to not use magic numbers
np.exp(-self.rect.center[0]), np.exp(-self.rect.center[0]),
np.exp(-self.rect.center[1]), np.exp(-self.rect.center[1]),
self._input.movement.direction.x, self._input.movement.direction.x,
@ -127,7 +137,6 @@ class Player(pygame.sprite.Sprite):
enemy_states = [] enemy_states = []
for distance, direction, enemy in sorted_distances[:5]: for distance, direction, enemy in sorted_distances[:5]:
# TODO: Find a way to not use magic numbers
enemy_states.extend([ enemy_states.extend([
np.exp(-distance), np.exp(-distance),
direction[0], direction[0],
@ -156,12 +165,13 @@ class Player(pygame.sprite.Sprite):
n_epochs=4) n_epochs=4)
try: try:
self.agent.load_models() self.agent.load_models()
except FileNotFoundError as e: except FileNotFoundError:
print(f"{e}. Skipping loading...") print("FileNotFoundError for agent.load_model().\
Skipping loading...")
def is_dead(self): def is_dead(self):
if self.stats.health == 0: if self.stats.health <= 0:
self.stats.exp = -100 self.stats.exp = max(0, self.stats.exp - .5)
return True return True
else: else:
return False return False
@ -182,11 +192,9 @@ class Player(pygame.sprite.Sprite):
self.animation.rect, self.animation.rect,
self) self)
self.done = self.is_dead()
self.score = self.stats.exp self.score = self.stats.exp
self.agent.remember(self.state_features, action, self.agent.remember(self.state_features, action,
probs, value, self.stats.exp, self.done) probs, value, self.stats.exp, self.is_dead())
if self.n_steps % self.N == 0: if self.n_steps % self.N == 0:
self.agent.learn() self.agent.learn()
@ -194,7 +202,7 @@ class Player(pygame.sprite.Sprite):
self.get_current_state() self.get_current_state()
if self.done: if self.is_dead():
self.agent.learn() self.agent.learn()
# Refresh objects based on input # Refresh objects based on input
@ -207,5 +215,6 @@ class Player(pygame.sprite.Sprite):
self.rect = self.animation.rect self.rect = self.animation.rect
# Cooldowns and Regen # Cooldowns and Regen
self.stats.health_recovery()
self.stats.energy_recovery() self.stats.energy_recovery()
self._input.cooldowns(self._input.combat.vulnerable) self._input.cooldowns(self._input.combat.vulnerable)

17
game.py
View file

@ -13,17 +13,26 @@ class Game:
self.screen = pygame.display.set_mode( self.screen = pygame.display.set_mode(
(WIDTH, HEIGHT)) (WIDTH, HEIGHT))
pygame.display.set_caption('Pneuma') pygame.display.set_caption('Pneuma')
img = pygame.image.load('assets/graphics/icon.png')
pygame.display.set_icon(img)
self.clock = pygame.time.Clock() self.clock = pygame.time.Clock()
self.level = Level() self.level = Level()
def calc_score(self):
self.scores = [0 for _ in range(len(self.level.player_sprites))]
for player in self.level.player_sprites:
self.scores[player.player_id] = player.stats.exp
def run(self): def run(self):
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
pygame.quit() self.quit()
sys.exit()
if event.type == pygame.KEYDOWN: if event.type == pygame.KEYDOWN:
if event.key == pygame.K_m: if event.key == pygame.K_m:
self.level.toggle_menu() self.level.toggle_menu()
@ -34,3 +43,7 @@ class Game:
pygame.display.update() pygame.display.update()
self.clock.tick(FPS) self.clock.tick(FPS)
def quit(self):
pygame.quit()
sys.exit()

View file

@ -1,5 +1,6 @@
import os import os
import pygame import pygame
import numpy as np
from random import choice from random import choice
@ -46,6 +47,7 @@ class Level:
else: else:
for player in self.player_sprites: for player in self.player_sprites:
player.get_max_num_states() player.get_max_num_states()
self.dead_players = np.zeros(len(self.player_sprites))
# UI setup # UI setup
self.ui = UI() self.ui = UI()
@ -174,11 +176,13 @@ class Level:
self.visible_sprites.custom_draw(self.player) self.visible_sprites.custom_draw(self.player)
self.ui.display(self.player) self.ui.display(self.player)
debug('v0.6') debug('v0.8')
for player in self.player_sprites: for player in self.player_sprites:
if player.is_dead(): if player.is_dead():
self.done = True self.dead_players[player.player_id] = True
self.done = True if self.dead_players.all() == 1 else False
if not self.game_paused: if not self.game_paused:
# Update the game # Update the game

72
main.py
View file

@ -1,3 +1,6 @@
import numpy as np
import matplotlib.pyplot as plt
from game import Game from game import Game
from tqdm import tqdm from tqdm import tqdm
@ -6,54 +9,67 @@ environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
if __name__ == '__main__': if __name__ == '__main__':
n_episodes = 3000
n_episodes = 1000
game_len = 10000
figure_file = 'plots/score.png' figure_file = 'plots/score.png'
score_history = []
best_score = 0 best_score = 0
avg_score = 0 avg_score = 0
agent_list = []
game_len = 5000
game = Game() game = Game()
agent_list = []
exp_points_list = []
score_history = np.zeros(
shape=(len(game.level.player_sprites), n_episodes, ))
best_score = np.zeros(len(game.level.player_sprites))
avg_score = np.zeros(len(game.level.player_sprites))
for i in tqdm(range(n_episodes)): for i in tqdm(range(n_episodes)):
# TODO: Make game.level.reset_map() so we don't __init__ everything all the time (such a waste) # TODO: Make game.level.reset_map() so we don't __init__ everything all the time (such a waste)
if i != 0: if i != 0:
game.level.__init__(reset=True) game.level.__init__(reset=True)
# TODO: Make game.level.reset_map() so we don't pull out and load the agent every time (There is -definitevly- a better way) # TODO: Make game.level.reset_map() so we don't pull out and load the agent every time (There is -definitevly- a better way)
for player in game.level.player_sprites: for player in game.level.player_sprites:
for player_id, agent in agent_list: for agent in agent_list:
if player.player_id == player_id: player.agent = agent_list[player.player_id]
player.agent = agent player.stats.exp = score_history[player.player_id][i-1]
agent_list = [] agent_list = []
done = False
score = 0
for _ in tqdm(range(game_len)):
if not game.level.done:
game.run()
else:
break
for player in game.level.player_sprites:
agent_list.append((player.player_id, player.agent))
if i == n_episodes-1 and game.level.enemy_sprites != []: for j in range(game_len):
if not game.level.done:
game.run()
game.calc_score()
if (j == game_len-1 or game.level.done) and game.level.enemy_sprites != []:
for player in game.level.player_sprites: for player in game.level.player_sprites:
for enemy in game.level.enemy_sprites: for enemy in game.level.enemy_sprites:
player.stats.exp -= 5 player.stats.exp *= .95
player.update() else:
break
for player in game.level.player_sprites: for player in game.level.player_sprites:
agent_list.append(player.agent)
exp_points = player.stats.exp
score_history[player.player_id][i] = exp_points
avg_score[player.player_id] = np.mean(
score_history[player.player_id])
if avg_score[player.player_id] >= best_score[player.player_id]:
player.agent.save_models() player.agent.save_models()
best_score[player.player_id] = avg_score[player.player_id]
# TODO: Make it so that scores appear here for each player print(
# score_history.append(game.level.player.score) f"\nCumulative score for player {player.player_id}:\
# print(score) {score_history[0][i]}\
# avg_score = np.mean(score_history[-100:]) \nAverage score for player {player.player_id}:\
{avg_score[player.player_id]}\
\nBest score for player {player.player_id}:\
{best_score[player.player_id]}")
# if avg_score > best_score: plt.plot(score_history[0])
# best_score = avg_score
# game.level.player.agent.save_models() game.quit()
plt.show()

Binary file not shown.

Binary file not shown.