From ba0cc4684111d6604d420031ae3efa405a878e9c Mon Sep 17 00:00:00 2001 From: Vasilis Valatsos Date: Mon, 13 Nov 2023 13:34:22 +0100 Subject: [PATCH] Ready to build AI --- assets/map/Entities.csv | 2 +- game/configs/game/monster_config.py | 8 +-- game/configs/game/player_config.py | 21 +++++- game/configs/system/window_config.py | 2 +- game/entities/components/_input.py | 22 +++--- game/entities/components/stats.py | 19 ++--- game/entities/enemy.py | 9 +-- game/entities/player.py | 5 +- game/level/level.py | 29 +++++--- game/main.py | 101 +++++++++++++++++++++++++-- 10 files changed, 170 insertions(+), 48 deletions(-) diff --git a/assets/map/Entities.csv b/assets/map/Entities.csv index adb00c2..a80baf5 100644 --- a/assets/map/Entities.csv +++ b/assets/map/Entities.csv @@ -19,7 +19,7 @@ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 --1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,394,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 -1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 diff --git a/game/configs/game/monster_config.py b/game/configs/game/monster_config.py index a8863fe..023fae1 100644 --- a/game/configs/game/monster_config.py +++ b/game/configs/game/monster_config.py @@ -6,7 +6,7 @@ asset_path = os.path.join( script_dir, '../../..', 'assets') monster_data = { - 'squid': {'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}, - 'raccoon': {'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}, - 'spirit': {'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}, - 'bamboo': {'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}} + '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}, + '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}, + '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}, + '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}} diff --git a/game/configs/game/player_config.py b/game/configs/game/player_config.py index b98eb74..e4d3c24 100644 --- a/game/configs/game/player_config.py +++ b/game/configs/game/player_config.py @@ -1,4 +1,23 @@ -stats = { +tank_stats = { + 'role_id': 1, + 'health': 150, + 'energy': 40, + 'attack': 7, + 'magic': 3, + 'speed': 3 +} + +mage_stats = { + 'role_id': 2, + 'health': 70, + 'energy': 80, + 'attack': 3, + 'magic': 6, + 'speed': 5 +} + +warrior_stats = { + 'role_id': 3, 'health': 100, 'energy': 60, 'attack': 10, diff --git a/game/configs/system/window_config.py b/game/configs/system/window_config.py index 5854f0a..da25df9 100644 --- a/game/configs/system/window_config.py +++ b/game/configs/system/window_config.py @@ -1,7 +1,7 @@ # game setup WIDTH = 1280 HEIGHT = 720 -FPS = 60 +FPS = 500 TILESIZE = 64 HITBOX_OFFSET = { 'player': (-6, -26), diff --git a/game/entities/components/_input.py b/game/entities/components/_input.py index 0a89ad6..fceb3f1 100644 --- a/game/entities/components/_input.py +++ b/game/entities/components/_input.py @@ -1,5 +1,5 @@ import pygame -from random import randint +from random import randint, choice from configs.game.spell_config import magic_data from configs.game.weapon_config import weapon_data @@ -11,7 +11,7 @@ from .combat import CombatHandler class InputHandler: # , status): - def __init__(self, sprite_type, animation_player): + def __init__(self, sprite_type, animation_player, ai_controller=False): self.status = 'down' self.sprite_type = sprite_type @@ -41,27 +41,27 @@ class InputHandler: keys = pygame.key.get_pressed() - # button = randint(0, 5) + button = randint(0, 4) self.move_time = pygame.time.get_ticks() # Movement Input - if keys[pygame.K_w]: # button == 0: # keys[pygame.K_w]: + if button == 0: # keys[pygame.K_w]: self.movement.direction.y = -1 self.status = 'up' self.can_move = False - elif keys[pygame.K_s]: # button == 1: # keys[pygame.K_s]: + elif button == 1: # keys[pygame.K_s]: self.movement.direction.y = 1 self.status = 'down' self.can_move = False else: self.movement.direction.y = 0 - if keys[pygame.K_a]: # button == 2: # keys[pygame.K_a]: + if button == 2: # keys[pygame.K_a]: self.movement.direction.x = -1 self.status = 'left' self.can_move = False - elif keys[pygame.K_d]: # keys[pygame.K_d]: + elif button == 3: # keys[pygame.K_d]: self.movement.direction.x = 1 self.status = 'right' self.can_move = False @@ -72,14 +72,14 @@ class InputHandler: if self.sprite_type == 'player': # Combat Input - if keys[pygame.K_e] and not self.attacking: # keys[pygame.K_e] + if button == 4 and not self.attacking: # keys[pygame.K_e] self.attacking = True self.attack_time = pygame.time.get_ticks() self.combat.create_attack_sprite(player) self.combat.weapon_attack_sound.play() # Magic Input - if keys[pygame.K_q]: # keys[pygame.K_q]: + if button == 5: # keys[pygame.K_q]: self.attacking = True self.attack_time = pygame.time.get_ticks() @@ -96,7 +96,7 @@ class InputHandler: # Rotating Weapons # keys[pygame.K_LSHIFT] - if keys[pygame.K_LSHIFT] and self.can_rotate_weapon: + if button == 6 and self.can_rotate_weapon: self.can_rotate_weapon = False self.weapon_rotation_time = pygame.time.get_ticks() if self.combat.weapon_index < len(list(weapon_data.keys())) - 1: @@ -109,7 +109,7 @@ class InputHandler: # Swap Spells # keys[pygame.K_LCTRL] : - if keys[pygame.K_LCTRL] and self.can_swap_magic: + if button == 7 and self.can_swap_magic: self.can_swap_magic = False self.magic_swap_time = pygame.time.get_ticks() if self.combat.magic_index < len(list(magic_data.keys())) - 1: diff --git a/game/entities/components/stats.py b/game/entities/components/stats.py index edd0b5e..49bfa36 100644 --- a/game/entities/components/stats.py +++ b/game/entities/components/stats.py @@ -1,29 +1,32 @@ -from configs.game.player_config import stats, max_stats, upgrade_costs +from configs.game.player_config import warrior_stats, mage_stats, tank_stats from configs.game.monster_config import monster_data class StatsHandler: - def __init__(self, sprite_type, monster_name=None): + def __init__(self, sprite_type, role=None, monster_name=None): if sprite_type == 'player': - self.stats = stats - - self.max_stats = max_stats - - self.upgrade_costs = upgrade_costs + if role == 'warrior': + self.stats = warrior_stats + elif role == 'tank': + self.stats = tank_stats + elif role == 'mage': + self.stats = mage_stats + self.role_id = self.stats['role_id'] self.health = self.stats['health'] self.energy = self.stats['energy'] self.attack = self.stats['attack'] self.magic = self.stats['magic'] self.speed = self.stats['speed'] - self.exp = 10000 + self.exp = 0 if sprite_type == 'enemy': self.monster_info = monster_data[monster_name] + self.monster_id = self.monster_info['id'] self.health = self.monster_info['health'] self.attack = self.monster_info['attack'] self.attack_type = self.monster_info['attack_type'] diff --git a/game/entities/enemy.py b/game/entities/enemy.py index 8aa0d65..fee03ae 100644 --- a/game/entities/enemy.py +++ b/game/entities/enemy.py @@ -22,7 +22,6 @@ class Enemy(pygame.sprite.Sprite): self.animation_player = AnimationPlayer() self.animation = AnimationHandler(self.sprite_type, self.name) self.animation.import_assets(position) - self.status = self.animation.status self.image = self.animation.image self.rect = self.animation.rect @@ -31,13 +30,11 @@ class Enemy(pygame.sprite.Sprite): self.sprite_type, self.animation_player) # Setup Stats - self.stats = StatsHandler(self.sprite_type, self.name) + self.stats = StatsHandler(self.sprite_type, monster_name=self.name) self.obstacle_sprites = obstacle_sprites self.distance_direction_from_player = None - self.kills = 0 - def get_action(self): player_distance = sorted( self.distance_direction_from_player, key=lambda x: x[0])[0] @@ -83,9 +80,9 @@ class Enemy(pygame.sprite.Sprite): def update(self): self.get_action() - self.status = self.animation.status - self.animation.animate(self.status, self._input.combat.vulnerable) + self.animation.animate(self.animation.status, + self._input.combat.vulnerable) self.image = self.animation.image self.rect = self.animation.rect diff --git a/game/entities/player.py b/game/entities/player.py index 585d19b..bdb666e 100644 --- a/game/entities/player.py +++ b/game/entities/player.py @@ -13,7 +13,7 @@ from effects.particle_effects import AnimationPlayer class Player(pygame.sprite.Sprite): - def __init__(self, position, groups, obstacle_sprites, visible_sprites, attack_sprites, attackable_sprites): + def __init__(self, position, groups, obstacle_sprites, visible_sprites, attack_sprites, attackable_sprites, role): super().__init__(groups) # Setup Sprites @@ -35,7 +35,8 @@ class Player(pygame.sprite.Sprite): self.sprite_type, self.animation_player) # , self.status) # Setup Stats - self.stats = StatsHandler(self.sprite_type) + self.role = role + self.stats = StatsHandler(self.sprite_type, self.role) self.distance_direction_from_enemy = None diff --git a/game/level/level.py b/game/level/level.py index cb1ccd6..471eaec 100644 --- a/game/level/level.py +++ b/game/level/level.py @@ -40,7 +40,7 @@ class Level: # UI setup self.ui = UI() - self.upgrade = Upgrade(self.player) + # self.upgrade = Upgrade(self.player) self.get_players_enemies() self.get_distance_direction() @@ -86,10 +86,20 @@ class Level: self.observer = Observer( (x, y), [self.visible_sprites]) - elif col == '394': + elif col == '400': # Player Generation - self.player = Player( - (x, y), [self.visible_sprites], self.obstacle_sprites, self.visible_sprites, self.attack_sprites, self.attackable_sprites) + Player( + (x, y), [self.visible_sprites], self.obstacle_sprites, self.visible_sprites, self.attack_sprites, self.attackable_sprites, 'tank') + + elif col == '401': + # Player Generation + Player( + (x, y), [self.visible_sprites], self.obstacle_sprites, self.visible_sprites, self.attack_sprites, self.attackable_sprites, 'warrior') + + elif col == '402': + # Player Generation + Player( + (x, y), [self.visible_sprites], self.obstacle_sprites, self.visible_sprites, self.attack_sprites, self.attackable_sprites, 'mage') else: # Monster Generation @@ -154,7 +164,9 @@ class Level: elif who == 'player': self.visible_sprites.custom_draw(self.player) self.ui.display(self.player) - debug('v0.5') + + debug('v0.6') + if not self.game_paused: # Update the game for player in self.player_sprites: @@ -168,7 +180,8 @@ class Level: # self.visible_sprites.enemy_update(self.player) # self.player_attack_logic() else: - self.upgrade.display() + debug('PAUSED') - if self.player.stats.health <= 0: - self.__init__() + for player in self.player_sprites: + if player.stats.health <= 0: + player.kill() diff --git a/game/main.py b/game/main.py index 3b5a43e..5dcf06c 100644 --- a/game/main.py +++ b/game/main.py @@ -1,13 +1,12 @@ -import pygame import sys +import numpy as np +import torch +import pygame from configs.system.window_config import WIDTH, HEIGHT, WATER_COLOR, FPS from level.level import Level -import os -import psutil - class Game: @@ -27,6 +26,90 @@ class Game: main_sound.set_volume(0.4) main_sound.play(loops=-1) + def extract_features(self): + self.features = [] + for i, player in enumerate(self.level.player_sprites): + + player_features = { + "player_position": player.rect.center, + "player role": player.stats.role_id, + "player_health": player.stats.health, + "player_energy": player.stats.energy, + "player_attack": player.stats.attack, + "player_magic": player.stats.magic, + "player_speed": player.stats.speed, + "player_exp": player.stats.exp, + "player_vulnerable": int(player._input.combat.vulnerable), + "player_can_move": int(player._input.can_move), + "player_attacking": int(player._input.attacking), + "player_can_rotate_weapon": int(player._input.can_rotate_weapon), + "playercan_swap_magic": int(player._input.can_swap_magic) + } + + distances_directions = [] + + for distance, direction, enemy in player.distance_direction_from_enemy: + distances_directions.append({ + "enemy_id": enemy.stats.monster_id, + "enemy_status": 0 if enemy.animation.status == "idle" else (1 if enemy.animation.status == "move" else 2), + "enemy_health": enemy.stats.health, + "enemy_attack": enemy.stats.attack, + "enemy_speed": enemy.stats.speed, + "enemy_attack_radius": enemy.stats.attack_radius, + "enemy_notice_radius": enemy.stats.notice_radius, + "enemy_exp": enemy.stats.exp, + "enemy_distance": distance, + "enemy_direction": direction + }) + + player_features["enemies"] = distances_directions + self.features.append(player_features) + + def convert_features_to_tensor(self): + + self.tensors = [] + for player_features in self.features: + info_array = [] + + # Adding player features to tensor + player_info = [ + player_features['player_position'][0], + player_features['player_position'][1], + player_features['player role'], + player_features['player_health'], + player_features['player_energy'], + player_features['player_attack'], + player_features['player_magic'], + player_features['player_speed'], + player_features['player_exp'], + player_features['player_vulnerable'], + player_features['player_can_move'], + player_features['player_attacking'], + player_features['player_can_rotate_weapon'], + player_features['playercan_swap_magic'], + ] + info_array.extend(player_info) + + for enemy in player_features['enemies']: + enemy_info = [ + enemy['enemy_id'], + enemy['enemy_status'], + enemy['enemy_health'], + enemy['enemy_attack'], + enemy['enemy_speed'], + enemy['enemy_attack_radius'], + enemy['enemy_notice_radius'], + enemy['enemy_exp'], + enemy['enemy_distance'], + enemy['enemy_direction'][0], + enemy['enemy_direction'][1] + ] + info_array.extend(enemy_info) + + player_tensor = torch.tensor( + np.array(info_array, dtype=np.float32)) + self.tensors.append(player_tensor) + def run(self): for event in pygame.event.get(): @@ -38,7 +121,11 @@ class Game: self.level.toggle_menu() self.screen.fill(WATER_COLOR) - self.level.run('player') + + self.extract_features() + self.convert_features_to_tensor() + + self.level.run('observer') pygame.display.update() self.clock.tick(FPS) @@ -46,5 +133,7 @@ class Game: if __name__ == '__main__': game = Game() - while True: + for _ in range(0, 10000): game.run() + game.extract_features() + game.convert_features_to_tensor()