0d96d1ae928f878edde7344059d318319dadaa62
[progcomp2013.git] / qchess / board.py
1 [w,h] = [8,8] # Width and height of board(s)
2
3 # Class to represent a quantum chess board
4 class Board():
5         # Initialise; if master=True then the secondary piece types are assigned
6         #       Otherwise, they are left as unknown
7         #       So you can use this class in Agent programs, and fill in the types as they are revealed
8         def __init__(self, style="agent"):
9                 self.style = style
10                 self.pieces = {"white" : [], "black" : []}
11                 self.grid = [[None] * w for _ in range(h)] # 2D List (you can get arrays in python, somehow, but they scare me)
12                 self.unrevealed_types = {"white" : piece_types.copy(), "black" : piece_types.copy()}
13                 self.king = {"white" : None, "black" : None} # We need to keep track of the king, because he is important
14                 for c in ["black", "white"]:
15                         del self.unrevealed_types[c]["unknown"]
16
17                 # Add all the pieces with known primary types
18                 for i in range(0, 2):
19                         
20                         s = ["black", "white"][i]
21                         c = self.pieces[s]
22                         y = [0, h-1][i]
23
24                         c.append(Piece(s, 0, y, ["rook"]))
25                         c.append(Piece(s, 1, y, ["knight"]))
26                         c.append(Piece(s, 2, y, ["bishop"]))
27                         k = Piece(s, 3, y, ["king", "king"]) # There can only be one ruler!
28                         k.types_revealed[1] = True
29                         k.current_type = "king"
30                         self.king[s] = k
31                         c.append(k)
32                         c.append(Piece(s, 4, y, ["queen"])) # Apparently he may have multiple wives though.
33                         c.append(Piece(s, 5, y, ["bishop"]))
34                         c.append(Piece(s, 6, y, ["knight"]))
35                         c.append(Piece(s, 7, y, ["rook"]))
36                         
37                         if y == 0: 
38                                 y += 1 
39                         else: 
40                                 y -= 1
41                         
42                         # Lots of pawn
43                         for x in range(0, w):
44                                 c.append(Piece(s, x, y, ["pawn"]))
45
46                         types_left = {}
47                         types_left.update(piece_types)
48                         del types_left["king"] # We don't want one of these randomly appearing (although it might make things interesting...)
49                         del types_left["unknown"] # We certainly don't want these!
50                         for piece in c:
51                                 # Add to grid
52                                 self.grid[piece.x][piece.y] = piece 
53
54                                 if len(piece.types) > 1:
55                                         continue                                
56                                 if style == "agent": # Assign placeholder "unknown" secondary type
57                                         piece.types.append("unknown")
58                                         continue
59
60                                 elif style == "quantum":
61                                         # The master allocates the secondary types
62                                         choice = types_left.keys()[random.randint(0, len(types_left.keys())-1)]
63                                         types_left[choice] -= 1
64                                         if types_left[choice] <= 0:
65                                                 del types_left[choice]
66                                         piece.types.append(choice)
67                                 elif style == "classical":
68                                         piece.types.append(piece.types[0])
69                                         piece.current_type = piece.types[0]
70                                         piece.types_revealed[1] = True
71                                         piece.choice = 0
72
73         def clone(self):
74                 newboard = Board(master = False)
75                 newpieces = newboard.pieces["white"] + newboard.pieces["black"]
76                 mypieces = self.pieces["white"] + self.pieces["black"]
77
78                 for i in range(len(mypieces)):
79                         newpieces[i].init_from_copy(mypieces[i])
80                         
81
82         def display_grid(self, window = None, grid_sz = [80,80]):
83                 if window == None:
84                         return # I was considering implementing a text only display, then I thought "Fuck that"
85
86                 # The indentation is getting seriously out of hand...
87                 for x in range(0, w):
88                         for y in range(0, h):
89                                 if (x + y) % 2 == 0:
90                                         c = pygame.Color(200,200,200)
91                                 else:
92                                         c = pygame.Color(64,64,64)
93                                 pygame.draw.rect(window, c, (x*grid_sz[0], y*grid_sz[1], (x+1)*grid_sz[0], (y+1)*grid_sz[1]))
94
95         def display_pieces(self, window = None, grid_sz = [80,80]):
96                 if window == None:
97                         return
98                 for p in self.pieces["white"] + self.pieces["black"]:
99                         p.draw(window, grid_sz, self.style)
100
101         # Draw the board in a pygame window
102         def display(self, window = None):
103                 self.display_grid(window)
104                 self.display_pieces(window)
105                 
106
107                 
108
109         def verify(self):
110                 for x in range(w):
111                         for y in range(h):
112                                 if self.grid[x][y] == None:
113                                         continue
114                                 if (self.grid[x][y].x != x or self.grid[x][y].y != y):
115                                         raise Exception(sys.argv[0] + ": MISMATCH " + str(self.grid[x][y]) + " should be at " + str(x) + "," + str(y))
116
117         # Select a piece on the board (colour is the colour of whoever is doing the selecting)
118         def select(self, x,y, colour=None):
119                 if not self.on_board(x, y): # Get on board everyone!
120                         raise Exception("BOUNDS")
121
122                 piece = self.grid[x][y]
123                 if piece == None:
124                         raise Exception("EMPTY")
125
126                 if colour != None and piece.colour != colour:
127                         raise Exception("COLOUR " + str(piece.colour) + " not " + str(colour))
128
129                 # I'm not quite sure why I made this return a string, but screw logical design
130                 return str(x) + " " + str(y) + " " + str(piece.select()) + " " + str(piece.current_type)
131
132
133         # Update the board when a piece has been selected
134         # "type" is apparently reserved, so I'll use "state"
135         def update_select(self, x, y, type_index, state):
136                 piece = self.grid[x][y]
137                 if piece.types[type_index] == "unknown":
138                         if not state in self.unrevealed_types[piece.colour].keys():
139                                 raise Exception("SANITY: Too many " + piece.colour + " " + state + "s")
140                         self.unrevealed_types[piece.colour][state] -= 1
141                         if self.unrevealed_types[piece.colour][state] <= 0:
142                                 del self.unrevealed_types[piece.colour][state]
143
144                 piece.types[type_index] = state
145                 piece.types_revealed[type_index] = True
146                 piece.current_type = state
147
148                 if len(self.possible_moves(piece)) <= 0:
149                         piece.deselect() # Piece can't move; deselect it
150                 
151         # Update the board when a piece has been moved
152         def update_move(self, x, y, x2, y2):
153                 piece = self.grid[x][y]
154                 self.grid[x][y] = None
155                 taken = self.grid[x2][y2]
156                 if taken != None:
157                         if taken.current_type == "king":
158                                 self.king[taken.colour] = None
159                         self.pieces[taken.colour].remove(taken)
160                 self.grid[x2][y2] = piece
161                 piece.x = x2
162                 piece.y = y2
163
164                 # If the piece is a pawn, and it reaches the final row, it becomes a queen
165                 # I know you are supposed to get a choice
166                 # But that would be effort
167                 if piece.current_type == "pawn" and ((piece.colour == "white" and piece.y == 0) or (piece.colour == "black" and piece.y == h-1)):
168                         if self.style == "classical":
169                                 piece.types[0] = "queen"
170                                 piece.types[1] = "queen"
171                         else:
172                                 piece.types[piece.choice] = "queen"
173                         piece.current_type = "queen"
174
175                 piece.deselect() # Uncollapse (?) the wavefunction!
176                 self.verify()   
177
178         # Update the board from a string
179         # Guesses what to do based on the format of the string
180         def update(self, result):
181                 #print "Update called with \"" + str(result) + "\""
182                 # String always starts with 'x y'
183                 try:
184                         s = result.split(" ")
185                         [x,y] = map(int, s[0:2])        
186                 except:
187                         raise Exception("GIBBERISH \""+ str(result) + "\"") # Raise expectations
188
189                 piece = self.grid[x][y]
190                 if piece == None:
191                         raise Exception("EMPTY")
192
193                 # If a piece is being moved, the third token is '->'
194                 # We could get away with just using four integers, but that wouldn't look as cool
195                 if "->" in s:
196                         # Last two tokens are the destination
197                         try:
198                                 [x2,y2] = map(int, s[3:])
199                         except:
200                                 raise Exception("GIBBERISH \"" + str(result) + "\"") # Raise the alarm
201
202                         # Move the piece (take opponent if possible)
203                         self.update_move(x, y, x2, y2)
204                         
205                 else:
206                         # Otherwise we will just assume a piece has been selected
207                         try:
208                                 type_index = int(s[2]) # We need to know which of the two types the piece is in; that's the third token
209                                 state = s[3] # The last token is a string identifying the type
210                         except:
211                                 raise Exception("GIBBERISH \"" + result + "\"") # Throw a hissy fit
212
213                         # Select the piece
214                         self.update_select(x, y, type_index, state)
215
216                 return result
217
218         # Gets each piece that could reach the given square and the probability that it could reach that square 
219         # Will include allied pieces that defend the attacker
220         def coverage(self, x, y, colour = None, reject_allied = True):
221                 result = {}
222                 
223                 if colour == None:
224                         pieces = self.pieces["white"] + self.pieces["black"]
225                 else:
226                         pieces = self.pieces[colour]
227
228                 for p in pieces:
229                         prob = self.probability_grid(p, reject_allied)[x][y]
230                         if prob > 0:
231                                 result.update({p : prob})
232                 
233                 self.verify()
234                 return result
235
236
237                 
238
239
240         # Associates each square with a probability that the piece could move into it
241         # Look, I'm doing all the hard work for you here...
242         def probability_grid(self, p, reject_allied = True):
243                 
244                 result = [[0.0] * w for _ in range(h)]
245                 if not isinstance(p, Piece):
246                         return result
247
248                 if p.current_type != "unknown":
249                         #sys.stderr.write(sys.argv[0] + ": " + str(p) + " moves " + str(self.possible_moves(p, reject_allied)) + "\n")
250                         for point in self.possible_moves(p, reject_allied):
251                                 result[point[0]][point[1]] = 1.0
252                         return result
253                 
254                 
255                 for i in range(len(p.types)):
256                         t = p.types[i]
257                         prob = 0.5
258                         if t == "unknown" or p.types_revealed[i] == False:
259                                 total_types = 0
260                                 for t2 in self.unrevealed_types[p.colour].keys():
261                                         total_types += self.unrevealed_types[p.colour][t2]
262                                 
263                                 for t2 in self.unrevealed_types[p.colour].keys():
264                                         prob2 = float(self.unrevealed_types[p.colour][t2]) / float(total_types)
265                                         p.current_type = t2
266                                         for point in self.possible_moves(p, reject_allied):
267                                                 result[point[0]][point[1]] += prob2 * prob
268                                 
269                         else:
270                                 p.current_type = t
271                                 for point in self.possible_moves(p, reject_allied):
272                                         result[point[0]][point[1]] += prob
273                 
274                 self.verify()
275                 p.current_type = "unknown"
276                 return result
277
278         def prob_is_type(self, p, state):
279                 prob = 0.5
280                 result = 0
281                 for i in range(len(p.types)):
282                         t = p.types[i]
283                         if t == state:
284                                 result += prob
285                                 continue        
286                         if t == "unknown" or p.types_revealed[i] == False:
287                                 total_prob = 0
288                                 for t2 in self.unrevealed_types[p.colour].keys():
289                                         total_prob += self.unrevealed_types[p.colour][t2]
290                                 for t2 in self.unrevealed_types[p.colour].keys():
291                                         if t2 == state:
292                                                 result += prob * float(self.unrevealed_types[p.colour][t2]) / float(total_prob)
293                                 
294
295
296         # Get all squares that the piece could move into
297         # This is probably inefficient, but I looked at some sample chess games and they seem to actually do things this way
298         # reject_allied indicates whether squares occupied by allied pieces will be removed
299         # (set to false to check for defense)
300         def possible_moves(self, p, reject_allied = True):
301                 result = []
302                 if p == None:
303                         return result
304
305                 
306                 if p.current_type == "unknown":
307                         raise Exception("SANITY: Piece state unknown")
308                         # The below commented out code causes things to break badly
309                         #for t in p.types:
310                         #       if t == "unknown":
311                         #               continue
312                         #       p.current_type = t
313                         #       result += self.possible_moves(p)                                                
314                         #p.current_type = "unknown"
315                         #return result
316
317                 if p.current_type == "king":
318                         result = [[p.x-1,p.y],[p.x+1,p.y],[p.x,p.y-1],[p.x,p.y+1], [p.x-1,p.y-1],[p.x-1,p.y+1],[p.x+1,p.y-1],[p.x+1,p.y+1]]
319                 elif p.current_type == "queen":
320                         for d in [[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1]]:
321                                 result += self.scan(p.x, p.y, d[0], d[1])
322                 elif p.current_type == "bishop":
323                         for d in [[-1,-1],[-1,1],[1,-1],[1,1]]: # There's a reason why bishops move diagonally
324                                 result += self.scan(p.x, p.y, d[0], d[1])
325                 elif p.current_type == "rook":
326                         for d in [[-1,0],[1,0],[0,-1],[0,1]]:
327                                 result += self.scan(p.x, p.y, d[0], d[1])
328                 elif p.current_type == "knight":
329                         # I would use two lines, but I'm not sure how python likes that
330                         result = [[p.x-2, p.y-1], [p.x-2, p.y+1], [p.x+2, p.y-1], [p.x+2,p.y+1], [p.x-1,p.y-2], [p.x-1, p.y+2],[p.x+1,p.y-2],[p.x+1,p.y+2]]
331                 elif p.current_type == "pawn":
332                         if p.colour == "white":
333                                 
334                                 # Pawn can't move forward into occupied square
335                                 if self.on_board(p.x, p.y-1) and self.grid[p.x][p.y-1] == None:
336                                         result = [[p.x,p.y-1]]
337                                 for f in [[p.x-1,p.y-1],[p.x+1,p.y-1]]:
338                                         if not self.on_board(f[0], f[1]):
339                                                 continue
340                                         if self.grid[f[0]][f[1]] != None:  # Pawn can take diagonally
341                                                 result.append(f)
342                                 if p.y == h-2:
343                                         # Slightly embarrassing if the pawn jumps over someone on its first move...
344                                         if self.grid[p.x][p.y-1] == None and self.grid[p.x][p.y-2] == None:
345                                                 result.append([p.x, p.y-2])
346                         else:
347                                 # Vice versa for the black pawn
348                                 if self.on_board(p.x, p.y+1) and self.grid[p.x][p.y+1] == None:
349                                         result = [[p.x,p.y+1]]
350
351                                 for f in [[p.x-1,p.y+1],[p.x+1,p.y+1]]:
352                                         if not self.on_board(f[0], f[1]):
353                                                 continue
354                                         if self.grid[f[0]][f[1]] != None:
355                                                 #sys.stderr.write(sys.argv[0] + " : "+str(p) + " can take " + str(self.grid[f[0]][f[1]]) + "\n")
356                                                 result.append(f)
357                                 if p.y == 1:
358                                         if self.grid[p.x][p.y+1] == None and self.grid[p.x][p.y+2] == None:
359                                                 result.append([p.x, p.y+2])
360
361                         #sys.stderr.write(sys.argv[0] + " : possible_moves for " + str(p) + " " + str(result) + "\n")
362
363                 # Remove illegal moves
364                 # Note: The result[:] creates a copy of result, so that the result.remove calls don't fuck things up
365                 for point in result[:]: 
366
367                         if (point[0] < 0 or point[0] >= w) or (point[1] < 0 or point[1] >= h):
368                                 result.remove(point) # Remove locations outside the board
369                                 continue
370                         g = self.grid[point[0]][point[1]]
371                         
372                         if g != None and (g.colour == p.colour and reject_allied == True):
373                                 result.remove(point) # Remove allied pieces
374                 
375                 self.verify()
376                 return result
377
378
379         # Scans in a direction until it hits a piece, returns all squares in the line
380         # (includes the final square (which contains a piece), but not the original square)
381         def scan(self, x, y, vx, vy):
382                 p = []
383                         
384                 xx = x
385                 yy = y
386                 while True:
387                         xx += vx
388                         yy += vy
389                         if not self.on_board(xx, yy):
390                                 break
391                         if not [xx,yy] in p:
392                                 p.append([xx, yy])
393                         g = self.grid[xx][yy]
394                         if g != None:
395                                 return p        
396                                         
397                 return p
398
399
400
401         # I typed the full statement about 30 times before writing this function...
402         def on_board(self, x, y):
403                 return (x >= 0 and x < w) and (y >= 0 and y < h)

UCC git Repository :: git.ucc.asn.au