diff --git a/agents/sample.py b/agents/sample.py
new file mode 100755
index 0000000000000000000000000000000000000000..34ce85895fc9bb3b3db51cde9daec3e52c199bbb
--- /dev/null
+++ b/agents/sample.py
@@ -0,0 +1,111 @@
+#!/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
+
+	
+
diff --git a/qchess/Makefile b/qchess/Makefile
index c6a748348bc9acb225cbff2dd59e2cf7d0f56409..6a6e81c0144a0b02d8620d685aacbd01a97dbffd 100644
--- a/qchess/Makefile
+++ b/qchess/Makefile
@@ -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 
diff --git a/qchess/build/exe.linux-x86_64-2.7.zip b/qchess/build/exe.linux-x86_64-2.7.zip
index 80e181927a9d41f66f39b41927f338a1bcf4c507..5b5221480b15bbb71407f309567a312495212f1b 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 402948b4cc498bb32ceda96da05d3194bd76e392..9d1041ba3ab84dcf734950128645afac6cff72f3 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 c0a8b6f9dbeb0178dab76457257ad1c704d57700..892a7ec8f506f9a13025370e5e473b155e7077c8 100644
--- a/qchess/data/help.txt
+++ b/qchess/data/help.txt
@@ -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
diff --git a/qchess/qchess.py b/qchess/qchess.py
index 219b34f7575bdef46077bfbdc7353259c120afba..02a724ab2ea05a25dce5ea6e31d748c0a8d42f64 100755
--- a/qchess/qchess.py
+++ b/qchess/qchess.py
@@ -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
diff --git a/qchess/src/board.py b/qchess/src/board.py
index 0d96d1ae928f878edde7344059d318319dadaa62..5dbf1dd2a9486f64a8636815b999ccb89fd0c357 100644
--- a/qchess/src/board.py
+++ b/qchess/src/board.py
@@ -1,5 +1,7 @@
 [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):
 			
diff --git a/qchess/src/game.py b/qchess/src/game.py
index e3f1c7cd3bdca0d421c952184836819f9f1b02ac..07a1325a9d96940b54820681139b6b17f0ef2309 100644
--- a/qchess/src/game.py
+++ b/qchess/src/game.py
@@ -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):
diff --git a/qchess/src/main.py b/qchess/src/main.py
index 0adecb0e5c1cd362e9315dadcf897ba903cc8899..137d1cb7f77a19395459012138d00d764bb707f7 100644
--- a/qchess/src/main.py
+++ b/qchess/src/main.py
@@ -67,8 +67,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"
@@ -105,6 +106,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"):
@@ -112,7 +115,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:
@@ -144,9 +151,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) 
@@ -219,17 +238,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:
@@ -239,4 +265,18 @@ 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)
+
diff --git a/qchess/src/piece.py b/qchess/src/piece.py
index d9acef27eca98b2acf89a14cf2826c98d1deeb32..400a79a991ee7a2ba8d6b7eede0e71982bba50ff 100644
--- a/qchess/src/piece.py
+++ b/qchess/src/piece.py
@@ -38,7 +38,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"):
@@ -58,7 +58,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
diff --git a/qchess/src/player.py b/qchess/src/player.py
index b3f0eb70b641beb9c01e9f9a8f75f3f7f801557e..0a4ddd6c971453cab68011061b92c6193ddae0e4 100644
--- a/qchess/src/player.py
+++ b/qchess/src/player.py
@@ -203,8 +203,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?":
@@ -229,7 +227,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)