1 [w,h] = [8,8] # Width and height of board(s)
3 always_reveal_states = False
5 # Class to represent a quantum chess 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"):
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
19 for c in ["black", "white"]:
20 del self.unrevealed_types[c]["unknown"]
25 # Add all the pieces with known primary types
28 s = ["black", "white"][i]
32 c.append(Piece(s, 0, y, ["rook"]))
33 c.append(Piece(s, 1, y, ["knight"]))
34 c.append(Piece(s, 2, y, ["bishop"]))
35 k = Piece(s, 3, y, ["king", "king"]) # There can only be one ruler!
36 k.current_type = "king"
39 c.append(Piece(s, 4, y, ["queen"])) # Apparently he may have multiple wives though.
40 c.append(Piece(s, 5, y, ["bishop"]))
41 c.append(Piece(s, 6, y, ["knight"]))
42 c.append(Piece(s, 7, y, ["rook"]))
51 c.append(Piece(s, x, y, ["pawn"]))
54 types_left.update(piece_types)
55 del types_left["king"] # We don't want one of these randomly appearing (although it might make things interesting...)
56 del types_left["unknown"] # We certainly don't want these!
59 self.grid[piece.x][piece.y] = piece
61 if len(piece.types) > 1:
63 if style == "agent": # Assign placeholder "unknown" secondary type
64 piece.types.append("unknown")
67 elif style == "quantum":
68 # The master allocates the secondary types
69 choice = types_left.keys()[random.randint(0, len(types_left.keys())-1)]
70 types_left[choice] -= 1
71 if types_left[choice] <= 0:
72 del types_left[choice]
73 piece.types.append('?' + choice)
74 elif style == "classical":
75 piece.types.append(piece.types[0])
76 piece.current_type = piece.types[0]
80 newboard = Board(master = False)
81 newpieces = newboard.pieces["white"] + newboard.pieces["black"]
82 mypieces = self.pieces["white"] + self.pieces["black"]
84 for i in range(len(mypieces)):
85 newpieces[i].init_from_copy(mypieces[i])
87 # Reset the board from a string
88 def reset_board(self, s):
89 self.pieces = {"white" : [], "black" : []}
90 self.king = {"white" : None, "black" : None}
91 self.grid = [[None] * w for _ in range(h)]
94 self.grid[x][y] = None
96 for line in s.split("\n"):
102 tokens = line.split(" ")
103 [x, y] = map(int, tokens[len(tokens)-1].split(","))
104 current_type = tokens[1]
105 types = map(lambda e : e.strip(" '[],"), line.split('[')[1].split(']')[0].split(','))
107 target = Piece(tokens[0], x, y, types)
108 target.current_type = current_type
111 target.choice = types.index(current_type)
115 self.pieces[tokens[0]].append(target)
116 if target.current_type == "king":
117 self.king[tokens[0]] = target
119 self.grid[x][y] = target
122 def display_grid(self, window = None, grid_sz = [80,80]):
124 return # I was considering implementing a text only display, then I thought "Fuck that"
126 # The indentation is getting seriously out of hand...
127 for x in range(0, w):
128 for y in range(0, h):
130 c = pygame.Color(200,200,200)
132 c = pygame.Color(64,64,64)
133 pygame.draw.rect(window, c, (x*grid_sz[0], y*grid_sz[1], (x+1)*grid_sz[0], (y+1)*grid_sz[1]))
135 def display_pieces(self, window = None, grid_sz = [80,80]):
138 for p in self.pieces["white"] + self.pieces["black"]:
139 p.draw(window, grid_sz, self.style)
141 # Draw the board in a pygame window
142 def display(self, window = None):
143 self.display_grid(window)
144 self.display_pieces(window)
152 if self.grid[x][y] == None:
154 if (self.grid[x][y].x != x or self.grid[x][y].y != y):
155 raise Exception(sys.argv[0] + ": MISMATCH " + str(self.grid[x][y]) + " should be at " + str(x) + "," + str(y))
157 # Select a piece on the board (colour is the colour of whoever is doing the selecting)
158 def select(self, x,y, colour=None):
159 if not self.on_board(x, y): # Get on board everyone!
160 raise Exception("BOUNDS " + str(x) + ","+str(y))
162 piece = self.grid[x][y]
164 raise Exception("EMPTY")
166 if colour != None and piece.colour != colour:
167 raise Exception("COLOUR " + str(piece.colour) + " not " + str(colour))
169 # I'm not quite sure why I made this return a string, but screw logical design
170 return str(x) + " " + str(y) + " " + str(piece.select()) + " " + str(piece.current_type)
173 # Update the board when a piece has been selected
174 # "type" is apparently reserved, so I'll use "state"
175 def update_select(self, x, y, type_index, state, sanity=True, deselect=True):
176 #debug(str(self) + " update_select called")
177 piece = self.grid[x][y]
178 if piece.types[type_index] == "unknown":
179 if not state in self.unrevealed_types[piece.colour].keys() and sanity == True:
180 raise Exception("SANITY: Too many " + piece.colour + " " + state + "s")
181 self.unrevealed_types[piece.colour][state] -= 1
182 if self.unrevealed_types[piece.colour][state] <= 0:
183 del self.unrevealed_types[piece.colour][state]
185 piece.types[type_index] = state
186 piece.current_type = state
188 if deselect == True and len(self.possible_moves(piece)) <= 0:
189 piece.deselect() # Piece can't move; deselect it
191 # Piece needs to recalculate moves
192 piece.possible_moves = None
194 # Update the board when a piece has been moved
195 def update_move(self, x, y, x2, y2, sanity=True):
196 #debug(str(self) + " update_move called \""+str(x)+ " " + str(y) + " -> " + str(x2) + " " + str(y2) + "\"")
197 piece = self.grid[x][y]
198 #print "Moving " + str(x) + "," + str(y) + " to " + str(x2) + "," + str(y2) + "; possible_moves are " + str(self.possible_moves(piece))
200 if not [x2,y2] in self.possible_moves(piece) and sanity == True:
201 raise Exception("ILLEGAL move " + str(x2)+","+str(y2))
203 self.grid[x][y] = None
204 taken = self.grid[x2][y2]
206 if taken.current_type == "king":
207 self.king[taken.colour] = None
208 self.pieces[taken.colour].remove(taken)
209 self.grid[x2][y2] = piece
213 # If the piece is a pawn, and it reaches the final row, it becomes a queen
214 # I know you are supposed to get a choice
215 # But that would be effort
216 if piece.current_type == "pawn" and ((piece.colour == "white" and piece.y == 0) or (piece.colour == "black" and piece.y == h-1)):
217 if self.style == "classical":
218 piece.types[0] = "queen"
219 piece.types[1] = "queen"
221 piece.types[piece.choice] = "queen"
222 piece.current_type = "queen"
224 piece.deselect() # Uncollapse (?) the wavefunction!
227 # All other pieces need to recalculate moves
228 for p in self.pieces["white"] + self.pieces["black"]:
229 p.possible_moves = None
233 # Update the board from a string
234 # Guesses what to do based on the format of the string
235 def update(self, result, sanity=True, deselect=True):
236 #debug(str(self) + " update called \""+str(result)+"\"")
237 # String always starts with 'x y'
239 s = result.split(" ")
240 [x,y] = map(int, s[0:2])
242 raise Exception("GIBBERISH \""+ str(result) + "\"") # Raise expectations
244 piece = self.grid[x][y]
245 if piece == None and sanity == True:
246 raise Exception("EMPTY " + str(x) + " " + str(y))
248 # If a piece is being moved, the third token is '->'
249 # We could get away with just using four integers, but that wouldn't look as cool
251 # Last two tokens are the destination
253 [x2,y2] = map(int, s[3:])
255 raise Exception("GIBBERISH \"" + str(result) + "\"") # Raise the alarm
257 # Move the piece (take opponent if possible)
258 self.update_move(x, y, x2, y2, sanity)
261 # Otherwise we will just assume a piece has been selected
263 type_index = int(s[2]) # We need to know which of the two types the piece is in; that's the third token
264 state = s[3] # The last token is a string identifying the type
266 raise Exception("GIBBERISH \"" + result + "\"") # Throw a hissy fit
270 self.update_select(x, y, type_index, state, sanity=sanity, deselect=deselect)
274 # Gets each piece that could reach the given square and the probability that it could reach that square
275 # Will include allied pieces that defend the attacker
276 def coverage(self, x, y, colour = None, reject_allied = True):
280 pieces = self.pieces["white"] + self.pieces["black"]
282 pieces = self.pieces[colour]
285 prob = self.probability_grid(p, reject_allied)[x][y]
287 result.update({p : prob})
296 # Associates each square with a probability that the piece could move into it
297 # Look, I'm doing all the hard work for you here...
298 def probability_grid(self, p, reject_allied = True):
300 result = [[0.0] * w for _ in range(h)]
301 if not isinstance(p, Piece):
304 if p.current_type != "unknown":
305 #sys.stderr.write(sys.argv[0] + ": " + str(p) + " moves " + str(self.possible_moves(p, reject_allied)) + "\n")
306 for point in self.possible_moves(p, reject_allied):
307 result[point[0]][point[1]] = 1.0
311 for i in range(len(p.types)):
313 prob = 1.0 / float(len(p.types))
314 if t == "unknown" or p.types[i][0] == '?':
316 for t2 in self.unrevealed_types[p.colour].keys():
317 total_types += self.unrevealed_types[p.colour][t2]
319 for t2 in self.unrevealed_types[p.colour].keys():
320 prob2 = float(self.unrevealed_types[p.colour][t2]) / float(total_types)
322 for point in self.possible_moves(p, reject_allied, state=t2):
323 result[point[0]][point[1]] += prob2 * prob
327 for point in self.possible_moves(p, reject_allied, state=t):
328 result[point[0]][point[1]] += prob
331 #p.current_type = "unknown"
334 def prob_is_type(self, p, state):
335 if p.current_type != 0:
336 if state == p.current_type:
343 for i in range(len(p.types)):
348 if t == "unknown" or p.types[i][0] == '?':
350 for t2 in self.unrevealed_types[p.colour].keys():
351 total_prob += self.unrevealed_types[p.colour][t2]
352 for t2 in self.unrevealed_types[p.colour].keys():
354 result += prob * float(self.unrevealed_types[p.colour][t2]) / float(total_prob)
358 # Get all squares that the piece could move into
359 # This is probably inefficient, but I looked at some sample chess games and they seem to actually do things this way
360 # reject_allied indicates whether squares occupied by allied pieces will be removed
361 # (set to false to check for defense)
362 def possible_moves(self, p, reject_allied = True, state=None):
364 raise Exception("SANITY: No piece")
368 if state != None and state != p.current_type:
369 old_type = p.current_type
370 p.current_type = state
371 result = self.possible_moves(p, reject_allied, state=None)
372 p.current_type = old_type
382 if p.current_type == "unknown":
383 raise Exception("SANITY: Unknown state for piece: "+str(p))
384 # The below commented out code causes things to break badly
389 # result += self.possible_moves(p)
390 #p.current_type = "unknown"
393 if p.current_type == "king":
394 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]]
395 elif p.current_type == "queen":
396 for d in [[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1]]:
397 result += self.scan(p.x, p.y, d[0], d[1])
398 elif p.current_type == "bishop":
399 for d in [[-1,-1],[-1,1],[1,-1],[1,1]]: # There's a reason why bishops move diagonally
400 result += self.scan(p.x, p.y, d[0], d[1])
401 elif p.current_type == "rook":
402 for d in [[-1,0],[1,0],[0,-1],[0,1]]:
403 result += self.scan(p.x, p.y, d[0], d[1])
404 elif p.current_type == "knight":
405 # I would use two lines, but I'm not sure how python likes that
406 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]]
407 elif p.current_type == "pawn":
408 if p.colour == "white":
410 # Pawn can't move forward into occupied square
411 if self.on_board(p.x, p.y-1) and self.grid[p.x][p.y-1] == None:
412 result = [[p.x,p.y-1]]
413 for f in [[p.x-1,p.y-1],[p.x+1,p.y-1]]:
414 if not self.on_board(f[0], f[1]):
416 if self.grid[f[0]][f[1]] != None: # Pawn can take diagonally
419 # Slightly embarrassing if the pawn jumps over someone on its first move...
420 if self.grid[p.x][p.y-1] == None and self.grid[p.x][p.y-2] == None:
421 result.append([p.x, p.y-2])
423 # Vice versa for the black pawn
424 if self.on_board(p.x, p.y+1) and self.grid[p.x][p.y+1] == None:
425 result = [[p.x,p.y+1]]
427 for f in [[p.x-1,p.y+1],[p.x+1,p.y+1]]:
428 if not self.on_board(f[0], f[1]):
430 if self.grid[f[0]][f[1]] != None:
431 #sys.stderr.write(sys.argv[0] + " : "+str(p) + " can take " + str(self.grid[f[0]][f[1]]) + "\n")
434 if self.grid[p.x][p.y+1] == None and self.grid[p.x][p.y+2] == None:
435 result.append([p.x, p.y+2])
437 #sys.stderr.write(sys.argv[0] + " : possible_moves for " + str(p) + " " + str(result) + "\n")
439 # Remove illegal moves
440 # Note: The result[:] creates a copy of result, so that the result.remove calls don't fuck things up
441 for point in result[:]:
443 if (point[0] < 0 or point[0] >= w) or (point[1] < 0 or point[1] >= h):
444 result.remove(point) # Remove locations outside the board
446 g = self.grid[point[0]][point[1]]
448 if g != None and (g.colour == p.colour and reject_allied == True):
449 result.remove(point) # Remove allied pieces
453 p.possible_moves = result
457 # Scans in a direction until it hits a piece, returns all squares in the line
458 # (includes the final square (which contains a piece), but not the original square)
459 def scan(self, x, y, vx, vy):
467 if not self.on_board(xx, yy):
471 g = self.grid[xx][yy]
477 # Returns "white", "black" or "DRAW" if the game should end
478 def end_condition(self):
479 if self.king["white"] == None:
480 if self.king["black"] == None:
481 return "DRAW" # This shouldn't happen
483 elif self.king["black"] == None:
485 elif len(self.pieces["white"]) == 1 and len(self.pieces["black"]) == 1:
487 elif self.max_moves != None and self.moves > self.max_moves:
492 # I typed the full statement about 30 times before writing this function...
493 def on_board(self, x, y):
494 return (x >= 0 and x < w) and (y >= 0 and y < h)
498 # Pushes a move temporarily
499 def push_move(self, piece, x, y):
500 target = self.grid[x][y]
501 self.move_stack.append([piece, target, piece.x, piece.y, x, y])
502 [piece.x, piece.y] = [x, y]
503 self.grid[x][y] = piece
504 self.grid[piece.x][piece.y] = None
506 for p in self.pieces["white"] + self.pieces["black"]:
507 p.possible_moves = None
511 #print str(self.move_stack)
512 [piece, target, x1, y1, x2, y2] = self.move_stack[len(self.move_stack)-1]
513 self.move_stack = self.move_stack[:-1]
516 self.grid[x1][y1] = piece
520 self.grid[x2][y2] = target
522 for p in self.pieces["white"] + self.pieces["black"]:
523 p.possible_moves = None