Commit ef3a2d18 authored by judge's avatar judge

Tried to fix borked permissions

parent 52068b63
File mode changed from 100755 to 100644
../../qchess/data/
\ No newline at end of file
NAME
qchess.py - Play quantum chess
SYNOPSIS
qchess.py [OPTIONS] [white] [black]
DESCRIPTION
An implementation of Quantum Chess as originally described and implemented here:
http://research.cs.queensu.ca/Parallel/QuantumChess/QuantumChess.html
Reimplemented for UCC::Progcomp 2013
http://progcomp.ucc.asn.au
IMPORTANT:
- This version does not implement castling or en passen rules.
- If a piece currently in a pawn state moves into the opposing back row, that state always becomes a queen.
- (The other state of the piece is unaffected).
ARGUMENTS
If no arguments are given, a window should appear asking you to pick each player.
Then the game will commence using default values.
white, black
Each of the two players in order. They need not be provided if graphics is enabled (default).
Any arguments that do not begin with a hyphen (-) are treated as the player arguments in the order they appear.
Player arguments that begin with '@' are treated as special players:
@human
A human player; if graphics are enabled, this players turns are made through the GUI
@network[:address]
A player over a network connection.
For example, if [email protected] wants to play [email protected]:
[email protected]:~$ ./qchess.py @network @human
[email protected]:~$ ./qchess.py @human @network:host1
IMPORTANT: Only ONE of the games should give the other's address.
@internal:name
An internal agent player
These agents run within the qchess program (unless there is a timeout setting... never mind).
Choices are:
AgentRandom - Makes random moves only
AgentBishop - Uses probability estimates and a min/max recursive (depth is only one) algorithm
- Will usually take a long time to run
OPTIONS
--help
Print this page
--graphics
Enable the GUI
If graphics are enabled (default), then the user will be prompted to choose any of the two players not supplied as arguments.
--no-graphics
Disable the GUI
--reveal
If graphics are enabled, the two states for pieces will always be shown, regardless of whether both states have been revealed.
Note that this switch only affects the GUI and does not provide any information to agent players.
If graphics are disabled, has no effect.
--file[=filename][:events]
Replay a game saved in file, or read from stdin if no filename given
If a number of events is supplied, the game will advance that many events before stopping.
If no players are given, the GUI will NOT ask for player selections.
The game will exit after the replay finishes. Events in the replay will be subject to the normal delay (see --delay).
If black and white players are supplied, the game will continue using those players.
In this case, there will be no delays between events in the replay (the game starts at the end of the replay)
(We hope that) this feature will be useful for comparing how different versions of an agent respond to the same situation.
--log[=filename]
Log moves to a file or stdout if no filename given
--delay[=time]
The game pauses between moves so that it can be followed by a human observer.
This option can be used to change the delay. If no time is given, the delay is disabled.
If graphics are enabled (default), the delay is 0.5s by default.
If graphics are disabled, there is no delay unless this option is used.
--timeout[=time]
Set the maximum time in seconds to wait before declaring an AI program unresponsive.
If no time is given, the timeout is disabled.
By default the timeout is disabled.
--blackout[=time]
Setting a blackout time will cause the display to become black if the mouse is not moved and no keys or buttons are pressed.
If no time is given, the blackout time is disabled.
By default the blackout is disabled.
This switch was introduced for entirely obscure purposes.
--classical
If this option is used, the game will treat pieces "classically", ie: as in standard chess.
Note that the game does not enforce rules related to check and checkmate.
--quantum
The game uses the quantum chess representation of pieces (default).
AUTHOR
Written for the UCC Programming Competition 2013 by Sam Moore.
UCC::Progcomp home page: http://progcomp.ucc.asn.au
REPORTING BUGS
Report bugs to [email protected]
Join IRC channel #progcomp on irc://irc.ucc.asn.au
COPYRIGHT
Copyright 2013 The University Computer Club, Inc.
Contact [email protected]
../../qchess/qchess.py
\ No newline at end of file
#!/usr/bin/python -u
import random
# I know using non-abreviated strings is inefficient, but this is python, who cares?
# Oh, yeah, this stores the number of pieces of each type in a normal chess game
piece_types = {"pawn" : 8, "bishop" : 2, "knight" : 2, "rook" : 2, "queen" : 1, "king" : 1, "unknown" : 0}
# Class to represent a quantum chess piece
class Piece():
def __init__(self, colour, x, y, types):
self.colour = colour # Colour (string) either "white" or "black"
self.x = x # x coordinate (0 - 8), none of this fancy 'a', 'b' shit here
self.y = y # y coordinate (0 - 8)
self.types = types # List of possible types the piece can be (should just be two)
self.current_type = "unknown" # Current type
self.choice = -1 # Index of the current type in self.types (-1 = unknown type)
self.last_state = None
self.move_pattern = None
self.coverage = None
self.possible_moves = {}
def init_from_copy(self, c):
self.colour = c.colour
self.x = c.x
self.y = c.y
self.types = c.types[:]
self.current_type = c.current_type
self.choice = c.choice
self.last_state = None
self.move_pattern = None
# Make a string for the piece (used for debug)
def __str__(self):
return str(self.colour) + " " + str(self.current_type) + " " + str(self.types) + " at " + str(self.x) + ","+str(self.y)
# Draw the piece in a pygame surface
def draw(self, window, grid_sz = [80,80], style="quantum"):
# First draw the image corresponding to self.current_type
img = images[self.colour][self.current_type]
rect = img.get_rect()
if style == "classical":
offset = [-rect.width/2, -rect.height/2]
else:
offset = [-rect.width/2,-3*rect.height/4]
window.blit(img, (self.x * grid_sz[0] + grid_sz[0]/2 + offset[0], self.y * grid_sz[1] + grid_sz[1]/2 + offset[1]))
if style == "classical":
return
# Draw the two possible types underneath the current_type image
for i in range(len(self.types)):
if always_reveal_states == True or self.types[i][0] != '?':
if self.types[i][0] == '?':
img = small_images[self.colour][self.types[i][1:]]
else:
img = small_images[self.colour][self.types[i]]
else:
img = small_images[self.colour]["unknown"] # If the type hasn't been revealed, show a placeholder
rect = img.get_rect()
offset = [-rect.width/2,-rect.height/2]
if i == 0:
target = (self.x * grid_sz[0] + grid_sz[0]/5 + offset[0], self.y * grid_sz[1] + 3*grid_sz[1]/4 + offset[1])
else:
target = (self.x * grid_sz[0] + 4*grid_sz[0]/5 + offset[0], self.y * grid_sz[1] + 3*grid_sz[1]/4 + offset[1])
window.blit(img, target) # Blit shit
# Collapses the wave function!
def select(self):
if self.current_type == "unknown" or not self.choice in [0,1]:
self.choice = random.randint(0,1)
if self.types[self.choice][0] == '?':
self.types[self.choice] = self.types[self.choice][1:]
self.current_type = self.types[self.choice]
return self.choice
# Uncollapses (?) the wave function!
def deselect(self):
#print "Deselect called"
if (self.x + self.y) % 2 != 0:
if (self.types[0] != self.types[1]) or (self.types[0][0] == '?' or self.types[1][0] == '?'):
self.current_type = "unknown"
self.choice = -1
else:
self.choice = 0 # Both the two types are the same
# The sad moment when you realise that you do not understand anything about a subject you studied for 4 years...
# --- piece.py --- #
[w,h] = [8,8] # Width and height of board(s)
always_reveal_states = False
# Class to represent a quantum chess board
class Board():
# Initialise; if master=True then the secondary piece types are assigned
# Otherwise, they are left as unknown
# So you can use this class in Agent programs, and fill in the types as they are revealed
def __init__(self, style="agent"):
self.style = style
self.pieces = {"white" : [], "black" : []}
self.grid = [[None] * w for _ in range(h)] # 2D List (you can get arrays in python, somehow, but they scare me)
self.unrevealed_types = {"white" : piece_types.copy(), "black" : piece_types.copy()}
self.king = {"white" : None, "black" : None} # We need to keep track of the king, because he is important
self.max_moves = None
self.moves = 0
self.move_stack = []
for c in ["black", "white"]:
del self.unrevealed_types[c]["unknown"]
if style == "empty":
return
# Add all the pieces with known primary types
for i in range(0, 2):
s = ["black", "white"][i]
c = self.pieces[s]
y = [0, h-1][i]
c.append(Piece(s, 0, y, ["rook"]))
c.append(Piece(s, 1, y, ["knight"]))
c.append(Piece(s, 2, y, ["bishop"]))
k = Piece(s, 3, y, ["king", "king"]) # There can only be one ruler!
k.current_type = "king"
self.king[s] = k
c.append(k)
c.append(Piece(s, 4, y, ["queen"])) # Apparently he may have multiple wives though.
c.append(Piece(s, 5, y, ["bishop"]))
c.append(Piece(s, 6, y, ["knight"]))
c.append(Piece(s, 7, y, ["rook"]))
if y == 0:
y += 1
else:
y -= 1
# Lots of pawn
for x in range(0, w):
c.append(Piece(s, x, y, ["pawn"]))
types_left = {}
types_left.update(piece_types)
del types_left["king"] # We don't want one of these randomly appearing (although it might make things interesting...)
del types_left["unknown"] # We certainly don't want these!
for piece in c:
# Add to grid
self.grid[piece.x][piece.y] = piece
if len(piece.types) > 1:
continue
if style == "agent": # Assign placeholder "unknown" secondary type
piece.types.append("unknown")
continue
elif style == "quantum":
# The master allocates the secondary types
choice = types_left.keys()[random.randint(0, len(types_left.keys())-1)]
types_left[choice] -= 1
if types_left[choice] <= 0:
del types_left[choice]
piece.types.append('?' + choice)
elif style == "classical":
piece.types.append(piece.types[0])
piece.current_type = piece.types[0]
piece.choice = 0
def clone(self):
newboard = Board(master = False)
newpieces = newboard.pieces["white"] + newboard.pieces["black"]
mypieces = self.pieces["white"] + self.pieces["black"]
for i in range(len(mypieces)):
newpieces[i].init_from_copy(mypieces[i])
# Reset the board from a string
def reset_board(self, s):
self.pieces = {"white" : [], "black" : []}
self.king = {"white" : None, "black" : None}
self.grid = [[None] * w for _ in range(h)]
for x in range(w):
for y in range(h):
self.grid[x][y] = None
for line in s.split("\n"):
if line == "":
continue
if line[0] == "#":
continue
tokens = line.split(" ")
[x, y] = map(int, tokens[len(tokens)-1].split(","))
current_type = tokens[1]
types = map(lambda e : e.strip(" '[],"), line.split('[')[1].split(']')[0].split(','))
target = Piece(tokens[0], x, y, types)
target.current_type = current_type
try:
target.choice = types.index(current_type)
except:
target.choice = -1
self.pieces[tokens[0]].append(target)
if target.current_type == "king":
self.king[tokens[0]] = target
self.grid[x][y] = target
def display_grid(self, window = None, grid_sz = [80,80]):
if window == None:
return # I was considering implementing a text only display, then I thought "Fuck that"
# The indentation is getting seriously out of hand...
for x in range(0, w):
for y in range(0, h):
if (x + y) % 2 == 0:
c = pygame.Color(200,200,200)
else:
c = pygame.Color(64,64,64)
pygame.draw.rect(window, c, (x*grid_sz[0], y*grid_sz[1], (x+1)*grid_sz[0], (y+1)*grid_sz[1]))
def display_pieces(self, window = None, grid_sz = [80,80]):
if window == None:
return
for p in self.pieces["white"] + self.pieces["black"]:
p.draw(window, grid_sz, self.style)
# Draw the board in a pygame window
def display(self, window = None):
self.display_grid(window)
self.display_pieces(window)
def verify(self):
for x in range(w):
for y in range(h):
if self.grid[x][y] == None:
continue
if (self.grid[x][y].x != x or self.grid[x][y].y != y):
raise Exception(sys.argv[0] + ": MISMATCH " + str(self.grid[x][y]) + " should be at " + str(x) + "," + str(y))
# Select a piece on the board (colour is the colour of whoever is doing the selecting)
def select(self, x,y, colour=None):
if not self.on_board(x, y): # Get on board everyone!
raise Exception("BOUNDS " + str(x) + ","+str(y))
piece = self.grid[x][y]
if piece == None:
raise Exception("EMPTY")
if colour != None and piece.colour != colour:
raise Exception("COLOUR " + str(piece.colour) + " not " + str(colour))
# I'm not quite sure why I made this return a string, but screw logical design
return str(x) + " " + str(y) + " " + str(piece.select()) + " " + str(piece.current_type)
# Update the board when a piece has been selected
# "type" is apparently reserved, so I'll use "state"
def update_select(self, x, y, type_index, state, sanity=True, deselect=True):
#debug(str(self) + " update_select called")
piece = self.grid[x][y]
if piece.types[type_index] == "unknown":
if not state in self.unrevealed_types[piece.colour].keys() and sanity == True:
raise Exception("SANITY: Too many " + piece.colour + " " + state + "s")
self.unrevealed_types[piece.colour][state] -= 1
if self.unrevealed_types[piece.colour][state] <= 0:
del self.unrevealed_types[piece.colour][state]
piece.types[type_index] = state
piece.current_type = state
if deselect == True and len(self.possible_moves(piece)) <= 0:
piece.deselect() # Piece can't move; deselect it
# Piece needs to recalculate moves
piece.possible_moves = None
# Update the board when a piece has been moved
def update_move(self, x, y, x2, y2, sanity=True):
#debug(str(self) + " update_move called \""+str(x)+ " " + str(y) + " -> " + str(x2) + " " + str(y2) + "\"")
piece = self.grid[x][y]
#print "Moving " + str(x) + "," + str(y) + " to " + str(x2) + "," + str(y2) + "; possible_moves are " + str(self.possible_moves(piece))
if not [x2,y2] in self.possible_moves(piece) and sanity == True:
raise Exception("ILLEGAL move " + str(x2)+","+str(y2))
self.grid[x][y] = None
taken = self.grid[x2][y2]
if taken != None:
if taken.current_type == "king":
self.king[taken.colour] = None
self.pieces[taken.colour].remove(taken)
self.grid[x2][y2] = piece
piece.x = x2
piece.y = y2
# If the piece is a pawn, and it reaches the final row, it becomes a queen
# I know you are supposed to get a choice
# But that would be effort
if piece.current_type == "pawn" and ((piece.colour == "white" and piece.y == 0) or (piece.colour == "black" and piece.y == h-1)):
if self.style == "classical":
piece.types[0] = "queen"
piece.types[1] = "queen"
else:
piece.types[piece.choice] = "queen"
piece.current_type = "queen"
piece.deselect() # Uncollapse (?) the wavefunction!
self.moves += 1
# All other pieces need to recalculate moves
for p in self.pieces["white"] + self.pieces["black"]:
p.possible_moves = None
#self.verify()
# Update the board from a string
# Guesses what to do based on the format of the string
def update(self, result, sanity=True, deselect=True):
#debug(str(self) + " update called \""+str(result)+"\"")
# String always starts with 'x y'
try:
s = result.split(" ")
[x,y] = map(int, s[0:2])
except:
raise Exception("GIBBERISH \""+ str(result) + "\"") # Raise expectations
piece = self.grid[x][y]
if piece == None and sanity == True:
raise Exception("EMPTY " + str(x) + " " + str(y))
# If a piece is being moved, the third token is '->'
# We could get away with just using four integers, but that wouldn't look as cool
if "->" in s:
# Last two tokens are the destination
try:
[x2,y2] = map(int, s[3:])
except:
raise Exception("GIBBERISH \"" + str(result) + "\"") # Raise the alarm
# Move the piece (take opponent if possible)
self.update_move(x, y, x2, y2, sanity)
else:
# Otherwise we will just assume a piece has been selected
try:
type_index = int(s[2]) # We need to know which of the two types the piece is in; that's the third token
state = s[3] # The last token is a string identifying the type
except:
raise Exception("GIBBERISH \"" + result + "\"") # Throw a hissy fit
# Select the piece
self.update_select(x, y, type_index, state, sanity=sanity, deselect=deselect)
return result
# Gets each piece that could reach the given square and the probability that it could reach that square
# Will include allied pieces that defend the attacker
def coverage(self, x, y, colour = None, reject_allied = True):
result = {}
if colour == None:
pieces = self.pieces["white"] + self.pieces["black"]
else:
pieces = self.pieces[colour]
for p in pieces:
prob = self.probability_grid(p, reject_allied)[x][y]
if prob > 0:
result.update({p : prob})
#self.verify()
return result
# Associates each square with a probability that the piece could move into it
# Look, I'm doing all the hard work for you here...
def probability_grid(self, p, reject_allied = True):
result = [[0.0] * w for _ in range(h)]
if not isinstance(p, Piece):
return result
if p.current_type != "unknown":
#sys.stderr.write(sys.argv[0] + ": " + str(p) + " moves " + str(self.possible_moves(p, reject_allied)) + "\n")
for point in self.possible_moves(p, reject_allied):
result[point[0]][point[1]] = 1.0
return result
for i in range(len(p.types)):
t = p.types[i]
prob = 1.0 / float(len(p.types))
if t == "unknown" or p.types[i][0] == '?':
total_types = 0
for t2 in self.unrevealed_types[p.colour].keys():
total_types += self.unrevealed_types[p.colour][t2]
for t2 in self.unrevealed_types[p.colour].keys():
prob2 = float(self.unrevealed_types[p.colour][t2]) / float(total_types)
#p.current_type = t2
for point in self.possible_moves(p, reject_allied, state=t2):
result[point[0]][point[1]] += prob2 * prob
else:
#p.current_type = t
for point in self.possible_moves(p, reject_allied, state=t):
result[point[0]][point[1]] += prob
#self.verify()
#p.current_type = "unknown"
return result
def prob_is_type(self, p, state):
prob = 0.5
result = 0
for i in range(len(p.types)):
t = p.types[i]
if t == state:
result += prob
continue
if t == "unknown" or p.types[i][0] == '?':
total_prob = 0
for t2 in self.unrevealed_types[p.colour].keys():
total_prob += self.unrevealed_types[p.colour][t2]
for t2 in self.unrevealed_types[p.colour].keys():
if t2 == state:
result += prob * float(self.unrevealed_types[p.colour][t2]) / float(total_prob)
# Get all squares that the piece could move into