-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPlayer.py
225 lines (188 loc) · 8.82 KB
/
Player.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
import itertools
from ChessPiece import ChessPiece
import random
from CheatAnalyser import CheatAnalyser
from Referee import Referee
class Player():
def __init__(self, name=None):
self.name = name
# Heuristic is determined by whether the player wins, or makes notable gains.
# Win condition: game over in favor of the player
# Favorable outcomes:
# - Given piece captures a piece
# - This is a greedy approach, but enemy piece count is the most reliable metric to measure progress.
# - As opposed to standard chess, the player does not know enemy pieces, so value of capture cannot influence decision.
# - This needs to be done without putting oneself in check, which the program will prevent anyways.
# - Gets out of check, which is a given for the game's rules.
# - Essentially, the given move should be legal and get as much as possible.
def heuristic(self, board):
# on losing game over:
if Referee.is_game_over(self.name, board):
return 0
#checking which player is the opponent
name = ""
if self.name == "White":
name = "Black"
if self.name == "Black":
name = "White"
# on winning game over:
if Referee.is_game_over(name, board):
return 200
# non-game over state:
# based on player, returns number of enemy pieces left.
# The fewer pieces left, the better the score
if self.name == "White":
return 10 - board.p2piececount
if self.name == "Black":
return 10 - board.p1piececount
def do_move(self, board):
"""
Choose a move and return it in the form (from, to) where
from and to are coordinates on the board.
"""
raise NotImplementedError("do_move method not implemented for Player: {}".format(self))
def notify(self, ref_output=None, moves_made=None):
"""
Player recieves output from the referee in the form of a RefereeOutput object.
"""
pass
class HumanPlayer(Player):
def __init__(self, *args, **kwargs):
self.analyser = CheatAnalyser()
super().__init__(*args, **kwargs)
def do_move(self, board):
print(board.sideboard)
print("History of referee outputs:")
print(" m#\tFrom\tTo\tOutput")
for o in self.analyser.ref_outputs:
print(" {}\t{}\t{}\t{}".format(o.moves_made, o.output.from_cell, o.output.to_cell, o.output))
board.print_board(show_key=True)
col_conversion = {a: b for a,b in zip(list("abcd"),[0,1,2,3])} #************
row_conversion = {a: b for a,b in zip(list("4321"),[0,1,2,3])} #************
in_string = input(">")
#Handle user input and potential errors if the format is incorrect:
try:
_from, _to = in_string.split(" ")
except ValueError:
print("Invalid input!")
print("\tMust be in format: [from_location] [to_location] (e.g 'a2 a3')")
return self.do_move(board)
try:
#Convert user input to numerical coordinates. a7 -> (0,1); a6 -> (0,2)...
from_cell = (col_conversion[_from[0]], row_conversion[_from[1]])
to_cell = (col_conversion[_to[0]], row_conversion[_to[1]])
except KeyError:
print("Invalid move coordinates.")
print("\tMust be in range a1-h7")
return self.do_move(board)
return (from_cell, to_cell)
"""
Override notify function.
Pass the referee output to the cheat analyser
"""
def notify(self, ref_output, moves_made):
super().notify(ref_output, moves_made)
self.analyser.add_ref_output(ref_output, moves_made)
# random player that replaces the old code's class.
class RandomPlayer(Player):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# what defines a random move?
# - takes a random piece
# - considers what that piece is, gets all moves based on piece
# - picks any random move. if invalid, removes it from list and picks another.
# - if this move is deemed illegal by the referee, do_move is called again
def do_move(self, board):
"""
sideboard is the dictionary of the pieces on the board and their current locations.
remove the comment on the below line to see.
This and below lines are useful for testing, but are cheating if playing.
"""
# print(board.sideboard)
"""
shows the player's board each attempted move.
remove comment for clarity on the random player's position, but note that it will print multiple times each AI turn.
"""
# board.print_board(show_key=True)
# a list of each piece available, which will be taken from the dictionary.
keyslist = []
# loops through dictionary of player's pieces rather than all 16 spaces of the board.
# this is intended to take less time, especially when there are multiple failed attempts.
for i in board.sideboard:
keyslist.append(i)
# from the available pieces, takes a random one.
chosenpiece = keyslist[random.randint(0, len(keyslist)-1)]
# gets the location of the chosen piece, and uses its coordinates to get a start location.
current = board.sideboard[chosenpiece]
start = (current[0], current[1])
# gets a list of all moves for the piece and chooses one at random.
moves = chosenpiece.get_moves()
moveindex = random.randint(0, len(moves)-1)
end = moves[moveindex]
# checks if the move is inbounds, rerolls until it is.
# this effectively locks in the piece to be used, unless that piece cannot be used at all.
while (start[0]-end[0] > 3 or start[0]-end[0] < 0 or start[1]-end[1] > 3 or start[1]-end[1] < 0):
moves.remove(end)
moveindex = random.randint(0, len(moves)-1)
end = moves[moveindex]
# once a valid move is found, the endpoint is set.
end = (start[0]-end[0], start[1]-end[1])
"""
The below comments can be used in testing to see what the ai ultimately picked as its move.
"""
# print("Start:" + str(start))
# print("End:" + str(end))
# returns the start and end locations the player wants to do.
return (start, end)
class mmPlayer(Player):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.player = Player
def getrandomStates(self, board):
starterBoard = board
boards = []
# for (length of set of all possible boards): place other player's pieces on board.
# consider available pieces, which is best done under assumption that players have same pieces
# if so, set variable to keep track of starting pieces and call it here.
# place pieces in empty spaces
# when set collected, return set
# due to restrictions on what do_move function calls want, do_move calls a helper function that performs minimax
def do_move(self, board):
start = self.mini(board, 2)[2]
end = self.mini(board, 2)[1]
return (start, end)
# based on Michael's group's A4 minimax
def mini(self, board, depth):
if depth == 0 or Referee.is_game_over(Referee, self.player, board):
return self.heuristic(board)
# player 1's move
if self.player == "White":
bestMove = 0
bestValue = -math.inf
start = 0
for piece in board.sideboard:
movelist = piece.get_moves
# currently judges the scores of random moves
# would call getRandomStates, and for all states would try minimax.
# would have summed and average the score to then be compared to the best value.
move = board.makeMove(movelist[random.randint(0, len(movelist)-1)])
score = self.mini(move, depth-1)
if score >= bestValue:
bestValue = score
start = board.sideboard[piece]
end = move
return (bestValue, end, piece)
# player 2's move; with the current presets, the AI is always player 2.
else:
bestMove = 0
bestValue = math.inf
start = 0
for piece in board.sideboard:
movelist = piece.get_moves
move = board.makeMove(movelist[random.randint(0, len(movelist)-1)])
score = self.mini(move, depth-1)
if score <= bestValue:
bestValue = score
start = board.sideboard[piece]
end = move
return (bestValue, end, piece)