Add 10 million timestep model, improve README, add diffculty/mode selector, score, make model training have instant graphs and also multiple envs for faster training, better plotting, improve RL model by including multiple players, better reard system, use EnemyFormation instead of single Enemy-es

This commit is contained in:
csd4ni3l
2025-11-16 21:27:21 +01:00
parent fb7b45b6df
commit dce64e5d3f
10 changed files with 644 additions and 273 deletions

View File

@@ -1,16 +1,17 @@
import arcade, arcade.gui, random, time
from utils.constants import button_style, ENEMY_ROWS, ENEMY_COLS, PLAYER_ATTACK_SPEED
from utils.constants import button_style, ENEMY_ATTACK_SPEED, ENEMY_SPEED
from utils.preload import button_texture, button_hovered_texture
from stable_baselines3 import PPO
from game.sprites import Enemy, Player, Bullet
from game.sprites import EnemyFormation, Player, Bullet
class Game(arcade.gui.UIView):
def __init__(self, pypresence_client):
def __init__(self, pypresence_client, settings):
super().__init__()
self.settings = settings
self.pypresence_client = pypresence_client
self.pypresence_client.update(state="Invading Space")
@@ -18,42 +19,53 @@ class Game(arcade.gui.UIView):
self.spritelist = arcade.SpriteList()
self.player = Player(self.window.width / 2 + random.randint(int(-self.window.width / 3), int(self.window.width / 3)), 100) # not actually player
self.spritelist.append(self.player)
self.last_player_shoot = time.perf_counter() # not actually player
self.players = []
for _ in range(settings["player_count"]):
self.players.append(Player(self.window.width / 2 + random.randint(int(-self.window.width / 3), int(self.window.width / 3)), 100)) # not actually player
self.spritelist.append(self.players[-1])
self.model = PPO.load("invader_agent.zip")
self.enemies: list[Enemy] = []
self.enemy_formation = EnemyFormation(self.window.width / 2 + random.randint(int(-self.window.width / 3), int(self.window.width / 3)), self.window.height * 0.9, self.spritelist, settings["enemy_rows"], settings["enemy_cols"])
self.player_bullets: list[Bullet] = []
self.enemy_bullets: list[Bullet] = []
self.summon_enemies()
self.player_respawns = settings["player_respawns"]
self.enemy_respawns = settings["enemy_respawns"]
self.score = 0
self.last_enemy_shoot = time.perf_counter()
self.game_over = False
def on_show_view(self):
super().on_show_view()
self.back_button = self.anchor.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50), anchor_x="left", anchor_y="top", align_x=5, align_y=-5)
self.back_button.on_click = lambda event: self.main_exit()
self.score_label = self.anchor.add(arcade.gui.UILabel("Score: 0", font_size=24), anchor_x="center", anchor_y="top")
def main_exit(self):
from menus.main import Main
self.window.show_view(Main(self.pypresence_client))
def summon_enemies(self):
enemy_start_x = self.window.width * 0.15
enemy_start_y = self.window.height * 0.9
for row in range(ENEMY_ROWS):
for col in range(ENEMY_COLS):
enemy_sprite = Enemy(enemy_start_x + col * 100, enemy_start_y - row * 100)
self.spritelist.append(enemy_sprite)
self.enemies.append(enemy_sprite)
def on_update(self, delta_time):
for enemy in self.enemies:
enemy.update()
if self.game_over:
return
if self.window.keyboard[arcade.key.LEFT] or self.window.keyboard[arcade.key.A]:
self.enemy_formation.move(self.window.width, self.window.height, "x", -ENEMY_SPEED)
if self.window.keyboard[arcade.key.RIGHT] or self.window.keyboard[arcade.key.D]:
self.enemy_formation.move(self.window.width, self.window.height, "x", ENEMY_SPEED)
if self.window.keyboard[arcade.key.DOWN] or self.window.keyboard[arcade.key.S]:
self.enemy_formation.move(self.window.width, self.window.height, "y", -ENEMY_SPEED)
if self.window.keyboard[arcade.key.UP] or self.window.keyboard[arcade.key.W]:
self.enemy_formation.move(self.window.width, self.window.height, "y", ENEMY_SPEED)
if self.enemy_formation.enemies and self.window.keyboard[arcade.key.SPACE] and time.perf_counter() - self.last_enemy_shoot >= ENEMY_ATTACK_SPEED:
self.last_enemy_shoot = time.perf_counter()
enemy = self.enemy_formation.get_lowest_enemy()
self.shoot(enemy.center_x, enemy.center_y, -1)
bullets_to_remove = []
@@ -62,18 +74,21 @@ class Game(arcade.gui.UIView):
bullet_hit = False
if bullet.direction_y == 1:
for enemy in self.enemies:
for enemy in self.enemy_formation.enemies:
if bullet.rect.intersection(enemy.rect):
self.spritelist.remove(enemy)
self.enemies.remove(enemy)
self.player.target = None
self.enemy_formation.remove_enemy(enemy)
bullets_to_remove.append(bullet)
bullet_hit = True
break
else:
if bullet.rect.intersection(self.player.rect):
bullets_to_remove.append(bullet)
bullet_hit = True
for player in self.players:
if bullet.rect.intersection(player.rect):
self.spritelist.remove(player)
self.players.remove(player)
bullets_to_remove.append(bullet)
bullet_hit = True
self.score += 75
break
if not bullet_hit and bullet.center_y > self.window.height or bullet.center_y < 0:
bullets_to_remove.append(bullet)
@@ -86,20 +101,40 @@ class Game(arcade.gui.UIView):
elif bullet_to_remove in self.player_bullets:
self.player_bullets.remove(bullet_to_remove)
self.player.update(self.model, self.enemies, self.enemy_bullets, self.window.width, self.window.height) # not actually player
for player in self.players:
player.update(self.model, self.enemy_formation, self.enemy_bullets, self.window.width, self.window.height, self.player_respawns / self.settings["player_respawns"], self.enemy_respawns / self.settings["enemy_respawns"]) # not actually player
if self.player.center_x > self.window.width:
self.player.center_x = self.window.width
elif self.player.center_x < 0:
self.player.center_x = 0
if player.center_x > self.window.width:
player.center_x = self.window.width
elif player.center_x < 0:
player.center_x = 0
if self.player.shoot:
self.player.shoot = False
self.shoot(self.player.center_x, self.player.center_y, 1)
if player.shoot:
player.shoot = False
self.shoot(player.center_x, player.center_y, 1)
if time.perf_counter() - self.last_player_shoot >= PLAYER_ATTACK_SPEED:
self.last_player_shoot = time.perf_counter()
self.shoot(self.player.center_x, self.player.center_y, 1)
if len(self.players) == 0:
if self.player_respawns > 0:
self.player_respawns -= 1
for _ in range(self.settings["player_count"]):
self.players.append(Player(self.window.width / 2 + random.randint(int(-self.window.width / 3), int(self.window.width / 3)), 100)) # not actually player
self.spritelist.append(self.players[-1])
self.score += 300
else:
self.game_over = True
self.game_over_label = self.anchor.add(arcade.gui.UILabel("You (The enemies) won!", font_size=48), anchor_x="center", anchor_y="center")
elif len(self.enemy_formation.enemies) == 0:
if self.enemy_respawns > 0:
self.enemy_respawns -= 1
self.enemy_formation.create_formation(self.window.width / 2 + random.randint(int(-self.window.width / 3), int(self.window.width / 3)), self.window.height * 0.9)
else:
self.game_over = True
self.game_over_label = self.anchor.add(arcade.gui.UILabel("You lost! The Players win!", font_size=48), anchor_x="center", anchor_y="center")
self.score += 5 * delta_time
self.score_label.text = f"Score: {int(self.score)}"
def shoot(self, x, y, direction_y):
bullet = Bullet(x, y, direction_y)
@@ -112,11 +147,6 @@ class Game(arcade.gui.UIView):
bullets.append(bullet)
def on_key_press(self, symbol, modifiers):
if symbol == arcade.key.SPACE:
enemy = random.choice(self.enemies)
self.shoot(enemy.center_x, enemy.center_y, -1)
def on_draw(self):
super().on_draw()