forked from suragnair/alpha-zero-general
-
Notifications
You must be signed in to change notification settings - Fork 0
/
RTSPlayers.py
285 lines (231 loc) · 12.4 KB
/
RTSPlayers.py
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import ctypes
import os
import sys
from math import sqrt
from typing import List
import numpy as np
import pygame
from pygame.rect import Rect
sys.path.append('..')
from rts.src.config import NUM_ACTS, P_NAME_IDX, A_TYPE_IDX, d_user_shortcuts, FPS, ACTS, d_a_type, ACTS_REV, d_user_shortcuts_rev
from rts.visualization.rts_pygame import init_visuals, update_graphics, message_display
from utils import dotdict
"""
RTSPlayers.py
Contains 3 players (human player, random player, greedy player (if searching for nnet player, it is defined by pre-learnt model)
Human player has defined input controls for Pygame and console
"""
class RandomPlayer:
def __init__(self, game):
self.game = game
def play(self, board):
a = np.random.randint(self.game.getActionSize())
valids = self.game.getValidMoves(board, 1)
while valids[a] != 1:
a = np.random.randint(self.game.getActionSize())
return a
class HumanRTSPlayer:
def __init__(self, game) -> None:
self.game = game
self.USER_PLAYER = 1 # used by Human Player - this does not change if human pit player is 1 or -1
def play(self, board: np.ndarray) -> List:
"""
Manages input using PyGame canvas/ console input
:param board: current board
:return: action to execute on current board
"""
from rts.src.config_class import CONFIG
n = board.shape[0]
valid = self.game.getValidMoves(board, 1)
self.display_valid_moves(board, valid)
while True:
if CONFIG.visibility > 3:
a = self._manage_input(board)
x, y, action_index = a
else:
a = (input('type one of above actions in "x y action_index" format\n')).split(" ")
x, y, action = a
action_index = ACTS[action]
# convert to action index in valids array
try:
tup = (int(y), int(x), int(action_index))
a = np.ravel_multi_index(tup, (n, n, NUM_ACTS))
except Exception as e:
print("Could not parse action")
if valid[a]:
break
else:
print('This action is invalid!')
self.display_valid_moves(board, valid)
return a
def display_valid_moves(self, board, valid) -> None:
"""
Displays all valid moves in console for specific board
:param board: board to display moves upon
:param valid: vector of valid moves
"""
if valid is None:
valid = self.game.getValidMoves(board, 1)
n = board.shape[0]
print("----------")
for i in range(len(valid)):
if valid[i]:
y, x, action_index = np.unravel_index(i, [n, n, NUM_ACTS])
print(x, y, ACTS_REV[action_index])
print("----------")
@staticmethod
def select_object(board: np.ndarray, click_location: tuple) -> dotdict:
"""
Selects object on PyGame canvas using mouse click
:param board: game state board
:param click_location: tuple (x,y) that represents canvas click location
:return: game tile coordinate (x,y)
"""
n = board.shape[0]
canvas_scale = int(ctypes.windll.user32.GetSystemMetrics(1) * (16 / 30) / n) # for drawing - it takes 2 thirds of screen height
# select object by clicking on it - you can select only your objects
for y in range(n):
for x in range(n):
actor_location = (int(x * canvas_scale + canvas_scale / 2 + canvas_scale), int(y * canvas_scale + canvas_scale / 2) + canvas_scale)
actor_x, actor_y = actor_location
actor_size = int(canvas_scale / 3)
click_x, click_y = click_location
dist = sqrt((actor_x - click_x) ** 2 + (actor_y - click_y) ** 2)
if dist <= actor_size:
return dotdict({"x": x, "y": y})
return dotdict({"x": -1, "y": -1})
def _manage_input(self, board: np.ndarray) -> list:
"""
Manages click and keyboard selections on PyGame canvas
:param board: game state
:return: /
"""
from rts.src.Board import Board
from rts.src.config_class import CONFIG
n = board.shape[0]
game_display, clock = init_visuals(n, n, CONFIG.visibility)
update_graphics(board, game_display, clock, FPS)
canvas_scale: int = int(ctypes.windll.user32.GetSystemMetrics(1) * (16 / 30) / n)
clicked_actor = None
clicked_actor_index_arr = []
while True:
for event in pygame.event.get():
# print(event)
if event.type == pygame.QUIT:
pygame.quit()
raise SystemExit(0)
if event.type == pygame.KEYDOWN:
if clicked_actor and (board[clicked_actor.x][clicked_actor.y][P_NAME_IDX] == self.USER_PLAYER):
try:
shortcut_pressed = d_user_shortcuts[event.unicode]
action_to_execute = shortcut_pressed
clicked_actor_index_arr.append(action_to_execute)
return clicked_actor_index_arr
except Exception as e:
print("shortcut '" + event.unicode + "' not supported.")
if event.key == pygame.K_ESCAPE:
pygame.quit()
os._exit(0)
# handle mouse
if event.type == pygame.MOUSEBUTTONUP:
lmb, rmb = 1, 3
pos = pygame.mouse.get_pos()
if event.button == lmb:
clicked_actor = self.select_object(board, pos)
if clicked_actor and board[clicked_actor.x][clicked_actor.y][P_NAME_IDX] == self.USER_PLAYER and board[clicked_actor.x][clicked_actor.y][A_TYPE_IDX] != d_a_type['Gold']:
clicked_actor_index_arr = [clicked_actor.x, clicked_actor.y]
# draw selected bounding box
game_display, clock = init_visuals(n, n, CONFIG.visibility)
update_graphics(board, game_display, clock, FPS)
actor_size = int(canvas_scale / 3)
actor_location = (int(clicked_actor.x * canvas_scale + canvas_scale / 2 + canvas_scale - actor_size), int(clicked_actor.y * canvas_scale + canvas_scale / 2 + canvas_scale - actor_size))
rect = Rect(actor_location, (2 * actor_size, 2 * actor_size))
blue = (0, 0, 255)
pygame.draw.rect(game_display, blue, rect, int(canvas_scale / 20))
# display valid actions on canvas
b = Board(n)
b.pieces = np.copy(board)
if self.USER_PLAYER == 1:
config = CONFIG.player1_config
else:
config = CONFIG.player2_config
valids_square = b.get_moves_for_square(clicked_actor.x, clicked_actor.y, config=config)
printed_actions = 0
for i in range(len(valids_square)):
if valids_square[i]:
text_scale = int(actor_size * 0.5)
message_display(game_display, u"" + ACTS_REV[i] + " s: '" + d_user_shortcuts_rev[i] + "'", (3 * canvas_scale + int(printed_actions % 3) * canvas_scale * 2, (n + 1) * canvas_scale + text_scale / 2 + int(printed_actions / 3) * text_scale + int(text_scale / 4)),
text_scale)
printed_actions += 1
# update display
pygame.display.update()
else:
print("You can select only your actors!")
if event.button == rmb:
if clicked_actor:
l_x = clicked_actor.x
l_y = clicked_actor.y
l_type = board[l_x][l_y][A_TYPE_IDX]
right_clicked_actor = self.select_object(board, pos)
# right clicked actor exists and (if player 1 or player -1) and not clicked self
if right_clicked_actor and board[right_clicked_actor.x][right_clicked_actor.y][P_NAME_IDX] != 0 and right_clicked_actor != clicked_actor:
r_x = right_clicked_actor.x
r_y = right_clicked_actor.y
r_type = board[r_x][r_y][A_TYPE_IDX]
r_player = board[r_x][r_y][P_NAME_IDX]
# this is actor of type MyActor
if l_type == d_a_type['Work']:
if r_player == self.USER_PLAYER:
if r_type == d_a_type['Gold']:
clicked_actor_index_arr.append(ACTS["mine_resources"])
if r_type == d_a_type['Hall']:
clicked_actor_index_arr.append(ACTS["return_resources"])
if l_type == d_a_type['Rifl']:
if r_player != self.USER_PLAYER and r_type != d_a_type['Gold']:
clicked_actor_index_arr.append(ACTS["attack"])
else:
actor_size = int(canvas_scale / 3)
clicked_x, clicked_y = pos
clicked_actors_world_x = int(l_x * canvas_scale + canvas_scale / 2 + canvas_scale - actor_size)
clicked_actors_world_y = int(l_y * canvas_scale + canvas_scale / 2 + canvas_scale - actor_size)
if abs(clicked_y - clicked_actors_world_y) > abs(clicked_x - clicked_actors_world_x):
# we moved mouse more in y direction than x, so its vertical movement
if clicked_y < clicked_actors_world_y:
print("clicked up...")
clicked_actor_index_arr.append(ACTS["up"])
if clicked_y > clicked_actors_world_y:
print("clicked down...")
clicked_actor_index_arr.append(ACTS["down"])
else:
# we moved mouse more in x direction than y, so its horizontal movement
if clicked_x < clicked_actors_world_x:
print("clicked left...")
clicked_actor_index_arr.append(ACTS["left"])
if clicked_x > clicked_actors_world_x:
print("clicked right...")
clicked_actor_index_arr.append(ACTS["right"])
if len(clicked_actor_index_arr) == 3:
return clicked_actor_index_arr
else:
print("invalid")
self.display_valid_moves(board, None)
else:
print("First left click on actor to select it")
class GreedyRTSPlayer:
def __init__(self, game):
self.game = game
def play(self, board):
valids = self.game.getValidMoves(board, 1)
print("sum valids", sum(valids))
candidates = []
for a in range(self.game.getActionSize()):
if valids[a] == 0:
continue
next_board, _ = self.game.getNextState(board, 1, a)
score = self.game.getScore(next_board, 1)
candidates += [(-score, a)]
candidates.sort()
n = board.shape[0]
y, x, action_index = np.unravel_index(candidates[0][1], [n, n, NUM_ACTS])
print("returned act", x, y, ACTS_REV[action_index])
return candidates[0][1]