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

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