From: Sam Moore Date: Tue, 29 Jan 2013 10:12:05 +0000 (+0800) Subject: Progress? X-Git-Url: https://git.ucc.asn.au/?p=progcomp2013.git;a=commitdiff_plain;h=877034f05346e24fdf822f6e6149ad50d891f030 Progress? - Fixed .dll files that were the wrong ones for cx_freeze win32 - Created "internal" agents, and made AgentBishop one of them - Implemented terrible wrapper class that runs an internal agent in a seperate python process - This is for when timeouts are used - select == better than threads - TimeoutPlayer appears to not work properly anyway - Tested win32 version (script and frozen binary) - ExternalAgent seems to break - TimeoutPlayer seems to break (even more than normal) - Pretty much everything breaks actually - More things seem to break with the frozen binary than with the script - Amazingly the pygame interface doesn't break, just the fancy threading/subprocess/socket/file related stuff - Somehow, the InternalAgents, ie: AgentBishop were working - So windows users can play the python sample agents, but otherwise the program is pretty useless - Implemented the --log and --file arguments - Get an error if the game hasn't actually finished when using --file, should probably fix - It might be cool to let people use --file for a set number of moves, and then continue play from that position - Would need more work... - When graphics are disabled, the game still works - But I need to test it on a server that doesn't have pygame at all TODO: Finish the qchess program and actually work on setting up the competition. --- diff --git a/agents/bishop.py b/agents/bishop.py index 3c7738e..ea96c2c 100755 --- a/agents/bishop.py +++ b/agents/bishop.py @@ -3,192 +3,15 @@ from qchess import * """ - Agent Bishop - ( an agent, not an implementation of a bishop chess piece!) -""" - - - - -# Skeleton class for your agent -class Agent(AgentRandom): # Inherits from AgentRandom (in qchess.py) - def __init__(self, name, colour): - AgentRandom.__init__(self, name, colour) - self.value = {"pawn" : 1, "bishop" : 3, "knight" : 3, "rook" : 5, "queen" : 9, "king" : 100, "unknown" : 4} - - self.aggression = 2.0 # Multiplier for scoring due to aggressive actions - self.defence = 1.0 # Multiplier for scoring due to defensive actions - - self.depth = 0 # Current depth - self.max_depth = 2 # Recurse this many times (for some reason, makes more mistakes when this is increased???) - self.recurse_for = -1 # Recurse for the best few moves each times (less than 0 = all moves) - - for p in self.board.pieces["white"] + self.board.pieces["black"]: - p.last_moves = None - p.selected_moves = None - - - - def get_value(self, piece): - if piece == None: - return 0.0 - return float(self.value[piece.types[0]] + self.value[piece.types[1]]) / 2.0 - - # Score possible moves for the piece - - def prioritise_moves(self, piece): - - #sys.stderr.write(sys.argv[0] + ": prioritise called for " + str(piece) + "\n") - - - - grid = self.board.probability_grid(piece) - #sys.stderr.write("\t Probability grid " + str(grid) + "\n") - moves = [] - for x in range(w): - for y in range(h): - if grid[x][y] < 0.3: # Throw out moves with < 30% probability - #sys.stderr.write("\tReject " + str(x) + "," + str(y) + " (" + str(grid[x][y]) + ")\n") - continue - - target = self.board.grid[x][y] - - - - - # Get total probability that the move is protected - [xx,yy] = [piece.x, piece.y] - [piece.x, piece.y] = [x, y] - self.board.grid[x][y] = piece - self.board.grid[xx][yy] = None - - defenders = self.board.coverage(x, y, piece.colour, reject_allied = False) - d_prob = 0.0 - for d in defenders.keys(): - d_prob += defenders[d] - if len(defenders.keys()) > 0: - d_prob /= float(len(defenders.keys())) - - if (d_prob > 1.0): - d_prob = 1.0 - - # Get total probability that the move is threatened - attackers = self.board.coverage(x, y, opponent(piece.colour), reject_allied = False) - a_prob = 0.0 - for a in attackers.keys(): - a_prob += attackers[a] - if len(attackers.keys()) > 0: - a_prob /= float(len(attackers.keys())) - - if (a_prob > 1.0): - a_prob = 1.0 - - self.board.grid[x][y] = target - self.board.grid[xx][yy] = piece - [piece.x, piece.y] = [xx, yy] - - - # Score of the move - value = self.aggression * (1.0 + d_prob) * self.get_value(target) - self.defence * (1.0 - d_prob) * a_prob * self.get_value(piece) - - # Adjust score based on movement of piece out of danger - attackers = self.board.coverage(piece.x, piece.y, opponent(piece.colour)) - s_prob = 0.0 - for a in attackers.keys(): - s_prob += attackers[a] - if len(attackers.keys()) > 0: - s_prob /= float(len(attackers.keys())) - - if (s_prob > 1.0): - s_prob = 1.0 - value += self.defence * s_prob * self.get_value(piece) - - # Adjust score based on probability that the move is actually possible - moves.append([[x, y], grid[x][y] * value]) - - moves.sort(key = lambda e : e[1], reverse = True) - #sys.stderr.write(sys.argv[0] + ": Moves for " + str(piece) + " are " + str(moves) + "\n") - - piece.last_moves = moves - piece.selected_moves = None - - - - - return moves - - def select_best(self, colour): - - self.depth += 1 - all_moves = {} - for p in self.board.pieces[colour]: - self.choice = p # Temporarily pick that piece - m = self.prioritise_moves(p) - if len(m) > 0: - all_moves.update({p : m[0]}) - - if len(all_moves.items()) <= 0: - return None - - - opts = all_moves.items() - opts.sort(key = lambda e : e[1][1], reverse = True) - - if self.depth >= self.max_depth: - self.depth -= 1 - return list(opts[0]) - - if self.recurse_for >= 0: - opts = opts[0:self.recurse_for] - #sys.stderr.write(sys.argv[0] + " : Before recurse, options are " + str(opts) + "\n") + This is a wrapper to AgentBishop, which can now be found directly in qchess as one of the internal agents + As well as wrapping, it will also show AgentBishop's thought processes in graphics, which is kind of cool - # Take the best few moves, and recurse - for choice in opts[0:self.recurse_for]: - [xx,yy] = [choice[0].x, choice[0].y] # Remember position - [nx,ny] = choice[1][0] # Target - [choice[0].x, choice[0].y] = [nx, ny] # Set position - target = self.board.grid[nx][ny] # Remember piece in spot - self.board.grid[xx][yy] = None # Remove piece - self.board.grid[nx][ny] = choice[0] # Replace with moving piece - - # Recurse - best_enemy_move = self.select_best(opponent(choice[0].colour)) - choice[1][1] -= best_enemy_move[1][1] / float(self.depth + 1.0) - - [choice[0].x, choice[0].y] = [xx, yy] # Restore position - self.board.grid[nx][ny] = target # Restore taken piece - self.board.grid[xx][yy] = choice[0] # Restore moved piece - - - - opts.sort(key = lambda e : e[1][1], reverse = True) - #sys.stderr.write(sys.argv[0] + " : After recurse, options are " + str(opts) + "\n") - - self.depth -= 1 - return list(opts[0]) - - + So basically, using `./qchess.py @internal:AgentBishop` is better, unless you want to see the graphics +""" - # Returns [x,y] of selected piece - def select(self): - - self.choice = self.select_best(self.colour)[0] - return [self.choice.x, self.choice.y] - # Returns [x,y] of square to move selected piece into - def get_move(self): - self.choice.selected_moves = self.choice.last_moves - moves = self.prioritise_moves(self.choice) - if len(moves) > 0: - return moves[0][0] - else: - return AgentRandom.get_move(self) - - - - # Horrible messy graphics class that draws what the agent is doing, kind of useful for testing -class AgentGraphics(GraphicsThread): +class AgentBishop_Graphics(GraphicsThread): def __init__(self, board, title): GraphicsThread.__init__(self, board, title, grid_sz = [64,64]) self.choice = None @@ -280,42 +103,13 @@ class AgentGraphics(GraphicsThread): self.moves = None pygame.display.quit() - - -# Main function; don't alter -def main(argv): - - global agent - colour = sys.stdin.readline().strip("\n") # Gets the colour of the agent from stdin - - agent = Agent(argv[0], colour) # Creates your agent - - #graphics = AgentGraphics(agent.board, title="Agent Bishop (" + str(colour) + ") - DEBUG VIEW") - #graphics.start() - - # Plays quantum chess using your agent - while True: - line = sys.stdin.readline().strip(" \r\n") - #sys.stderr.write(argv[0] + ": gets line \"" + str(line) + "\"\n") - if line == "SELECTION?": - [x,y] = agent.select() # Gets your agent's selection - #print "Select " + str(x) + "," + str(y) - sys.stdout.write(str(x) + " " + str(y) + "\n") - elif line == "MOVE?": - [x,y] = agent.get_move() # Gets your agent's move - sys.stdout.write(str(x) + " " + str(y) + "\n") - elif line.split(" ")[0] == "QUIT": - agent.quit(" ".join(line.split(" ")[1:])) # Quits the game -# graphics.stop() - break - else: - agent.update(line) # Updates agent.board - - #graphics.stop() - #graphics.join() - return 0 - -# Don't touch this if __name__ == "__main__": - sys.exit(main(sys.argv)) + + colour = sys.stdin.readline().strip("\r\n") + agent = AgentBishop(sys.argv[0], colour) + graphics = AgentBishop_Graphics(agent.board, "Agent Bishop ("+agent.colour+") DEBUG") + graphics.start() + run_agent(agent) + graphics.stop() + graphics.join() diff --git a/agents/win32_bishop.bat b/agents/win32_bishop.bat deleted file mode 100755 index 92d9440..0000000 --- a/agents/win32_bishop.bat +++ /dev/null @@ -1,4 +0,0 @@ -:: 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 9da5885..c6a7483 100644 --- a/qchess/Makefile +++ b/qchess/Makefile @@ -6,7 +6,7 @@ DLL_PATH=win32_dll 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 + cd build; for d in $$(ls); do if [ -d $$d ]; then zip -r $$d.zip $$d; rm -r $$d; fi; done python_native : make -C src diff --git a/qchess/build/exe.linux-x86_64-2.7.zip b/qchess/build/exe.linux-x86_64-2.7.zip index 41103d1..80e1819 100644 Binary files a/qchess/build/exe.linux-x86_64-2.7.zip 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 index 4ef51c0..402948b 100644 Binary files a/qchess/build/exe.win32-2.7.zip and b/qchess/build/exe.win32-2.7.zip differ diff --git a/qchess/data/help.txt b/qchess/data/help.txt index 1c4671c..c0a8b6f 100644 --- a/qchess/data/help.txt +++ b/qchess/data/help.txt @@ -42,6 +42,18 @@ ARGUMENTS IMPORTANT: Only ONE of the games should give the other's address. + @internal:name + An internal agent player + + These agents run within the qchess program (unless there is a timeout setting... never mind). + + Choices are: + + AgentRandom - Makes random moves only + + AgentBishop - Uses probability estimates and a min/max recursive (depth is only one) algorithm + - Will usually take a long time to run + OPTIONS --help diff --git a/qchess/data/images/black_bishop.png b/qchess/data/images/black_bishop.png new file mode 100644 index 0000000..cd410c3 Binary files /dev/null and b/qchess/data/images/black_bishop.png differ diff --git a/qchess/data/images/black_bishop_small.png b/qchess/data/images/black_bishop_small.png new file mode 100644 index 0000000..676ecad Binary files /dev/null and b/qchess/data/images/black_bishop_small.png differ diff --git a/qchess/data/images/black_king.png b/qchess/data/images/black_king.png new file mode 100644 index 0000000..f755e10 Binary files /dev/null and b/qchess/data/images/black_king.png differ diff --git a/qchess/data/images/black_king_small.png b/qchess/data/images/black_king_small.png new file mode 100644 index 0000000..b71fb8a Binary files /dev/null and b/qchess/data/images/black_king_small.png differ diff --git a/qchess/data/images/black_knight.png b/qchess/data/images/black_knight.png new file mode 100644 index 0000000..e664c33 Binary files /dev/null and b/qchess/data/images/black_knight.png differ diff --git a/qchess/data/images/black_knight_small.png b/qchess/data/images/black_knight_small.png new file mode 100644 index 0000000..b78cd05 Binary files /dev/null and b/qchess/data/images/black_knight_small.png differ diff --git a/qchess/data/images/black_pawn.png b/qchess/data/images/black_pawn.png new file mode 100644 index 0000000..4aa24ef Binary files /dev/null and b/qchess/data/images/black_pawn.png differ diff --git a/qchess/data/images/black_pawn_small.png b/qchess/data/images/black_pawn_small.png new file mode 100644 index 0000000..6899430 Binary files /dev/null and b/qchess/data/images/black_pawn_small.png differ diff --git a/qchess/data/images/black_queen.png b/qchess/data/images/black_queen.png new file mode 100644 index 0000000..a750934 Binary files /dev/null and b/qchess/data/images/black_queen.png differ diff --git a/qchess/data/images/black_queen_small.png b/qchess/data/images/black_queen_small.png new file mode 100644 index 0000000..9134bbd Binary files /dev/null and b/qchess/data/images/black_queen_small.png differ diff --git a/qchess/data/images/black_rook.png b/qchess/data/images/black_rook.png new file mode 100644 index 0000000..d034d09 Binary files /dev/null and b/qchess/data/images/black_rook.png differ diff --git a/qchess/data/images/black_rook_small.png b/qchess/data/images/black_rook_small.png new file mode 100644 index 0000000..3ae6829 Binary files /dev/null and b/qchess/data/images/black_rook_small.png differ diff --git a/qchess/data/images/black_unknown.png b/qchess/data/images/black_unknown.png new file mode 100644 index 0000000..5ed378d Binary files /dev/null and b/qchess/data/images/black_unknown.png differ diff --git a/qchess/data/images/black_unknown_small.png b/qchess/data/images/black_unknown_small.png new file mode 100644 index 0000000..3d44fc4 Binary files /dev/null and b/qchess/data/images/black_unknown_small.png differ diff --git a/qchess/data/images/white_bishop.png b/qchess/data/images/white_bishop.png new file mode 100644 index 0000000..5c782de Binary files /dev/null and b/qchess/data/images/white_bishop.png differ diff --git a/qchess/data/images/white_bishop_small.png b/qchess/data/images/white_bishop_small.png new file mode 100644 index 0000000..de1e97f Binary files /dev/null and b/qchess/data/images/white_bishop_small.png differ diff --git a/qchess/data/images/white_king.png b/qchess/data/images/white_king.png new file mode 100644 index 0000000..74e8468 Binary files /dev/null and b/qchess/data/images/white_king.png differ diff --git a/qchess/data/images/white_king_small.png b/qchess/data/images/white_king_small.png new file mode 100644 index 0000000..a6152ab Binary files /dev/null and b/qchess/data/images/white_king_small.png differ diff --git a/qchess/data/images/white_knight.png b/qchess/data/images/white_knight.png new file mode 100644 index 0000000..0bf4759 Binary files /dev/null and b/qchess/data/images/white_knight.png differ diff --git a/qchess/data/images/white_knight_small.png b/qchess/data/images/white_knight_small.png new file mode 100644 index 0000000..b42669d Binary files /dev/null and b/qchess/data/images/white_knight_small.png differ diff --git a/qchess/data/images/white_pawn.png b/qchess/data/images/white_pawn.png new file mode 100644 index 0000000..a99fce5 Binary files /dev/null and b/qchess/data/images/white_pawn.png differ diff --git a/qchess/data/images/white_pawn_small.png b/qchess/data/images/white_pawn_small.png new file mode 100644 index 0000000..62f0aab Binary files /dev/null and b/qchess/data/images/white_pawn_small.png differ diff --git a/qchess/data/images/white_queen.png b/qchess/data/images/white_queen.png new file mode 100644 index 0000000..0610382 Binary files /dev/null and b/qchess/data/images/white_queen.png differ diff --git a/qchess/data/images/white_queen_small.png b/qchess/data/images/white_queen_small.png new file mode 100644 index 0000000..1b7820b Binary files /dev/null and b/qchess/data/images/white_queen_small.png differ diff --git a/qchess/data/images/white_rook.png b/qchess/data/images/white_rook.png new file mode 100644 index 0000000..9d07adf Binary files /dev/null and b/qchess/data/images/white_rook.png differ diff --git a/qchess/data/images/white_rook_small.png b/qchess/data/images/white_rook_small.png new file mode 100644 index 0000000..c67c593 Binary files /dev/null and b/qchess/data/images/white_rook_small.png differ diff --git a/qchess/data/images/white_unknown.png b/qchess/data/images/white_unknown.png new file mode 100644 index 0000000..fc23f56 Binary files /dev/null and b/qchess/data/images/white_unknown.png differ diff --git a/qchess/data/images/white_unknown_small.png b/qchess/data/images/white_unknown_small.png new file mode 100644 index 0000000..c261af9 Binary files /dev/null and b/qchess/data/images/white_unknown_small.png differ diff --git a/qchess/qchess.py b/qchess/qchess.py index 8347a75..219b34f 100755 --- a/qchess/qchess.py +++ b/qchess/qchess.py @@ -516,13 +516,16 @@ class Player(): self.name = name self.colour = colour + def update(self, result): + pass + # Player that runs from another process -class AgentPlayer(Player): +class ExternalAgent(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.p = subprocess.Popen(name,bufsize=0,stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True,universal_newlines=True) self.send_message(colour) @@ -532,13 +535,13 @@ class AgentPlayer(Player): else: ready = [self.p.stdin] if self.p.stdin in ready: - #print "Writing to p.stdin" + #sys.stderr.write("Writing \'" + s + "\' to " + str(self.p) + "\n") try: self.p.stdin.write(s + "\n") except: raise Exception("UNRESPONSIVE") else: - raise Exception("UNRESPONSIVE") + raise Exception("TIMEOUT") def get_response(self): if agent_timeout > 0.0: @@ -546,13 +549,15 @@ class AgentPlayer(Player): else: ready = [self.p.stdout] if self.p.stdout in ready: - #print "Reading from p.stdout" + #sys.stderr.write("Reading from " + str(self.p) + " 's stdout...\n") try: - return self.p.stdout.readline().strip("\r\n") + result = self.p.stdout.readline().strip("\r\n") + #sys.stderr.write("Read \'" + result + "\' from " + str(self.p) + "\n") + return result except: # Exception, e: raise Exception("UNRESPONSIVE") else: - raise Exception("UNRESPONSIVE") + raise Exception("TIMEOUT") def select(self): @@ -648,14 +653,28 @@ class HumanPlayer(Player): sys.stdout.write(result + "\n") -# Player that makes random moves -class AgentRandom(Player): +# Default internal player (makes random moves) +class InternalAgent(Player): def __init__(self, name, colour): Player.__init__(self, name, colour) self.choice = None self.board = Board(style = "agent") + + + def update(self, result): + + self.board.update(result) + self.board.verify() + + def quit(self, final_result): + pass + +class AgentRandom(InternalAgent): + def __init__(self, name, colour): + InternalAgent.__init__(self, name, colour) + def select(self): while True: self.choice = self.board.pieces[self.colour][random.randint(0, len(self.board.pieces[self.colour])-1)] @@ -680,21 +699,229 @@ class AgentRandom(Player): 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 +# Terrible, terrible hacks + +def run_agent(agent): + #sys.stderr.write(sys.argv[0] + " : Running agent " + str(agent) + "\n") + colour = sys.stdin.readline().strip(" \r\n") + agent.colour = colour + while True: + line = sys.stdin.readline().strip(" \r\n") + if line == "SELECTION?": + #sys.stderr.write(sys.argv[0] + " : Make selection\n") + [x,y] = agent.select() # Gets your agent's selection + #sys.stderr.write(sys.argv[0] + " : Selection was " + str(agent.choice) + "\n") + sys.stdout.write(str(x) + " " + str(y) + "\n") + elif line == "MOVE?": + #sys.stderr.write(sys.argv[0] + " : Make move\n") + [x,y] = agent.get_move() # Gets your agent's move + sys.stdout.write(str(x) + " " + str(y) + "\n") + elif line.split(" ")[0] == "QUIT": + #sys.stderr.write(sys.argv[0] + " : Quitting\n") + agent.quit(" ".join(line.split(" ")[1:])) # Quits the game + break + else: + agent.update(line) # Updates agent.board + return 0 + + +# Sort of works? +class ExternalWrapper(ExternalAgent): + def __init__(self, agent): + run = "python -u -c \"import sys;import os;from qchess import *;agent = " + agent.__class__.__name__ + "('" + agent.name + "','"+agent.colour+"');sys.exit(run_agent(agent))\"" + # str(run) + ExternalAgent.__init__(self, run, agent.colour) + + # --- player.py --- # +# A sample agent + + +class AgentBishop(InternalAgent): # Inherits from InternalAgent (in qchess) + def __init__(self, name, colour): + InternalAgent.__init__(self, name, colour) + self.value = {"pawn" : 1, "bishop" : 3, "knight" : 3, "rook" : 5, "queen" : 9, "king" : 100, "unknown" : 4} + + self.aggression = 2.0 # Multiplier for scoring due to aggressive actions + self.defence = 1.0 # Multiplier for scoring due to defensive actions + + self.depth = 0 # Current depth + self.max_depth = 2 # Recurse this many times (for some reason, makes more mistakes when this is increased???) + self.recurse_for = -1 # Recurse for the best few moves each times (less than 0 = all moves) + + for p in self.board.pieces["white"] + self.board.pieces["black"]: + p.last_moves = None + p.selected_moves = None + + + + def get_value(self, piece): + if piece == None: + return 0.0 + return float(self.value[piece.types[0]] + self.value[piece.types[1]]) / 2.0 + + # Score possible moves for the piece + + def prioritise_moves(self, piece): + + #sys.stderr.write(sys.argv[0] + " : " + str(self) + " prioritise called for " + str(piece) + "\n") + + + + grid = self.board.probability_grid(piece) + #sys.stderr.write("\t Probability grid " + str(grid) + "\n") + moves = [] + for x in range(w): + for y in range(h): + if grid[x][y] < 0.3: # Throw out moves with < 30% probability + #sys.stderr.write("\tReject " + str(x) + "," + str(y) + " (" + str(grid[x][y]) + ")\n") + continue + + target = self.board.grid[x][y] + + + + + # Get total probability that the move is protected + [xx,yy] = [piece.x, piece.y] + [piece.x, piece.y] = [x, y] + self.board.grid[x][y] = piece + self.board.grid[xx][yy] = None + + defenders = self.board.coverage(x, y, piece.colour, reject_allied = False) + d_prob = 0.0 + for d in defenders.keys(): + d_prob += defenders[d] + if len(defenders.keys()) > 0: + d_prob /= float(len(defenders.keys())) + + if (d_prob > 1.0): + d_prob = 1.0 + + # Get total probability that the move is threatened + attackers = self.board.coverage(x, y, opponent(piece.colour), reject_allied = False) + a_prob = 0.0 + for a in attackers.keys(): + a_prob += attackers[a] + if len(attackers.keys()) > 0: + a_prob /= float(len(attackers.keys())) + + if (a_prob > 1.0): + a_prob = 1.0 + + self.board.grid[x][y] = target + self.board.grid[xx][yy] = piece + [piece.x, piece.y] = [xx, yy] + + + # Score of the move + value = self.aggression * (1.0 + d_prob) * self.get_value(target) - self.defence * (1.0 - d_prob) * a_prob * self.get_value(piece) + + # Adjust score based on movement of piece out of danger + attackers = self.board.coverage(piece.x, piece.y, opponent(piece.colour)) + s_prob = 0.0 + for a in attackers.keys(): + s_prob += attackers[a] + if len(attackers.keys()) > 0: + s_prob /= float(len(attackers.keys())) + + if (s_prob > 1.0): + s_prob = 1.0 + value += self.defence * s_prob * self.get_value(piece) + + # Adjust score based on probability that the move is actually possible + moves.append([[x, y], grid[x][y] * value]) + + moves.sort(key = lambda e : e[1], reverse = True) + #sys.stderr.write(sys.argv[0] + ": Moves for " + str(piece) + " are " + str(moves) + "\n") + + piece.last_moves = moves + piece.selected_moves = None + + + + + return moves + + def select_best(self, colour): + + self.depth += 1 + all_moves = {} + for p in self.board.pieces[colour]: + self.choice = p # Temporarily pick that piece + m = self.prioritise_moves(p) + if len(m) > 0: + all_moves.update({p : m[0]}) + + if len(all_moves.items()) <= 0: + return None + + + opts = all_moves.items() + opts.sort(key = lambda e : e[1][1], reverse = True) + + if self.depth >= self.max_depth: + self.depth -= 1 + return list(opts[0]) + + if self.recurse_for >= 0: + opts = opts[0:self.recurse_for] + #sys.stderr.write(sys.argv[0] + " : Before recurse, options are " + str(opts) + "\n") + + # Take the best few moves, and recurse + for choice in opts[0:self.recurse_for]: + [xx,yy] = [choice[0].x, choice[0].y] # Remember position + [nx,ny] = choice[1][0] # Target + [choice[0].x, choice[0].y] = [nx, ny] # Set position + target = self.board.grid[nx][ny] # Remember piece in spot + self.board.grid[xx][yy] = None # Remove piece + self.board.grid[nx][ny] = choice[0] # Replace with moving piece + + # Recurse + best_enemy_move = self.select_best(opponent(choice[0].colour)) + choice[1][1] -= best_enemy_move[1][1] / float(self.depth + 1.0) + + [choice[0].x, choice[0].y] = [xx, yy] # Restore position + self.board.grid[nx][ny] = target # Restore taken piece + self.board.grid[xx][yy] = choice[0] # Restore moved piece + + + + opts.sort(key = lambda e : e[1][1], reverse = True) + #sys.stderr.write(sys.argv[0] + " : After recurse, options are " + str(opts) + "\n") + + self.depth -= 1 + return list(opts[0]) + + + + # Returns [x,y] of selected piece + def select(self): + #sys.stderr.write("Getting choice...") + self.choice = self.select_best(self.colour)[0] + #sys.stderr.write(" Done " + str(self.choice)+"\n") + return [self.choice.x, self.choice.y] + + # Returns [x,y] of square to move selected piece into + def get_move(self): + #sys.stderr.write("Choice is " + str(self.choice) + "\n") + self.choice.selected_moves = self.choice.last_moves + moves = self.prioritise_moves(self.choice) + if len(moves) > 0: + return moves[0][0] + else: + return InternalAgent.get_move(self) + +# --- agent_bishop.py --- # import multiprocessing # Hacky alternative to using select for timing out players # WARNING: Do not wrap around HumanPlayer or things breakify +# WARNING: Do not use in general or things breakify class Sleeper(multiprocessing.Process): def __init__(self, timeout): @@ -734,7 +961,7 @@ def TimeoutFunction(function, args, timeout): elif not s.is_alive(): w.terminate() s.join() - raise Exception("UNRESPONSIVE") + raise Exception("TIMEOUT") @@ -970,6 +1197,17 @@ class StoppableThread(threading.Thread): return self._stop.isSet() # --- thread_util.py --- # + +log_file = None + +def log(s): + if log_file != None: + import datetime + log_file.write(str(datetime.datetime.now()) + " : " + s + "\n") + + + + # A thread that runs the game class GameThread(StoppableThread): def __init__(self, board, players): @@ -1007,6 +1245,7 @@ class GameThread(StoppableThread): p2.update(result) # Inform players of what happened + log(result) target = self.board.grid[x][y] if isinstance(graphics, GraphicsThread): @@ -1035,9 +1274,12 @@ class GameThread(StoppableThread): if self.stopped(): break - result = self.board.update_move(x, y, x2, y2) + self.board.update_move(x, y, x2, y2) + result = str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2) for p2 in self.players: - p2.update(str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2)) # Inform players of what happened + p2.update(result) # Inform players of what happened + + log(result) if isinstance(graphics, GraphicsThread): with graphics.lock: @@ -1081,10 +1323,80 @@ class GameThread(StoppableThread): for p2 in self.players: p2.quit(self.final_result) + log(self.final_result) + graphics.stop() +# A thread that replays a log file +class ReplayThread(GameThread): + def __init__(self, players, src): + self.board = Board(style="agent") + GameThread.__init__(self, self.board, players) + self.src = src + + self.ended = False + + def run(self): + i = 0 + phase = 0 + for line in self.src: + + if self.stopped(): + self.ended = True + break + + with self.lock: + self.state["turn"] = self.players[i] + line = line.split(":") + result = line[len(line)-1].strip(" \r\n") + log(result) + + try: + self.board.update(result) + except: + self.ended = True + self.final_result = result + if isinstance(graphics, GraphicsThread): + graphics.stop() + break + + [x,y] = map(int, result.split(" ")[0:2]) + target = self.board.grid[x][y] + + if isinstance(graphics, GraphicsThread): + if phase == 0: + with graphics.lock: + graphics.state["moves"] = self.board.possible_moves(target) + graphics.state["select"] = target + + time.sleep(turn_delay) + + elif phase == 1: + [x2,y2] = map(int, result.split(" ")[3:5]) + with graphics.lock: + graphics.state["moves"] = [[x2,y2]] + + time.sleep(turn_delay) + + with graphics.lock: + graphics.state["select"] = None + graphics.state["dest"] = None + graphics.state["moves"] = None + + + + + + for p in self.players: + p.update(result) + + phase = (phase + 1) % 2 + if phase == 0: + i = (i + 1) % 2 + + def opponent(colour): if colour == "white": @@ -1144,13 +1456,12 @@ def load_images(image_dir=os.path.join(os.path.curdir, "data", "images")): 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 - - - - - - +graphics_enabled = True +try: + import pygame +except: + graphics_enabled = False + @@ -1513,38 +1824,51 @@ class GraphicsThread(StoppableThread): 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)) + import inspect + internal_agents = inspect.getmembers(sys.modules[__name__], inspect.isclass) + internal_agents = [x for x in internal_agents if issubclass(x[1], InternalAgent)] + internal_agents.remove(('InternalAgent', InternalAgent)) + if len(internal_agents) > 0: + choice2 = self.SelectButton(["internal", "external"], prompt="Type of agent") 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 + choice2 = 1 + if choice2 == 0: + agent = internal_agents[self.SelectButton(map(lambda e : e[0], internal_agents), prompt="Choose internal agent")] + players.append(agent[1](agent[0], colour)) + elif choice2 == 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() - - try: - p = make_player(path, colour) - except: + players.append(make_player(path, colour)) + except: + + p = None + while p == None: self.board.display_grid(self.window, self.grid_sz) pygame.display.flip() - self.message("Invalid path!") - time.sleep(1) - p = None - players.append(p) + 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 == "": @@ -1602,9 +1926,29 @@ def make_player(name, colour): if len(s) > 1: address = s[1] return NetworkReceiver(colour, address) + if s[0] == "internal": + + import inspect + internal_agents = inspect.getmembers(sys.modules[__name__], inspect.isclass) + internal_agents = [x for x in internal_agents if issubclass(x[1], InternalAgent)] + internal_agents.remove(('InternalAgent', InternalAgent)) + + if len(s) != 2: + sys.stderr.write(sys.argv[0] + " : '@internal' should be followed by ':' and an agent name\n") + sys.stderr.write(sys.argv[0] + " : Choices are: " + str(map(lambda e : e[0], internal_agents)) + "\n") + return None + + for a in internal_agents: + if s[1] == a[0]: + return a[1](name, colour) + + sys.stderr.write(sys.argv[0] + " : Can't find an internal agent matching \"" + s[1] + "\"\n") + sys.stderr.write(sys.argv[0] + " : Choices are: " + str(map(lambda e : e[0], internal_agents)) + "\n") + return None + else: - return AgentPlayer(name, colour) + return ExternalAgent(name, colour) @@ -1620,13 +1964,20 @@ def main(argv): global agent_timeout global log_file global src_file + global graphics_enabled - + src_file = None style = "quantum" colour = "white" - graphics_enabled = True + + # Get the important warnings out of the way + if platform.system() == "Windows": + sys.stderr.write(sys.argv[0] + " : Warning - You are using " + platform.system() + "\n") + if platform.release() == "Vista": + sys.stderr.write(sys.argv[0] + " : God help you.\n") + players = [] i = 0 @@ -1634,7 +1985,11 @@ def main(argv): i += 1 arg = argv[i] if arg[0] != '-': - players.append(make_player(arg, colour)) + p = make_player(arg, colour) + if not isinstance(p, Player): + sys.stderr.write(sys.argv[0] + " : Fatal error creating " + colour + " player\n") + return 100 + players.append(p) if colour == "white": colour = "black" elif colour == "black": @@ -1653,15 +2008,15 @@ def main(argv): elif (arg[1] == '-' and arg[2:].split("=")[0] == "file"): # Load game from file if len(arg[2:].split("=")) == 1: - src_file = sys.stdout + src_file = sys.stdin else: - src_file = arg[2:].split("=")[1] + src_file = open(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] + log_file = open(arg[2:].split("=")[1], "w") elif (arg[1] == '-' and arg[2:].split("=")[0] == "delay"): # Delay if len(arg[2:].split("=")) == 1: @@ -1683,13 +2038,23 @@ def main(argv): # Create the board - board = Board(style) + + # Construct a GameThread! Make it global! Damn the consequences! + + if src_file != None: + if len(players) == 0: + players = [Player("dummy", "white"), Player("dummy", "black")] + game = ReplayThread(players, src_file) + else: + board = Board(style) + game = GameThread(board, players) + # Initialise GUI if graphics_enabled == True: try: - graphics = GraphicsThread(board, grid_sz = [64,64]) # Construct a GraphicsThread! + graphics = GraphicsThread(game.board, grid_sz = [64,64]) # Construct a GraphicsThread! except Exception,e: graphics = None @@ -1732,23 +2097,24 @@ def main(argv): # 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") + if agent_timeout > 0: + if platform.system() == "Windows": + for i in range(len(players)): + if isinstance(players[i], ExternalAgent) or isinstance(players[i], InternalAgent): + players[i] = TimeoutPlayer(players[i], agent_timeout) - for i in range(len(players)): - if isinstance(players[i], AgentPlayer): - players[i] = TimeoutPlayer(players[i], agent_timeout) + else: + warned = False + # InternalAgents get wrapped to an ExternalAgent when there is a timeout + # This is not confusing at all. + for i in range(len(players)): + if isinstance(players[i], InternalAgent): + players[i] = ExternalWrapper(players[i]) - # Could potentially wrap TimeoutPlayer around internal classes... - # But that would suck - - # Construct a GameThread! Make it global! Damn the consequences! - game = GameThread(board, players) @@ -1756,13 +2122,21 @@ def main(argv): game.start() # This runs in a new thread graphics.run() game.join() - return game.error + graphics.error + error = game.error + graphics.error else: game.run() - return game.error + error = game.error + + if log_file != None and log_file != sys.stdout: + log_file.close() + + if src_file != None and src_file != sys.stdin: + src_file.close() + + return error # This is how python does a main() function... if __name__ == "__main__": sys.exit(main(sys.argv)) # --- main.py --- # -# EOF - created from make on Mon Jan 28 22:52:28 WST 2013 +# EOF - created from make on Tue Jan 29 18:10:18 WST 2013 diff --git a/qchess/src/Makefile b/qchess/src/Makefile index 8ed1944..268cbfe 100644 --- a/qchess/src/Makefile +++ b/qchess/src/Makefile @@ -1,7 +1,7 @@ # 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 = piece.py board.py player.py agent_bishop.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) diff --git a/qchess/src/agent_bishop.py b/qchess/src/agent_bishop.py new file mode 100644 index 0000000..13be5bd --- /dev/null +++ b/qchess/src/agent_bishop.py @@ -0,0 +1,178 @@ +# A sample agent + + +class AgentBishop(InternalAgent): # Inherits from InternalAgent (in qchess) + def __init__(self, name, colour): + InternalAgent.__init__(self, name, colour) + self.value = {"pawn" : 1, "bishop" : 3, "knight" : 3, "rook" : 5, "queen" : 9, "king" : 100, "unknown" : 4} + + self.aggression = 2.0 # Multiplier for scoring due to aggressive actions + self.defence = 1.0 # Multiplier for scoring due to defensive actions + + self.depth = 0 # Current depth + self.max_depth = 2 # Recurse this many times (for some reason, makes more mistakes when this is increased???) + self.recurse_for = -1 # Recurse for the best few moves each times (less than 0 = all moves) + + for p in self.board.pieces["white"] + self.board.pieces["black"]: + p.last_moves = None + p.selected_moves = None + + + + def get_value(self, piece): + if piece == None: + return 0.0 + return float(self.value[piece.types[0]] + self.value[piece.types[1]]) / 2.0 + + # Score possible moves for the piece + + def prioritise_moves(self, piece): + + #sys.stderr.write(sys.argv[0] + " : " + str(self) + " prioritise called for " + str(piece) + "\n") + + + + grid = self.board.probability_grid(piece) + #sys.stderr.write("\t Probability grid " + str(grid) + "\n") + moves = [] + for x in range(w): + for y in range(h): + if grid[x][y] < 0.3: # Throw out moves with < 30% probability + #sys.stderr.write("\tReject " + str(x) + "," + str(y) + " (" + str(grid[x][y]) + ")\n") + continue + + target = self.board.grid[x][y] + + + + + # Get total probability that the move is protected + [xx,yy] = [piece.x, piece.y] + [piece.x, piece.y] = [x, y] + self.board.grid[x][y] = piece + self.board.grid[xx][yy] = None + + defenders = self.board.coverage(x, y, piece.colour, reject_allied = False) + d_prob = 0.0 + for d in defenders.keys(): + d_prob += defenders[d] + if len(defenders.keys()) > 0: + d_prob /= float(len(defenders.keys())) + + if (d_prob > 1.0): + d_prob = 1.0 + + # Get total probability that the move is threatened + attackers = self.board.coverage(x, y, opponent(piece.colour), reject_allied = False) + a_prob = 0.0 + for a in attackers.keys(): + a_prob += attackers[a] + if len(attackers.keys()) > 0: + a_prob /= float(len(attackers.keys())) + + if (a_prob > 1.0): + a_prob = 1.0 + + self.board.grid[x][y] = target + self.board.grid[xx][yy] = piece + [piece.x, piece.y] = [xx, yy] + + + # Score of the move + value = self.aggression * (1.0 + d_prob) * self.get_value(target) - self.defence * (1.0 - d_prob) * a_prob * self.get_value(piece) + + # Adjust score based on movement of piece out of danger + attackers = self.board.coverage(piece.x, piece.y, opponent(piece.colour)) + s_prob = 0.0 + for a in attackers.keys(): + s_prob += attackers[a] + if len(attackers.keys()) > 0: + s_prob /= float(len(attackers.keys())) + + if (s_prob > 1.0): + s_prob = 1.0 + value += self.defence * s_prob * self.get_value(piece) + + # Adjust score based on probability that the move is actually possible + moves.append([[x, y], grid[x][y] * value]) + + moves.sort(key = lambda e : e[1], reverse = True) + #sys.stderr.write(sys.argv[0] + ": Moves for " + str(piece) + " are " + str(moves) + "\n") + + piece.last_moves = moves + piece.selected_moves = None + + + + + return moves + + def select_best(self, colour): + + self.depth += 1 + all_moves = {} + for p in self.board.pieces[colour]: + self.choice = p # Temporarily pick that piece + m = self.prioritise_moves(p) + if len(m) > 0: + all_moves.update({p : m[0]}) + + if len(all_moves.items()) <= 0: + return None + + + opts = all_moves.items() + opts.sort(key = lambda e : e[1][1], reverse = True) + + if self.depth >= self.max_depth: + self.depth -= 1 + return list(opts[0]) + + if self.recurse_for >= 0: + opts = opts[0:self.recurse_for] + #sys.stderr.write(sys.argv[0] + " : Before recurse, options are " + str(opts) + "\n") + + # Take the best few moves, and recurse + for choice in opts[0:self.recurse_for]: + [xx,yy] = [choice[0].x, choice[0].y] # Remember position + [nx,ny] = choice[1][0] # Target + [choice[0].x, choice[0].y] = [nx, ny] # Set position + target = self.board.grid[nx][ny] # Remember piece in spot + self.board.grid[xx][yy] = None # Remove piece + self.board.grid[nx][ny] = choice[0] # Replace with moving piece + + # Recurse + best_enemy_move = self.select_best(opponent(choice[0].colour)) + choice[1][1] -= best_enemy_move[1][1] / float(self.depth + 1.0) + + [choice[0].x, choice[0].y] = [xx, yy] # Restore position + self.board.grid[nx][ny] = target # Restore taken piece + self.board.grid[xx][yy] = choice[0] # Restore moved piece + + + + opts.sort(key = lambda e : e[1][1], reverse = True) + #sys.stderr.write(sys.argv[0] + " : After recurse, options are " + str(opts) + "\n") + + self.depth -= 1 + return list(opts[0]) + + + + # Returns [x,y] of selected piece + def select(self): + #sys.stderr.write("Getting choice...") + self.choice = self.select_best(self.colour)[0] + #sys.stderr.write(" Done " + str(self.choice)+"\n") + return [self.choice.x, self.choice.y] + + # Returns [x,y] of square to move selected piece into + def get_move(self): + #sys.stderr.write("Choice is " + str(self.choice) + "\n") + self.choice.selected_moves = self.choice.last_moves + moves = self.prioritise_moves(self.choice) + if len(moves) > 0: + return moves[0][0] + else: + return InternalAgent.get_move(self) + diff --git a/qchess/src/game.py b/qchess/src/game.py index 934c043..e3f1c7c 100644 --- a/qchess/src/game.py +++ b/qchess/src/game.py @@ -1,4 +1,15 @@ + +log_file = None + +def log(s): + if log_file != None: + import datetime + log_file.write(str(datetime.datetime.now()) + " : " + s + "\n") + + + + # A thread that runs the game class GameThread(StoppableThread): def __init__(self, board, players): @@ -36,6 +47,7 @@ class GameThread(StoppableThread): p2.update(result) # Inform players of what happened + log(result) target = self.board.grid[x][y] if isinstance(graphics, GraphicsThread): @@ -64,9 +76,12 @@ class GameThread(StoppableThread): if self.stopped(): break - result = self.board.update_move(x, y, x2, y2) + self.board.update_move(x, y, x2, y2) + result = str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2) for p2 in self.players: - p2.update(str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2)) # Inform players of what happened + p2.update(result) # Inform players of what happened + + log(result) if isinstance(graphics, GraphicsThread): with graphics.lock: @@ -110,10 +125,80 @@ class GameThread(StoppableThread): for p2 in self.players: p2.quit(self.final_result) + log(self.final_result) + graphics.stop() +# A thread that replays a log file +class ReplayThread(GameThread): + def __init__(self, players, src): + self.board = Board(style="agent") + GameThread.__init__(self, self.board, players) + self.src = src + + self.ended = False + + def run(self): + i = 0 + phase = 0 + for line in self.src: + + if self.stopped(): + self.ended = True + break + + with self.lock: + self.state["turn"] = self.players[i] + + line = line.split(":") + result = line[len(line)-1].strip(" \r\n") + log(result) + + try: + self.board.update(result) + except: + self.ended = True + self.final_result = result + if isinstance(graphics, GraphicsThread): + graphics.stop() + break + + [x,y] = map(int, result.split(" ")[0:2]) + target = self.board.grid[x][y] + + if isinstance(graphics, GraphicsThread): + if phase == 0: + with graphics.lock: + graphics.state["moves"] = self.board.possible_moves(target) + graphics.state["select"] = target + + time.sleep(turn_delay) + + elif phase == 1: + [x2,y2] = map(int, result.split(" ")[3:5]) + with graphics.lock: + graphics.state["moves"] = [[x2,y2]] + + time.sleep(turn_delay) + + with graphics.lock: + graphics.state["select"] = None + graphics.state["dest"] = None + graphics.state["moves"] = None + + + + + + for p in self.players: + p.update(result) + + phase = (phase + 1) % 2 + if phase == 0: + i = (i + 1) % 2 + def opponent(colour): if colour == "white": diff --git a/qchess/src/graphics.py b/qchess/src/graphics.py index c068900..041b137 100644 --- a/qchess/src/graphics.py +++ b/qchess/src/graphics.py @@ -1,10 +1,9 @@ -import pygame - - - - - - +graphics_enabled = True +try: + import pygame +except: + graphics_enabled = False + @@ -367,38 +366,51 @@ class GraphicsThread(StoppableThread): 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)) + import inspect + internal_agents = inspect.getmembers(sys.modules[__name__], inspect.isclass) + internal_agents = [x for x in internal_agents if issubclass(x[1], InternalAgent)] + internal_agents.remove(('InternalAgent', InternalAgent)) + if len(internal_agents) > 0: + choice2 = self.SelectButton(["internal", "external"], prompt="Type of agent") 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 + choice2 = 1 + if choice2 == 0: + agent = internal_agents[self.SelectButton(map(lambda e : e[0], internal_agents), prompt="Choose internal agent")] + players.append(agent[1](agent[0], colour)) + elif choice2 == 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() - - try: - p = make_player(path, colour) - except: + players.append(make_player(path, colour)) + except: + + p = None + while p == None: self.board.display_grid(self.window, self.grid_sz) pygame.display.flip() - self.message("Invalid path!") - time.sleep(1) - p = None - players.append(p) + 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 == "": diff --git a/qchess/src/main.py b/qchess/src/main.py index 8dac056..0adecb0 100644 --- a/qchess/src/main.py +++ b/qchess/src/main.py @@ -28,9 +28,29 @@ def make_player(name, colour): if len(s) > 1: address = s[1] return NetworkReceiver(colour, address) + if s[0] == "internal": + + import inspect + internal_agents = inspect.getmembers(sys.modules[__name__], inspect.isclass) + internal_agents = [x for x in internal_agents if issubclass(x[1], InternalAgent)] + internal_agents.remove(('InternalAgent', InternalAgent)) + + if len(s) != 2: + sys.stderr.write(sys.argv[0] + " : '@internal' should be followed by ':' and an agent name\n") + sys.stderr.write(sys.argv[0] + " : Choices are: " + str(map(lambda e : e[0], internal_agents)) + "\n") + return None + + for a in internal_agents: + if s[1] == a[0]: + return a[1](name, colour) + + sys.stderr.write(sys.argv[0] + " : Can't find an internal agent matching \"" + s[1] + "\"\n") + sys.stderr.write(sys.argv[0] + " : Choices are: " + str(map(lambda e : e[0], internal_agents)) + "\n") + return None + else: - return AgentPlayer(name, colour) + return ExternalAgent(name, colour) @@ -46,13 +66,20 @@ def main(argv): global agent_timeout global log_file global src_file + global graphics_enabled - + src_file = None style = "quantum" colour = "white" - graphics_enabled = True + + # Get the important warnings out of the way + if platform.system() == "Windows": + sys.stderr.write(sys.argv[0] + " : Warning - You are using " + platform.system() + "\n") + if platform.release() == "Vista": + sys.stderr.write(sys.argv[0] + " : God help you.\n") + players = [] i = 0 @@ -60,7 +87,11 @@ def main(argv): i += 1 arg = argv[i] if arg[0] != '-': - players.append(make_player(arg, colour)) + p = make_player(arg, colour) + if not isinstance(p, Player): + sys.stderr.write(sys.argv[0] + " : Fatal error creating " + colour + " player\n") + return 100 + players.append(p) if colour == "white": colour = "black" elif colour == "black": @@ -79,15 +110,15 @@ def main(argv): elif (arg[1] == '-' and arg[2:].split("=")[0] == "file"): # Load game from file if len(arg[2:].split("=")) == 1: - src_file = sys.stdout + src_file = sys.stdin else: - src_file = arg[2:].split("=")[1] + src_file = open(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] + log_file = open(arg[2:].split("=")[1], "w") elif (arg[1] == '-' and arg[2:].split("=")[0] == "delay"): # Delay if len(arg[2:].split("=")) == 1: @@ -109,13 +140,23 @@ def main(argv): # Create the board - board = Board(style) + + # Construct a GameThread! Make it global! Damn the consequences! + + if src_file != None: + if len(players) == 0: + players = [Player("dummy", "white"), Player("dummy", "black")] + game = ReplayThread(players, src_file) + else: + board = Board(style) + game = GameThread(board, players) + # Initialise GUI if graphics_enabled == True: try: - graphics = GraphicsThread(board, grid_sz = [64,64]) # Construct a GraphicsThread! + graphics = GraphicsThread(game.board, grid_sz = [64,64]) # Construct a GraphicsThread! except Exception,e: graphics = None @@ -158,23 +199,24 @@ def main(argv): # 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") + if agent_timeout > 0: + if platform.system() == "Windows": + for i in range(len(players)): + if isinstance(players[i], ExternalAgent) or isinstance(players[i], InternalAgent): + players[i] = TimeoutPlayer(players[i], agent_timeout) - for i in range(len(players)): - if isinstance(players[i], AgentPlayer): - players[i] = TimeoutPlayer(players[i], agent_timeout) + else: + warned = False + # InternalAgents get wrapped to an ExternalAgent when there is a timeout + # This is not confusing at all. + for i in range(len(players)): + if isinstance(players[i], InternalAgent): + players[i] = ExternalWrapper(players[i]) - # Could potentially wrap TimeoutPlayer around internal classes... - # But that would suck - - # Construct a GameThread! Make it global! Damn the consequences! - game = GameThread(board, players) @@ -182,10 +224,18 @@ def main(argv): game.start() # This runs in a new thread graphics.run() game.join() - return game.error + graphics.error + error = game.error + graphics.error else: game.run() - return game.error + error = game.error + + if log_file != None and log_file != sys.stdout: + log_file.close() + + if src_file != None and src_file != sys.stdin: + src_file.close() + + return error # This is how python does a main() function... if __name__ == "__main__": diff --git a/qchess/src/player.py b/qchess/src/player.py index 3e6faba..b3f0eb7 100644 --- a/qchess/src/player.py +++ b/qchess/src/player.py @@ -15,13 +15,16 @@ class Player(): self.name = name self.colour = colour + def update(self, result): + pass + # Player that runs from another process -class AgentPlayer(Player): +class ExternalAgent(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.p = subprocess.Popen(name,bufsize=0,stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True,universal_newlines=True) self.send_message(colour) @@ -31,13 +34,13 @@ class AgentPlayer(Player): else: ready = [self.p.stdin] if self.p.stdin in ready: - #print "Writing to p.stdin" + #sys.stderr.write("Writing \'" + s + "\' to " + str(self.p) + "\n") try: self.p.stdin.write(s + "\n") except: raise Exception("UNRESPONSIVE") else: - raise Exception("UNRESPONSIVE") + raise Exception("TIMEOUT") def get_response(self): if agent_timeout > 0.0: @@ -45,13 +48,15 @@ class AgentPlayer(Player): else: ready = [self.p.stdout] if self.p.stdout in ready: - #print "Reading from p.stdout" + #sys.stderr.write("Reading from " + str(self.p) + " 's stdout...\n") try: - return self.p.stdout.readline().strip("\r\n") + result = self.p.stdout.readline().strip("\r\n") + #sys.stderr.write("Read \'" + result + "\' from " + str(self.p) + "\n") + return result except: # Exception, e: raise Exception("UNRESPONSIVE") else: - raise Exception("UNRESPONSIVE") + raise Exception("TIMEOUT") def select(self): @@ -147,14 +152,28 @@ class HumanPlayer(Player): sys.stdout.write(result + "\n") -# Player that makes random moves -class AgentRandom(Player): +# Default internal player (makes random moves) +class InternalAgent(Player): def __init__(self, name, colour): Player.__init__(self, name, colour) self.choice = None self.board = Board(style = "agent") + + + def update(self, result): + + self.board.update(result) + self.board.verify() + + def quit(self, final_result): + pass + +class AgentRandom(InternalAgent): + def __init__(self, name, colour): + InternalAgent.__init__(self, name, colour) + def select(self): while True: self.choice = self.board.pieces[self.colour][random.randint(0, len(self.board.pieces[self.colour])-1)] @@ -179,12 +198,40 @@ class AgentRandom(Player): 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 +# Terrible, terrible hacks + +def run_agent(agent): + #sys.stderr.write(sys.argv[0] + " : Running agent " + str(agent) + "\n") + colour = sys.stdin.readline().strip(" \r\n") + agent.colour = colour + while True: + line = sys.stdin.readline().strip(" \r\n") + if line == "SELECTION?": + #sys.stderr.write(sys.argv[0] + " : Make selection\n") + [x,y] = agent.select() # Gets your agent's selection + #sys.stderr.write(sys.argv[0] + " : Selection was " + str(agent.choice) + "\n") + sys.stdout.write(str(x) + " " + str(y) + "\n") + elif line == "MOVE?": + #sys.stderr.write(sys.argv[0] + " : Make move\n") + [x,y] = agent.get_move() # Gets your agent's move + sys.stdout.write(str(x) + " " + str(y) + "\n") + elif line.split(" ")[0] == "QUIT": + #sys.stderr.write(sys.argv[0] + " : Quitting\n") + agent.quit(" ".join(line.split(" ")[1:])) # Quits the game + break + else: + agent.update(line) # Updates agent.board + return 0 + + +# Sort of works? + +class ExternalWrapper(ExternalAgent): + def __init__(self, agent): + run = "python -u -c \"import sys;import os;from qchess import *;agent = " + agent.__class__.__name__ + "('" + agent.name + "','"+agent.colour+"');sys.exit(run_agent(agent))\"" + # str(run) + ExternalAgent.__init__(self, run, agent.colour) + diff --git a/qchess/src/timeout_player.py b/qchess/src/timeout_player.py index 36f9e20..937c94c 100644 --- a/qchess/src/timeout_player.py +++ b/qchess/src/timeout_player.py @@ -3,6 +3,7 @@ import multiprocessing # Hacky alternative to using select for timing out players # WARNING: Do not wrap around HumanPlayer or things breakify +# WARNING: Do not use in general or things breakify class Sleeper(multiprocessing.Process): def __init__(self, timeout): @@ -42,7 +43,7 @@ def TimeoutFunction(function, args, timeout): elif not s.is_alive(): w.terminate() s.join() - raise Exception("UNRESPONSIVE") + raise Exception("TIMEOUT") diff --git a/qchess/win32_dll/SDL.dll b/qchess/win32_dll/SDL.dll index 69fd61e..7d7efac 100644 Binary files a/qchess/win32_dll/SDL.dll and b/qchess/win32_dll/SDL.dll differ diff --git a/qchess/win32_dll/SDL_image.dll b/qchess/win32_dll/SDL_image.dll index e891b16..0ddfc76 100644 Binary files a/qchess/win32_dll/SDL_image.dll 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 index 89def76..0f06313 100644 Binary files a/qchess/win32_dll/SDL_mixer.dll 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 index a8f1bcc..7855215 100644 Binary files a/qchess/win32_dll/SDL_ttf.dll and b/qchess/win32_dll/SDL_ttf.dll differ diff --git a/qchess/win32_dll/jpeg.dll b/qchess/win32_dll/jpeg.dll index 72c33d2..0b3b770 100644 Binary files a/qchess/win32_dll/jpeg.dll and b/qchess/win32_dll/jpeg.dll differ diff --git a/qchess/win32_dll/libfreetype-6.dll b/qchess/win32_dll/libfreetype-6.dll index 3b2e69d..4a1c4dd 100644 Binary files a/qchess/win32_dll/libfreetype-6.dll and b/qchess/win32_dll/libfreetype-6.dll differ diff --git a/qchess/win32_dll/libogg-0.dll b/qchess/win32_dll/libogg-0.dll new file mode 100644 index 0000000..48293a8 Binary files /dev/null and b/qchess/win32_dll/libogg-0.dll differ diff --git a/qchess/win32_dll/libpng12-0.dll b/qchess/win32_dll/libpng12-0.dll index f9709be..9261a65 100644 Binary files a/qchess/win32_dll/libpng12-0.dll and b/qchess/win32_dll/libpng12-0.dll differ diff --git a/qchess/win32_dll/libtiff.dll b/qchess/win32_dll/libtiff.dll new file mode 100644 index 0000000..e7706ae Binary files /dev/null and b/qchess/win32_dll/libtiff.dll differ diff --git a/qchess/win32_dll/libvorbis-0.dll b/qchess/win32_dll/libvorbis-0.dll new file mode 100644 index 0000000..6614162 Binary files /dev/null and b/qchess/win32_dll/libvorbis-0.dll differ diff --git a/qchess/win32_dll/libvorbisfile-3.dll b/qchess/win32_dll/libvorbisfile-3.dll new file mode 100644 index 0000000..c2540f8 Binary files /dev/null and b/qchess/win32_dll/libvorbisfile-3.dll differ diff --git a/qchess/win32_dll/portmidi.dll b/qchess/win32_dll/portmidi.dll new file mode 100644 index 0000000..391c338 Binary files /dev/null and b/qchess/win32_dll/portmidi.dll differ diff --git a/qchess/win32_dll/smpeg.dll b/qchess/win32_dll/smpeg.dll index 3742ad2..49191c4 100644 Binary files a/qchess/win32_dll/smpeg.dll and b/qchess/win32_dll/smpeg.dll differ diff --git a/qchess/win32_dll/sqlite3.dll b/qchess/win32_dll/sqlite3.dll new file mode 100644 index 0000000..325bf52 Binary files /dev/null and b/qchess/win32_dll/sqlite3.dll differ diff --git a/qchess/win32_dll/tcl85.dll b/qchess/win32_dll/tcl85.dll index a933e79..3d81a2f 100644 Binary files a/qchess/win32_dll/tcl85.dll and b/qchess/win32_dll/tcl85.dll differ diff --git a/qchess/win32_dll/tclpip85.dll b/qchess/win32_dll/tclpip85.dll new file mode 100644 index 0000000..d9b9bbc Binary files /dev/null and b/qchess/win32_dll/tclpip85.dll differ diff --git a/qchess/win32_dll/zlib1.dll b/qchess/win32_dll/zlib1.dll index 53f3376..0bfb28f 100644 Binary files a/qchess/win32_dll/zlib1.dll and b/qchess/win32_dll/zlib1.dll differ