1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
#+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
self._player_starts = [] # starting player positions
''' 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, s_start=None):
'''
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_starts.append(s_start)
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
tail = self._get_player_bodies()
danger_array = np.array([
head.y-self._units < 0 or PlayersCollection.Point(head.x, head.y-self._units) in tail, # up
head.x+self._units >= self._window_width or PlayersCollection.Point(head.x+self._units, head.y) in tail, # right
head.y+self._units >= self._window_height or PlayersCollection.Point(head.x, head.y+self._units) in tail, # down
head.x-self._units < 0 or PlayersCollection.Point(head.x-self._units, head.y) in tail, # 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, self._player_starts, window_width=self._window_width, window_height=self._window_height, game_units=self._units)
self._goal = GoalCollection.Goal(self.display, self._window_width, self._window_height, 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.insert(0, 0) # FIXME (why is this required?)
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 = []
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()
|