Used inheritance, fixed metrics bugs and fixed save location

This commit is contained in:
Vasilis Valatsos 2024-04-23 20:43:39 +02:00
parent ef78027ba7
commit 936674419b
25 changed files with 1524 additions and 278 deletions

BIN
assets/.DS_Store vendored

Binary file not shown.

BIN
assets/map/.DS_Store vendored

Binary file not shown.

View file

@ -25,6 +25,15 @@ warrior_stats = {
'speed': 5 'speed': 5
} }
base_stats = {
'role_id': 0,
'health': 150,
'energy': 70,
'attack': 10,
'magic': 5,
'speed': 5
}
max_stats = { max_stats = {
'health': 300, 'health': 300,
'energy': 150, 'energy': 150,

BIN
effects/.DS_Store vendored

Binary file not shown.

View file

@ -9,31 +9,31 @@ class MagicPlayer:
self.animation_player = animation_player self.animation_player = animation_player
def heal(self, player, strength, cost, groups): def heal(self, player, strength, cost, groups):
if player.stats.energy >= cost: if player.energy >= cost:
self.sounds['heal'].play() self.sounds['heal'].play()
player.stats.health += strength player.health += strength
player.stats.energy -= cost player.energy -= cost
if player.stats.health >= player.stats.stats['health']: if player.health >= player.stats['health']:
player.stats.health = player.stats.stats['health'] player.health = player.stats['health']
self.animation_player.generate_particles( self.animation_player.generate_particles(
'aura', 'aura',
player.animation.rect.center, player.rect.center,
groups) groups)
self.animation_player.generate_particles( self.animation_player.generate_particles(
'heal', 'heal',
player.animation.rect.center + pygame.math.Vector2(0, -50), player.rect.center + pygame.math.Vector2(0, -50),
groups) groups)
def flame(self, player, cost, groups): def flame(self, player, cost, groups):
if player.stats.energy >= cost: if player.energy >= cost:
player.stats.energy -= cost player.energy -= cost
if player._input.status.split('_')[0] == 'right': if player.status.split('_')[0] == 'right':
direction = pygame.math.Vector2(1, 0) direction = pygame.math.Vector2(1, 0)
elif player._input.status.split('_')[0] == 'left': elif player.status.split('_')[0] == 'left':
direction = pygame.math.Vector2(-1, 0) direction = pygame.math.Vector2(-1, 0)
elif player._input.status.split('_')[0] == 'up': elif player.status.split('_')[0] == 'up':
direction = pygame.math.Vector2(0, -1) direction = pygame.math.Vector2(0, -1)
else: else:
direction = pygame.math.Vector2(0, 1) direction = pygame.math.Vector2(0, 1)
@ -41,17 +41,17 @@ class MagicPlayer:
for i in range(1, 6): for i in range(1, 6):
if direction.x: if direction.x:
offset_x = direction.x * i * TILESIZE offset_x = direction.x * i * TILESIZE
x = player.animation.rect.centerx + offset_x + \ x = player.rect.centerx + offset_x + \
randint(-TILESIZE // 3, TILESIZE // 3) randint(-TILESIZE // 3, TILESIZE // 3)
y = player.animation.rect.centery + \ y = player.rect.centery + \
randint(-TILESIZE // 3, TILESIZE // 3) randint(-TILESIZE // 3, TILESIZE // 3)
self.animation_player.generate_particles( self.animation_player.generate_particles(
'flame', (x, y), groups) 'flame', (x, y), groups)
else: else:
offset_y = direction.y * i * TILESIZE offset_y = direction.y * i * TILESIZE
x = player.animation.rect.centerx + \ x = player.rect.centerx + \
randint(-TILESIZE // 3, TILESIZE // 3) randint(-TILESIZE // 3, TILESIZE // 3)
y = player.animation.rect.centery + offset_y + \ y = player.rect.centery + offset_y + \
randint(-TILESIZE // 3, TILESIZE // 3) randint(-TILESIZE // 3, TILESIZE // 3)
self.animation_player.generate_particles( self.animation_player.generate_particles(
'flame', (x, y), groups) 'flame', (x, y), groups)

View file

@ -10,26 +10,26 @@ class Weapon(pygame.sprite.Sprite):
super().__init__(groups) super().__init__(groups)
self.sprite_type = 'weapon' self.sprite_type = 'weapon'
direction = player._input.status.split('_')[0] direction = player.status.split('_')[0]
# Graphic # Graphic
self.image = pygame.image.load(import_assets(os.path.join( self.image = pygame.image.load(import_assets(os.path.join(
'graphics', 'graphics',
'weapons', 'weapons',
player._input.combat.weapon, player.weapon,
f"{direction}.png")) f"{direction}.png"))
).convert_alpha() ).convert_alpha()
# Sprite Placement # Sprite Placement
if direction == 'right': if direction == 'right':
self.rect = self.image.get_rect( self.rect = self.image.get_rect(
midleft=player.animation.rect.midright + pygame.math.Vector2(0, 16)) midleft=player.rect.midright + pygame.math.Vector2(0, 16))
elif direction == 'left': elif direction == 'left':
self.rect = self.image.get_rect( self.rect = self.image.get_rect(
midright=player.animation.rect.midleft + pygame.math.Vector2(0, 16)) midright=player.rect.midleft + pygame.math.Vector2(0, 16))
elif direction == 'down': elif direction == 'down':
self.rect = self.image.get_rect( self.rect = self.image.get_rect(
midtop=player.animation.rect.midbottom + pygame.math.Vector2(-10, 0)) midtop=player.rect.midbottom + pygame.math.Vector2(-10, 0))
else: else:
self.rect = self.image.get_rect( self.rect = self.image.get_rect(
midbottom=player.animation.rect.midtop + pygame.math.Vector2(-10, 0)) midbottom=player.rect.midtop + pygame.math.Vector2(-10, 0))

BIN
entities/.DS_Store vendored

Binary file not shown.

0
entities/__init__.py Normal file
View file

View file

@ -3,25 +3,26 @@ from random import randint, choice
from config.game.spell_config import magic_data from config.game.spell_config import magic_data
from config.game.weapon_config import weapon_data from config.game.weapon_config import weapon_data
#
from .movement import MovementHandler from .movement import MovementHandler
from .combat import CombatHandler from .combat import CombatHandler
class InputHandler: class InputHandler(MovementHandler, CombatHandler):
def __init__(self):
MovementHandler.__init__(self)
CombatHandler.__init__(self)
def __init__(self, sprite_type, animation_player, ai_controller=False):
self.status = 'down' self.status = 'down'
self.sprite_type = sprite_type
# Setup Movement # Setup Movement
self.movement = MovementHandler(self.sprite_type)
self.move_cooldown = 15 self.move_cooldown = 15
self.can_move = True self.can_move = True
self.move_time = None self.move_time = None
# Setup Combat # Setup Combat
self.combat = CombatHandler(animation_player)
self.attacking = False self.attacking = False
self.attack_cooldown = 400 self.attack_cooldown = 400
self.attack_time = None self.attack_time = None
@ -35,7 +36,7 @@ class InputHandler:
self.magic_swap_time = None self.magic_swap_time = None
# Setup Action Space # Setup Action Space
self.possible_actions = [0, 1, 2, 3, 4, 5] self.possible_actions = [0, 1, 2, 3, 4]
self.action = 10 self.action = 10
def check_input(self, def check_input(self,
@ -43,95 +44,95 @@ class InputHandler:
speed, speed,
hitbox, hitbox,
obstacle_sprites, obstacle_sprites,
rect, rect
player): ):
if not self.attacking and self.can_move: if not self.attacking and self.can_move:
self.move_time = pygame.time.get_ticks() self.move_time = pygame.time.get_ticks()
# Movement Input # Movement Input
if button == 0: # keys[pygame.K_w]: if self.action == 0: # keys[pygame.K_w]:
self.movement.direction.y = -1 self.direction.y = -1
self.status = 'up' self.status = 'up'
self.can_move = False self.can_move = False
self.action = 0 self.action = 0
elif button == 1: # keys[pygame.K_s]: elif self.action == 1: # keys[pygame.K_s]:
self.movement.direction.y = 1 self.direction.y = 1
self.status = 'down' self.status = 'down'
self.can_move = False self.can_move = False
self.action = 1 self.action = 1
else: else:
self.movement.direction.y = 0 self.direction.y = 0
if button == 2: # keys[pygame.K_a]: if self.action == 2: # keys[pygame.K_a]:
self.movement.direction.x = -1 self.direction.x = -1
self.status = 'left' self.status = 'left'
self.can_move = False self.can_move = False
self.action = 2 self.action = 2
elif button == 3: # keys[pygame.K_d]: elif self.action == 3: # keys[pygame.K_d]:
self.movement.direction.x = 1 self.direction.x = 1
self.status = 'right' self.status = 'right'
self.can_move = False self.can_move = False
self.action = 3 self.action = 3
else: else:
self.movement.direction.x = 0 self.direction.x = 0
self.movement.move(speed, hitbox, obstacle_sprites, rect) self.move(speed, hitbox, obstacle_sprites, rect)
# Combat Input # Combat Input
if button == 4 and not self.attacking: # keys[pygame.K_e] if self.action == 4 and not self.attacking: # keys[pygame.K_e]
self.attacking = True self.attacking = True
self.attack_time = pygame.time.get_ticks() self.attack_time = pygame.time.get_ticks()
self.combat.create_attack_sprite(player) self.create_attack_sprite()
self.action = 4 self.action = 4
# Magic Input # Magic Input
if button == 5: if self.action == 5:
self.attacking = True self.attacking = True
self.attack_time = pygame.time.get_ticks() self.attack_time = pygame.time.get_ticks()
self.combat.magic = list(magic_data.keys())[ self.magic = list(magic_data.keys())[
self.combat.magic_index] self.magic_index]
strength = list(magic_data.values())[ strength = list(magic_data.values())[
self.combat.magic_index]['strength'] + player.stats.magic self.magic_index]['strength'] + self.stats['magic']
cost = list(magic_data.values())[ cost = list(magic_data.values())[
self.combat.magic_index]['cost'] self.magic_index]['cost']
self.combat.create_magic_sprite( self.create_magic_sprite(
player, self.combat.magic, strength, cost) self.magic, strength, cost)
self.action = 5 self.action = 5
# Rotating Weapons # Rotating Weapons
if button == 6 and self.can_rotate_weapon: if self.action == 6 and self.can_rotate_weapon:
self.can_rotate_weapon = False self.can_rotate_weapon = False
self.weapon_rotation_time = pygame.time.get_ticks() self.weapon_rotation_time = pygame.time.get_ticks()
if self.combat.weapon_index\ if self.weapon_index\
< len(list(weapon_data.keys())) - 1: < len(list(weapon_data.keys())) - 1:
self.combat.weapon_index += 1 self.weapon_index += 1
else: else:
self.combat.weapon_index = 0 self.weapon_index = 0
self.combat.weapon = list(weapon_data.keys())[ self.weapon = list(weapon_data.keys())[
self.combat.weapon_index] self.weapon_index]
self.action = 6 self.action = 6
# Swap Spells # Swap Spells
if button == 7 and self.can_swap_magic: if self.action == 7 and self.can_swap_magic:
self.can_swap_magic = False self.can_swap_magic = False
self.magic_swap_time = pygame.time.get_ticks() self.magic_swap_time = pygame.time.get_ticks()
if self.combat.magic_index < len(list(magic_data.keys())) - 1: if self.magic_index < len(list(magic_data.keys())) - 1:
self.combat.magic_index += 1 self.magic_index += 1
else: else:
self.combat.magic_index = 0 self.magic_index = 0
self.action = 7 self.action = 7
def cooldowns(self, vulnerable): def cooldowns(self, vulnerable):
@ -141,11 +142,11 @@ class InputHandler:
if self.attacking: if self.attacking:
if current_time - self.attack_time\ if current_time - self.attack_time\
> self.attack_cooldown\ > self.attack_cooldown\
+ weapon_data[self.combat.weapon]['cooldown']: + weapon_data[self.weapon]['cooldown']:
self.attacking = False self.attacking = False
if self.combat.current_attack: if self.current_attack:
self.combat.delete_attack_sprite() self.delete_attack_sprite()
if not self.can_rotate_weapon: if not self.can_rotate_weapon:
if current_time - self.weapon_rotation_time\ if current_time - self.weapon_rotation_time\
@ -160,10 +161,10 @@ class InputHandler:
self.can_swap_magic = True self.can_swap_magic = True
if not vulnerable: if not vulnerable:
if current_time - self.combat.hurt_time\ if current_time - self.hurt_time\
>= self.combat.invulnerability_duration: >= self.invulnerability_duration:
self.combat.vulnerable = True self.vulnerable = True
if not self.can_move: if not self.can_move:
if current_time - self.move_time\ if current_time - self.move_time\

View file

@ -1,25 +1,27 @@
import os import os
import pygame import pygame
from math import sin
from utils.resource_loader import import_folder, import_assets from math import sin
from config.system.window import HITBOX_OFFSET from config.system.window import HITBOX_OFFSET
from effects.particle_effects import AnimationPlayer
class AnimationHandler: from .stats import StatsHandler
from utils.resource_loader import import_folder, import_assets
class AnimationHandler(StatsHandler):
def __init__(self):
def __init__(self, sprite_type, name=None):
self.frame_index = 0 self.frame_index = 0
self.animation_speed = 0.15 self.animation_speed = 0.15
self.sprite_type = sprite_type
self.name = name
def import_assets(self, position): def import_assets(self, position):
# Import Graphic Assets # Import graphic assets
if self.sprite_type == 'player': if self.sprite_type == 'player':
self.image = pygame.image.load( self.image = pygame.image.load(
import_assets(os.path.join('graphics', import_assets(os.path.join('graphics',
@ -83,8 +85,8 @@ class AnimationHandler:
else: else:
self.image.set_alpha(255) self.image.set_alpha(255)
def trigger_death_particles(self, animation_player, position, particle_type, groups): def trigger_death_particles(self, position, particle_type, groups):
animation_player.generate_particles( AnimationPlayer().generate_particles(
particle_type, position, groups) particle_type, position, groups)
def wave_value(self): def wave_value(self):

View file

@ -1,18 +1,19 @@
from effects.weapon_effects import Weapon
from effects.magic_effects import MagicPlayer
from config.game.weapon_config import weapon_data from config.game.weapon_config import weapon_data
from config.game.spell_config import magic_data from config.game.spell_config import magic_data
from effects.magic_effects import MagicPlayer
from effects.particle_effects import AnimationPlayer
from effects.weapon_effects import Weapon
class CombatHandler: class CombatHandler:
def __init__(self, animation_player): def __init__(self):
self.animation_player = animation_player
# Setup Combat # Setup Combat
self.magic_player = MagicPlayer(animation_player) self.magic_player = MagicPlayer(AnimationPlayer())
self.current_attack = None self.current_attack = None
# Spell and Weapon Rotation # Spell and Weapon Rotation
@ -27,20 +28,20 @@ class CombatHandler:
self.hurt_time = None self.hurt_time = None
self.invulnerability_duration = 300 self.invulnerability_duration = 300
def create_attack_sprite(self, player): def create_attack_sprite(self):
self.current_attack = Weapon( self.current_attack = Weapon(
player, [player.visible_sprites, player.attack_sprites]) self, [self.visible_sprites, self.attack_sprites])
def delete_attack_sprite(self): def delete_attack_sprite(self):
if self.current_attack: if self.current_attack:
self.current_attack.kill() self.current_attack.kill()
self.current_attack = None self.current_attack = None
def create_magic_sprite(self, player, style, strength, cost): def create_magic_sprite(self, style, strength, cost):
if style == 'heal': if style == 'heal':
self.magic_player.heal(player, strength, cost, [ self.magic_player.heal(self, strength, cost, [
player.visible_sprites]) self.visible_sprites])
if style == 'flame': if style == 'flame':
self.magic_player.flame( self.magic_player.flame(
player, cost, [player.visible_sprites, player.attack_sprites]) self, cost, [self.visible_sprites, self.attack_sprites])

View file

@ -3,8 +3,7 @@ import pygame
class MovementHandler: class MovementHandler:
def __init__(self, sprite_type): def __init__(self):
self.direction = pygame.math.Vector2() self.direction = pygame.math.Vector2()
def move(self, speed, hitbox, obstacle_sprites, rect): def move(self, speed, hitbox, obstacle_sprites, rect):

View file

@ -1,19 +1,19 @@
from config.game.player_config import warrior_stats, mage_stats, tank_stats from config.game.player_config import warrior_stats, mage_stats, tank_stats
from config.game.monster_config import monster_data from config.game.monster_config import monster_data
class StatsHandler: class StatsHandler:
def __init__(self, sprite_type, role=None, monster_name=None): def get_stats(self, sprite_type, role=None, monster_name=None):
if sprite_type == 'player': if sprite_type == 'player':
if role == 'warrior': if role == 'warrior':
self.stats = warrior_stats self.stats = warrior_stats
elif role == 'tank': elif role == 'tank':
self.stats = tank_stats self.stats = tank_stats
elif role == 'mage': elif role == 'mage':
self.stats = mage_stats self.stats = mage_stats
else:
self.stats = base_stats
self.role_id = self.stats['role_id'] self.role_id = self.stats['role_id']
self.health = self.stats['health'] self.health = self.stats['health']
@ -24,7 +24,6 @@ class StatsHandler:
self.exp = 0 self.exp = 0
if sprite_type == 'enemy': if sprite_type == 'enemy':
self.monster_info = monster_data[monster_name] self.monster_info = monster_data[monster_name]
self.monster_id = self.monster_info['id'] self.monster_id = self.monster_info['id']
self.health = self.monster_info['health'] self.health = self.monster_info['health']

View file

@ -1,36 +1,34 @@
import pygame import pygame
from .components.animation import AnimationHandler from .entity import Entity
from .components.stats import StatsHandler
from .components._input import InputHandler
from effects.particle_effects import AnimationPlayer from effects.particle_effects import AnimationPlayer
class Enemy(pygame.sprite.Sprite): class Enemy(Entity):
def __init__(self, name, position, groups, visible_sprites, obstacle_sprites): def __init__(self,
super().__init__(groups) name,
position,
groups,
visible_sprites,
obstacle_sprites
):
self.sprite_type = "enemy" super().__init__(groups=groups,
visible_sprites=visible_sprites,
obstacle_sprites=obstacle_sprites,
attack_sprites=None,
attackable_sprites=None)
# Setup stats
self.sprite_type = 'enemy'
self.name = name self.name = name
self.get_stats(self.sprite_type, monster_name=self.name)
self.visible_sprites = visible_sprites # Graphics Setup
# Setup Graphics
self.animation_player = AnimationPlayer() self.animation_player = AnimationPlayer()
self.animation = AnimationHandler(self.sprite_type, self.name) self.import_assets(position)
self.animation.import_assets(position)
self.image = self.animation.image
self.rect = self.animation.rect
# Setup Inputs
self._input = InputHandler(
self.sprite_type, self.animation_player)
# Setup Stats
self.stats = StatsHandler(self.sprite_type, monster_name=self.name)
self.obstacle_sprites = obstacle_sprites
self.distance_direction_from_player = None self.distance_direction_from_player = None
@ -38,49 +36,48 @@ class Enemy(pygame.sprite.Sprite):
player_distance = sorted( player_distance = sorted(
self.distance_direction_from_player, key=lambda x: x[0])[0] self.distance_direction_from_player, key=lambda x: x[0])[0]
if player_distance[0] < self.stats.notice_radius and player_distance[0] >= self.stats.attack_radius: if player_distance[0] < self.notice_radius and player_distance[0] >= self.attack_radius:
self._input.movement.direction = player_distance[1] self.direction = player_distance[1]
self.animation.status = "move" self.status = "move"
self._input.movement.move( self.move(
self.stats.speed, self.animation.hitbox, self.obstacle_sprites, self.animation.rect) self.speed, self.hitbox, self.obstacle_sprites, self.rect)
elif player_distance[0] <= self.stats.attack_radius: elif player_distance[0] <= self.attack_radius:
self.animation.status = 'attack' self.status = 'attack'
else: else:
self.animation.status = 'idle' self.status = 'idle'
def add_exp(self, player): def add_exp(self, player):
player.stats.exp += self.stats.exp player.exp += self.exp
def check_death(self, player): def check_death(self, player):
if self.stats.health <= 0: if self.health <= 0:
self.add_exp(player) self.add_exp(player)
self.animation.trigger_death_particles( self.trigger_death_particles(
self.animation_player, self.rect.center, self.name, self.visible_sprites) self.rect.center, self.name, self.visible_sprites)
self.kill() self.kill()
def get_damaged(self, player, attack_type): def get_damaged(self, player, attack_type):
if self._input.combat.vulnerable: if self.vulnerable:
for _, direction, attacking_player in self.distance_direction_from_player: for _, direction, attacking_player in self.distance_direction_from_player:
if attacking_player == player: if attacking_player == player:
self._input.movement.direction = -direction self.direction = -direction
self._input.movement.move( self.move(
self.stats.speed * self.stats.knockback, self.animation.hitbox, self.obstacle_sprites, self.animation.rect) self.speed * self.knockback, self.hitbox, self.obstacle_sprites, self.rect)
break break
if attack_type == 'weapon': if attack_type == 'weapon':
self.stats.health -= player.get_full_weapon_damage() self.health -= player.get_full_weapon_damage()
else: else:
self.stats.health -= player.get_full_magic_damage() self.health -= player.get_full_magic_damage()
self.check_death(player) self.check_death(player)
self._input.combat.hurt_time = pygame.time.get_ticks() self.hurt_time = pygame.time.get_ticks()
self._input.combat.vulnerable = False self.vulnerable = False
def update(self): def update(self):
self.get_action() self.get_action()
self.animation.animate(self.animation.status, self.animate(self.status, self.vulnerable)
self._input.combat.vulnerable) self.image = self.image
self.image = self.animation.image self.rect = self.rect
self.rect = self.animation.rect
self._input.cooldowns(self._input.combat.vulnerable) self.cooldowns(self.vulnerable)

33
entities/entity.py Normal file
View file

@ -0,0 +1,33 @@
import pygame
import numpy as np
from random import randint
from .components._input import InputHandler
from .components.animation import AnimationHandler
from effects.particle_effects import AnimationPlayer
class Entity(pygame.sprite.Sprite, AnimationHandler, InputHandler):
def __init__(
self,
groups,
obstacle_sprites,
visible_sprites,
attack_sprites = None,
attackable_sprites = None
):
super().__init__(groups)
AnimationHandler.__init__(self)
InputHandler.__init__(self)
# Sprite Setup
self.obstacle_sprites = obstacle_sprites
self.visible_sprites = visible_sprites
self.attack_sprites = attack_sprites
self.attackable_sprites = attackable_sprites

View file

@ -5,16 +5,14 @@ from random import randint
from config.game.weapon_config import weapon_data from config.game.weapon_config import weapon_data
from config.game.spell_config import magic_data from config.game.spell_config import magic_data
from .components.stats import StatsHandler from .entity import Entity
from .components._input import InputHandler
from .components.animation import AnimationHandler
from effects.particle_effects import AnimationPlayer from effects.particle_effects import AnimationPlayer
from ml.ppo.agent import Agent from ml.ppo.agent import Agent
class Player(pygame.sprite.Sprite): class Player(Entity):
def __init__(self, def __init__(self,
player_id, player_id,
role, role,
@ -26,32 +24,26 @@ class Player(pygame.sprite.Sprite):
attack_sprites, attack_sprites,
attackable_sprites attackable_sprites
): ):
super().__init__(groups)
super().__init__(groups=groups,
obstacle_sprites=obstacle_sprites,
visible_sprites=visible_sprites,
attack_sprites=attack_sprites, attackable_sprites=attackable_sprites)
# Setup stats
self.sprite_type = 'player'
self.get_stats(self.sprite_type, role=role)
# Graphics Setup
self.animation_player = AnimationPlayer()
self.import_assets(position)
# Set misc
self.initial_position = position self.initial_position = position
self.map_edge = map_edge self.map_edge = map_edge
self.player_id = player_id self.player_id = player_id
self.distance_direction_from_enemy = None self.distance_direction_from_enemy = None
# Sprite Setup
self.sprite_type = "player"
self.obstacle_sprites = obstacle_sprites
self.visible_sprites = visible_sprites
self.attack_sprites = attack_sprites
self.attackable_sprites = attackable_sprites
# Graphics Setup
self.animation_player = AnimationPlayer()
self.animation = AnimationHandler(self.sprite_type)
self.animation.import_assets(position)
# Input Setup
self._input = InputHandler(
self.sprite_type, self.animation_player)
# Setup Stats
self.role = role
self.stats = StatsHandler(self.sprite_type, self.role)
def setup_agent(self, def setup_agent(self,
gamma, gamma,
alpha, alpha,
@ -69,7 +61,7 @@ class Player(pygame.sprite.Sprite):
self.agent = Agent( self.agent = Agent(
input_dims=self.num_features, input_dims=self.num_features,
n_actions=len(self._input.possible_actions), n_actions=len(self.possible_actions),
gamma=gamma, gamma=gamma,
alpha=alpha, alpha=alpha,
policy_clip=policy_clip, policy_clip=policy_clip,
@ -97,24 +89,24 @@ class Player(pygame.sprite.Sprite):
\nSkipping loading ...\n") \nSkipping loading ...\n")
def get_status(self): def get_status(self):
if self._input.movement.direction.x == 0\ if self.direction.x == 0\
and self._input.movement.direction.y == 0: and self.direction.y == 0:
if 'idle' not in self._input.status and 'attack' not in self._input.status: if 'idle' not in self.status and 'attack' not in self.status:
self._input.status += '_idle' self.status += '_idle'
if self._input.attacking: if self.attacking:
self._input.movement.direction.x = 0 self.direction.x = 0
self._input.movement.direction.y = 0 self.direction.y = 0
if 'attack' not in self._input.status: if 'attack' not in self.status:
if 'idle' in self._input.status: if 'idle' in self.status:
self._input.status = self._input.status.replace( self.status = self.status.replace(
'idle', 'attack') 'idle', 'attack')
else: else:
self._input.status += '_attack' self.status += '_attack'
else: else:
if 'attack' in self._input.status: if 'attack' in self.status:
self._input.status = self._input.status.replace('_attack', '') self.status = self.status.replace('_attack', '')
def attack_logic(self): def attack_logic(self):
if self.attack_sprites: if self.attack_sprites:
@ -137,13 +129,13 @@ class Player(pygame.sprite.Sprite):
self, attack_sprite.sprite_type) self, attack_sprite.sprite_type)
def get_full_weapon_damage(self): def get_full_weapon_damage(self):
base_damage = self.stats.attack base_damage = self.attack
weapon_damage = weapon_data[self._input.combat.weapon]['damage'] weapon_damage = weapon_data[self.weapon]['damage']
return (base_damage + weapon_damage) return (base_damage + weapon_damage)
def get_full_magic_damage(self): def get_full_magic_damage(self):
base_damage = self.stats.magic base_damage = self.stats['magic']
spell_damage = magic_data[self._input.combat.magic]['strength'] spell_damage = magic_data[self.magic]['strength']
return (base_damage + spell_damage) return (base_damage + spell_damage)
def get_current_state(self): def get_current_state(self):
@ -156,30 +148,24 @@ class Player(pygame.sprite.Sprite):
nearest_dist, nearest_en_dir, nearest_enemy = sorted_distances[0] nearest_dist, nearest_en_dir, nearest_enemy = sorted_distances[0]
self.action_features = [self._input.action] self.action_features = [self.action]
if hasattr(self, 'state_features'): if hasattr(self, 'state_features'):
self.old_state_features = self.state_features self.old_state_features = self.state_features
self.reward = self.stats.exp\ self.reward = self.exp\
+ self.stats.health/self.stats.stats['health'] + self.health/self.stats['health']
# - nearest_dist/np.sqrt(np.sum(self.map_edge)) # - nearest_dist/np.sqrt(np.sum(self.map_edge))
self.state_features = [ self.state_features = [
self.animation.rect.center[0]/self.map_edge[0], self.rect.center[0]/self.map_edge[0],
self.animation.rect.center[1]/self.map_edge[1], self.rect.center[1]/self.map_edge[1],
self._input.movement.direction.x, self.direction.x,
self._input.movement.direction.y, self.direction.y,
self.stats.health/self.stats.stats['health'], self.health/self.stats['health'],
self.stats.energy/self.stats.stats['energy'], self.energy/self.stats['energy'],
] ]
# self.state_features.extend([
# nearest_dist/np.sqrt(np.sum(self.map_edge)),
# nearest_en_dir[0],
# nearest_en_dir[1],
# nearest_enemy.stats.exp
# ])
for distance, direction, enemy in sorted_distances[:5]: for distance, direction, enemy in sorted_distances[:5]:
self.state_features.extend([ self.state_features.extend([
@ -189,10 +175,10 @@ class Player(pygame.sprite.Sprite):
direction[1], direction[1],
enemy.stats.health / enemy.health /
enemy.stats.monster_info['health'], enemy.monster_info['health'],
enemy.stats.exp, enemy.exp,
]) ])
if hasattr(self, 'num_features'): if hasattr(self, 'num_features'):
@ -202,9 +188,9 @@ class Player(pygame.sprite.Sprite):
self.state_features = np.array(self.state_features) self.state_features = np.array(self.state_features)
def is_dead(self): def is_dead(self):
if self.stats.health <= 0: if self.health <= 0:
self.stats.health = 0 self.health = 0
self.animation.import_assets((3264, 448)) self.import_assets((3264, 448))
return True return True
else: else:
return False return False
@ -215,18 +201,18 @@ class Player(pygame.sprite.Sprite):
self.get_current_state() self.get_current_state()
# Choose action based on current state # Choose action based on current state
action, probs, value\ self.action, probs, value\
= self.agent.choose_action(self.state_features) = self.agent.choose_action(self.state_features)
# Apply chosen action # Apply chosen action
self._input.check_input(action, self.check_input(self.action,
self.stats.speed, self.speed,
self.animation.hitbox, self.hitbox,
self.obstacle_sprites, self.obstacle_sprites,
self.animation.rect, self.rect
self) )
self.agent.remember(self.state_features, action, self.agent.remember(self.state_features, self.action,
probs, value, self.reward, self.is_dead()) probs, value, self.reward, self.is_dead())
self.get_current_state() self.get_current_state()
@ -236,11 +222,11 @@ class Player(pygame.sprite.Sprite):
self.agent_update() self.agent_update()
# Cooldowns and Regen # Cooldowns and Regen
self.stats.health_recovery() self.health_recovery()
self.stats.energy_recovery() self.energy_recovery()
# Refresh player based on input and animate # Refresh player based on input and animate
self.get_status() self.get_status()
self.animation.animate( self.animate(
self._input.status, self._input.combat.vulnerable) self.status, self.vulnerable)
self._input.cooldowns(self._input.combat.vulnerable) self.cooldowns(self.vulnerable)

BIN
figures/.DS_Store vendored

Binary file not shown.

View file

@ -50,8 +50,6 @@ class Level:
self.possible_player_locations = [] self.possible_player_locations = []
player_id = 0
self.layouts = { self.layouts = {
'boundary': import_csv_layout(os.path.join('map', 'boundary': import_csv_layout(os.path.join('map',
'FloorBlocks.csv')), 'FloorBlocks.csv')),
@ -195,16 +193,16 @@ class Level:
for player in self.player_sprites: for player in self.player_sprites:
player.animation.import_assets( player.import_assets(
choice(self.possible_player_locations)) choice(self.possible_player_locations))
player.stats.health\ player.health\
= player.stats.stats['health'] = player.stats['health']
player.stats.energy\ player.energy\
= player.stats.stats['energy'] = player.stats['energy']
player.stats.exp = 0 player.exp = 0
self.get_entities() self.get_entities()
self.get_distance_direction() self.get_distance_direction()
@ -235,12 +233,12 @@ class Level:
for player in self.player_sprites: for player in self.player_sprites:
if not player.is_dead(): if not player.is_dead():
player_vector = pygame.math.Vector2( player_vector = pygame.math.Vector2(
player.animation.rect.center player.rect.center
) )
for enemy in self.enemy_sprites: for enemy in self.enemy_sprites:
enemy_vector = pygame.math.Vector2( enemy_vector = pygame.math.Vector2(
enemy.animation.rect.center enemy.rect.center
) )
distance\ distance\
= (player_vector - enemy_vector).magnitude() = (player_vector - enemy_vector).magnitude()
@ -261,12 +259,12 @@ class Level:
for enemy in self.enemy_sprites: for enemy in self.enemy_sprites:
for distance, _, player in enemy.distance_direction_from_player: for distance, _, player in enemy.distance_direction_from_player:
if (distance < enemy.stats.attack_radius if (distance < enemy.attack_radius
and player._input.combat.vulnerable): and player.vulnerable):
player.stats.health -= enemy.stats.attack player.health -= enemy.attack
player._input.combat.vulnerable = False player.vulnerable = False
player._input.combat.hurt_time = pygame.time.get_ticks() player.hurt_time = pygame.time.get_ticks()
def toggle_pause(self): def toggle_pause(self):
self.paused = not self.paused self.paused = not self.paused
@ -279,7 +277,7 @@ class Level:
if not self.paused: if not self.paused:
# Update the game # Update the game
for player in self.player_sprites: for player in self.player_sprites:
if player.stats.health > 0: if player.health > 0:
player.attack_logic() player.attack_logic()
self.get_entities() self.get_entities()

View file

@ -180,8 +180,9 @@ def main():
# Save models # Save models
player.agent.save_models( player.agent.save_models(
f"A{player.player_id}", os.path.join(chkpt_path, f"A{player.player_id}"),
f"C{player.player_id}") os.path.join(chkpt_path, f"C{player.player_id}")
)
print(f"Models saved to {chkpt_path}") print(f"Models saved to {chkpt_path}")

1190
poetry.lock generated Normal file

File diff suppressed because it is too large Load diff

45
pyproject.toml Normal file
View file

@ -0,0 +1,45 @@
[tool.poetry]
name = "thesis"
version = "0.8.1"
description = "A training environment for MARL"
authors = ["Vasilis Valatsos <vasilvalat@gmail.com>"]
license = "MPL-2.0"
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.11"
contourpy = "^1.2.0"
cycler = "^0.12.1"
docstring-to-markdown = "^0.13"
filelock = "^3.13.1"
fonttools = "^4.45.1"
fsspec = "^2023.10.0"
jedi = "^0.19.1"
jinja2 = "^3.1.2"
kiwisolver = "^1.4.5"
markupsafe = "^2.1.3"
matplotlib = "^3.8.2"
mpmath = "^1.3.0"
networkx = "^3.2.1"
numpy = "^1.26.2"
packaging = "^23.2"
parso = "^0.8.3"
pillow = "^10.1.0"
pluggy = "^1.3.0"
pygame = "^2.5.2"
pyparsing = "^3.1.1"
python-dateutil = "^2.8.2"
python-lsp-jsonrpc = "^1.1.2"
python-lsp-server = "^1.9.0"
six = "^1.16.0"
sympy = "^1.12"
torch = "^2.1.0"
tqdm = "^4.66.1"
typing-extensions = "^4.8.0"
ujson = "^5.8.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View file

@ -1,29 +0,0 @@
contourpy==1.2.0
cycler==0.12.1
docstring-to-markdown==0.13
filelock==3.13.1
fonttools==4.45.1
fsspec==2023.10.0
jedi==0.19.1
Jinja2==3.1.2
kiwisolver==1.4.5
MarkupSafe==2.1.3
matplotlib==3.8.2
mpmath==1.3.0
networkx==3.2.1
numpy==1.26.2
packaging==23.2
parso==0.8.3
Pillow==10.1.0
pluggy==1.3.0
pygame==2.5.2
pyparsing==3.1.1
python-dateutil==2.8.2
python-lsp-jsonrpc==1.1.2
python-lsp-server==1.9.0
six==1.16.0
sympy==1.12
torch==2.1.0
tqdm==4.66.1
typing_extensions==4.8.0
ujson==5.8.0

11
shell.nix Normal file
View file

@ -0,0 +1,11 @@
with import <nixpkgs> {};
mkShell {
NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [
stdenv.cc.cc
];
NIX_LD = lib.fileContents "${stdenv.cc}/nix-support/dynamic-linker";
shellHook = ''
export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH
'';
}

View file

@ -13,7 +13,8 @@ def plot_learning_curve(scores, num_players, figure_path, n_episodes):
for score in scores: for score in scores:
running_avg = np.zeros(len(score)) running_avg = np.zeros(len(score))
for i in range(len(score)): for i in range(len(score)):
running_avg[i] = np.mean(score[max(0, i-int(n_episodes/10)):(i+1)]) if score[i] != 0:
running_avg[i] = np.mean(score[max(0, i-int(n_episodes/10)):i+1])
plt.plot(running_avg) plt.plot(running_avg)
plt.savefig(os.path.join(figure_path, "avg_score.png")) plt.savefig(os.path.join(figure_path, "avg_score.png"))
plt.close() plt.close()
@ -50,7 +51,8 @@ def plot_loss(nn_type, losses, num_players, figure_path, n_episodes):
for loss in losses: for loss in losses:
running_avg = np.zeros(len(loss)) running_avg = np.zeros(len(loss))
for i in range(len(loss)): for i in range(len(loss)):
running_avg[i] = np.mean(loss[max(0, i-int(n_episodes/10)):(i+1)]) if loss[i] != 0:
running_avg[i] = np.mean(loss[max(0, i-int(n_episodes/10)):(i+1)])
plt.plot(running_avg) plt.plot(running_avg)
plt.savefig(os.path.join(figure_path, f"{nn_type}_loss.png")) plt.savefig(os.path.join(figure_path, f"{nn_type}_loss.png"))
plt.close() plt.close()
@ -66,7 +68,8 @@ def plot_parameter(name, parameter, num_players, figure_path, n_episodes):
for param in parameter: for param in parameter:
running_avg = np.zeros(len(param)) running_avg = np.zeros(len(param))
for i in range(len(param)): for i in range(len(param)):
running_avg[i] = np.mean(param[max(0, i-int(n_episodes/10)):(i+1)]) if param[i] != 0:
running_avg[i] = np.mean(param[max(0, i-int(n_episodes/10)):(i+1)])
plt.plot(running_avg) plt.plot(running_avg)
plt.savefig(os.path.join(figure_path, f"{name}.png")) plt.savefig(os.path.join(figure_path, f"{name}.png"))
plt.close() plt.close()