Commit a35e4dc5 authored by Sam Moore's avatar Sam Moore

Added sample agent + log file writing/parsing

I figured no one wants to sift through the qchess internal agent stuff,
So I wrote a python external agent agents/sample.py

I ended up implementing the cool idea about log files.
You can replay a log file up to an arbitrary point, and then continue play.

I also fixed a bug with external agents calling run_agent.
Removed the reading of the colour line from there, since the external agent should do it.
Added reading of the colour line to the ExternalWrapper class, to avoid breaking that.

Added --reveal so that all states can be seen in the GUI (but they are not communicated to Agents).

Added handling of SIGINT to main.py

Probably did something else I forgot.

qchess will probably be ready soon (famous last words), I should start on a scoring/entry system...
And a webpage.
parent 877034f0
#!/usr/bin/python -u
# Sample agent
# Copy this file, change the agent as needed
from qchess import * # This is normally considered bad practice in python, but good practice in UCC::Progcomp
import random # For the example which makes random moves
# The first thing to do is pick a cool name...
class AgentSample(InternalAgent):
def __init__(self, name, colour):
InternalAgent.__init__(self, name, colour) # The InternalAgent class gives you some useful stuff
# You can access self.board to get a qchess.Board that stores the state as recorded by the agent
# This board is automatically updated by the InternalAgent base class
# As well as a grid of pieces, qchess.Board gives you lists of pieces and other useful functions; see qchess/src/board.py
#TODO: Any extra initialisation
# You should print debug messages like this:
sys.stderr.write(sys.argv[0] + " : " + str(self) + " : Initialised agent\n")
# I would appreciate it if you removed them before submitting an entry though.
# Must return [x,y] of selected piece
# Your agent will call select(), followed by get_move() and so on
# TODO: Implement
def select(self):
# debug message
sys.stderr.write(sys.argv[0] + " : " + str(self) + " : Selecting piece...\n")
# Here is a random choice algorithm to help you start
# It is a slight improvement on purely random; it will pick a piece that has at least one known possible move
# BUT it has a possibility to loop infinitely! You should fix that.
while True:
# Randomly pick a piece
# Use self.board.pieces[self.colour] to get a list of your pieces
# Use self.board.pieces[opponent(self.colour)] to get opponent pieces
# Use self.board.king[self.colour], vice versa, to get the king
choices = self.board.pieces[self.colour] # All the agent's pieces
choice_index = random.randint(0, len(choices)-1) # Get the index in the list of the chosen piece
self.choice = choices[choice_index] # Choose the piece, and remember it
# Find all known possible moves for the piece
# Use self.board.possible_moves(piece) to get a list of possible moves for a piece
# *BUT* Make sure the type of the piece is known (you can temporarily set it) first!
# Use Piece.current_type to get/set the current type of a piece
all_moves = [] # Will store all possible moves for the piece
tmp = self.choice.current_type # Remember the chosen piece's current type
if tmp == "unknown": # For pieces that are in a supperposition, try both types
for t in self.choice.types:
if t == "unknown":
continue # Ignore unknown types
self.choice.current_type = t # Temporarily overwrite the piece's type
all_moves += self.board.possible_moves(self.choice) # Add the possible moves for that type
else:
all_moves = self.board.possible_moves(self.choice) # The piece is in a classical state; add possible moves
self.choice.current_type = tmp # Reset the piece's current type
if len(all_moves) > 0:
break # If the piece had *any* possible moves, it is a good choice; leave the loop
# Otherwise the loop will try again
# End while loop
return [self.choice.x, self.choice.y] # Return the position of the selected piece
# Must return [x,y] of square to move the piece previously selected into
# Your agent will call select(), followed by get_move() and so on
# TODO: Implement this
def get_move(self):
# debug message
sys.stderr.write(sys.argv[0] + " : " + str(self) + " : Moving piece ("+str(self.choice)+")\n")
# As an example we will just pick a random move for the piece previously chosen in select()
# Note that whichever piece was previously selected will have collapsed into a classical state
# self.board.possible_moves(piece) will return a list of [x,y] pairs for valid moves
moves = self.board.possible_moves(self.choice) # Get all moves for the selected piece
move_index = random.randint(0, len(moves)-1) # Get the index in the list of the chosen move
return moves[move_index] # This is a randomly chosen [x,y] pair for a valid move of the piece
# Hints:
# select will probably have to be more complicated than get_move, because by the time get_move is called, the piece's state is known
# If you want to see if a square is threatened/defended, you can call self.board.coverage([x,y]); see qchess/src/board.py
# A good approach is min/max. For each move, associate a score. Then subtract the scores for moves that the opponent could make. Then pick the move with the highest score.
# Look at qchess/src/agent_bishop.py for a more effective (but less explained) agent
if __name__ == "__main__":
colour = sys.stdin.readline().strip("\r\n")
agent = AgentSample(sys.argv[0], colour) # Change the class name here
run_agent(agent) # This is provided by qchess. It calls the functions of your agent as required during the game.
# You can run this as an external agent with the qchess program
# Just run ./qchess.py and apply common sense (or read the help file)
# If you are feeling adventurous you can add it to the qchess program as an internal agent
# This might give better performance... unless you use the --timeout switch, in which case there is absolutely no point
# 1. Delete the lines that run the agent (the block that starts with if __name__ == "__main__")
# 2. Copy the file to qchess/src/agent_sample.py (or whatever you want to call it)
# 3. Edit qchess/src/Makefile so that agent_sample.py appears as one of the files in COMPONENTS
# 4. Rebuild by running make in qchess
# Again, run ./qchess.py and apply common sense
......@@ -3,14 +3,16 @@
SCRIPT=qchess.py
DLL_PATH=win32_dll
python_native :
make -C src
mv src/$(SCRIPT) ./
all : python_native frozen
frozen : win32_frozen linux64_frozen
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
mv src/$(SCRIPT) ./
images :
cd tools; python image_builder.py
......
......@@ -64,9 +64,23 @@ OPTIONS
If graphics are enabled (default), then the user will be prompted to choose any of the two players not supplied as arguments.
--reveal
If graphics are enabled, the two states for pieces will always be shown, regardless of whether both states have been revealed.
Note that this switch only affects the GUI and does not provide any information to agent players.
--file[=filename]
If graphics are disabled, has no effect.
--file[=filename][:events]
Replay a game saved in file, or read from stdin if no filename given
If a number of events is supplied, the game will advance that many events before stopping.
If no players are given, the GUI will NOT ask for player selections.
The game will exit after the replay finishes. Events in the replay will be subject to the normal delay (see --delay).
If black and white players are supplied, the game will continue using those players.
In this case, there will be no delays between events in the replay (the game starts at the end of the replay)
(We hope that) this feature will be useful for comparing how different versions of an agent respond to the same situation.
--log[=filename]
Log moves to a file or stdout if no filename given
......
......@@ -39,7 +39,7 @@ class Piece():
# 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)
return str(self.colour) + " " + str(self.current_type) + " " + str(self.types) + " at " + str(self.x) + ","+str(self.y)
# Draw the piece in a pygame surface
def draw(self, window, grid_sz = [80,80], style="quantum"):
......@@ -59,7 +59,7 @@ class Piece():
# Draw the two possible types underneath the current_type image
for i in range(len(self.types)):
if self.types_revealed[i] == True:
if always_reveal_states == True or 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
......@@ -97,6 +97,8 @@ class Piece():
# --- piece.py --- #
[w,h] = [8,8] # Width and height of board(s)
always_reveal_states = False
# Class to represent a quantum chess board
class Board():
# Initialise; if master=True then the secondary piece types are assigned
......@@ -111,6 +113,9 @@ class Board():
for c in ["black", "white"]:
del self.unrevealed_types[c]["unknown"]
if style == "empty":
return
# Add all the pieces with known primary types
for i in range(0, 2):
......@@ -704,8 +709,6 @@ class AgentRandom(InternalAgent):
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?":
......@@ -730,7 +733,7 @@ def run_agent(agent):
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))\""
run = "python -u -c \"import sys;import os;from qchess import *;agent = " + agent.__class__.__name__ + "('" + agent.name + "','"+agent.colour+"');sys.stdin.readline();sys.exit(run_agent(agent))\""
# str(run)
ExternalAgent.__init__(self, run, agent.colour)
......@@ -1205,7 +1208,20 @@ def log(s):
import datetime
log_file.write(str(datetime.datetime.now()) + " : " + s + "\n")
def log_init(board, players):
if log_file != None:
import datetime
log_file.write("# Log starts " + str(datetime.datetime.now()) + "\n")
for p in players:
log_file.write("# " + p.colour + " : " + p.name + "\n")
log_file.write("# Initial board\n")
for x in range(0, w):
for y in range(0, h):
if board.grid[x][y] != None:
log_file.write(str(board.grid[x][y]) + "\n")
log_file.write("# Start game\n")
# A thread that runs the game
......@@ -1219,6 +1235,8 @@ class GameThread(StoppableThread):
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):
......@@ -1330,20 +1348,53 @@ class GameThread(StoppableThread):
# A thread that replays a log file
class ReplayThread(GameThread):
def __init__(self, players, src):
self.board = Board(style="agent")
def __init__(self, players, src, end=False,max_lines=None):
self.board = Board(style="empty")
GameThread.__init__(self, self.board, players)
self.src = src
self.max_lines = max_lines
self.line_number = 0
self.end = end
self.ended = False
try:
while self.src.readline().strip(" \r\n") != "# Initial board":
self.line_number += 1
line = self.src.readline().strip(" \r\n")
while line != "# Start game":
#print "Reading line " + str(line)
self.line_number += 1
[x,y] = map(int, line.split("at")[1].strip(" \r\n").split(","))
colour = line.split(" ")[0]
current_type = line.split(" ")[1]
types = map(lambda e : e.strip(" [],'"), line.split(" ")[2:4])
p = Piece(colour, x, y, types)
if current_type != "unknown":
p.current_type = current_type
p.choice = types.index(current_type)
self.board.pieces[colour].append(p)
self.board.grid[x][y] = p
if current_type == "king":
self.board.king[colour] = p
line = self.src.readline().strip(" \r\n")
except Exception, e:
raise Exception("FILE line: " + str(self.line_number) + " \""+str(line)+"\"") #\n" + e.message)
def run(self):
i = 0
phase = 0
for line in self.src:
count = 0
line = self.src.readline().strip(" \r\n")
while line != "# EOF":
count += 1
if self.max_lines != None and count > self.max_lines:
self.stop()
if self.stopped():
self.ended = True
break
with self.lock:
......@@ -1356,10 +1407,8 @@ class ReplayThread(GameThread):
try:
self.board.update(result)
except:
self.ended = True
self.final_result = result
if isinstance(graphics, GraphicsThread):
graphics.stop()
self.stop()
break
[x,y] = map(int, result.split(" ")[0:2])
......@@ -1371,14 +1420,16 @@ class ReplayThread(GameThread):
graphics.state["moves"] = self.board.possible_moves(target)
graphics.state["select"] = target
time.sleep(turn_delay)
if self.end:
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)
if self.end:
time.sleep(turn_delay)
with graphics.lock:
graphics.state["select"] = None
......@@ -1396,6 +1447,21 @@ class ReplayThread(GameThread):
if phase == 0:
i = (i + 1) % 2
line = self.src.readline().strip(" \r\n")
if self.max_lines != None and self.max_lines > count:
sys.stderr.write(sys.argv[0] + " : Replaying from file; stopping at last line (" + str(count) + ")\n")
sys.stderr.write(sys.argv[0] + " : (You requested line " + str(self.max_lines) + ")\n")
if self.end and isinstance(graphics, GraphicsThread):
#graphics.stop()
pass # Let the user stop the display
elif not self.end:
global game
game = GameThread(self.board, self.players)
game.run()
def opponent(colour):
......@@ -1965,8 +2031,9 @@ def main(argv):
global log_file
global src_file
global graphics_enabled
global always_reveal_states
max_lines = None
src_file = None
style = "quantum"
......@@ -2003,6 +2070,8 @@ def main(argv):
style = "classical"
elif arg[1] == '-' and arg[2:] == "quantum":
style = "quantum"
elif arg[1] == '-' and arg[2:] == "reveal":
always_reveal_states = True
elif (arg[1] == '-' and arg[2:] == "graphics"):
graphics_enabled = not graphics_enabled
elif (arg[1] == '-' and arg[2:].split("=")[0] == "file"):
......@@ -2010,7 +2079,11 @@ def main(argv):
if len(arg[2:].split("=")) == 1:
src_file = sys.stdin
else:
src_file = open(arg[2:].split("=")[1])
src_file = open(arg[2:].split("=")[1].split(":")[0])
if len(arg[2:].split(":")) == 2:
max_lines = int(arg[2:].split(":")[1])
elif (arg[1] == '-' and arg[2:].split("=")[0] == "log"):
# Log file
if len(arg[2:].split("=")) == 1:
......@@ -2042,9 +2115,21 @@ def main(argv):
# Construct a GameThread! Make it global! Damn the consequences!
if src_file != None:
if len(players) == 0:
# Hack to stop ReplayThread from exiting
#if len(players) == 0:
# players = [HumanPlayer("dummy", "white"), HumanPlayer("dummy", "black")]
# Normally the ReplayThread exits if there are no players
# TODO: Decide which behaviour to use, and fix it
end = (len(players) == 0)
if end:
players = [Player("dummy", "white"), Player("dummy", "black")]
game = ReplayThread(players, src_file)
elif len(players) != 2:
sys.stderr.write(sys.argv[0] + " : Usage " + sys.argv[0] + " white black\n")
if graphics_enabled:
sys.stderr.write(sys.argv[0] + " : (You won't get a GUI, because --file was used, and the author is lazy)\n")
return 44
game = ReplayThread(players, src_file, end=end, max_lines=max_lines)
else:
board = Board(style)
game = GameThread(board, players)
......@@ -2117,17 +2202,24 @@ def main(argv):
log_init(game.board, players)
if graphics != None:
game.start() # This runs in a new thread
graphics.run()
game.join()
if game.is_alive():
game.join()
error = game.error + graphics.error
else:
game.run()
error = game.error
if log_file != None and log_file != sys.stdout:
log_file.write("# EOF\n")
log_file.close()
if src_file != None and src_file != sys.stdin:
......@@ -2137,6 +2229,20 @@ def main(argv):
# This is how python does a main() function...
if __name__ == "__main__":
sys.exit(main(sys.argv))
try:
sys.exit(main(sys.argv))
except KeyboardInterrupt:
sys.stderr.write(sys.argv[0] + " : Got KeyboardInterrupt. Stopping everything\n")
if isinstance(graphics, StoppableThread):
graphics.stop()
graphics.run() # Will clean up graphics because it is stopped, not run it (a bit dodgy)
if isinstance(game, StoppableThread):
game.stop()
if game.is_alive():
game.join()
sys.exit(102)
# --- main.py --- #
# EOF - created from make on Tue Jan 29 18:10:18 WST 2013
# EOF - created from make on Wed Jan 30 00:45:46 WST 2013
[w,h] = [8,8] # Width and height of board(s)
always_reveal_states = False
# Class to represent a quantum chess board
class Board():
# Initialise; if master=True then the secondary piece types are assigned
......@@ -14,6 +16,9 @@ class Board():
for c in ["black", "white"]:
del self.unrevealed_types[c]["unknown"]
if style == "empty":
return
# Add all the pieces with known primary types
for i in range(0, 2):
......
......@@ -7,7 +7,20 @@ def log(s):
import datetime
log_file.write(str(datetime.datetime.now()) + " : " + s + "\n")
def log_init(board, players):
if log_file != None:
import datetime
log_file.write("# Log starts " + str(datetime.datetime.now()) + "\n")
for p in players:
log_file.write("# " + p.colour + " : " + p.name + "\n")
log_file.write("# Initial board\n")
for x in range(0, w):
for y in range(0, h):
if board.grid[x][y] != None:
log_file.write(str(board.grid[x][y]) + "\n")
log_file.write("# Start game\n")
# A thread that runs the game
......@@ -21,6 +34,8 @@ class GameThread(StoppableThread):
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):
......@@ -132,20 +147,53 @@ class GameThread(StoppableThread):
# A thread that replays a log file
class ReplayThread(GameThread):
def __init__(self, players, src):
self.board = Board(style="agent")
def __init__(self, players, src, end=False,max_lines=None):
self.board = Board(style="empty")
GameThread.__init__(self, self.board, players)
self.src = src
self.max_lines = max_lines
self.line_number = 0
self.end = end
self.ended = False
try:
while self.src.readline().strip(" \r\n") != "# Initial board":
self.line_number += 1
line = self.src.readline().strip(" \r\n")
while line != "# Start game":
#print "Reading line " + str(line)
self.line_number += 1
[x,y] = map(int, line.split("at")[1].strip(" \r\n").split(","))
colour = line.split(" ")[0]
current_type = line.split(" ")[1]
types = map(lambda e : e.strip(" [],'"), line.split(" ")[2:4])
p = Piece(colour, x, y, types)
if current_type != "unknown":
p.current_type = current_type
p.choice = types.index(current_type)
self.board.pieces[colour].append(p)
self.board.grid[x][y] = p
if current_type == "king":
self.board.king[colour] = p
line = self.src.readline().strip(" \r\n")
except Exception, e:
raise Exception("FILE line: " + str(self.line_number) + " \""+str(line)+"\"") #\n" + e.message)
def run(self):
i = 0
phase = 0
for line in self.src:
count = 0
line = self.src.readline().strip(" \r\n")
while line != "# EOF":
count += 1
if self.max_lines != None and count > self.max_lines:
self.stop()
if self.stopped():
self.ended = True
break
with self.lock:
......@@ -158,10 +206,8 @@ class ReplayThread(GameThread):
try:
self.board.update(result)
except:
self.ended = True
self.final_result = result
if isinstance(graphics, GraphicsThread):
graphics.stop()
self.stop()
break
[x,y] = map(int, result.split(" ")[0:2])
......@@ -173,14 +219,16 @@ class ReplayThread(GameThread):
graphics.state["moves"] = self.board.possible_moves(target)
graphics.state["select"] = target
time.sleep(turn_delay)
if self.end:
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)
if self.end:
time.sleep(turn_delay)
with graphics.lock:
graphics.state["select"] = None
......@@ -198,6 +246,21 @@ class ReplayThread(GameThread):
if phase == 0:
i = (i + 1) % 2
line = self.src.readline().strip(" \r\n")
if self.max_lines != None and self.max_lines > count:
sys.stderr.write(sys.argv[0] + " : Replaying from file; stopping at last line (" + str(count) + ")\n")
sys.stderr.write(sys.argv[0] + " : (You requested line " + str(self.max_lines) + ")\n")
if self.end and isinstance(graphics, GraphicsThread):
#graphics.stop()
pass # Let the user stop the display
elif not self.end:
global game
game = GameThread(self.board, self.players)
game.run()
def opponent(colour):
......
......@@ -67,8 +67,9 @@ def main(argv):
global log_file