summaryrefslogtreecommitdiff
path: root/GameEngine/multiplayer.py
diff options
context:
space:
mode:
Diffstat (limited to 'GameEngine/multiplayer.py')
-rw-r--r--GameEngine/multiplayer.py170
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()