diff options
author | bd-912 <bdunahu@gmail.com> | 2023-11-12 20:10:57 -0700 |
---|---|---|
committer | bd-912 <bdunahu@gmail.com> | 2023-11-12 20:26:49 -0700 |
commit | a2b56742da7b30afa00f33c9a806fa6031be68a5 (patch) | |
tree | 94acd653183c0cc57e0434f39f5d3917eb99fdc0 /GameEngine/multiplayer.py | |
parent | fa75138690814ad7a06194883a12f25c3936a15e (diff) |
Added initial files
Diffstat (limited to 'GameEngine/multiplayer.py')
-rw-r--r-- | GameEngine/multiplayer.py | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/GameEngine/multiplayer.py b/GameEngine/multiplayer.py new file mode 100644 index 0000000..9f6b651 --- /dev/null +++ b/GameEngine/multiplayer.py @@ -0,0 +1,170 @@ +#+AUTHOR: bdunahu +#+TITLE: multiplayer.py +#+DESCRIPTION game engine for multiagent snake + +from enum import Enum +import pygame as pg +import numpy as np + +from GameEngine import PlayersCollection +from GameEngine import GoalCollection + +BLACK = (0, 0, 0) + +class CollisionType(Enum): # each of these means different outcome for model + DEATH = 0 + GOAL = 1 + NONE = 2 + + +class Playfield: + def __init__(self, window_width=640, window_height=480, units=40, g_speed=25, s_size=3): + ''' initialize pygame modules, snake, goal, and score objects ''' + global DISPLAY + + self._g_speed = g_speed # game speed + self._s_size = s_size # initial snake size + self._units = units + self._window_width = window_width + self._window_height = window_height + self._player_count = -1 # number of registered players + ''' for human feedback ''' + self._game_state = False # false is game over + self._draw_on = True + self._clock = pg.time.Clock() + + ''' objects ''' + pg.init() + self.display = pg.display.set_mode( + [self._window_width, self._window_height], + pg.HWSURFACE) # display object (see explanation in engine.org) + self._players = None + self._goal = None + + def add_player(self): + ''' + Returns the player's number to the callee. + If the player count is over four, returns None + ''' + if self._player_count < 4 or self._game_state == True: + self._player_count += 1 + return self._player_count + return None + + def get_heads_tails_and_goal(self): + ''' + Returns an array of heads, an array of tail positions, + and the goal position + ''' + heads = [] + for player in self._players.players: + heads.append(player.head) + return heads, self._get_player_bodies(), self._goal.location + + def get_viable_actions(self, player_id): + ''' + Given a player's id, + returns a list of actions that does + not result in immediate death + ''' + head = self._players.players[player_id].head + tail = self._players.players[player_id].snake + danger_array = np.array([ + head.y-self._units < 0 or PlayersCollection.Point(head.x, head.y-self._units) in tail[1:], # up + head.x+self._units >= self._window_width or PlayersCollection.Point(head.x+self._units, head.y) in tail[1:], # right + head.y+self._units >= self._window_height or PlayersCollection.Point(head.x, head.y+self._units) in tail[1:], # down + head.x-self._units < 0 or PlayersCollection.Point(head.x-self._units, head.y) in tail[1:], # left + ]) + + return np.where(danger_array == False)[0] + + def start_game(self): + ''' + Initializes player objects, starts the game + ''' + self._players = PlayersCollection.Players(self._s_size, self._player_count+1, self.display, game_units=self._units) + self._goal = GoalCollection.Goal(self.display, game_units=self._units) + self._reset() + print(f'Game starting with {self._player_count+1} players.') + + def stop_game(self): + ''' + Restarts the game, allows adding/removing players + ''' + self._game_state = False + print(f'Game over!') + + def cleanup(self): + ''' end game session ''' + pg.quit() + + def player_advance(self, actions, noise=0.0): + ''' given a list of snake actions ''' + ''' return a list of results, and ''' + ''' update game state ''' + + for player_id, action in enumerate(actions): + if np.random.uniform < noise: + # random action (noise) + random_choice = [0, 1, 2, 3] + random_choice.remove(self._players[player_id].direction) + self._players[player_id].direction = np.random.choice(random_choice) + else: + self._players[player_id].direction = action + + self._players.move_all() + collisions = self._check_player_collisions() + if self._draw_on: + self._update_ui() + return collisions + + def toggle_draw(self): + ''' turns off and on UI ''' + self._draw_on = not self._draw_on + print(f'Draw is now {self._draw_on}.') + + def _check_player_collisions(self): + results = [] + for player in self._players: + ''' determine what obstacle was hit ''' + hazards = self._get_player_bodies() + hazards.remove(player.head) + if (player.head in hazards or player.in_wall()): + self._players.reward_killer(player) + player.reset() + results.append(CollisionType.DEATH) + elif player.head == self._goal.location: + player.score.curr_score += 1 + player.score.total_score += 1 + self._goal.reset(hazards) + results.append(CollisionType.GOAL) + else: + if player.deficit <= 0: + player.snake.pop() + else: + player.deficit -= 1 + results.append(CollisionType.NONE) + return results + + def _get_player_bodies(self): + ''' return an array of all tail coordinates ''' + tails = [0] + for player in self._players: + tails += player.snake + return tails + + def _update_ui(self): + ''' flush new positions to screen ''' + self.display.fill(BLACK) + self._players.draw() + self._goal.draw() + + pg.display.flip() # full screen update + self._clock.tick(self._g_speed) + + def _reset(self): + ''' reset game state ''' + ''' required by training ''' + self._game_state = True # false is game over + self._goal.reset() + self._players.full_reset() |