From: Sam Moore Date: Mon, 28 Jan 2013 14:54:56 +0000 (+0800) Subject: Self inflicted wounds using cx_freeze X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=444244d5c7698bb7861cdb7c0ec6bfb0e8cebfb7;p=progcomp2013.git Self inflicted wounds using cx_freeze The pain is freezing me. I've been trying to freeze the qchess.py script into a win32 binary, so that windows people don't need to install python. Except I've realised that they will probably install python to enter anyway, since it is the easiest language. HER DURP. I mostly got cx_freeze to work. Fun tip: Unicode strings cause segmentation faults So wrote hacky script to save unicode characters to png files, then use png files in frozen versions. cx_freeze in linux worked amazingly well. For windows... I installed python and cx_freeze in wine... this was probably a mistake, because all the dll files were missing. But I found all the dll files, so that's fixed. qchess.exe works in wine, but can't load python agent programs... because windows doesn't understand #! Well, fuck. Also annoying bug with Tk open file dialog mouse which I cannot be bothered chasing. Tried hacky batch file, but it doesn't work in wine. Will try in Windows 7 soon. PS: Also changed directory structure somewhat. Ironically, using cx_freeze doesn't result in freezing programs! --- diff --git a/agents/bishop.py b/agents/bishop.py index 8c88a1f..3c7738e 100755 --- a/agents/bishop.py +++ b/agents/bishop.py @@ -291,8 +291,8 @@ def main(argv): agent = Agent(argv[0], colour) # Creates your agent - graphics = AgentGraphics(agent.board, title="Agent Bishop (" + str(colour) + ") - DEBUG VIEW") - graphics.start() + #graphics = AgentGraphics(agent.board, title="Agent Bishop (" + str(colour) + ") - DEBUG VIEW") + #graphics.start() # Plays quantum chess using your agent while True: @@ -312,8 +312,8 @@ def main(argv): else: agent.update(line) # Updates agent.board - graphics.stop() - graphics.join() + #graphics.stop() + #graphics.join() return 0 # Don't touch this diff --git a/agents/win32_bishop.bat b/agents/win32_bishop.bat new file mode 100755 index 0000000..92d9440 --- /dev/null +++ b/agents/win32_bishop.bat @@ -0,0 +1,4 @@ +:: Replace the path to the python interpreter if necessary +:: Picking this in qchess *should* work for running bishop.py +:: I hope +C:\\Python27\\python.exe bishop.py diff --git a/qchess/Makefile b/qchess/Makefile index 2ad3fef..9da5885 100644 --- a/qchess/Makefile +++ b/qchess/Makefile @@ -1,16 +1,40 @@ -# Makefile that builds qchess.py from the component files +# Makefile that builds qchess for UCC::Progcomp2013 -TARGET = qchess.py -COMPONENTS = piece.py board.py player.py network.py thread_util.py game.py graphics.py main.py -#COMPONENTS=$(shell ls *.py | tr '\t' '\n' | grep -v $(TARGET)) +SCRIPT=qchess.py +DLL_PATH=win32_dll -$(TARGET) : $(COMPONENTS) - echo "#!/usr/bin/python -u" > $(TARGET) - for f in $(COMPONENTS); do echo "# +++ $$f +++ #" >> $(TARGET); cat $$f >> $(TARGET); echo "# --- $$f --- #" >> $(TARGET); done - echo "# EOF - created from make on $$(date)" >> $(TARGET) - chmod u+x $(TARGET) +all : python_native frozen + +frozen : win32_frozen linux64_frozen + cd build; for d in $$(ls); do zip -r $$d.zip $$d; rm -r $$d; done + +python_native : + make -C src + mv src/$(SCRIPT) ./ + +images : + cd tools; python image_builder.py + +win32_frozen : $(SCRIPT) images + sed -i 's:create_images(grid_sz):load_images():g' $(SCRIPT) + wine "C:\\Python27\\python.exe" build.py build + for d in $$(ls $(DLL_PATH)); do cp $(DLL_PATH)/$$d build/exe.win32-2.7/; done + for b in $$(ls build); do if [ -d build/$$b ]; then cp -r data build/$$b; fi; done + sed -i 's:load_images():create_images(grid_sz):g' $(SCRIPT) + +linux64_frozen : $(SCRIPT) images + sed -i 's:create_images(grid_sz):load_images():g' $(SCRIPT) + python build.py build + for b in $$(ls build); do if [ -d build/$$b ]; then cp -r data build/$$b; fi; done + sed -i 's:load_images():create_images(grid_sz):g' $(SCRIPT) + + clean : + make -C src clean + rm -f $(SCRIPT) rm -f *~ - rm -f *.pyc - rm -f $(TARGET) + rm -rf build + rm -rf data/images + rm -f tools/*~ + rm -f tools/*.pyc diff --git a/qchess/board.py b/qchess/board.py deleted file mode 100644 index 0d96d1a..0000000 --- a/qchess/board.py +++ /dev/null @@ -1,403 +0,0 @@ -[w,h] = [8,8] # Width and height of board(s) - -# 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 - for c in ["black", "white"]: - del self.unrevealed_types[c]["unknown"] - - # 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.types_revealed[1] = True - 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.types_revealed[1] = True - 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]) - - - 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") - - 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): - piece = self.grid[x][y] - if piece.types[type_index] == "unknown": - if not state in self.unrevealed_types[piece.colour].keys(): - 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.types_revealed[type_index] = True - piece.current_type = state - - if len(self.possible_moves(piece)) <= 0: - piece.deselect() # Piece can't move; deselect it - - # Update the board when a piece has been moved - def update_move(self, x, y, x2, y2): - piece = self.grid[x][y] - 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.verify() - - # Update the board from a string - # Guesses what to do based on the format of the string - def update(self, result): - #print "Update called with \"" + 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: - raise Exception("EMPTY") - - # 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) - - 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) - - 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 = 0.5 - if t == "unknown" or p.types_revealed[i] == False: - 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): - result[point[0]][point[1]] += prob2 * prob - - else: - p.current_type = t - for point in self.possible_moves(p, reject_allied): - 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_revealed[i] == False: - 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 - # This is probably inefficient, but I looked at some sample chess games and they seem to actually do things this way - # reject_allied indicates whether squares occupied by allied pieces will be removed - # (set to false to check for defense) - def possible_moves(self, p, reject_allied = True): - result = [] - if p == None: - return result - - - if p.current_type == "unknown": - raise Exception("SANITY: Piece state unknown") - # The below commented out code causes things to break badly - #for t in p.types: - # if t == "unknown": - # continue - # p.current_type = t - # result += self.possible_moves(p) - #p.current_type = "unknown" - #return result - - if p.current_type == "king": - 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]] - elif p.current_type == "queen": - for d in [[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1]]: - result += self.scan(p.x, p.y, d[0], d[1]) - elif p.current_type == "bishop": - for d in [[-1,-1],[-1,1],[1,-1],[1,1]]: # There's a reason why bishops move diagonally - result += self.scan(p.x, p.y, d[0], d[1]) - elif p.current_type == "rook": - for d in [[-1,0],[1,0],[0,-1],[0,1]]: - result += self.scan(p.x, p.y, d[0], d[1]) - elif p.current_type == "knight": - # I would use two lines, but I'm not sure how python likes that - 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]] - elif p.current_type == "pawn": - if p.colour == "white": - - # Pawn can't move forward into occupied square - if self.on_board(p.x, p.y-1) and self.grid[p.x][p.y-1] == None: - result = [[p.x,p.y-1]] - for f in [[p.x-1,p.y-1],[p.x+1,p.y-1]]: - if not self.on_board(f[0], f[1]): - continue - if self.grid[f[0]][f[1]] != None: # Pawn can take diagonally - result.append(f) - if p.y == h-2: - # Slightly embarrassing if the pawn jumps over someone on its first move... - if self.grid[p.x][p.y-1] == None and self.grid[p.x][p.y-2] == None: - result.append([p.x, p.y-2]) - else: - # Vice versa for the black pawn - if self.on_board(p.x, p.y+1) and self.grid[p.x][p.y+1] == None: - result = [[p.x,p.y+1]] - - for f in [[p.x-1,p.y+1],[p.x+1,p.y+1]]: - if not self.on_board(f[0], f[1]): - continue - if self.grid[f[0]][f[1]] != None: - #sys.stderr.write(sys.argv[0] + " : "+str(p) + " can take " + str(self.grid[f[0]][f[1]]) + "\n") - result.append(f) - if p.y == 1: - if self.grid[p.x][p.y+1] == None and self.grid[p.x][p.y+2] == None: - result.append([p.x, p.y+2]) - - #sys.stderr.write(sys.argv[0] + " : possible_moves for " + str(p) + " " + str(result) + "\n") - - # Remove illegal moves - # Note: The result[:] creates a copy of result, so that the result.remove calls don't fuck things up - for point in result[:]: - - if (point[0] < 0 or point[0] >= w) or (point[1] < 0 or point[1] >= h): - result.remove(point) # Remove locations outside the board - continue - g = self.grid[point[0]][point[1]] - - if g != None and (g.colour == p.colour and reject_allied == True): - result.remove(point) # Remove allied pieces - - self.verify() - return result - - - # Scans in a direction until it hits a piece, returns all squares in the line - # (includes the final square (which contains a piece), but not the original square) - def scan(self, x, y, vx, vy): - p = [] - - xx = x - yy = y - while True: - xx += vx - yy += vy - if not self.on_board(xx, yy): - break - if not [xx,yy] in p: - p.append([xx, yy]) - g = self.grid[xx][yy] - if g != None: - return p - - return p - - - - # I typed the full statement about 30 times before writing this function... - def on_board(self, x, y): - return (x >= 0 and x < w) and (y >= 0 and y < h) diff --git a/qchess/build.py b/qchess/build.py new file mode 100644 index 0000000..9d854dd --- /dev/null +++ b/qchess/build.py @@ -0,0 +1,4 @@ +import sys +from cx_Freeze import setup, Executable + +setup(name="qchess", executables=[Executable("qchess.py")]) diff --git a/qchess/build/exe.linux-x86_64-2.7.zip b/qchess/build/exe.linux-x86_64-2.7.zip new file mode 100644 index 0000000..41103d1 Binary files /dev/null and b/qchess/build/exe.linux-x86_64-2.7.zip differ diff --git a/qchess/build/exe.win32-2.7.zip b/qchess/build/exe.win32-2.7.zip new file mode 100644 index 0000000..4ef51c0 Binary files /dev/null and b/qchess/build/exe.win32-2.7.zip differ diff --git a/qchess/game.py b/qchess/game.py deleted file mode 100644 index efca0f8..0000000 --- a/qchess/game.py +++ /dev/null @@ -1,121 +0,0 @@ - -# A thread that runs the game -class GameThread(StoppableThread): - def __init__(self, board, players): - StoppableThread.__init__(self) - self.board = board - self.players = players - self.state = {"turn" : None} # The game state - self.error = 0 # Whether the thread exits with an error - self.lock = threading.RLock() #lock for access of self.state - self.cond = threading.Condition() # conditional for some reason, I forgot - self.final_result = "" - - # Run the game (run in new thread with start(), run in current thread with run()) - def run(self): - result = "" - while not self.stopped(): - - for p in self.players: - with self.lock: - if isinstance(p, NetworkSender): - self.state["turn"] = p.base_player # "turn" contains the player who's turn it is - else: - self.state["turn"] = p - try: - [x,y] = p.select() # Player selects a square - if self.stopped(): - break - - - - - result = self.board.select(x, y, colour = p.colour) - for p2 in self.players: - p2.update(result) # Inform players of what happened - - - - target = self.board.grid[x][y] - if isinstance(graphics, GraphicsThread): - with graphics.lock: - graphics.state["moves"] = self.board.possible_moves(target) - graphics.state["select"] = target - - time.sleep(turn_delay) - - - if len(self.board.possible_moves(target)) == 0: - #print "Piece cannot move" - target.deselect() - if isinstance(graphics, GraphicsThread): - with graphics.lock: - graphics.state["moves"] = None - graphics.state["select"] = None - graphics.state["dest"] = None - continue - - try: - [x2,y2] = p.get_move() # Player selects a destination - except: - self.stop() - - if self.stopped(): - break - - result = self.board.update_move(x, y, x2, y2) - for p2 in self.players: - p2.update(str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2)) # Inform players of what happened - - if isinstance(graphics, GraphicsThread): - with graphics.lock: - graphics.state["moves"] = [[x2,y2]] - - time.sleep(turn_delay) - - if isinstance(graphics, GraphicsThread): - with graphics.lock: - graphics.state["select"] = None - graphics.state["dest"] = None - graphics.state["moves"] = None - - # Commented out exception stuff for now, because it makes it impossible to tell if I made an IndentationError somewhere - except Exception,e: - result = e.message - #sys.stderr.write(result + "\n") - - self.stop() - with self.lock: - self.final_result = self.state["turn"].colour + " " + e.message - - if self.board.king["black"] == None: - if self.board.king["white"] == None: - with self.lock: - self.final_result = self.state["turn"].colour + " DRAW" - else: - with self.lock: - self.final_result = "white" - self.stop() - elif self.board.king["white"] == None: - with self.lock: - self.final_result = "black" - self.stop() - - - if self.stopped(): - break - - - for p2 in self.players: - p2.quit(self.final_result) - - graphics.stop() - - - - -def opponent(colour): - if colour == "white": - return "black" - else: - return "white" diff --git a/qchess/graphics.py b/qchess/graphics.py deleted file mode 100644 index 8e2a5fa..0000000 --- a/qchess/graphics.py +++ /dev/null @@ -1,431 +0,0 @@ -import pygame - -# Dictionary that stores the unicode character representations of the different pieces -# Chess was clearly the reason why unicode was invented -# For some reason none of the pygame chess implementations I found used them! -piece_char = {"white" : {"king" : u'\u2654', - "queen" : u'\u2655', - "rook" : u'\u2656', - "bishop" : u'\u2657', - "knight" : u'\u2658', - "pawn" : u'\u2659', - "unknown" : '?'}, - "black" : {"king" : u'\u265A', - "queen" : u'\u265B', - "rook" : u'\u265C', - "bishop" : u'\u265D', - "knight" : u'\u265E', - "pawn" : u'\u265F', - "unknown" : '?'}} - -images = {"white" : {}, "black" : {}} -small_images = {"white" : {}, "black" : {}} - -# A thread to make things pretty -class GraphicsThread(StoppableThread): - def __init__(self, board, title = "UCC::Progcomp 2013 - QChess", grid_sz = [80,80]): - StoppableThread.__init__(self) - - self.board = board - pygame.init() - self.window = pygame.display.set_mode((grid_sz[0] * w, grid_sz[1] * h)) - pygame.display.set_caption(title) - self.grid_sz = grid_sz[:] - self.state = {"select" : None, "dest" : None, "moves" : None, "overlay" : None, "coverage" : None} - self.error = 0 - self.lock = threading.RLock() - self.cond = threading.Condition() - - # Get the font sizes - l_size = 5*(self.grid_sz[0] / 8) - s_size = 3*(self.grid_sz[0] / 8) - for p in piece_types.keys(): - c = "black" - images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", l_size).render(piece_char[c][p], True,(0,0,0))}) - small_images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", s_size).render(piece_char[c][p],True,(0,0,0))}) - c = "white" - - images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", l_size+1).render(piece_char["black"][p], True,(255,255,255))}) - images[c][p].blit(pygame.font.Font("data/DejaVuSans.ttf", l_size).render(piece_char[c][p], True,(0,0,0)),(0,0)) - small_images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", s_size+1).render(piece_char["black"][p],True,(255,255,255))}) - small_images[c][p].blit(pygame.font.Font("data/DejaVuSans.ttf", s_size).render(piece_char[c][p],True,(0,0,0)),(0,0)) - - - - - - # On the run from the world - def run(self): - - while not self.stopped(): - - self.board.display_grid(window = self.window, grid_sz = self.grid_sz) # Draw the board - - self.overlay() - - self.board.display_pieces(window = self.window, grid_sz = self.grid_sz) # Draw the board - - pygame.display.flip() - - for event in pygame.event.get(): - if event.type == pygame.QUIT: - if isinstance(game, GameThread): - with game.lock: - game.final_result = "" - if game.state["turn"] != None: - game.final_result = game.state["turn"].colour + " " - game.final_result += "terminated" - game.stop() - self.stop() - break - elif event.type == pygame.MOUSEBUTTONDOWN: - self.mouse_down(event) - elif event.type == pygame.MOUSEBUTTONUP: - self.mouse_up(event) - - - - - - - self.message("Game ends, result \""+str(game.final_result) + "\"") - time.sleep(1) - - # Wake up anyone who is sleeping - self.cond.acquire() - self.cond.notify() - self.cond.release() - - pygame.quit() # Time to say goodbye - - # Mouse release event handler - def mouse_up(self, event): - if event.button == 3: - with self.lock: - self.state["overlay"] = None - elif event.button == 2: - with self.lock: - self.state["coverage"] = None - - # Mouse click event handler - def mouse_down(self, event): - if event.button == 1: - m = [event.pos[i] / self.grid_sz[i] for i in range(2)] - if isinstance(game, GameThread): - with game.lock: - p = game.state["turn"] - else: - p = None - - - if isinstance(p, HumanPlayer): - with self.lock: - s = self.board.grid[m[0]][m[1]] - select = self.state["select"] - if select == None: - if s != None and s.colour != p.colour: - self.message("Wrong colour") # Look at all this user friendliness! - time.sleep(1) - return - # Notify human player of move - self.cond.acquire() - with self.lock: - self.state["select"] = s - self.state["dest"] = None - self.cond.notify() - self.cond.release() - return - - if select == None: - return - - - if self.state["moves"] == None: - return - - if not m in self.state["moves"]: - self.message("Illegal Move") # I still think last year's mouse interface was adequate - time.sleep(2) - return - - with self.lock: - if self.state["dest"] == None: - self.cond.acquire() - self.state["dest"] = m - self.state["select"] = None - self.state["moves"] = None - self.cond.notify() - self.cond.release() - elif event.button == 3: - m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))] - if isinstance(game, GameThread): - with game.lock: - p = game.state["turn"] - else: - p = None - - - if isinstance(p, HumanPlayer): - with self.lock: - self.state["overlay"] = self.board.probability_grid(self.board.grid[m[0]][m[1]]) - - elif event.button == 2: - m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))] - if isinstance(game, GameThread): - with game.lock: - p = game.state["turn"] - else: - p = None - - - if isinstance(p, HumanPlayer): - with self.lock: - self.state["coverage"] = self.board.coverage(m[0], m[1], None, self.state["select"]) - - # Draw the overlay - def overlay(self): - - square_img = pygame.Surface((self.grid_sz[0], self.grid_sz[1]),pygame.SRCALPHA) # A square image - # Draw square over the selected piece - with self.lock: - select = self.state["select"] - if select != None: - mp = [self.grid_sz[i] * [select.x, select.y][i] for i in range(len(self.grid_sz))] - square_img.fill(pygame.Color(0,255,0,64)) - self.window.blit(square_img, mp) - # If a piece is selected, draw all reachable squares - # (This quality user interface has been patented) - with self.lock: - m = self.state["moves"] - if m != None: - square_img.fill(pygame.Color(255,0,0,128)) # Draw them in blood red - for move in m: - mp = [self.grid_sz[i] * move[i] for i in range(2)] - self.window.blit(square_img, mp) - # If a piece is overlayed, show all squares that it has a probability to reach - with self.lock: - m = self.state["overlay"] - if m != None: - for x in range(w): - for y in range(h): - if m[x][y] > 0.0: - mp = [self.grid_sz[i] * [x,y][i] for i in range(2)] - square_img.fill(pygame.Color(255,0,255,int(m[x][y] * 128))) # Draw in purple - self.window.blit(square_img, mp) - font = pygame.font.Font(None, 14) - text = font.render("{0:.2f}".format(round(m[x][y],2)), 1, pygame.Color(0,0,0)) - self.window.blit(text, mp) - - # If a square is selected, highlight all pieces that have a probability to reach it - with self.lock: - m = self.state["coverage"] - if m != None: - for p in m: - mp = [self.grid_sz[i] * [p.x,p.y][i] for i in range(2)] - square_img.fill(pygame.Color(0,255,255, int(m[p] * 196))) # Draw in pale blue - self.window.blit(square_img, mp) - font = pygame.font.Font(None, 14) - text = font.render("{0:.2f}".format(round(m[p],2)), 1, pygame.Color(0,0,0)) - self.window.blit(text, mp) - # Draw a square where the mouse is - # This also serves to indicate who's turn it is - - if isinstance(game, GameThread): - with game.lock: - turn = game.state["turn"] - else: - turn = None - - if isinstance(turn, HumanPlayer): - mp = [self.grid_sz[i] * int(pygame.mouse.get_pos()[i] / self.grid_sz[i]) for i in range(2)] - square_img.fill(pygame.Color(0,0,255,128)) - if turn.colour == "white": - c = pygame.Color(255,255,255) - else: - c = pygame.Color(0,0,0) - pygame.draw.rect(square_img, c, (0,0,self.grid_sz[0], self.grid_sz[1]), self.grid_sz[0]/10) - self.window.blit(square_img, mp) - - # Message in a bottle - def message(self, string, pos = None, colour = None, font_size = 32): - font = pygame.font.Font(None, font_size) - if colour == None: - colour = pygame.Color(0,0,0) - - text = font.render(string, 1, colour) - - - s = pygame.Surface((text.get_width(), text.get_height()), pygame.SRCALPHA) - s.fill(pygame.Color(128,128,128)) - - tmp = self.window.get_size() - - if pos == None: - pos = (tmp[0] / 2 - text.get_width() / 2, tmp[1] / 3 - text.get_height()) - else: - pos = (pos[0]*text.get_width() + tmp[0] / 2 - text.get_width() / 2, pos[1]*text.get_height() + tmp[1] / 3 - text.get_height()) - - - rect = (pos[0], pos[1], text.get_width(), text.get_height()) - - pygame.draw.rect(self.window, pygame.Color(0,0,0), pygame.Rect(rect), 1) - self.window.blit(s, pos) - self.window.blit(text, pos) - - pygame.display.flip() - - def getstr(self, prompt = None): - s = pygame.Surface((self.window.get_width(), self.window.get_height())) - s.blit(self.window, (0,0)) - result = "" - - while True: - #print "LOOP" - if prompt != None: - self.message(prompt) - self.message(result, pos = (0, 1)) - - pygame.event.pump() - for event in pygame.event.get(): - if event.type == pygame.QUIT: - return None - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_BACKSPACE: - result = result[0:len(result)-1] - self.window.blit(s, (0,0)) # Revert the display - continue - - - try: - if event.unicode == '\r': - return result - - result += str(event.unicode) - except: - continue - - - # Function to pick a button - def SelectButton(self, choices, prompt = None, font_size=32): - self.board.display_grid(self.window, self.grid_sz) - if prompt != None: - self.message(prompt) - font = pygame.font.Font(None, font_size) - targets = [] - sz = self.window.get_size() - - - for i in range(len(choices)): - c = choices[i] - - text = font.render(c, 1, pygame.Color(0,0,0)) - p = (sz[0] / 2 - (1.5*text.get_width())/2, sz[1] / 2 +(i-1)*text.get_height()+(i*2)) - targets.append((p[0], p[1], p[0] + 1.5*text.get_width(), p[1] + text.get_height())) - - while True: - mp =pygame.mouse.get_pos() - for i in range(len(choices)): - c = choices[i] - if mp[0] > targets[i][0] and mp[0] < targets[i][2] and mp[1] > targets[i][1] and mp[1] < targets[i][3]: - font_colour = pygame.Color(255,0,0) - box_colour = pygame.Color(0,0,255,128) - else: - font_colour = pygame.Color(0,0,0) - box_colour = pygame.Color(128,128,128) - - text = font.render(c, 1, font_colour) - s = pygame.Surface((text.get_width()*1.5, text.get_height()), pygame.SRCALPHA) - s.fill(box_colour) - pygame.draw.rect(s, (0,0,0), (0,0,1.5*text.get_width(), text.get_height()), self.grid_sz[0]/10) - s.blit(text, ((text.get_width()*1.5)/2 - text.get_width()/2 ,0)) - self.window.blit(s, targets[i][0:2]) - - - pygame.display.flip() - - for event in pygame.event.get(): - if event.type == pygame.QUIT: - return None - elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: - for i in range(len(targets)): - t = targets[i] - if event.pos[0] > t[0] and event.pos[0] < t[2]: - if event.pos[1] > t[1] and event.pos[1] < t[3]: - return i - #print "Reject " + str(i) + str(event.pos) + " vs " + str(t) - - - # Function to pick players in a nice GUI way - def SelectPlayers(self, players = []): - - - - missing = ["white", "black"] - for p in players: - missing.remove(p.colour) - - for colour in missing: - - - choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player", font_size=32) - if choice == 0: - players.append(HumanPlayer("human", colour)) - elif choice == 1: - try: - import Tkinter - from tkFileDialog import askopenfilename - root = Tkinter.Tk() # Need a root to make Tkinter behave - root.withdraw() # Some sort of magic incantation - path = askopenfilename(parent=root, initialdir="../agents",title= -'Choose an agent.') - if path == "": - return self.SelectPlayers() - players.append(make_player(path, colour)) - except Exception,e: - print "Exception was " + str(e.message) - p = None - while p == None: - self.board.display_grid(self.window, self.grid_sz) - pygame.display.flip() - path = self.getstr(prompt = "Enter path:") - if path == None: - return None - - if path == "": - return self.SelectPlayers() - - try: - p = make_player(path, colour) - except: - self.board.display_grid(self.window, self.grid_sz) - pygame.display.flip() - self.message("Invalid path!") - time.sleep(1) - p = None - players.append(p) - elif choice == 2: - address = "" - while address == "": - self.board.display_grid(self.window, self.grid_sz) - - address = self.getstr(prompt = "Address? (leave blank for server)") - if address == None: - return None - if address == "": - address = None - continue - try: - map(int, address.split(".")) - except: - self.board.display_grid(self.window, self.grid_sz) - self.message("Invalid IPv4 address!") - address = "" - - players.append(NetworkReceiver(colour, address)) - else: - return None - #print str(self) + ".SelectPlayers returns " + str(players) - return players - - - diff --git a/qchess/main.py b/qchess/main.py deleted file mode 100644 index 82aad82..0000000 --- a/qchess/main.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/python -u - -# Do you know what the -u does? It unbuffers stdin and stdout -# I can't remember why, but last year things broke without that - -""" - UCC::Progcomp 2013 Quantum Chess game - @author Sam Moore [SZM] "matches" - @copyright The University Computer Club, Incorporated - (ie: You can copy it for not for profit purposes) -""" - -# system python modules or whatever they are called -import sys -import os -import time - -turn_delay = 0.5 -[game, graphics] = [None, None] - -def make_player(name, colour): - if name[0] == '@': - if name[1:] == "human": - return HumanPlayer(name, colour) - s = name[1:].split(":") - if s[0] == "network": - address = None - if len(s) > 1: - address = s[1] - return NetworkReceiver(colour, address) - - else: - return AgentPlayer(name, colour) - - - -# The main function! It does the main stuff! -def main(argv): - - # Apparently python will silently treat things as local unless you do this - # Anyone who says "You should never use a global variable" can die in a fire - global game - global graphics - - global turn_delay - global agent_timeout - global log_file - global src_file - - - - - style = "quantum" - colour = "white" - graphics_enabled = True - - players = [] - i = 0 - while i < len(argv)-1: - i += 1 - arg = argv[i] - if arg[0] != '-': - players.append(make_player(arg, colour)) - if colour == "white": - colour = "black" - elif colour == "black": - pass - else: - sys.stderr.write(sys.argv[0] + " : Too many players (max 2)\n") - continue - - # Option parsing goes here - if arg[1] == '-' and arg[2:] == "classical": - style = "classical" - elif arg[1] == '-' and arg[2:] == "quantum": - style = "quantum" - elif (arg[1] == '-' and arg[2:] == "graphics"): - graphics_enabled = not graphics_enabled - elif (arg[1] == '-' and arg[2:].split("=")[0] == "file"): - # Load game from file - if len(arg[2:].split("=")) == 1: - src_file = sys.stdout - else: - src_file = arg[2:].split("=")[1] - elif (arg[1] == '-' and arg[2:].split("=")[0] == "log"): - # Log file - if len(arg[2:].split("=")) == 1: - log_file = sys.stdout - else: - log_file = arg[2:].split("=")[1] - elif (arg[1] == '-' and arg[2:].split("=")[0] == "delay"): - # Delay - if len(arg[2:].split("=")) == 1: - turn_delay = 0 - else: - turn_delay = float(arg[2:].split("=")[1]) - - elif (arg[1] == '-' and arg[2:].split("=")[0] == "timeout"): - # Timeout - if len(arg[2:].split("=")) == 1: - agent_timeout = -1 - elif platform.system() != "Windows": # Windows breaks this option - agent_timeout = float(arg[2:].split("=")[1]) - else: - sys.stderr.write(sys.argv[0] + " : Warning - You are using Windows\n") - agent_timeout = -1 - - elif (arg[1] == '-' and arg[2:] == "help"): - # Help - os.system("less data/help.txt") # The best help function - return 0 - - - # Create the board - board = Board(style) - - - # Initialise GUI - if graphics_enabled == True: - try: - graphics = GraphicsThread(board, grid_sz = [64,64]) # Construct a GraphicsThread! - except Exception,e: - graphics = None - sys.stderr.write(sys.argv[0] + " : Got exception trying to initialise graphics\n"+str(e.message)+"\nDisabled graphics\n") - graphics_enabled = False - - # If there are no players listed, display a nice pretty menu - if len(players) != 2: - if graphics != None: - players = graphics.SelectPlayers(players) - else: - sys.stderr.write(sys.argv[0] + " : Usage " + sys.argv[0] + " white black\n") - return 44 - - # If there are still no players, quit - if players == None or len(players) != 2: - sys.stderr.write(sys.argv[0] + " : Graphics window closed before players chosen\n") - return 45 - - - # Wrap NetworkSender players around original players if necessary - for i in range(len(players)): - if isinstance(players[i], NetworkReceiver): - players[i].board = board # Network players need direct access to the board - for j in range(len(players)): - if j == i: - continue - if isinstance(players[j], NetworkSender) or isinstance(players[j], NetworkReceiver): - continue - players[j] = NetworkSender(players[j], players[i].address) - players[j].board = board - - # Connect the networked players - for p in players: - if isinstance(p, NetworkSender) or isinstance(p, NetworkReceiver): - if graphics != None: - graphics.board.display_grid(graphics.window, graphics.grid_sz) - graphics.message("Connecting to " + p.colour + " player...") - p.connect() - - - # Construct a GameThread! Make it global! Damn the consequences! - game = GameThread(board, players) - - - - if graphics != None: - game.start() # This runs in a new thread - graphics.run() - game.join() - return game.error + graphics.error - else: - game.run() - return game.error - -# This is how python does a main() function... -if __name__ == "__main__": - sys.exit(main(sys.argv)) diff --git a/qchess/network.py b/qchess/network.py deleted file mode 100644 index 72467d8..0000000 --- a/qchess/network.py +++ /dev/null @@ -1,191 +0,0 @@ -import socket -import select - -network_timeout_start = -1.0 # Timeout in seconds to wait for the start of a message -network_timeout_delay = 1.0 # Maximum time between two characters being received - -class Network(): - def __init__(self, colour, address = None): - self.socket = socket.socket() - #self.socket.setblocking(0) - - if colour == "white": - self.port = 4562 - else: - self.port = 4563 - - self.src = None - - # print str(self) + " listens on port " + str(self.port) - - if address == None: - self.host = socket.gethostname() - self.socket.bind((self.host, self.port)) - self.socket.listen(5) - - self.src, self.address = self.socket.accept() - self.src.send("ok\n") - if self.get_response() == "QUIT": - self.src.close() - else: - self.host = address - self.socket.connect((address, self.port)) - self.src = self.socket - self.src.send("ok\n") - if self.get_response() == "QUIT": - self.src.close() - - def get_response(self): - # Timeout the start of the message (first character) - if network_timeout_start > 0.0: - ready = select.select([self.src], [], [], network_timeout_start)[0] - else: - ready = [self.src] - if self.src in ready: - s = self.src.recv(1) - else: - raise Exception("UNRESPONSIVE") - - - while s[len(s)-1] != '\n': - # Timeout on each character in the message - if network_timeout_delay > 0.0: - ready = select.select([self.src], [], [], network_timeout_delay)[0] - else: - ready = [self.src] - if self.src in ready: - s += self.src.recv(1) - else: - raise Exception("UNRESPONSIVE") - - return s.strip(" \r\n") - - def send_message(self,s): - if network_timeout_start > 0.0: - ready = select.select([], [self.src], [], network_timeout_start)[1] - else: - ready = [self.src] - - if self.src in ready: - self.src.send(s + "\n") - else: - raise Exception("UNRESPONSIVE") - - def check_quit(self, s): - s = s.split(" ") - if s[0] == "QUIT": - with game.lock: - game.final_result = " ".join(s[1:]) + " " + str(opponent(self.colour)) - game.stop() - return True - - - -class NetworkSender(Player,Network): - def __init__(self, base_player, address = None): - self.base_player = base_player - Player.__init__(self, base_player.name, base_player.colour) - - self.address = address - - def connect(self): - Network.__init__(self, self.base_player.colour, self.address) - - - - def select(self): - [x,y] = self.base_player.select() - choice = self.board.grid[x][y] - s = str(x) + " " + str(y) - #print str(self) + ".select sends " + s - self.send_message(s) - return [x,y] - - def get_move(self): - [x,y] = self.base_player.get_move() - s = str(x) + " " + str(y) - #print str(self) + ".get_move sends " + s - self.send_message(s) - return [x,y] - - def update(self, s): - self.base_player.update(s) - s = s.split(" ") - [x,y] = map(int, s[0:2]) - selected = self.board.grid[x][y] - if selected != None and selected.colour == self.colour and len(s) > 2 and not "->" in s: - s = " ".join(s[0:3]) - for i in range(2): - if selected.types_revealed[i] == True: - s += " " + str(selected.types[i]) - else: - s += " unknown" - #print str(self) + ".update sends " + s - self.send_message(s) - - - def quit(self, final_result): - self.base_player.quit(final_result) - #self.src.send("QUIT " + str(final_result) + "\n") - self.src.close() - -class NetworkReceiver(Player,Network): - def __init__(self, colour, address=None): - - Player.__init__(self, address, colour) - - self.address = address - - self.board = None - - def connect(self): - Network.__init__(self, self.colour, self.address) - - - def select(self): - - s = self.get_response() - #print str(self) + ".select gets " + s - [x,y] = map(int,s.split(" ")) - if x == -1 and y == -1: - #print str(self) + ".select quits the game" - with game.lock: - game.final_state = "network terminated " + self.colour - game.stop() - return [x,y] - def get_move(self): - s = self.get_response() - #print str(self) + ".get_move gets " + s - [x,y] = map(int,s.split(" ")) - if x == -1 and y == -1: - #print str(self) + ".get_move quits the game" - with game.lock: - game.final_state = "network terminated " + self.colour - game.stop() - return [x,y] - - def update(self, result): - - result = result.split(" ") - [x,y] = map(int, result[0:2]) - selected = self.board.grid[x][y] - if selected != None and selected.colour == self.colour and len(result) > 2 and not "->" in result: - s = self.get_response() - #print str(self) + ".update - receives " + str(s) - s = s.split(" ") - selected.choice = int(s[2]) - for i in range(2): - selected.types[i] = str(s[3+i]) - if s[3+i] == "unknown": - selected.types_revealed[i] = False - else: - selected.types_revealed[i] = True - selected.current_type = selected.types[selected.choice] - else: - pass - #print str(self) + ".update - ignore result " + str(result) - - - def quit(self, final_result): - self.src.close() - diff --git a/qchess/piece.py b/qchess/piece.py deleted file mode 100644 index d9acef2..0000000 --- a/qchess/piece.py +++ /dev/null @@ -1,95 +0,0 @@ -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.types_revealed = [True, False] # Whether the types are known (by default the first type is always known at game start) - - - # - self.last_state = None - self.move_pattern = None - - - - 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.types_revealed = c.types_revealed[:] - - self.last_state = None - self.move_pattern = None - - - - # Make a string for the piece (used for debug) - def __str__(self): - return 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 self.types_revealed[i] == True: - 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": - self.choice = random.randint(0,1) - self.current_type = self.types[self.choice] - self.types_revealed[self.choice] = True - 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_revealed[0] == False or self.types_revealed[1] == False): - 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... diff --git a/qchess/player.py b/qchess/player.py deleted file mode 100644 index ce0ec71..0000000 --- a/qchess/player.py +++ /dev/null @@ -1,187 +0,0 @@ -import subprocess -import select -import platform - -agent_timeout = -1.0 # Timeout in seconds for AI players to make moves - # WARNING: Won't work for windows based operating systems - -if platform.system() == "Windows": - agent_timeout = -1 # Hence this - -# A player who can't play -class Player(): - def __init__(self, name, colour): - self.name = name - self.colour = colour - -# Player that runs from another process -class AgentPlayer(Player): - - - def __init__(self, name, colour): - Player.__init__(self, name, colour) - self.p = subprocess.Popen(name, stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE) - - self.send_message(colour) - - def send_message(self, s): - if agent_timeout > 0.0: - ready = select.select([], [self.p.stdin], [], agent_timeout)[1] - else: - ready = [self.p.stdin] - if self.p.stdin in ready: - #print "Writing to p.stdin" - try: - self.p.stdin.write(s + "\n") - except: - raise Exception("UNRESPONSIVE") - else: - raise Exception("UNRESPONSIVE") - - def get_response(self): - if agent_timeout > 0.0: - ready = select.select([self.p.stdout], [], [], agent_timeout)[0] - else: - ready = [self.p.stdout] - if self.p.stdout in ready: - #print "Reading from p.stdout" - try: - return self.p.stdout.readline().strip("\r\n") - except: # Exception, e: - raise Exception("UNRESPONSIVE") - else: - raise Exception("UNRESPONSIVE") - - def select(self): - - self.send_message("SELECTION?") - line = self.get_response() - - try: - result = map(int, line.split(" ")) - except: - raise Exception("GIBBERISH \"" + str(line) + "\"") - return result - - def update(self, result): - #print "Update " + str(result) + " called for AgentPlayer" - self.send_message(result) - - - def get_move(self): - - self.send_message("MOVE?") - line = self.get_response() - - try: - result = map(int, line.split(" ")) - except: - raise Exception("GIBBERISH \"" + str(line) + "\"") - return result - - def quit(self, final_result): - try: - self.send_message("QUIT " + final_result) - except: - self.p.kill() - -# So you want to be a player here? -class HumanPlayer(Player): - def __init__(self, name, colour): - Player.__init__(self, name, colour) - - # Select your preferred account - def select(self): - if isinstance(graphics, GraphicsThread): - # Basically, we let the graphics thread do some shit and then return that information to the game thread - graphics.cond.acquire() - # We wait for the graphics thread to select a piece - while graphics.stopped() == False and graphics.state["select"] == None: - graphics.cond.wait() # The difference between humans and machines is that humans sleep - select = graphics.state["select"] - - - graphics.cond.release() - if graphics.stopped(): - return [-1,-1] - return [select.x, select.y] - else: - # Since I don't display the board in this case, I'm not sure why I filled it in... - while True: - sys.stdout.write("SELECTION?\n") - try: - p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) - except: - sys.stderr.write("ILLEGAL GIBBERISH\n") - continue - # It's your move captain - def get_move(self): - if isinstance(graphics, GraphicsThread): - graphics.cond.acquire() - while graphics.stopped() == False and graphics.state["dest"] == None: - graphics.cond.wait() - graphics.cond.release() - - return graphics.state["dest"] - else: - - while True: - sys.stdout.write("MOVE?\n") - try: - p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) - except: - sys.stderr.write("ILLEGAL GIBBERISH\n") - continue - - # Are you sure you want to quit? - def quit(self, final_result): - if graphics == None: - sys.stdout.write("QUIT " + final_result + "\n") - - # Completely useless function - def update(self, result): - if isinstance(graphics, GraphicsThread): - pass - else: - sys.stdout.write(result + "\n") - - -# Player that makes random moves -class AgentRandom(Player): - def __init__(self, name, colour): - Player.__init__(self, name, colour) - self.choice = None - - self.board = Board(style = "agent") - - def select(self): - while True: - self.choice = self.board.pieces[self.colour][random.randint(0, len(self.board.pieces[self.colour])-1)] - all_moves = [] - # Check that the piece has some possibility to move - tmp = self.choice.current_type - if tmp == "unknown": # For unknown pieces, try both types - for t in self.choice.types: - if t == "unknown": - continue - self.choice.current_type = t - all_moves += self.board.possible_moves(self.choice) - else: - all_moves = self.board.possible_moves(self.choice) - self.choice.current_type = tmp - if len(all_moves) > 0: - break - return [self.choice.x, self.choice.y] - - def get_move(self): - moves = self.board.possible_moves(self.choice) - move = moves[random.randint(0, len(moves)-1)] - return move - - def update(self, result): - #sys.stderr.write(sys.argv[0] + " : Update board for AgentRandom\n") - self.board.update(result) - self.board.verify() - - def quit(self, final_result): - pass diff --git a/qchess/qchess.py b/qchess/qchess.py index 501b3fd..8347a75 100755 --- a/qchess/qchess.py +++ b/qchess/qchess.py @@ -1,5 +1,4 @@ #!/usr/bin/python -u -# +++ piece.py +++ # import random # I know using non-abreviated strings is inefficient, but this is python, who cares? @@ -96,7 +95,6 @@ class Piece(): # The sad moment when you realise that you do not understand anything about a subject you studied for 4 years... # --- piece.py --- # -# +++ board.py +++ # [w,h] = [8,8] # Width and height of board(s) # Class to represent a quantum chess board @@ -501,11 +499,11 @@ class Board(): def on_board(self, x, y): return (x >= 0 and x < w) and (y >= 0 and y < h) # --- board.py --- # -# +++ player.py +++ # import subprocess import select import platform + agent_timeout = -1.0 # Timeout in seconds for AI players to make moves # WARNING: Won't work for windows based operating systems @@ -689,8 +687,80 @@ class AgentRandom(Player): def quit(self, final_result): pass + + # --- player.py --- # -# +++ network.py +++ # +import multiprocessing + +# Hacky alternative to using select for timing out players + +# WARNING: Do not wrap around HumanPlayer or things breakify + +class Sleeper(multiprocessing.Process): + def __init__(self, timeout): + multiprocessing.Process.__init__(self) + self.timeout = timeout + + def run(self): + time.sleep(self.timeout) + + +class Worker(multiprocessing.Process): + def __init__(self, function, args, q): + multiprocessing.Process.__init__(self) + self.function = function + self.args = args + self.q = q + + def run(self): + #print str(self) + " runs " + str(self.function) + " with args " + str(self.args) + self.q.put(self.function(*self.args)) + + + +def TimeoutFunction(function, args, timeout): + q = multiprocessing.Queue() + w = Worker(function, args, q) + s = Sleeper(timeout) + w.start() + s.start() + while True: # Busy loop of crappyness + if not w.is_alive(): + s.terminate() + result = q.get() + w.join() + #print "TimeoutFunction gets " + str(result) + return result + elif not s.is_alive(): + w.terminate() + s.join() + raise Exception("UNRESPONSIVE") + + + + +# A player that wraps another player and times out its moves +# Uses threads +# A (crappy) alternative to the use of select() +class TimeoutPlayer(Player): + def __init__(self, base_player, timeout): + Player.__init__(self, base_player.name, base_player.colour) + self.base_player = base_player + self.timeout = timeout + + def select(self): + return TimeoutFunction(self.base_player.select, [], self.timeout) + + + def get_move(self): + return TimeoutFunction(self.base_player.get_move, [], self.timeout) + + def update(self, result): + return TimeoutFunction(self.base_player.update, [result], self.timeout) + + def quit(self, final_result): + return TimeoutFunction(self.base_player.quit, [final_result], self.timeout) +# --- timeout_player.py --- # import socket import select @@ -883,7 +953,6 @@ class NetworkReceiver(Player,Network): self.src.close() # --- network.py --- # -# +++ thread_util.py +++ # import threading # A thread that can be stopped! @@ -900,7 +969,6 @@ class StoppableThread(threading.Thread): def stopped(self): return self._stop.isSet() # --- thread_util.py --- # -# +++ game.py +++ # # A thread that runs the game class GameThread(StoppableThread): @@ -925,7 +993,8 @@ class GameThread(StoppableThread): self.state["turn"] = p.base_player # "turn" contains the player who's turn it is else: self.state["turn"] = p - try: + #try: + if True: [x,y] = p.select() # Player selects a square if self.stopped(): break @@ -983,13 +1052,13 @@ class GameThread(StoppableThread): graphics.state["moves"] = None # Commented out exception stuff for now, because it makes it impossible to tell if I made an IndentationError somewhere - except Exception,e: - result = e.message - #sys.stderr.write(result + "\n") - - self.stop() - with self.lock: - self.final_result = self.state["turn"].colour + " " + e.message + # except Exception,e: + # result = e.message + # #sys.stderr.write(result + "\n") + # + # self.stop() + # with self.lock: + # self.final_result = self.state["turn"].colour + " " + e.message if self.board.king["black"] == None: if self.board.king["white"] == None: @@ -1023,8 +1092,8 @@ def opponent(colour): else: return "white" # --- game.py --- # -# +++ graphics.py +++ # import pygame +import os # Dictionary that stores the unicode character representations of the different pieces # Chess was clearly the reason why unicode was invented @@ -1047,6 +1116,44 @@ piece_char = {"white" : {"king" : u'\u2654', images = {"white" : {}, "black" : {}} small_images = {"white" : {}, "black" : {}} +def create_images(grid_sz, font_name=os.path.join(os.path.curdir, "data", "DejaVuSans.ttf")): + + # Get the font sizes + l_size = 5*(grid_sz[0] / 8) + s_size = 3*(grid_sz[0] / 8) + + for c in piece_char.keys(): + + if c == "black": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0))}) + small_images[c].update({p : pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0))}) + elif c == "white": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size+1).render(piece_char["black"][p], True,(255,255,255))}) + images[c][p].blit(pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0)),(0,0)) + small_images[c].update({p : pygame.font.Font(font_name, s_size+1).render(piece_char["black"][p],True,(255,255,255))}) + small_images[c][p].blit(pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0)),(0,0)) + + +def load_images(image_dir=os.path.join(os.path.curdir, "data", "images")): + if not os.path.exists(image_dir): + raise Exception("Couldn't load images from " + image_dir + " (path doesn't exist)") + for c in piece_char.keys(): + for p in piece_char[c].keys(): + images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + ".png"))}) + small_images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + "_small.png"))}) +# --- images.py --- # +import pygame + + + + + + + + + # A thread to make things pretty class GraphicsThread(StoppableThread): def __init__(self, board, title = "UCC::Progcomp 2013 - QChess", grid_sz = [80,80]): @@ -1056,25 +1163,27 @@ class GraphicsThread(StoppableThread): pygame.init() self.window = pygame.display.set_mode((grid_sz[0] * w, grid_sz[1] * h)) pygame.display.set_caption(title) + + #print "Initialised properly" + self.grid_sz = grid_sz[:] self.state = {"select" : None, "dest" : None, "moves" : None, "overlay" : None, "coverage" : None} self.error = 0 self.lock = threading.RLock() self.cond = threading.Condition() - # Get the font sizes - l_size = 5*(self.grid_sz[0] / 8) - s_size = 3*(self.grid_sz[0] / 8) - for p in piece_types.keys(): - c = "black" - images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", l_size).render(piece_char[c][p], True,(0,0,0))}) - small_images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", s_size).render(piece_char[c][p],True,(0,0,0))}) - c = "white" + #print "Test font" + pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 32).render("Hello", True,(0,0,0)) + + #create_images(grid_sz) + create_images(grid_sz) - images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", l_size+1).render(piece_char["black"][p], True,(255,255,255))}) - images[c][p].blit(pygame.font.Font("data/DejaVuSans.ttf", l_size).render(piece_char[c][p], True,(0,0,0)),(0,0)) - small_images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", s_size+1).render(piece_char["black"][p],True,(255,255,255))}) - small_images[c][p].blit(pygame.font.Font("data/DejaVuSans.ttf", s_size).render(piece_char[c][p],True,(0,0,0)),(0,0)) + """ + for c in images.keys(): + for p in images[c].keys(): + images[c][p] = images[c][p].convert(self.window) + small_images[c][p] = small_images[c][p].convert(self.window) + """ @@ -1085,10 +1194,13 @@ class GraphicsThread(StoppableThread): while not self.stopped(): + #print "Display grid" self.board.display_grid(window = self.window, grid_sz = self.grid_sz) # Draw the board + #print "Display overlay" self.overlay() + #print "Display pieces" self.board.display_pieces(window = self.window, grid_sz = self.grid_sz) # Draw the board pygame.display.flip() @@ -1238,7 +1350,7 @@ class GraphicsThread(StoppableThread): mp = [self.grid_sz[i] * [x,y][i] for i in range(2)] square_img.fill(pygame.Color(255,0,255,int(m[x][y] * 128))) # Draw in purple self.window.blit(square_img, mp) - font = pygame.font.Font(None, 14) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14) text = font.render("{0:.2f}".format(round(m[x][y],2)), 1, pygame.Color(0,0,0)) self.window.blit(text, mp) @@ -1250,7 +1362,7 @@ class GraphicsThread(StoppableThread): mp = [self.grid_sz[i] * [p.x,p.y][i] for i in range(2)] square_img.fill(pygame.Color(0,255,255, int(m[p] * 196))) # Draw in pale blue self.window.blit(square_img, mp) - font = pygame.font.Font(None, 14) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14) text = font.render("{0:.2f}".format(round(m[p],2)), 1, pygame.Color(0,0,0)) self.window.blit(text, mp) # Draw a square where the mouse is @@ -1273,8 +1385,9 @@ class GraphicsThread(StoppableThread): self.window.blit(square_img, mp) # Message in a bottle - def message(self, string, pos = None, colour = None, font_size = 32): - font = pygame.font.Font(None, font_size) + def message(self, string, pos = None, colour = None, font_size = 20): + #print "Drawing message..." + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size) if colour == None: colour = pygame.Color(0,0,0) @@ -1332,11 +1445,13 @@ class GraphicsThread(StoppableThread): # Function to pick a button - def SelectButton(self, choices, prompt = None, font_size=32): + def SelectButton(self, choices, prompt = None, font_size=20): + + #print "Select button called!" self.board.display_grid(self.window, self.grid_sz) if prompt != None: self.message(prompt) - font = pygame.font.Font(None, font_size) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size) targets = [] sz = self.window.get_size() @@ -1385,6 +1500,7 @@ class GraphicsThread(StoppableThread): def SelectPlayers(self, players = []): + #print "SelectPlayers called" missing = ["white", "black"] for p in players: @@ -1393,11 +1509,11 @@ class GraphicsThread(StoppableThread): for colour in missing: - choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player", font_size=32) + choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player") if choice == 0: players.append(HumanPlayer("human", colour)) elif choice == 1: - try: + if True: import Tkinter from tkFileDialog import askopenfilename root = Tkinter.Tk() # Need a root to make Tkinter behave @@ -1407,7 +1523,7 @@ class GraphicsThread(StoppableThread): if path == "": return self.SelectPlayers() players.append(make_player(path, colour)) - except Exception,e: + else: print "Exception was " + str(e.message) p = None while p == None: @@ -1456,7 +1572,6 @@ class GraphicsThread(StoppableThread): # --- graphics.py --- # -# +++ main.py +++ # #!/usr/bin/python -u # Do you know what the -u does? It unbuffers stdin and stdout @@ -1558,11 +1673,8 @@ def main(argv): # Timeout if len(arg[2:].split("=")) == 1: agent_timeout = -1 - elif platform.system() != "Windows": # Windows breaks this option - agent_timeout = float(arg[2:].split("=")[1]) else: - sys.stderr.write(sys.argv[0] + " : Warning - You are using Windows\n") - agent_timeout = -1 + agent_timeout = float(arg[2:].split("=")[1]) elif (arg[1] == '-' and arg[2:] == "help"): # Help @@ -1578,6 +1690,7 @@ def main(argv): if graphics_enabled == True: try: graphics = GraphicsThread(board, grid_sz = [64,64]) # Construct a GraphicsThread! + except Exception,e: graphics = None sys.stderr.write(sys.argv[0] + " : Got exception trying to initialise graphics\n"+str(e.message)+"\nDisabled graphics\n") @@ -1617,6 +1730,22 @@ def main(argv): graphics.message("Connecting to " + p.colour + " player...") p.connect() + + # If using windows, select won't work; use horrible TimeoutPlayer hack + if agent_timeout > 0 and platform.system() == "Windows": + sys.stderr.write(sys.argv[0] + " : Warning - You are using Windows\n") + sys.stderr.write(sys.argv[0] + " : - Timeouts will be implemented with a terrible hack.\n") + + for i in range(len(players)): + if isinstance(players[i], AgentPlayer): + players[i] = TimeoutPlayer(players[i], agent_timeout) + + # Could potentially wrap TimeoutPlayer around internal classes... + # But that would suck + + + + # Construct a GameThread! Make it global! Damn the consequences! game = GameThread(board, players) @@ -1636,4 +1765,4 @@ def main(argv): if __name__ == "__main__": sys.exit(main(sys.argv)) # --- main.py --- # -# EOF - created from make on Thu Jan 24 17:04:54 WST 2013 +# EOF - created from make on Mon Jan 28 22:52:28 WST 2013 diff --git a/qchess/src/Makefile b/qchess/src/Makefile new file mode 100644 index 0000000..8ed1944 --- /dev/null +++ b/qchess/src/Makefile @@ -0,0 +1,16 @@ +# Makefile that builds qchess.py from the component files + +TARGET = qchess.py +COMPONENTS = piece.py board.py player.py timeout_player.py network.py thread_util.py game.py images.py graphics.py main.py +#COMPONENTS=$(shell ls *.py | tr '\t' '\n' | grep -v $(TARGET)) + +$(TARGET) : $(COMPONENTS) + echo "#!/usr/bin/python -u" > $(TARGET) + for f in $(COMPONENTS); do echo "# +++ $$f +++ #" >> ../$(TARGET); cat $$f >> $(TARGET); echo "# --- $$f --- #" >> $(TARGET); done + echo "# EOF - created from make on $$(date)" >> $(TARGET) + chmod u+x $(TARGET) + +clean : + rm -f *~ + rm -f *.pyc + rm -f $(TARGET) diff --git a/qchess/src/board.py b/qchess/src/board.py new file mode 100644 index 0000000..0d96d1a --- /dev/null +++ b/qchess/src/board.py @@ -0,0 +1,403 @@ +[w,h] = [8,8] # Width and height of board(s) + +# 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 + for c in ["black", "white"]: + del self.unrevealed_types[c]["unknown"] + + # 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.types_revealed[1] = True + 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.types_revealed[1] = True + 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]) + + + 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") + + 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): + piece = self.grid[x][y] + if piece.types[type_index] == "unknown": + if not state in self.unrevealed_types[piece.colour].keys(): + 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.types_revealed[type_index] = True + piece.current_type = state + + if len(self.possible_moves(piece)) <= 0: + piece.deselect() # Piece can't move; deselect it + + # Update the board when a piece has been moved + def update_move(self, x, y, x2, y2): + piece = self.grid[x][y] + 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.verify() + + # Update the board from a string + # Guesses what to do based on the format of the string + def update(self, result): + #print "Update called with \"" + 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: + raise Exception("EMPTY") + + # 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) + + 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) + + 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 = 0.5 + if t == "unknown" or p.types_revealed[i] == False: + 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): + result[point[0]][point[1]] += prob2 * prob + + else: + p.current_type = t + for point in self.possible_moves(p, reject_allied): + 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_revealed[i] == False: + 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 + # This is probably inefficient, but I looked at some sample chess games and they seem to actually do things this way + # reject_allied indicates whether squares occupied by allied pieces will be removed + # (set to false to check for defense) + def possible_moves(self, p, reject_allied = True): + result = [] + if p == None: + return result + + + if p.current_type == "unknown": + raise Exception("SANITY: Piece state unknown") + # The below commented out code causes things to break badly + #for t in p.types: + # if t == "unknown": + # continue + # p.current_type = t + # result += self.possible_moves(p) + #p.current_type = "unknown" + #return result + + if p.current_type == "king": + 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]] + elif p.current_type == "queen": + for d in [[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1]]: + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "bishop": + for d in [[-1,-1],[-1,1],[1,-1],[1,1]]: # There's a reason why bishops move diagonally + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "rook": + for d in [[-1,0],[1,0],[0,-1],[0,1]]: + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "knight": + # I would use two lines, but I'm not sure how python likes that + 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]] + elif p.current_type == "pawn": + if p.colour == "white": + + # Pawn can't move forward into occupied square + if self.on_board(p.x, p.y-1) and self.grid[p.x][p.y-1] == None: + result = [[p.x,p.y-1]] + for f in [[p.x-1,p.y-1],[p.x+1,p.y-1]]: + if not self.on_board(f[0], f[1]): + continue + if self.grid[f[0]][f[1]] != None: # Pawn can take diagonally + result.append(f) + if p.y == h-2: + # Slightly embarrassing if the pawn jumps over someone on its first move... + if self.grid[p.x][p.y-1] == None and self.grid[p.x][p.y-2] == None: + result.append([p.x, p.y-2]) + else: + # Vice versa for the black pawn + if self.on_board(p.x, p.y+1) and self.grid[p.x][p.y+1] == None: + result = [[p.x,p.y+1]] + + for f in [[p.x-1,p.y+1],[p.x+1,p.y+1]]: + if not self.on_board(f[0], f[1]): + continue + if self.grid[f[0]][f[1]] != None: + #sys.stderr.write(sys.argv[0] + " : "+str(p) + " can take " + str(self.grid[f[0]][f[1]]) + "\n") + result.append(f) + if p.y == 1: + if self.grid[p.x][p.y+1] == None and self.grid[p.x][p.y+2] == None: + result.append([p.x, p.y+2]) + + #sys.stderr.write(sys.argv[0] + " : possible_moves for " + str(p) + " " + str(result) + "\n") + + # Remove illegal moves + # Note: The result[:] creates a copy of result, so that the result.remove calls don't fuck things up + for point in result[:]: + + if (point[0] < 0 or point[0] >= w) or (point[1] < 0 or point[1] >= h): + result.remove(point) # Remove locations outside the board + continue + g = self.grid[point[0]][point[1]] + + if g != None and (g.colour == p.colour and reject_allied == True): + result.remove(point) # Remove allied pieces + + self.verify() + return result + + + # Scans in a direction until it hits a piece, returns all squares in the line + # (includes the final square (which contains a piece), but not the original square) + def scan(self, x, y, vx, vy): + p = [] + + xx = x + yy = y + while True: + xx += vx + yy += vy + if not self.on_board(xx, yy): + break + if not [xx,yy] in p: + p.append([xx, yy]) + g = self.grid[xx][yy] + if g != None: + return p + + return p + + + + # I typed the full statement about 30 times before writing this function... + def on_board(self, x, y): + return (x >= 0 and x < w) and (y >= 0 and y < h) diff --git a/qchess/src/game.py b/qchess/src/game.py new file mode 100644 index 0000000..934c043 --- /dev/null +++ b/qchess/src/game.py @@ -0,0 +1,122 @@ + +# A thread that runs the game +class GameThread(StoppableThread): + def __init__(self, board, players): + StoppableThread.__init__(self) + self.board = board + self.players = players + self.state = {"turn" : None} # The game state + self.error = 0 # Whether the thread exits with an error + self.lock = threading.RLock() #lock for access of self.state + self.cond = threading.Condition() # conditional for some reason, I forgot + self.final_result = "" + + # Run the game (run in new thread with start(), run in current thread with run()) + def run(self): + result = "" + while not self.stopped(): + + for p in self.players: + with self.lock: + if isinstance(p, NetworkSender): + self.state["turn"] = p.base_player # "turn" contains the player who's turn it is + else: + self.state["turn"] = p + #try: + if True: + [x,y] = p.select() # Player selects a square + if self.stopped(): + break + + + + + result = self.board.select(x, y, colour = p.colour) + for p2 in self.players: + p2.update(result) # Inform players of what happened + + + + target = self.board.grid[x][y] + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = self.board.possible_moves(target) + graphics.state["select"] = target + + time.sleep(turn_delay) + + + if len(self.board.possible_moves(target)) == 0: + #print "Piece cannot move" + target.deselect() + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = None + graphics.state["select"] = None + graphics.state["dest"] = None + continue + + try: + [x2,y2] = p.get_move() # Player selects a destination + except: + self.stop() + + if self.stopped(): + break + + result = self.board.update_move(x, y, x2, y2) + for p2 in self.players: + p2.update(str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2)) # Inform players of what happened + + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = [[x2,y2]] + + time.sleep(turn_delay) + + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["select"] = None + graphics.state["dest"] = None + graphics.state["moves"] = None + + # Commented out exception stuff for now, because it makes it impossible to tell if I made an IndentationError somewhere + # except Exception,e: + # result = e.message + # #sys.stderr.write(result + "\n") + # + # self.stop() + # with self.lock: + # self.final_result = self.state["turn"].colour + " " + e.message + + if self.board.king["black"] == None: + if self.board.king["white"] == None: + with self.lock: + self.final_result = self.state["turn"].colour + " DRAW" + else: + with self.lock: + self.final_result = "white" + self.stop() + elif self.board.king["white"] == None: + with self.lock: + self.final_result = "black" + self.stop() + + + if self.stopped(): + break + + + for p2 in self.players: + p2.quit(self.final_result) + + graphics.stop() + + + + +def opponent(colour): + if colour == "white": + return "black" + else: + return "white" diff --git a/qchess/src/graphics.py b/qchess/src/graphics.py new file mode 100644 index 0000000..c068900 --- /dev/null +++ b/qchess/src/graphics.py @@ -0,0 +1,427 @@ +import pygame + + + + + + + + + +# A thread to make things pretty +class GraphicsThread(StoppableThread): + def __init__(self, board, title = "UCC::Progcomp 2013 - QChess", grid_sz = [80,80]): + StoppableThread.__init__(self) + + self.board = board + pygame.init() + self.window = pygame.display.set_mode((grid_sz[0] * w, grid_sz[1] * h)) + pygame.display.set_caption(title) + + #print "Initialised properly" + + self.grid_sz = grid_sz[:] + self.state = {"select" : None, "dest" : None, "moves" : None, "overlay" : None, "coverage" : None} + self.error = 0 + self.lock = threading.RLock() + self.cond = threading.Condition() + + #print "Test font" + pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 32).render("Hello", True,(0,0,0)) + + #load_images() + create_images(grid_sz) + + """ + for c in images.keys(): + for p in images[c].keys(): + images[c][p] = images[c][p].convert(self.window) + small_images[c][p] = small_images[c][p].convert(self.window) + """ + + + + + + # On the run from the world + def run(self): + + while not self.stopped(): + + #print "Display grid" + self.board.display_grid(window = self.window, grid_sz = self.grid_sz) # Draw the board + + #print "Display overlay" + self.overlay() + + #print "Display pieces" + self.board.display_pieces(window = self.window, grid_sz = self.grid_sz) # Draw the board + + pygame.display.flip() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + if isinstance(game, GameThread): + with game.lock: + game.final_result = "" + if game.state["turn"] != None: + game.final_result = game.state["turn"].colour + " " + game.final_result += "terminated" + game.stop() + self.stop() + break + elif event.type == pygame.MOUSEBUTTONDOWN: + self.mouse_down(event) + elif event.type == pygame.MOUSEBUTTONUP: + self.mouse_up(event) + + + + + + + self.message("Game ends, result \""+str(game.final_result) + "\"") + time.sleep(1) + + # Wake up anyone who is sleeping + self.cond.acquire() + self.cond.notify() + self.cond.release() + + pygame.quit() # Time to say goodbye + + # Mouse release event handler + def mouse_up(self, event): + if event.button == 3: + with self.lock: + self.state["overlay"] = None + elif event.button == 2: + with self.lock: + self.state["coverage"] = None + + # Mouse click event handler + def mouse_down(self, event): + if event.button == 1: + m = [event.pos[i] / self.grid_sz[i] for i in range(2)] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + s = self.board.grid[m[0]][m[1]] + select = self.state["select"] + if select == None: + if s != None and s.colour != p.colour: + self.message("Wrong colour") # Look at all this user friendliness! + time.sleep(1) + return + # Notify human player of move + self.cond.acquire() + with self.lock: + self.state["select"] = s + self.state["dest"] = None + self.cond.notify() + self.cond.release() + return + + if select == None: + return + + + if self.state["moves"] == None: + return + + if not m in self.state["moves"]: + self.message("Illegal Move") # I still think last year's mouse interface was adequate + time.sleep(2) + return + + with self.lock: + if self.state["dest"] == None: + self.cond.acquire() + self.state["dest"] = m + self.state["select"] = None + self.state["moves"] = None + self.cond.notify() + self.cond.release() + elif event.button == 3: + m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + self.state["overlay"] = self.board.probability_grid(self.board.grid[m[0]][m[1]]) + + elif event.button == 2: + m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + self.state["coverage"] = self.board.coverage(m[0], m[1], None, self.state["select"]) + + # Draw the overlay + def overlay(self): + + square_img = pygame.Surface((self.grid_sz[0], self.grid_sz[1]),pygame.SRCALPHA) # A square image + # Draw square over the selected piece + with self.lock: + select = self.state["select"] + if select != None: + mp = [self.grid_sz[i] * [select.x, select.y][i] for i in range(len(self.grid_sz))] + square_img.fill(pygame.Color(0,255,0,64)) + self.window.blit(square_img, mp) + # If a piece is selected, draw all reachable squares + # (This quality user interface has been patented) + with self.lock: + m = self.state["moves"] + if m != None: + square_img.fill(pygame.Color(255,0,0,128)) # Draw them in blood red + for move in m: + mp = [self.grid_sz[i] * move[i] for i in range(2)] + self.window.blit(square_img, mp) + # If a piece is overlayed, show all squares that it has a probability to reach + with self.lock: + m = self.state["overlay"] + if m != None: + for x in range(w): + for y in range(h): + if m[x][y] > 0.0: + mp = [self.grid_sz[i] * [x,y][i] for i in range(2)] + square_img.fill(pygame.Color(255,0,255,int(m[x][y] * 128))) # Draw in purple + self.window.blit(square_img, mp) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14) + text = font.render("{0:.2f}".format(round(m[x][y],2)), 1, pygame.Color(0,0,0)) + self.window.blit(text, mp) + + # If a square is selected, highlight all pieces that have a probability to reach it + with self.lock: + m = self.state["coverage"] + if m != None: + for p in m: + mp = [self.grid_sz[i] * [p.x,p.y][i] for i in range(2)] + square_img.fill(pygame.Color(0,255,255, int(m[p] * 196))) # Draw in pale blue + self.window.blit(square_img, mp) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14) + text = font.render("{0:.2f}".format(round(m[p],2)), 1, pygame.Color(0,0,0)) + self.window.blit(text, mp) + # Draw a square where the mouse is + # This also serves to indicate who's turn it is + + if isinstance(game, GameThread): + with game.lock: + turn = game.state["turn"] + else: + turn = None + + if isinstance(turn, HumanPlayer): + mp = [self.grid_sz[i] * int(pygame.mouse.get_pos()[i] / self.grid_sz[i]) for i in range(2)] + square_img.fill(pygame.Color(0,0,255,128)) + if turn.colour == "white": + c = pygame.Color(255,255,255) + else: + c = pygame.Color(0,0,0) + pygame.draw.rect(square_img, c, (0,0,self.grid_sz[0], self.grid_sz[1]), self.grid_sz[0]/10) + self.window.blit(square_img, mp) + + # Message in a bottle + def message(self, string, pos = None, colour = None, font_size = 20): + #print "Drawing message..." + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size) + if colour == None: + colour = pygame.Color(0,0,0) + + text = font.render(string, 1, colour) + + + s = pygame.Surface((text.get_width(), text.get_height()), pygame.SRCALPHA) + s.fill(pygame.Color(128,128,128)) + + tmp = self.window.get_size() + + if pos == None: + pos = (tmp[0] / 2 - text.get_width() / 2, tmp[1] / 3 - text.get_height()) + else: + pos = (pos[0]*text.get_width() + tmp[0] / 2 - text.get_width() / 2, pos[1]*text.get_height() + tmp[1] / 3 - text.get_height()) + + + rect = (pos[0], pos[1], text.get_width(), text.get_height()) + + pygame.draw.rect(self.window, pygame.Color(0,0,0), pygame.Rect(rect), 1) + self.window.blit(s, pos) + self.window.blit(text, pos) + + pygame.display.flip() + + def getstr(self, prompt = None): + s = pygame.Surface((self.window.get_width(), self.window.get_height())) + s.blit(self.window, (0,0)) + result = "" + + while True: + #print "LOOP" + if prompt != None: + self.message(prompt) + self.message(result, pos = (0, 1)) + + pygame.event.pump() + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return None + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_BACKSPACE: + result = result[0:len(result)-1] + self.window.blit(s, (0,0)) # Revert the display + continue + + + try: + if event.unicode == '\r': + return result + + result += str(event.unicode) + except: + continue + + + # Function to pick a button + def SelectButton(self, choices, prompt = None, font_size=20): + + #print "Select button called!" + self.board.display_grid(self.window, self.grid_sz) + if prompt != None: + self.message(prompt) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size) + targets = [] + sz = self.window.get_size() + + + for i in range(len(choices)): + c = choices[i] + + text = font.render(c, 1, pygame.Color(0,0,0)) + p = (sz[0] / 2 - (1.5*text.get_width())/2, sz[1] / 2 +(i-1)*text.get_height()+(i*2)) + targets.append((p[0], p[1], p[0] + 1.5*text.get_width(), p[1] + text.get_height())) + + while True: + mp =pygame.mouse.get_pos() + for i in range(len(choices)): + c = choices[i] + if mp[0] > targets[i][0] and mp[0] < targets[i][2] and mp[1] > targets[i][1] and mp[1] < targets[i][3]: + font_colour = pygame.Color(255,0,0) + box_colour = pygame.Color(0,0,255,128) + else: + font_colour = pygame.Color(0,0,0) + box_colour = pygame.Color(128,128,128) + + text = font.render(c, 1, font_colour) + s = pygame.Surface((text.get_width()*1.5, text.get_height()), pygame.SRCALPHA) + s.fill(box_colour) + pygame.draw.rect(s, (0,0,0), (0,0,1.5*text.get_width(), text.get_height()), self.grid_sz[0]/10) + s.blit(text, ((text.get_width()*1.5)/2 - text.get_width()/2 ,0)) + self.window.blit(s, targets[i][0:2]) + + + pygame.display.flip() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return None + elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + for i in range(len(targets)): + t = targets[i] + if event.pos[0] > t[0] and event.pos[0] < t[2]: + if event.pos[1] > t[1] and event.pos[1] < t[3]: + return i + #print "Reject " + str(i) + str(event.pos) + " vs " + str(t) + + + # Function to pick players in a nice GUI way + def SelectPlayers(self, players = []): + + + #print "SelectPlayers called" + + missing = ["white", "black"] + for p in players: + missing.remove(p.colour) + + for colour in missing: + + + choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player") + if choice == 0: + players.append(HumanPlayer("human", colour)) + elif choice == 1: + if True: + import Tkinter + from tkFileDialog import askopenfilename + root = Tkinter.Tk() # Need a root to make Tkinter behave + root.withdraw() # Some sort of magic incantation + path = askopenfilename(parent=root, initialdir="../agents",title= +'Choose an agent.') + if path == "": + return self.SelectPlayers() + players.append(make_player(path, colour)) + else: + print "Exception was " + str(e.message) + p = None + while p == None: + self.board.display_grid(self.window, self.grid_sz) + pygame.display.flip() + path = self.getstr(prompt = "Enter path:") + if path == None: + return None + + if path == "": + return self.SelectPlayers() + + try: + p = make_player(path, colour) + except: + self.board.display_grid(self.window, self.grid_sz) + pygame.display.flip() + self.message("Invalid path!") + time.sleep(1) + p = None + players.append(p) + elif choice == 2: + address = "" + while address == "": + self.board.display_grid(self.window, self.grid_sz) + + address = self.getstr(prompt = "Address? (leave blank for server)") + if address == None: + return None + if address == "": + address = None + continue + try: + map(int, address.split(".")) + except: + self.board.display_grid(self.window, self.grid_sz) + self.message("Invalid IPv4 address!") + address = "" + + players.append(NetworkReceiver(colour, address)) + else: + return None + #print str(self) + ".SelectPlayers returns " + str(players) + return players + + + diff --git a/qchess/src/images.py b/qchess/src/images.py new file mode 100644 index 0000000..dd79b10 --- /dev/null +++ b/qchess/src/images.py @@ -0,0 +1,51 @@ +import pygame +import os + +# Dictionary that stores the unicode character representations of the different pieces +# Chess was clearly the reason why unicode was invented +# For some reason none of the pygame chess implementations I found used them! +piece_char = {"white" : {"king" : u'\u2654', + "queen" : u'\u2655', + "rook" : u'\u2656', + "bishop" : u'\u2657', + "knight" : u'\u2658', + "pawn" : u'\u2659', + "unknown" : '?'}, + "black" : {"king" : u'\u265A', + "queen" : u'\u265B', + "rook" : u'\u265C', + "bishop" : u'\u265D', + "knight" : u'\u265E', + "pawn" : u'\u265F', + "unknown" : '?'}} + +images = {"white" : {}, "black" : {}} +small_images = {"white" : {}, "black" : {}} + +def create_images(grid_sz, font_name=os.path.join(os.path.curdir, "data", "DejaVuSans.ttf")): + + # Get the font sizes + l_size = 5*(grid_sz[0] / 8) + s_size = 3*(grid_sz[0] / 8) + + for c in piece_char.keys(): + + if c == "black": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0))}) + small_images[c].update({p : pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0))}) + elif c == "white": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size+1).render(piece_char["black"][p], True,(255,255,255))}) + images[c][p].blit(pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0)),(0,0)) + small_images[c].update({p : pygame.font.Font(font_name, s_size+1).render(piece_char["black"][p],True,(255,255,255))}) + small_images[c][p].blit(pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0)),(0,0)) + + +def load_images(image_dir=os.path.join(os.path.curdir, "data", "images")): + if not os.path.exists(image_dir): + raise Exception("Couldn't load images from " + image_dir + " (path doesn't exist)") + for c in piece_char.keys(): + for p in piece_char[c].keys(): + images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + ".png"))}) + small_images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + "_small.png"))}) diff --git a/qchess/src/main.py b/qchess/src/main.py new file mode 100644 index 0000000..8dac056 --- /dev/null +++ b/qchess/src/main.py @@ -0,0 +1,192 @@ +#!/usr/bin/python -u + +# Do you know what the -u does? It unbuffers stdin and stdout +# I can't remember why, but last year things broke without that + +""" + UCC::Progcomp 2013 Quantum Chess game + @author Sam Moore [SZM] "matches" + @copyright The University Computer Club, Incorporated + (ie: You can copy it for not for profit purposes) +""" + +# system python modules or whatever they are called +import sys +import os +import time + +turn_delay = 0.5 +[game, graphics] = [None, None] + +def make_player(name, colour): + if name[0] == '@': + if name[1:] == "human": + return HumanPlayer(name, colour) + s = name[1:].split(":") + if s[0] == "network": + address = None + if len(s) > 1: + address = s[1] + return NetworkReceiver(colour, address) + + else: + return AgentPlayer(name, colour) + + + +# The main function! It does the main stuff! +def main(argv): + + # Apparently python will silently treat things as local unless you do this + # Anyone who says "You should never use a global variable" can die in a fire + global game + global graphics + + global turn_delay + global agent_timeout + global log_file + global src_file + + + + + style = "quantum" + colour = "white" + graphics_enabled = True + + players = [] + i = 0 + while i < len(argv)-1: + i += 1 + arg = argv[i] + if arg[0] != '-': + players.append(make_player(arg, colour)) + if colour == "white": + colour = "black" + elif colour == "black": + pass + else: + sys.stderr.write(sys.argv[0] + " : Too many players (max 2)\n") + continue + + # Option parsing goes here + if arg[1] == '-' and arg[2:] == "classical": + style = "classical" + elif arg[1] == '-' and arg[2:] == "quantum": + style = "quantum" + elif (arg[1] == '-' and arg[2:] == "graphics"): + graphics_enabled = not graphics_enabled + elif (arg[1] == '-' and arg[2:].split("=")[0] == "file"): + # Load game from file + if len(arg[2:].split("=")) == 1: + src_file = sys.stdout + else: + src_file = arg[2:].split("=")[1] + elif (arg[1] == '-' and arg[2:].split("=")[0] == "log"): + # Log file + if len(arg[2:].split("=")) == 1: + log_file = sys.stdout + else: + log_file = arg[2:].split("=")[1] + elif (arg[1] == '-' and arg[2:].split("=")[0] == "delay"): + # Delay + if len(arg[2:].split("=")) == 1: + turn_delay = 0 + else: + turn_delay = float(arg[2:].split("=")[1]) + + elif (arg[1] == '-' and arg[2:].split("=")[0] == "timeout"): + # Timeout + if len(arg[2:].split("=")) == 1: + agent_timeout = -1 + else: + agent_timeout = float(arg[2:].split("=")[1]) + + elif (arg[1] == '-' and arg[2:] == "help"): + # Help + os.system("less data/help.txt") # The best help function + return 0 + + + # Create the board + board = Board(style) + + + # Initialise GUI + if graphics_enabled == True: + try: + graphics = GraphicsThread(board, grid_sz = [64,64]) # Construct a GraphicsThread! + + except Exception,e: + graphics = None + sys.stderr.write(sys.argv[0] + " : Got exception trying to initialise graphics\n"+str(e.message)+"\nDisabled graphics\n") + graphics_enabled = False + + # If there are no players listed, display a nice pretty menu + if len(players) != 2: + if graphics != None: + players = graphics.SelectPlayers(players) + else: + sys.stderr.write(sys.argv[0] + " : Usage " + sys.argv[0] + " white black\n") + return 44 + + # If there are still no players, quit + if players == None or len(players) != 2: + sys.stderr.write(sys.argv[0] + " : Graphics window closed before players chosen\n") + return 45 + + + # Wrap NetworkSender players around original players if necessary + for i in range(len(players)): + if isinstance(players[i], NetworkReceiver): + players[i].board = board # Network players need direct access to the board + for j in range(len(players)): + if j == i: + continue + if isinstance(players[j], NetworkSender) or isinstance(players[j], NetworkReceiver): + continue + players[j] = NetworkSender(players[j], players[i].address) + players[j].board = board + + # Connect the networked players + for p in players: + if isinstance(p, NetworkSender) or isinstance(p, NetworkReceiver): + if graphics != None: + graphics.board.display_grid(graphics.window, graphics.grid_sz) + graphics.message("Connecting to " + p.colour + " player...") + p.connect() + + + # If using windows, select won't work; use horrible TimeoutPlayer hack + if agent_timeout > 0 and platform.system() == "Windows": + sys.stderr.write(sys.argv[0] + " : Warning - You are using Windows\n") + sys.stderr.write(sys.argv[0] + " : - Timeouts will be implemented with a terrible hack.\n") + + for i in range(len(players)): + if isinstance(players[i], AgentPlayer): + players[i] = TimeoutPlayer(players[i], agent_timeout) + + # Could potentially wrap TimeoutPlayer around internal classes... + # But that would suck + + + + + + # Construct a GameThread! Make it global! Damn the consequences! + game = GameThread(board, players) + + + + if graphics != None: + game.start() # This runs in a new thread + graphics.run() + game.join() + return game.error + graphics.error + else: + game.run() + return game.error + +# This is how python does a main() function... +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/qchess/src/network.py b/qchess/src/network.py new file mode 100644 index 0000000..72467d8 --- /dev/null +++ b/qchess/src/network.py @@ -0,0 +1,191 @@ +import socket +import select + +network_timeout_start = -1.0 # Timeout in seconds to wait for the start of a message +network_timeout_delay = 1.0 # Maximum time between two characters being received + +class Network(): + def __init__(self, colour, address = None): + self.socket = socket.socket() + #self.socket.setblocking(0) + + if colour == "white": + self.port = 4562 + else: + self.port = 4563 + + self.src = None + + # print str(self) + " listens on port " + str(self.port) + + if address == None: + self.host = socket.gethostname() + self.socket.bind((self.host, self.port)) + self.socket.listen(5) + + self.src, self.address = self.socket.accept() + self.src.send("ok\n") + if self.get_response() == "QUIT": + self.src.close() + else: + self.host = address + self.socket.connect((address, self.port)) + self.src = self.socket + self.src.send("ok\n") + if self.get_response() == "QUIT": + self.src.close() + + def get_response(self): + # Timeout the start of the message (first character) + if network_timeout_start > 0.0: + ready = select.select([self.src], [], [], network_timeout_start)[0] + else: + ready = [self.src] + if self.src in ready: + s = self.src.recv(1) + else: + raise Exception("UNRESPONSIVE") + + + while s[len(s)-1] != '\n': + # Timeout on each character in the message + if network_timeout_delay > 0.0: + ready = select.select([self.src], [], [], network_timeout_delay)[0] + else: + ready = [self.src] + if self.src in ready: + s += self.src.recv(1) + else: + raise Exception("UNRESPONSIVE") + + return s.strip(" \r\n") + + def send_message(self,s): + if network_timeout_start > 0.0: + ready = select.select([], [self.src], [], network_timeout_start)[1] + else: + ready = [self.src] + + if self.src in ready: + self.src.send(s + "\n") + else: + raise Exception("UNRESPONSIVE") + + def check_quit(self, s): + s = s.split(" ") + if s[0] == "QUIT": + with game.lock: + game.final_result = " ".join(s[1:]) + " " + str(opponent(self.colour)) + game.stop() + return True + + + +class NetworkSender(Player,Network): + def __init__(self, base_player, address = None): + self.base_player = base_player + Player.__init__(self, base_player.name, base_player.colour) + + self.address = address + + def connect(self): + Network.__init__(self, self.base_player.colour, self.address) + + + + def select(self): + [x,y] = self.base_player.select() + choice = self.board.grid[x][y] + s = str(x) + " " + str(y) + #print str(self) + ".select sends " + s + self.send_message(s) + return [x,y] + + def get_move(self): + [x,y] = self.base_player.get_move() + s = str(x) + " " + str(y) + #print str(self) + ".get_move sends " + s + self.send_message(s) + return [x,y] + + def update(self, s): + self.base_player.update(s) + s = s.split(" ") + [x,y] = map(int, s[0:2]) + selected = self.board.grid[x][y] + if selected != None and selected.colour == self.colour and len(s) > 2 and not "->" in s: + s = " ".join(s[0:3]) + for i in range(2): + if selected.types_revealed[i] == True: + s += " " + str(selected.types[i]) + else: + s += " unknown" + #print str(self) + ".update sends " + s + self.send_message(s) + + + def quit(self, final_result): + self.base_player.quit(final_result) + #self.src.send("QUIT " + str(final_result) + "\n") + self.src.close() + +class NetworkReceiver(Player,Network): + def __init__(self, colour, address=None): + + Player.__init__(self, address, colour) + + self.address = address + + self.board = None + + def connect(self): + Network.__init__(self, self.colour, self.address) + + + def select(self): + + s = self.get_response() + #print str(self) + ".select gets " + s + [x,y] = map(int,s.split(" ")) + if x == -1 and y == -1: + #print str(self) + ".select quits the game" + with game.lock: + game.final_state = "network terminated " + self.colour + game.stop() + return [x,y] + def get_move(self): + s = self.get_response() + #print str(self) + ".get_move gets " + s + [x,y] = map(int,s.split(" ")) + if x == -1 and y == -1: + #print str(self) + ".get_move quits the game" + with game.lock: + game.final_state = "network terminated " + self.colour + game.stop() + return [x,y] + + def update(self, result): + + result = result.split(" ") + [x,y] = map(int, result[0:2]) + selected = self.board.grid[x][y] + if selected != None and selected.colour == self.colour and len(result) > 2 and not "->" in result: + s = self.get_response() + #print str(self) + ".update - receives " + str(s) + s = s.split(" ") + selected.choice = int(s[2]) + for i in range(2): + selected.types[i] = str(s[3+i]) + if s[3+i] == "unknown": + selected.types_revealed[i] = False + else: + selected.types_revealed[i] = True + selected.current_type = selected.types[selected.choice] + else: + pass + #print str(self) + ".update - ignore result " + str(result) + + + def quit(self, final_result): + self.src.close() + diff --git a/qchess/src/piece.py b/qchess/src/piece.py new file mode 100644 index 0000000..d9acef2 --- /dev/null +++ b/qchess/src/piece.py @@ -0,0 +1,95 @@ +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.types_revealed = [True, False] # Whether the types are known (by default the first type is always known at game start) + + + # + self.last_state = None + self.move_pattern = None + + + + 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.types_revealed = c.types_revealed[:] + + self.last_state = None + self.move_pattern = None + + + + # Make a string for the piece (used for debug) + def __str__(self): + return 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 self.types_revealed[i] == True: + 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": + self.choice = random.randint(0,1) + self.current_type = self.types[self.choice] + self.types_revealed[self.choice] = True + 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_revealed[0] == False or self.types_revealed[1] == False): + 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... diff --git a/qchess/src/player.py b/qchess/src/player.py new file mode 100644 index 0000000..3e6faba --- /dev/null +++ b/qchess/src/player.py @@ -0,0 +1,190 @@ +import subprocess +import select +import platform + + +agent_timeout = -1.0 # Timeout in seconds for AI players to make moves + # WARNING: Won't work for windows based operating systems + +if platform.system() == "Windows": + agent_timeout = -1 # Hence this + +# A player who can't play +class Player(): + def __init__(self, name, colour): + self.name = name + self.colour = colour + +# Player that runs from another process +class AgentPlayer(Player): + + + def __init__(self, name, colour): + Player.__init__(self, name, colour) + self.p = subprocess.Popen(name, stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE) + + self.send_message(colour) + + def send_message(self, s): + if agent_timeout > 0.0: + ready = select.select([], [self.p.stdin], [], agent_timeout)[1] + else: + ready = [self.p.stdin] + if self.p.stdin in ready: + #print "Writing to p.stdin" + try: + self.p.stdin.write(s + "\n") + except: + raise Exception("UNRESPONSIVE") + else: + raise Exception("UNRESPONSIVE") + + def get_response(self): + if agent_timeout > 0.0: + ready = select.select([self.p.stdout], [], [], agent_timeout)[0] + else: + ready = [self.p.stdout] + if self.p.stdout in ready: + #print "Reading from p.stdout" + try: + return self.p.stdout.readline().strip("\r\n") + except: # Exception, e: + raise Exception("UNRESPONSIVE") + else: + raise Exception("UNRESPONSIVE") + + def select(self): + + self.send_message("SELECTION?") + line = self.get_response() + + try: + result = map(int, line.split(" ")) + except: + raise Exception("GIBBERISH \"" + str(line) + "\"") + return result + + def update(self, result): + #print "Update " + str(result) + " called for AgentPlayer" + self.send_message(result) + + + def get_move(self): + + self.send_message("MOVE?") + line = self.get_response() + + try: + result = map(int, line.split(" ")) + except: + raise Exception("GIBBERISH \"" + str(line) + "\"") + return result + + def quit(self, final_result): + try: + self.send_message("QUIT " + final_result) + except: + self.p.kill() + +# So you want to be a player here? +class HumanPlayer(Player): + def __init__(self, name, colour): + Player.__init__(self, name, colour) + + # Select your preferred account + def select(self): + if isinstance(graphics, GraphicsThread): + # Basically, we let the graphics thread do some shit and then return that information to the game thread + graphics.cond.acquire() + # We wait for the graphics thread to select a piece + while graphics.stopped() == False and graphics.state["select"] == None: + graphics.cond.wait() # The difference between humans and machines is that humans sleep + select = graphics.state["select"] + + + graphics.cond.release() + if graphics.stopped(): + return [-1,-1] + return [select.x, select.y] + else: + # Since I don't display the board in this case, I'm not sure why I filled it in... + while True: + sys.stdout.write("SELECTION?\n") + try: + p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + except: + sys.stderr.write("ILLEGAL GIBBERISH\n") + continue + # It's your move captain + def get_move(self): + if isinstance(graphics, GraphicsThread): + graphics.cond.acquire() + while graphics.stopped() == False and graphics.state["dest"] == None: + graphics.cond.wait() + graphics.cond.release() + + return graphics.state["dest"] + else: + + while True: + sys.stdout.write("MOVE?\n") + try: + p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + except: + sys.stderr.write("ILLEGAL GIBBERISH\n") + continue + + # Are you sure you want to quit? + def quit(self, final_result): + if graphics == None: + sys.stdout.write("QUIT " + final_result + "\n") + + # Completely useless function + def update(self, result): + if isinstance(graphics, GraphicsThread): + pass + else: + sys.stdout.write(result + "\n") + + +# Player that makes random moves +class AgentRandom(Player): + def __init__(self, name, colour): + Player.__init__(self, name, colour) + self.choice = None + + self.board = Board(style = "agent") + + def select(self): + while True: + self.choice = self.board.pieces[self.colour][random.randint(0, len(self.board.pieces[self.colour])-1)] + all_moves = [] + # Check that the piece has some possibility to move + tmp = self.choice.current_type + if tmp == "unknown": # For unknown pieces, try both types + for t in self.choice.types: + if t == "unknown": + continue + self.choice.current_type = t + all_moves += self.board.possible_moves(self.choice) + else: + all_moves = self.board.possible_moves(self.choice) + self.choice.current_type = tmp + if len(all_moves) > 0: + break + return [self.choice.x, self.choice.y] + + def get_move(self): + moves = self.board.possible_moves(self.choice) + move = moves[random.randint(0, len(moves)-1)] + return move + + def update(self, result): + #sys.stderr.write(sys.argv[0] + " : Update board for AgentRandom\n") + self.board.update(result) + self.board.verify() + + def quit(self, final_result): + pass + + diff --git a/qchess/src/thread_util.py b/qchess/src/thread_util.py new file mode 100644 index 0000000..2c79330 --- /dev/null +++ b/qchess/src/thread_util.py @@ -0,0 +1,15 @@ +import threading + +# A thread that can be stopped! +# Except it can only be stopped if it checks self.stopped() periodically +# So it can sort of be stopped +class StoppableThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self._stop = threading.Event() + + def stop(self): + self._stop.set() + + def stopped(self): + return self._stop.isSet() diff --git a/qchess/src/timeout_player.py b/qchess/src/timeout_player.py new file mode 100644 index 0000000..36f9e20 --- /dev/null +++ b/qchess/src/timeout_player.py @@ -0,0 +1,70 @@ +import multiprocessing + +# Hacky alternative to using select for timing out players + +# WARNING: Do not wrap around HumanPlayer or things breakify + +class Sleeper(multiprocessing.Process): + def __init__(self, timeout): + multiprocessing.Process.__init__(self) + self.timeout = timeout + + def run(self): + time.sleep(self.timeout) + + +class Worker(multiprocessing.Process): + def __init__(self, function, args, q): + multiprocessing.Process.__init__(self) + self.function = function + self.args = args + self.q = q + + def run(self): + #print str(self) + " runs " + str(self.function) + " with args " + str(self.args) + self.q.put(self.function(*self.args)) + + + +def TimeoutFunction(function, args, timeout): + q = multiprocessing.Queue() + w = Worker(function, args, q) + s = Sleeper(timeout) + w.start() + s.start() + while True: # Busy loop of crappyness + if not w.is_alive(): + s.terminate() + result = q.get() + w.join() + #print "TimeoutFunction gets " + str(result) + return result + elif not s.is_alive(): + w.terminate() + s.join() + raise Exception("UNRESPONSIVE") + + + + +# A player that wraps another player and times out its moves +# Uses threads +# A (crappy) alternative to the use of select() +class TimeoutPlayer(Player): + def __init__(self, base_player, timeout): + Player.__init__(self, base_player.name, base_player.colour) + self.base_player = base_player + self.timeout = timeout + + def select(self): + return TimeoutFunction(self.base_player.select, [], self.timeout) + + + def get_move(self): + return TimeoutFunction(self.base_player.get_move, [], self.timeout) + + def update(self, result): + return TimeoutFunction(self.base_player.update, [result], self.timeout) + + def quit(self, final_result): + return TimeoutFunction(self.base_player.quit, [final_result], self.timeout) diff --git a/qchess/thread_util.py b/qchess/thread_util.py deleted file mode 100644 index 2c79330..0000000 --- a/qchess/thread_util.py +++ /dev/null @@ -1,15 +0,0 @@ -import threading - -# A thread that can be stopped! -# Except it can only be stopped if it checks self.stopped() periodically -# So it can sort of be stopped -class StoppableThread(threading.Thread): - def __init__(self): - threading.Thread.__init__(self) - self._stop = threading.Event() - - def stop(self): - self._stop.set() - - def stopped(self): - return self._stop.isSet() diff --git a/qchess/tools/image_builder.py b/qchess/tools/image_builder.py new file mode 100644 index 0000000..c9760ef --- /dev/null +++ b/qchess/tools/image_builder.py @@ -0,0 +1,33 @@ +#!/usr/bin/python + +import sys +from images import * + +def main(argv): + pygame.init() + try: + target = str(argv[1]) + except: + target = os.path.join(os.path.curdir, "..","data","images") + + try: + grid_size = int(argv[2]) + except: + grid_size = 64 + + + if not os.path.exists(target): + os.mkdir(target) + + create_images([grid_size, grid_size], font_name=os.path.join(os.path.curdir, "..", "data", "DejaVuSans.ttf")) + + for colour in piece_char.keys(): + for piece in piece_char[colour].keys(): + pygame.image.save(images[colour][piece], os.path.join(target,str(colour)+"_"+str(piece)+".png")) + pygame.image.save(small_images[colour][piece], os.path.join(target,str(colour)+"_"+str(piece)+"_small.png")) + + pygame.quit() + return 0 + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/qchess/tools/images.py b/qchess/tools/images.py new file mode 120000 index 0000000..a1ec45a --- /dev/null +++ b/qchess/tools/images.py @@ -0,0 +1 @@ +../src/images.py \ No newline at end of file diff --git a/qchess/tools/images.pyc b/qchess/tools/images.pyc new file mode 100644 index 0000000..0a48eb3 Binary files /dev/null and b/qchess/tools/images.pyc differ diff --git a/qchess/update.sh b/qchess/update.sh deleted file mode 100644 index bec40ea..0000000 --- a/qchess/update.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash - -# I still can't believe I am doing this - -# This updates the component files from the target, as well as the target from the components -# You can't do that with gnu make, because of the circular dependency -# But this should probably not be used by any sane person - -exit 1 - -target=qchess.py -components="piece.py board.py player.py network.py thread_util.py game.py graphics.py main.py" -# The below seems nicer, but doesn't work because things need to be imported in the right order :( -#components=$(ls *.py | tr '\t' '\n' | grep -v $target) - -header="#!/usr/bin/python -u" -footer="# EOF - created from update.sh on $(date)" - - - -# If the target was modified more recently than any of the components, update the component file -target_mod=$(stat -c %Y $target 2>/dev/null) - -if [ $? -ne 0 ]; then - merge_required=true -else - merge_required=true - - - for f in $components; do - - component_mod=$(stat -c %Y $f 2>/dev/null) - if [ $? -ne 0 ]; then - update_required=true - elif [ $component_mod -lt $target_mod ]; then - update_required=true - else - update_required=false - fi - - if $update_required; then - echo "$0 - update $f from $target" - sanity=$(egrep "(+++ $f +++)|(--- $f ---)" $target | wc -l) - if [ $sanity -ne 2 ]; then - $(echo "$0 - $target does not have markers for $f in it!") 1>&2 - exit 1 - fi - cp $f $f~ - new_contents=$(nawk "/+++ $f +++/, /--- $f ---/" $target | grep -v "+++ $f +++" | grep -v "\--- $f ---") - - echo "$new_contents" > $f - else - echo "$0 - $f is newer than $target" - merge_required=true - fi - done - - -fi - -# If any components were modified more recently than the target, merge the components into the target -if $merge_required; then - echo $header > $target - for f in $components; do - echo "$0 - merge $f into $target" - echo "# +++ $f +++ #" >> $target - cat $f >> $target - echo "# --- $f --- #" >> $target - done - - echo $footer >> $target - chmod u+x $target -fi diff --git a/qchess/win32_dll/SDL.dll b/qchess/win32_dll/SDL.dll new file mode 100644 index 0000000..69fd61e Binary files /dev/null and b/qchess/win32_dll/SDL.dll differ diff --git a/qchess/win32_dll/SDL_image.dll b/qchess/win32_dll/SDL_image.dll new file mode 100644 index 0000000..e891b16 Binary files /dev/null and b/qchess/win32_dll/SDL_image.dll differ diff --git a/qchess/win32_dll/SDL_mixer.dll b/qchess/win32_dll/SDL_mixer.dll new file mode 100644 index 0000000..89def76 Binary files /dev/null and b/qchess/win32_dll/SDL_mixer.dll differ diff --git a/qchess/win32_dll/SDL_ttf.dll b/qchess/win32_dll/SDL_ttf.dll new file mode 100644 index 0000000..a8f1bcc Binary files /dev/null and b/qchess/win32_dll/SDL_ttf.dll differ diff --git a/qchess/win32_dll/jpeg.dll b/qchess/win32_dll/jpeg.dll new file mode 100644 index 0000000..72c33d2 Binary files /dev/null and b/qchess/win32_dll/jpeg.dll differ diff --git a/qchess/win32_dll/libfreetype-6.dll b/qchess/win32_dll/libfreetype-6.dll new file mode 100644 index 0000000..3b2e69d Binary files /dev/null and b/qchess/win32_dll/libfreetype-6.dll differ diff --git a/qchess/win32_dll/libjpeg-8.dll b/qchess/win32_dll/libjpeg-8.dll new file mode 100644 index 0000000..74285d2 Binary files /dev/null and b/qchess/win32_dll/libjpeg-8.dll differ diff --git a/qchess/win32_dll/libpng12-0.dll b/qchess/win32_dll/libpng12-0.dll new file mode 100644 index 0000000..f9709be Binary files /dev/null and b/qchess/win32_dll/libpng12-0.dll differ diff --git a/qchess/win32_dll/libpng15-15.dll b/qchess/win32_dll/libpng15-15.dll new file mode 100644 index 0000000..a5854d5 Binary files /dev/null and b/qchess/win32_dll/libpng15-15.dll differ diff --git a/qchess/win32_dll/libtiff-5.dll b/qchess/win32_dll/libtiff-5.dll new file mode 100644 index 0000000..f449d75 Binary files /dev/null and b/qchess/win32_dll/libtiff-5.dll differ diff --git a/qchess/win32_dll/libwebp-2.dll b/qchess/win32_dll/libwebp-2.dll new file mode 100644 index 0000000..e89ac8b Binary files /dev/null and b/qchess/win32_dll/libwebp-2.dll differ diff --git a/qchess/win32_dll/mfc42.dll b/qchess/win32_dll/mfc42.dll new file mode 100644 index 0000000..27692e5 Binary files /dev/null and b/qchess/win32_dll/mfc42.dll differ diff --git a/qchess/win32_dll/smpeg.dll b/qchess/win32_dll/smpeg.dll new file mode 100644 index 0000000..3742ad2 Binary files /dev/null and b/qchess/win32_dll/smpeg.dll differ diff --git a/qchess/win32_dll/tcl85.dll b/qchess/win32_dll/tcl85.dll new file mode 100644 index 0000000..a933e79 Binary files /dev/null and b/qchess/win32_dll/tcl85.dll differ diff --git a/qchess/win32_dll/tk85.dll b/qchess/win32_dll/tk85.dll new file mode 100644 index 0000000..163c2a3 Binary files /dev/null and b/qchess/win32_dll/tk85.dll differ diff --git a/qchess/win32_dll/zlib1.dll b/qchess/win32_dll/zlib1.dll new file mode 100644 index 0000000..53f3376 Binary files /dev/null and b/qchess/win32_dll/zlib1.dll differ