diff --git a/agents/asmodeus/basic_python.pyc b/agents/asmodeus/basic_python.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4338c88ee0ea8a0e8eed17b454241cbb2e932868
Binary files /dev/null and b/agents/asmodeus/basic_python.pyc differ
diff --git a/agents/asmodeus/path.pyc b/agents/asmodeus/path.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..73e171a42de1c13694f80e114a34df59fec29d8c
Binary files /dev/null and b/agents/asmodeus/path.pyc differ
diff --git a/agents/basic_cpp/basic_cpp b/agents/basic_cpp/basic_cpp
new file mode 100755
index 0000000000000000000000000000000000000000..70af5d61e6f12454ce340a7c2233a52e97a6e603
Binary files /dev/null and b/agents/basic_cpp/basic_cpp differ
diff --git a/agents/basic_cpp/basic_cpp.o b/agents/basic_cpp/basic_cpp.o
new file mode 100644
index 0000000000000000000000000000000000000000..d3c66dd094573baca3a1107c9b755b76de051476
Binary files /dev/null and b/agents/basic_cpp/basic_cpp.o differ
diff --git a/agents/celsius-v1.1.tar b/agents/celsius-v1.1.tar
new file mode 100644
index 0000000000000000000000000000000000000000..e0dec595632363f6fa9591ddc2673f2d7daeb402
Binary files /dev/null and b/agents/celsius-v1.1.tar differ
diff --git a/agents/celsius-v1.2.tar.gz b/agents/celsius-v1.2.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..7e220da3fb8b8dcdc0feb24473613f017b7d3722
Binary files /dev/null and b/agents/celsius-v1.2.tar.gz differ
diff --git a/agents/celsius1.1/celsius.py b/agents/celsius1.1/celsius.py
new file mode 100755
index 0000000000000000000000000000000000000000..cc71b90ba8c936ce53e509d1a2fca455c9e5b5e8
--- /dev/null
+++ b/agents/celsius1.1/celsius.py
@@ -0,0 +1,539 @@
+#!/usr/bin/python -u
+
+#NOTE: The -u option is required for unbuffered stdin/stdout.
+#	If stdin/stdout are buffered, the manager program will not recieve any messages and assume that the agent has timed out.
+
+
+import sys
+import random
+
+ranks = ['B','1','2','3','4','5','6','7','8','9','s','F', '?', '!', '+']
+
+"""
+The scaretable lists how `scary' pieces are to each other; pieces will move
+in the least scary direction.
+"""
+
+#	         B   1  2  3  4  5  6  7  8  9  s  F  ?  !  +
+scaretable = [[  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #B
+              [  0,  0,-8,-8,-7,-6,-5,-4,-3,-2, 5,-9, 0,-7, 0], #1
+	      [  0,  4, 0,-7,-6,-5,-4,-3,-2,-1,-2,-9,-3,-6, 0], #2
+	      [  0,  4, 2, 0,-6,-5,-4,-3,-2,-1,-2,-9,-2,-5, 0], #3
+	      [  0,  3, 2, 2, 0,-5,-4,-3,-2,-1,-2,-9,-1,-3, 0], #4 
+	      [  0,  3, 2, 2, 2, 0,-4,-3,-2,-1,-2,-9, 0,-2, 0], #5
+	      [  0,  3, 2, 2, 2, 2, 0,-3,-2,-1,-2,-9, 1,-1, 0], #6
+	      [  0,  3, 2, 2, 2, 2, 2, 0,-2,-1,-2,-9,-1, 0, 0], #7
+	      [-40,  3, 2, 2, 2, 2, 2, 2, 0,-2,-2,-9,-1, 1, 0], #8
+	      [  0,  3, 2, 2, 2, 2, 2, 2, 2, 0,-2,-9,-2, 2, 0], #9
+	      [  0, -5, 3, 3, 3, 3, 3, 3, 3, 3,-1,-9, 5, 3, 0], #s
+	      [  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #F
+	      [  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #?
+	      [  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #!
+	      [  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] #+
+
+"""
+The override table allows moves to be forced or prevented, thus ensuring
+that sacrifices are not made.
+"""
+#	        B  1  2  3  4  5  6  7  8  9  s  F  ?  !  +
+overrides  = [[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #B
+              [ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,-1,-1, 0, 0, 1], #1
+	      [ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #2
+	      [ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #3
+	      [ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #4 
+	      [ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #5
+	      [ 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,-1, 0, 0, 1], #6
+	      [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,-1, 0, 0, 1], #7
+	      [-1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,-1, 0, 0, 1], #8
+	      [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,-1, 0, 0, 1], #9
+	      [ 1,-1, 1, 1, 1, 1, 1, 1, 1, 1,-1,-1, 0, 0, 1], #s
+	      [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #F
+	      [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #?
+	      [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #!
+	      [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] #+
+
+
+def is_integer(s):
+	""" Using exceptions for this feels... wrong..."""
+	try:
+		int(s)
+		return True
+	except ValueError:
+		return False
+
+def move(x, y, direction, multiplier):
+	""" Moves point (x,y) in direction, returns a pair """
+	if direction == "UP":
+		return (x,y-multiplier)
+	elif direction == "DOWN":
+		return (x,y+multiplier)
+	elif direction == "LEFT":
+		return (x-multiplier, y)
+	elif direction == "RIGHT":
+		return (x+multiplier, y)
+	return (x,y)
+
+
+
+def oppositeColour(colour):
+	""" Returns the opposite colour to that given """
+	if colour == "RED":
+		return "BLUE"
+	elif colour == "BLUE":
+		return "RED"
+	else:
+		return "NONE"
+
+class Piece:
+	""" Class representing a piece 
+		Pieces have colour, rank and co-ordinates	
+	"""
+	def __init__(self, colour, rank, x, y):
+		self.colour = colour
+		self.rank = rank
+		self.x = x
+		self.y = y
+		self.lastMoved = -1
+		self.beenRevealed = False
+		self.positions = [(x, y)]
+		
+
+
+		self.heatmap = []
+		self.turnCount = 0
+
+	def mobile(self):
+		return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
+
+	def valuedRank(self):
+		if ranks.count(self.rank) > 0:
+			return len(ranks) - 2 - ranks.index(self.rank)
+		else:
+			return 0
+
+	def scariness(self, other):
+		scare = scaretable[ranks.index(self.rank)][ranks.index(other.rank)]
+		if scare > 0:
+			scare = scare * 1
+		return scare
+
+	def getOverride(self, other):
+		return overrides[ranks.index(self.rank)][ranks.index(other.rank)]
+
+	def getHeatmap(self, x,y,w,h):
+		if (x < 0) or (x >= w) or (y < 0) or (y >= h):
+			return 10
+		else:
+			return self.heatmap[x][y]
+
+	def validSquare(self, x, y, width, height, board):
+		if x < 0:
+			return False
+		if y < 0:
+			return False
+		if x >= width:
+			return False
+		if y >= height:
+			return False
+		if board[x][y] != None and board[x][y].colour == self.colour:
+			return False
+		if board[x][y] != None and board[x][y].rank == '#':
+			return False
+		return True
+
+	def generateHeatmap(self, width, height, board):
+		self.heatmap = []
+		newmap = []
+		for x in range(0,width):
+			self.heatmap.append([])
+			newmap.append([])
+			for y in range(0,height):
+				self.heatmap[x].append(0)
+				newmap[x].append(0)
+				if board[x][y] == None:
+					self.heatmap[x][y] = 0
+					continue
+				if board[x][y].colour == self.colour:
+					if board[x][y].rank == 'F':
+						self.heatmap[x][y] = -5 # + self.valuedRank()		# Defend our flag
+				else:
+					self.heatmap[x][y] = self.scariness(board[x][y])
+
+		# Make pieces prefer to stay where they are
+		#self.heatmap[self.x][self.y] = -0.5
+
+		for i in range(0,min(30,len(self.positions))):
+			p = self.positions[len(self.positions)-1-i]
+			if board[p[0]][p[1]] != None:
+				self.heatmap[p[0]][p[1]] += 0.2 * ((50 - i)/50)
+				
+
+
+		for n in range(0,8):
+			for x in range(0,width):
+				for y in range(0,height):
+					if self.heatmap[x][y] != 0:
+						newmap[x][y] = self.heatmap[x][y]
+						continue
+					newmap[x][y] = 0 #self.heatmap[x][y] * 0.2
+					if self.validSquare(x-1,y,width,height,board):
+						newmap[x][y] += self.heatmap[x-1][y] * 0.2
+					else:
+						newmap[x][y] += 0 #self.heatmap[x][y] * 0.1
+					if self.validSquare(x+1,y,width,height,board):
+						newmap[x][y] += self.heatmap[x+1][y] * 0.2
+					else:
+						newmap[x][y] += 0 #self.heatmap[x][y] * 0.1
+					if self.validSquare(x,y-1,width,height,board):
+						newmap[x][y] += self.heatmap[x][y-1] * 0.2
+					else:
+						newmap[x][y] += 0 #self.heatmap[x][y] * 0.1
+					if self.validSquare(x,y+1,width,height,board):
+						newmap[x][y] += self.heatmap[x][y+1] * 0.2
+					else:
+						newmap[x][y] += 0 #self.heatmap[x][y] * 0.1
+			self.heatmap = newmap
+
+	def debugPrintHeat(self,w,h):
+		""" For debug purposes only. Prints the board to stderr.
+			Does not indicate difference between allied and enemy pieces
+			Unknown (enemy) pieces are shown as '?'
+ 		"""
+		sys.stderr.write("Pos: " + str(self.x) + ", " + str(self.y) + " -- rank: " + str(self.rank) + "\n")
+		for y in range(0, h):
+			for x in range(0, w):
+				if (self.heatmap[x][y] - self.heatmap[self.x][self.y] > 0.0):
+					sys.stderr.write("O")
+				elif (self.heatmap[x][y] - self.heatmap[self.x][self.y] == 0.0):
+					sys.stderr.write("X")
+				elif (self.heatmap[x][y] - self.heatmap[self.x][self.y] < 0.0):
+					sys.stderr.write(".")
+				else:
+					sys.stderr.write(" ")
+			sys.stderr.write("\n")
+		sys.stderr.write("\n")
+				
+
+	
+
+def valuedRank(rank):
+	if ranks.count(rank) > 0:
+		return len(ranks) - 2 - ranks.index(rank)
+	else:
+		return 0
+
+
+
+class SulixAI:
+	"""
+		BasicAI class to play a game of stratego
+ 		Implements the protocol correctly. Stores the state of the board in self.board
+		Only makes random moves.
+		Override method "MakeMove" for more complex moves
+	"""
+	def __init__(self):	
+		""" Constructs the BasicAI agent, and starts it playing the game """
+		#sys.stderr.write("BasicAI __init__ here...\n");
+		self.turn = 0
+		self.board = []
+		self.units = []
+		self.enemyUnits = []
+
+		self.total_turns = 0
+
+		self.totalAllies = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1}
+		self.totalEnemies = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1}
+		self.hiddenEnemies = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1}
+		self.hiddenAllies = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1}
+		self.lastMoved = None
+
+		
+
+	def Setup(self):
+		""" Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """
+		#sys.stderr.write("BasicAI Setup here...\n");
+		setup = sys.stdin.readline().split(' ')
+		if len(setup) != 4:
+			sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n")
+		self.colour = setup[0]
+		self.opponentName = setup[1]
+		self.width = int(setup[2])
+		self.height = int(setup[3])
+		for x in range(0, self.width):
+			self.board.append([])
+			for y in range(0, self.height):		
+				self.board[x].append(None)
+		if self.colour == "RED":
+			print "FB8sB979B8\nBB99555583\n6724898974\nB314676699"
+		elif self.colour == "BLUE":
+			print "B314676699\n6724898974\nBB99555583\nFB8sB979B8"
+		return True
+
+	def MoveCycle(self):
+		#sys.stderr.write("BasicAI MakeMove here...\n");
+		if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
+			return False
+		self.turn += 1
+		return self.InterpretResult()
+
+	def MakeMove(self):
+		""" Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """
+		#TODO: Over-ride this function in base classes with more complex move behaviour
+
+		#sys.stderr.write("Sulix's AI makes a move...\n")
+		#self.debugPrintBoard()
+
+		if len(self.units) <= 0:
+			return False
+
+		index = random.randint(0, len(self.units)-1)
+		startIndex = index
+
+		directions = ("UP", "DOWN", "LEFT", "RIGHT")
+		bestdir = 0
+		bestScare = 999
+		bestpiece = None
+		while True:
+			piece = self.units[index]
+
+			if piece != None and piece.mobile():
+				dirIndex = random.randint(0, len(directions)-1)
+				startDirIndex = dirIndex
+				piece.generateHeatmap(self.width, self.height, self.board)		
+				currentScary = piece.getHeatmap(piece.x, piece.y, self.width, self.height) * 0 + piece.turnCount*0 #Perhaps just look for the best move
+				piece.turnCount = piece.turnCount + 1
+				while True:
+					#sys.stderr.write("Trying index " + str(dirIndex) + "\n")
+					p = move(piece.x, piece.y, directions[dirIndex],1)
+					if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height:
+						target = self.board[p[0]][p[1]]
+						if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"):	
+							scare = piece.getHeatmap(p[0], p[1],self.width, self.height) - currentScary
+							override = 0
+							if target != None:
+								override = piece.getOverride(target)
+							
+							if (self.total_turns % 250 < 15) and (self.total_turns > 250):
+								scare += random.randint(0, 5)
+
+
+							if override == 1:
+								scare = 999
+							elif override == -1:
+								piece.turnCount = 0
+								print str(piece.x) + " " + str(piece.y) + " " + directions[dirIndex]
+								return True
+
+
+							
+
+							if scare < bestScare:
+								bestdir = dirIndex
+								bestScare = scare
+								bestpiece = piece
+
+					dirIndex = (dirIndex + 1) % len(directions)
+					if startDirIndex == dirIndex:
+						break
+
+
+			index = (index + 1) % len(self.units)
+			if startIndex == index:
+				if bestScare != 999:
+					bestpiece.turnCount = 0
+					print str(bestpiece.x) + " " + str(bestpiece.y) + " "+directions[bestdir]
+#					bestpiece.debugPrintHeat(self.width, self.height)
+					return True
+				else:
+					print "SURRENDER"
+					return True
+							
+			
+	def ReadBoard(self):
+		""" Reads in the board. 
+			On the very first turn, sets up the self.board structure
+			On subsequent turns, the board is simply read, but the self.board structure is not updated here.
+		"""
+		#sys.stderr.write("BasicAI ReadBoard here...\n");
+		for y in range(0,self.height):
+			row = sys.stdin.readline().strip()
+			if len(row) < self.width:
+				sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n")
+				return False
+			for x in range(0,self.width):
+				if self.turn == 0:
+					if row[x] == '.':
+						pass
+					elif row[x] == '#':
+						self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
+						self.enemyUnits.append(self.board[x][y])
+					elif row[x] == '+':
+						self.board[x][y] = Piece("NONE", '+', x, y)
+					else:
+						self.board[x][y] = Piece(self.colour, row[x],x,y)
+						self.units.append(self.board[x][y])
+				else:
+					pass
+		return True
+		
+
+	def InterpretResult(self):
+		""" Interprets the result of a move, and updates the board. 
+			The very first move is ignored. 
+			On subsequent moves, the self.board structure is updated
+		"""
+
+		self.total_turns = self.total_turns + 1
+
+		#sys.stderr.write("BasicAI InterpretResult here...\n")
+		result = sys.stdin.readline().split(' ')
+		#sys.stderr.write("	Read status line \"" + str(result) + "\"\n")
+		if self.turn == 0:
+			return True
+
+		if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
+			return False
+
+		if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
+			return True
+
+		if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
+			return False
+
+		x = int(result[0].strip())
+		y = int(result[1].strip())
+
+
+		# The piece moved! It's not a bomb
+		if self.board[x][y].rank == '?':
+			self.board[x][y].rank = '!'
+		#sys.stderr.write("	Board position " + str(x) + " " + str(y) + " is OK!\n")		
+
+		direction = result[2].strip()
+
+		multiplier = 1
+		outcome = result[3].strip()
+		outIndex = 3
+		if is_integer(outcome):
+			multiplier = int(outcome)
+			outcome = result[4].strip()
+			outIndex = 4
+		
+		p = move(x,y,direction, multiplier)
+
+		# It's a scout! I saw it move.
+		if multiplier > 1:
+			self.board[x][y].rank = '9'
+
+		#Determine attacking piece
+		attacker = self.board[x][y]
+		self.board[x][y] = None
+
+		if attacker == None:
+			return False
+
+		lastMoved = attacker
+
+		defender = self.board[p[0]][p[1]]
+
+		#Update attacker's position (Don't overwrite the board yet though)
+
+		attacker.x = p[0]
+		attacker.y = p[1]
+		attacker.positions.insert(0, (attacker.x, attacker.y))
+
+		
+		#Determine ranks of pieces if supplied
+		if len(result) >= outIndex + 3:
+			if defender == None:
+				return False
+			attacker.rank = result[outIndex+1].strip()
+			if attacker.beenRevealed == False:
+				if attacker.colour == self.colour:
+					self.hiddenAllies[attacker.rank] -= 1
+				elif attacker.colour == oppositeColour(self.colour):
+					self.hiddenEnemies[attacker.rank] -= 1
+			attacker.beenRevealed = True
+			defender.rank = result[outIndex+2].strip()
+			if defender.beenRevealed == False:
+				if defender.colour == self.colour:
+					self.hiddenAllies[defender.rank] -= 1
+				elif defender.colour == oppositeColour(self.colour):
+					self.hiddenEnemies[defender.rank] -= 1
+
+			defender.beenRevealed = True
+
+			
+		
+		if outcome == "OK":
+			self.board[p[0]][p[1]] = attacker
+			
+		elif outcome == "KILLS":
+			self.board[p[0]][p[1]] = attacker
+
+			if defender.colour == self.colour:
+				self.totalAllies[defender.rank] -= 1
+				self.units.remove(defender)
+			elif defender.colour == oppositeColour(self.colour):
+				self.totalEnemies[defender.rank] -= 1
+				self.enemyUnits.remove(defender)
+	
+		elif outcome == "DIES":
+			if attacker.colour == self.colour:
+				self.totalAllies[attacker.rank] -= 1
+				self.units.remove(attacker)
+			elif attacker.colour == oppositeColour(self.colour):
+				self.totalEnemies[attacker.rank] -= 1
+				self.enemyUnits.remove(attacker)
+
+		elif outcome == "BOTHDIE":
+			self.board[p[0]][p[1]] = None
+
+			if defender.colour == self.colour:
+				self.totalAllies[defender.rank] -= 1
+				self.units.remove(defender)
+			elif defender.colour == oppositeColour(self.colour):
+				self.totalEnemies[defender.rank] -= 1
+				self.enemyUnits.remove(defender)
+
+			if attacker.colour == self.colour:
+				self.totalAllies[attacker.rank] -= 1
+				self.units.remove(attacker)
+			elif attacker.colour == oppositeColour(self.colour):
+				self.totalEnemies[attacker.rank] -= 1
+				self.enemyUnits.remove(attacker)
+
+		elif outcome == "FLAG":
+			#sys.stderr.write("	Game over!\n")
+			return False
+		elif outcome == "ILLEGAL":
+			#sys.stderr.write("	Illegal move!\n")
+			return False
+		else:
+			#sys.stderr.write("	Don't understand outcome \"" + outcome + "\"!\n");
+			return False
+
+		#sys.stderr.write("	Completed interpreting move!\n");		
+		return True
+
+
+
+	def debugPrintBoard(self):
+		""" For debug purposes only. Prints the board to stderr.
+			Does not indicate difference between allied and enemy pieces
+			Unknown (enemy) pieces are shown as '?'
+ 		"""
+		for y in range(0, self.height):
+			for x in range(0, self.width):
+				if self.board[x][y] == None:
+					sys.stderr.write(".");
+				else:
+					sys.stderr.write(str(self.board[x][y].rank));
+			sys.stderr.write("\n")
+
+if __name__ == "__main__":
+	sulixAI = SulixAI()
+	if sulixAI.Setup():
+		while sulixAI.MoveCycle():
+			pass
+
diff --git a/agents/celsius1.1/info b/agents/celsius1.1/info
new file mode 100644
index 0000000000000000000000000000000000000000..aedb008a5e55eb88cf7c090c39d2445c22424116
--- /dev/null
+++ b/agents/celsius1.1/info
@@ -0,0 +1,4 @@
+celsius.py
+David Gow
+python
+Generates a heatmap of the board, and uses this to control pieces.
diff --git a/agents/hunter/basic_python.pyc b/agents/hunter/basic_python.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..158229fa785d60f2aedf6278736b52fb44ece0a9
Binary files /dev/null and b/agents/hunter/basic_python.pyc differ
diff --git a/agents/peternlewis.zip b/agents/peternlewis.zip
new file mode 100644
index 0000000000000000000000000000000000000000..45848bc7dee04fab8c844b6ffe36567b5aa716da
Binary files /dev/null and b/agents/peternlewis.zip differ
diff --git a/agents/ramen.tar b/agents/ramen.tar
new file mode 100644
index 0000000000000000000000000000000000000000..69a5119a8f4e3e1dadb49e73f759f017410af722
Binary files /dev/null and b/agents/ramen.tar differ
diff --git a/agents/ramen/28f56d3c_peternlewis.ramen b/agents/ramen/28f56d3c_peternlewis.ramen
new file mode 100644
index 0000000000000000000000000000000000000000..cd917d2ecdedc85c4a4e432d934b30bd4a263be7
Binary files /dev/null and b/agents/ramen/28f56d3c_peternlewis.ramen differ
diff --git a/agents/ramen/ramen b/agents/ramen/ramen
new file mode 100755
index 0000000000000000000000000000000000000000..c583e6611d6beba5c0d31d73e359fd9730fb12bf
Binary files /dev/null and b/agents/ramen/ramen differ
diff --git a/agents/ramen/src/Makefile b/agents/ramen/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..06068b3fec7fec39a2248b2ab6a31add424fe6c2
--- /dev/null
+++ b/agents/ramen/src/Makefile
@@ -0,0 +1,25 @@
+
+OBJ = main.o ai.o db.o
+BIN = ../ramen
+
+CFLAGS = -Wall -Wextra -g -std=gnu99
+LINKFLAGS = -g -lm
+
+DEPFILES = $(OBJ:%=%.dep)
+
+.PHONY: all clean
+
+all: $(BIN)
+
+clean:
+	$(RM) $(BIN) $(OBJ) $(DEPFILES)
+
+$(BIN): $(OBJ)
+	$(CC) -o $@ $(OBJ) $(LINKFLAGS)
+
+%.o: %.c
+	$(CC) -o $@ -c $< $(CFLAGS) $(CPPFLAGS)
+	$(CPP) $(CPPFLAGS) $< -MM -o $@.dep
+
+-include $(DEPFILES)
+
diff --git a/agents/ramen/src/ai.c b/agents/ramen/src/ai.c
new file mode 100644
index 0000000000000000000000000000000000000000..6625d2401b5c17848e71ddb3db58b8c191824e6e
--- /dev/null
+++ b/agents/ramen/src/ai.c
@@ -0,0 +1,874 @@
+/*
+ * UCC 2012 Programming Competition Entry
+ * - "Ramen"
+ *
+ * By John Hodge [TPG]
+ */
+#define ENABLE_DEBUG	0
+#define SHOW_TARGET_MAP	0
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <limits.h>
+#include "interface.h"
+#include "ai_common.h"
+
+// === CONSTANTS ===
+//! \brief Maximum recusion depth
+#define MAX_SEARCH_DEPTH	5
+
+//! \brief Threshold before repeat modifier is applied
+#define REPEAT_THRESHOLD	3
+//! \brief Modifier applied to a repeated move
+#define REPEAT_MOVE_MODIFY(score)	do{score /= (giNumRepeatedMove <= 5 ? 2 : 10); score -= giNumRepeatedMove*10;}while(0);
+
+//! \brief Number of moves by this AI before the defensive modifier kicks in
+#define DEFENSIVE_THRESHOLD	20
+//! \brief Modifier applied to offensive moves when > DEFENSIVE_THRESHOLD defensive moves have been done in a row
+#define DEFENSIVE_MODIFY(score)	do{score *= 1+(giTurnsSinceLastTake/15);}while(0)
+/**
+ * \name AI move outcome scores
+ * \{
+ */
+#define OC_LOSE	-100
+#define OC_DRAW	0
+#define OC_UNK	40
+#define OC_WIN	100
+#define OC_FLAG	150
+/**
+ * \}
+ */
+
+// === PROTOTYPES ===
+// - Wrapper and Initialisation
+void	AI_Initialise(enum eColours Colour, const char *Opponent);
+void	AI_HandleMove(int bMyMove, const tMove *Move);
+void	UpdateStates(const tMove *OpponentMove);
+void	AI_DoMove(tMove *MyMove);
+// - Management
+tPiece	*GetPieceByPos(int X, int Y);
+void	MovePieceTo(tPiece *Piece, int X, int Y);
+void	UpdateRank(tPiece *Piece, char RankChar);
+void	PieceExposed(tPiece *Piece);
+void	RemovePiece(tPiece *Piece);
+// -- AI Core 
+ int	GetBestMove(tPlayerStats *Attacker, tPlayerStats *Defender, tMove *Move, int Level);
+ int	GetPositionScore(tPlayerStats *Attacker, tPlayerStats *Defender, int Level, int X, int Y, tPiece *Piece);
+ int	GetScore(tPiece *This, tPiece *Target);
+ int	GetRawScore(tPiece *This, tPiece *Target);
+// -- Helpers
+static inline int	GetOutcome(int Attacker, int Defender);
+static inline int	ABS(int Val);
+static inline int	RANGE(int Min, int Val, int Max);
+
+// === GLOBALS ===
+enum eColours	gMyColour;
+tPiece	gBlockPiece = {.Team = 2};
+ int	giGameStateSize;
+tGameState	*gpCurrentGameState;
+BOOL	gbFirstTurn = true;
+//tPiece	**gaBoardPieces;
+const char *gsOpponentDbFilename;
+// -- State variables to avoid deadlocking
+ int	giNumRepeatedMove;
+ int	giTurnsSinceLastTake;
+tMove	gLastMove;
+tPiece	*gLastMove_Target, *gLastMove_Piece;
+
+// === CODE ===
+void AI_Initialise(enum eColours Colour, const char *Opponent)
+{
+	gMyColour = Colour;
+
+	// TODO: Get opponent filename
+	gsOpponentDbFilename = DB_GetOpponentFile(Opponent);
+	
+	// Select setup
+//	setup_id = rand() % 3;
+	int setup_id = 1;
+	switch( setup_id )
+	{
+//	case 0:	// Bomb-off (dick move)
+//		// 39 pieces, bombs blocking gates, high level pieces backing up
+//		{
+//		const char *setup[] = {
+//			"8.88979993\n",
+//			"6689995986\n",
+//			"F72434s174\n",
+//			"BB56BB55BB\n"
+//			};
+//		if( Colour == COLOUR_RED )
+//			for(int i = 0; i < 4; i ++ )	printf(setup[i]);
+//		else
+//			for(int i = 4; i --; )		printf(setup[i]);
+//		break;
+//		}
+	case 1:
+		{
+		const char *setup[] = {
+			"FB8sB479B8\n",
+			"BB31555583\n",
+			"6724898974\n",
+			"967B669999\n"
+			};
+		if( Colour == COLOUR_RED )
+			for(int i = 0; i < 4; i ++ )	printf(setup[i]);
+		else
+			for(int i = 4; i --; )		printf(setup[i]);
+		}
+		break ;
+	default:
+		exit(1);
+	}
+
+
+	giGameStateSize = sizeof(tGameState) + giBoardHeight*giBoardWidth*sizeof(tPieceRef);
+	gpCurrentGameState = calloc( giGameStateSize, 1 );
+	gpCurrentGameState->Opponent.Colour = !Colour;
+	gpCurrentGameState->MyExposed.Colour = Colour;
+	gpCurrentGameState->MyActual.Colour = Colour;
+//	gaBoardPieces = calloc( giBoardHeight*giBoardWidth, sizeof(tPiece*) );
+}
+
+void AI_int_InitialiseBoardState(void)
+{
+	 int	piece_index = 0;
+	 int	my_piece_index = 0;
+	for( int y = 0; y < giBoardHeight; y ++ )
+	{
+		for( int x = 0; x < giBoardWidth; x ++ )
+		{
+			tPiece	*p;
+			char b;
+			
+			b = gaBoardState[y*giBoardWidth+x];
+	
+			if( b == '.' )	continue ;
+			if( b == '+' )
+			{
+				gpCurrentGameState->BoardState[ y*giBoardWidth+x ].Team = 3;
+				continue ;
+			}
+
+			if( b == '#' )
+			{
+				if( piece_index >= N_PIECES ) {
+					piece_index ++;
+					continue ;
+				}
+				p = &gpCurrentGameState->Opponent.Pieces[piece_index++];
+				p->Rank = RANK_UNKNOWN;
+				p->X = x;	p->StartX = x;
+				p->Y = y;	p->StartY = y;
+				p->bHasMoved = false;
+				p->Team = !gMyColour;
+				gpCurrentGameState->BoardState[ y*giBoardWidth+x ].Team = 2;
+				gpCurrentGameState->BoardState[ y*giBoardWidth+x ].Index = piece_index - 1;
+				DEBUG("Enemy at %i,%i", x, y);
+			}
+			else
+			{
+				if( my_piece_index >= N_PIECES ) {
+					my_piece_index ++;
+					continue ;
+				}
+				p = &gpCurrentGameState->MyActual.Pieces[my_piece_index++];
+				p->X = x;
+				p->Y = y;
+				p->Team = gMyColour;
+				UpdateRank(p, b);
+				gpCurrentGameState->MyActual.nRanks[p->Rank] ++;
+				gpCurrentGameState->BoardState[ y*giBoardWidth+x ].Team = 1;
+				gpCurrentGameState->BoardState[ y*giBoardWidth+x ].Index = my_piece_index - 1;
+			}
+
+			
+		}
+	}
+	gpCurrentGameState->Opponent.nPieces = piece_index;
+	if( piece_index > N_PIECES )
+		DEBUG("GAH! Too many opposing pieces (%i > 40)", piece_index);
+	if( my_piece_index > N_PIECES )
+		DEBUG("GAH! Too many of my pieces (%i > 40)", my_piece_index);
+
+	// Catch for if I don't put enough pieces out (shouldn't happen)
+	while( my_piece_index < N_PIECES ) {
+		gpCurrentGameState->MyActual.Pieces[my_piece_index].bDead = true;
+		gpCurrentGameState->MyActual.Pieces[my_piece_index].Rank = RANK_UNKNOWN;
+		my_piece_index ++;
+	}
+
+	// Load guesses at what each piece is
+	DB_LoadGuesses(gsOpponentDbFilename, !gMyColour);
+	gpCurrentGameState->Opponent.bGuessValid = true;
+}
+
+void AI_HandleMove(int bMyMove, const tMove *Move)
+{
+	if( gbFirstTurn )
+	{
+		gbFirstTurn = false;
+		
+		AI_int_InitialiseBoardState();
+
+		// Reverse the first move
+		if( Move->dir != DIR_INVAL )
+		{
+			tPiece	*p;
+			switch(Move->dir)
+			{
+			case DIR_INVAL:	ASSERT(Move->dir != DIR_INVAL);	break;
+			case DIR_LEFT:	p = GetPieceByPos( Move->x-1, Move->y );	break ;
+			case DIR_RIGHT:	p = GetPieceByPos( Move->x+1, Move->y );	break ;
+			case DIR_UP:	p = GetPieceByPos( Move->x, Move->y-1 );	break ;
+			case DIR_DOWN:	p = GetPieceByPos( Move->x, Move->y+1 );	break ;
+			}
+			MovePieceTo( p, Move->x, Move->y );
+			p->StartX = Move->x;
+			p->StartY = Move->y;
+		}
+	}
+
+	if(Move->result == RESULT_VICTORY)
+	{
+		// TODO: Distiguish between victory conditions?
+		// - Note flag location?
+
+		// TODO: Save back initial board state
+		DB_WriteBackInitialState(gsOpponentDbFilename, !gMyColour, gpCurrentGameState->Opponent.Pieces);
+	}
+
+	if( !bMyMove )
+	{
+		if( Move->dir != DIR_INVAL )
+			UpdateStates(Move);
+	}
+	else
+	{
+		tPiece	*p = GetPieceByPos(Move->x, Move->y);
+		ASSERT(p);
+
+	 	int newx = p->X, newy = p->Y;
+		switch(Move->dir)
+		{
+		case DIR_INVAL:	break;
+		case DIR_LEFT:	newx -= Move->dist;	break;
+		case DIR_RIGHT:	newx += Move->dist;	break;
+		case DIR_UP:	newy -= Move->dist;	break;
+		case DIR_DOWN:	newy += Move->dist;	break;
+		}
+		tPiece	*target = GetPieceByPos(newx, newy);
+
+		switch(Move->result)
+		{
+		case RESULT_ILLEGAL:	break;
+		case RESULT_INVAL:	break;
+		case RESULT_OK:
+			MovePieceTo(p, newx, newy);
+			break;
+		case RESULT_KILL:
+			UpdateRank(target, Move->defender);
+			RemovePiece(target);
+			MovePieceTo(p, newx, newy);
+			PieceExposed(p);	// TODO: Update oponent's view
+			giTurnsSinceLastTake = 0;
+			break;
+		case RESULT_DIES:
+		case RESULT_VICTORY:
+			UpdateRank(target, Move->defender);
+			PieceExposed(p);
+			RemovePiece(p);
+			giTurnsSinceLastTake = 0;
+			break;
+		case RESULT_BOTHDIE:
+			UpdateRank(target, Move->defender);
+			PieceExposed(p);
+			RemovePiece(p);
+			RemovePiece(target);
+			giTurnsSinceLastTake = 0;
+			break;
+		}
+	}
+}
+
+void UpdateStates(const tMove *OpponentMove)
+{
+	// --- Get moved piece, update position ---
+	tPiece	*moved_piece = GetPieceByPos(OpponentMove->x, OpponentMove->y);
+	// - Sanity
+	ASSERT( moved_piece );
+	ASSERT( moved_piece->Team == !gMyColour );
+	// - Only scouts can move multiple squares
+	if( moved_piece->Rank == RANK_UNKNOWN && OpponentMove->dist > 1 )
+		UpdateRank(moved_piece, '9');
+	// - Update position
+	 int newx = moved_piece->X, newy = moved_piece->Y;
+	switch(OpponentMove->dir)
+	{
+	case DIR_INVAL:	break;
+	case DIR_LEFT:	newx -= OpponentMove->dist;	break;
+	case DIR_RIGHT:	newx += OpponentMove->dist;	break;
+	case DIR_UP:	newy -= OpponentMove->dist;	break;
+	case DIR_DOWN:	newy += OpponentMove->dist;	break;
+	}
+	tPiece	*my_piece = GetPieceByPos(newx, newy);
+	
+	// Check if one of my pieces has been taken
+	switch( OpponentMove->result )
+	{
+	case RESULT_ILLEGAL:	break;
+	case RESULT_INVAL:	break;
+	case RESULT_OK:
+		MovePieceTo(moved_piece, newx, newy);
+		break;
+	case RESULT_KILL:
+	case RESULT_VICTORY:
+		UpdateRank(moved_piece, OpponentMove->attacker);
+		PieceExposed(my_piece);
+		RemovePiece(my_piece);
+		MovePieceTo(moved_piece, newx, newy);
+		break;
+	case RESULT_DIES:
+		UpdateRank(moved_piece, OpponentMove->attacker);
+		PieceExposed(my_piece);
+		RemovePiece(moved_piece);
+		break;
+	case RESULT_BOTHDIE:
+		UpdateRank(moved_piece, OpponentMove->attacker);
+		RemovePiece(moved_piece);
+		PieceExposed(my_piece);
+		RemovePiece(my_piece);
+		break;
+	}
+
+	// Update rank if revealed
+	if( moved_piece->Rank == RANK_UNKNOWN )
+		UpdateRank(moved_piece, gaBoardState[moved_piece->Y*giBoardWidth+moved_piece->X]);
+
+	// - Update piece states
+	DEBUG("Updating piece states");
+	for( int y = 0; y < giBoardHeight; y ++ )
+	{
+		for( int x = 0; x < giBoardWidth; x ++ )
+		{
+			char	c = gaBoardState[y*giBoardWidth+x];
+			if( c == '.' )	continue;
+			if( c == '+' )	continue;
+			tPiece *p = GetPieceByPos(x, y);
+			if(!p) DEBUG("c = %c", c);
+			ASSERT(p);
+			if( p->Team == gMyColour )	continue ;
+			if( p->Rank == RANK_UNKNOWN && c != '#' )
+				UpdateRank(p, c);
+		}
+	}
+}
+
+void AI_DoMove(tMove *MyMove)
+{
+#if 1
+	// Sanity checks
+	for( int i = 0; i < N_PIECES; i ++ )
+	{
+		tPiece	*p = &gpCurrentGameState->MyActual.Pieces[i];
+		if(p->bDead)	continue;
+
+		if( p != GetPieceByPos(p->X, p->Y) ) {
+			DEBUG("Piece %p(%i,%i R%i) not at stated position",
+				p, p->X, p->Y, p->Rank);
+		}
+	}
+#endif
+
+	DEBUG("Deciding on move");
+	GetBestMove(&gpCurrentGameState->MyActual, &gpCurrentGameState->Opponent, MyMove, 0);
+}
+
+tPiece *GetPieceByPos(int X, int Y)
+{
+	tPieceRef	*pr = &gpCurrentGameState->BoardState[Y*giBoardWidth+X];
+	switch( pr->Team )
+	{
+	case 0:	return NULL;
+	case 1:	return &gpCurrentGameState->MyActual.Pieces[ (int)pr->Index ];
+	case 2:	return &gpCurrentGameState->Opponent.Pieces[ (int)pr->Index ];
+	case 3:	return &gBlockPiece;
+	}
+	return NULL;
+}
+
+void MovePieceTo(tPiece *Piece, int X, int Y)
+{
+	DEBUG("Moved %p(%i,%i) to (%i,%i)",
+		Piece, Piece->X, Piece->Y, X, Y);
+	
+	gpCurrentGameState->BoardState[Y*giBoardWidth + X]
+		= gpCurrentGameState->BoardState[Piece->Y*giBoardWidth + Piece->X];
+	gpCurrentGameState->BoardState[Piece->Y*giBoardWidth + Piece->X].Team = 0;
+
+	Piece->X = X;
+	Piece->Y = Y;
+
+	if( !Piece->bHasMoved )
+	{
+		if( Piece->Team == gMyColour )
+		{
+			gpCurrentGameState->MyExposed.nMoved ++;
+		}
+		else
+		{
+			gpCurrentGameState->Opponent.nMoved ++;
+		}
+	}
+	
+	Piece->bHasMoved = true;
+}
+
+void UpdateRank(tPiece *Piece, char RankChar)
+{
+	enum eRanks rank;
+
+	rank = CharToRank(RankChar);
+
+	if( Piece->Rank == rank )
+		return ;
+
+	if( Piece->Rank != RANK_UNKNOWN )
+	{
+		if(Piece->Rank != rank )
+		{
+			DEBUG("Rank of piece %p(%i,%i) has changed, was %i now %i",
+				Piece, Piece->X, Piece->Y, Piece->Rank, rank);
+			Piece->Rank = rank;
+		}
+		return ;
+	}
+
+	if( Piece->Team == !gMyColour && rank != RANK_UNKNOWN )
+	{
+		if( gpCurrentGameState->Opponent.nRanks[rank] >= MAX_RANK_COUNTS[rank] ) {
+			DEBUG("ERROR: Bookkeeping failed, >%i units of rank %i on board",
+				MAX_RANK_COUNTS[rank], rank);
+		}
+		DEBUG("Found a %i", rank);
+		gpCurrentGameState->Opponent.nRanks[rank] ++;
+		if( gpCurrentGameState->Opponent.nIdentified == gpCurrentGameState->Opponent.nPieces ) {
+			DEBUG("ERROR: Bookkeeping failed, >%i units identified",
+				gpCurrentGameState->Opponent.nPieces);
+		}
+		gpCurrentGameState->Opponent.nIdentified ++;
+
+		if( Piece->GuessedRank != RANK_UNKNOWN && Piece->GuessedRank != rank )
+		{
+			fprintf(stderr, "Assumption failed, saved %c != act %c",
+				cRANK_CHARS[Piece->GuessedRank], cRANK_CHARS[rank]);
+			gpCurrentGameState->Opponent.bGuessValid = false;
+		}
+
+	}
+	Piece->Rank = rank;
+	if( Piece->Team == !gMyColour )
+	{
+		// Expensive? What's that?
+		DB_WriteBackInitialState(gsOpponentDbFilename, !gMyColour, gpCurrentGameState->Opponent.Pieces);
+	}
+}
+
+void PieceExposed(tPiece *Piece)
+{
+	ASSERT(Piece->Team == gMyColour);
+	if( Piece->bExposed == false )
+	{
+		gpCurrentGameState->MyExposed.nRanks[Piece->Rank] ++;
+		gpCurrentGameState->MyExposed.nIdentified ++;
+		Piece->bExposed = true;
+	}
+}
+
+/**
+ * \brief Remove a piece from the board
+ */
+void RemovePiece(tPiece *Piece)
+{
+	tPlayerStats	*owner;
+	gpCurrentGameState->BoardState[Piece->Y*giBoardWidth + Piece->X].Team = 0;
+	if( Piece->Team == !gMyColour ) {
+		owner = &gpCurrentGameState->Opponent;
+	}
+	else {
+		owner = &gpCurrentGameState->MyExposed;
+		gpCurrentGameState->MyActual.nRanks[Piece->Rank] --;
+	}
+	owner->nKilledRanks[Piece->Rank] ++;
+	owner->nRanks[Piece->Rank] --;
+	owner->nIdentified --;
+	owner->nPieces --;
+	Piece->bDead = true;
+}
+
+// ----------------------------------------------------------------------------
+// - AI Core
+// ----------------------------------------------------------------------------
+#define TARGET_GRID_W	10
+#define TARGET_GRID_H	10
+#define TARGET_GRID_SIZE	(TARGET_GRID_W*TARGET_GRID_H)
+typedef struct sGridSlot {
+	tPiece	*p;
+	char	dist;
+	char	complexity;
+	enum eDirections	firstdir;
+	char	firstdist;
+	char	bDirect;
+} tTargetGrid[TARGET_GRID_SIZE];
+int GetTargetsFrom(tPiece *Piece, tTargetGrid *grid)
+{
+	 int	n_targets;
+	
+	memset(*grid, 0, sizeof(*grid));
+
+	int cur_dist = 1;
+	int b_updates = 0;
+
+	void _check_dir(struct sGridSlot *pgs, struct sGridSlot *gs, int x, int y, enum eDirections dir)
+	{
+		if( !gs )	return ;
+		if( gs->dist )	return ;
+		if( pgs->p )	return ;
+
+		tPiece *p = GetPieceByPos(x, y);
+		if( p && (p == &gBlockPiece || p->Team == Piece->Team) )
+			p = (void*)-1;
+		gs->dist = cur_dist + 1;
+		gs->p = p;
+		DEBUG("%p at %i,%i %i away", p, x, y, cur_dist);
+		if( pgs->firstdir == DIR_INVAL || (pgs->firstdir == dir && pgs->bDirect) ) {
+			gs->bDirect = 1;
+			gs->firstdir = dir;
+			gs->firstdist = pgs->firstdist + 1;
+		}
+		else {
+			gs->firstdist = pgs->firstdist;
+			gs->firstdir = pgs->firstdir;
+			gs->bDirect = 0;
+		}
+		b_updates = 1;
+	}
+
+	(*grid)[ Piece->X + Piece->Y * TARGET_GRID_W ].dist = -1;
+
+	do {
+		b_updates = 0;
+		for( int i = 0; i < TARGET_GRID_SIZE; i ++ )
+		{
+			int x = i % TARGET_GRID_W;
+			int y = i / TARGET_GRID_H;
+			struct sGridSlot	*gs = &(*grid)[i];
+
+			struct sGridSlot	*gs_u = NULL, *gs_d = NULL;
+			struct sGridSlot	*gs_l = NULL, *gs_r = NULL;
+
+			if( !gs->dist )	continue ;
+
+			// Get adjacent cells
+			if( y > 0 )
+				gs_u = &(*grid)[i - TARGET_GRID_W];
+			if( x > 0 )
+				gs_l = &(*grid)[i - 1];
+			if( y < TARGET_GRID_H - 1 )
+				gs_d = &(*grid)[i + TARGET_GRID_W];
+			if( x < TARGET_GRID_W - 1 )
+				gs_r = &(*grid)[i + 1];
+			
+			_check_dir(gs, gs_u, x, y-1, DIR_UP);
+			_check_dir(gs, gs_d, x, y+1, DIR_DOWN);
+			_check_dir(gs, gs_l, x-1, y, DIR_LEFT);
+			_check_dir(gs, gs_r, x+1, y, DIR_RIGHT);
+		}
+
+		cur_dist ++;
+	} while(b_updates);
+
+#if SHOW_TARGET_MAP
+	fprintf(stderr, "%p Type %c\n", Piece, cRANK_CHARS[Piece->Rank]);
+	for( int i = 0; i < 10*10; i ++ )
+	{
+		tPiece	*np = (*grid)[i].p;
+		if( i == Piece->X + Piece->Y * TARGET_GRID_W )
+			fprintf(stderr, "?");
+		else if( (*grid)[i].dist == 0 )
+			fprintf(stderr, "#");	// Unreachable
+		else if( !np )
+			fprintf(stderr, " ");	// Empty
+		else if( np == (void*)-1 )
+			fprintf(stderr, ".");	// My team/block
+		else
+			fprintf(stderr, "X");	// Viable target!
+		if( i % 10 == 9 )
+			fprintf(stderr, "\n");
+	}
+#endif
+
+	DEBUG("Getting targets");
+	n_targets = 0;
+	for( int i = 0; i < TARGET_GRID_SIZE; i ++ )
+	{
+		if( (*grid)[i].p == (void*)-1 )
+			(*grid)[i].p = NULL;
+		if( (*grid)[i].p ) {
+			DEBUG("Target (%i,%i) %p %i dist",
+				i%10, i/10, (*grid)[i].p, (*grid)[i].dist);
+			(*grid)[i].dist -= 1;
+			n_targets ++;
+		}
+	}
+
+	return n_targets;
+}
+
+int GetBestMove(tPlayerStats *Attacker, tPlayerStats *Defender, tMove *Move, int Level)
+{
+	// Avoid infinite recursion
+	if( Level == MAX_SEARCH_DEPTH )	return 1;
+
+	 int	best_score = INT_MIN;
+	tMove	best_move;
+	tPiece	*best_p = NULL;
+	tPiece	*best_target = NULL;
+	tTargetGrid	grid;
+
+	// - Check possible moves
+	for( int i = 0; i < N_PIECES; i ++ )
+	{
+		tPiece	*p = &Attacker->Pieces[i];
+		 int	p_score = 0;	// Piece score
+		struct sGridSlot	*bt_gs = NULL;
+		 int	bt_score;	// Best target score
+
+		// Dead, ignore
+		if( p->bDead )
+			continue ;
+		// These cannot move
+		if( p->Rank == RANK_BOMB || p->Rank == RANK_FLAG )
+			continue ;
+
+		// Get what pieces are able to be attacked from this piece
+		int nt = GetTargetsFrom(p, &grid);
+		DEBUG("(%i,%i) %i targets", p->X, p->Y, nt);
+		if( nt <= 0 )	continue ;
+
+		// Find the best target of those
+		for( int j = 0; j < TARGET_GRID_SIZE; j ++ )
+		{
+			struct sGridSlot *gs = &grid[j];
+			if( !gs->p )	continue ;
+
+			int t_score = GetScore(p, gs->p);
+
+#if 1
+			if( gs->p == gLastMove_Target && p == gLastMove_Piece && giNumRepeatedMove > REPEAT_THRESHOLD)
+			{
+				REPEAT_MOVE_MODIFY(t_score);
+			}
+#endif
+
+			// TODO: For scouts, use gs->complexity
+			// TODO: Don't use a linear relationship on distance
+			p_score += t_score / (gs->dist < 2 ? 1 : 2);
+
+			// Best target
+			if( !bt_gs || t_score > bt_score ) {
+				bt_score = t_score;
+				bt_gs = gs;
+			}
+		}
+
+		DEBUG("p_score = %i, bt_score = %i", p_score, bt_score);
+
+		// Best move is towards that piece
+		if( best_move.dir == DIR_INVAL || best_score < p_score )
+		{
+			best_move.dir = bt_gs->firstdir;
+			best_move.x = p->X;
+			best_move.y = p->Y;
+			best_move.dist = (p->Rank == RANK_SCOUT) ? bt_gs->firstdist : 1;
+			best_score = p_score;
+			best_p = p;
+			best_target = bt_gs->p;
+		}
+	}
+
+
+	if( Move )
+	{
+		ASSERT(best_move.dir != DIR_INVAL);
+		*Move = best_move;
+		
+		if( ((Move->dir-1)^1) == gLastMove.dir-1 && Move->dist == gLastMove.dist
+		&& Move->x == gLastMove.x && Move->y == gLastMove.y )
+		{
+			giNumRepeatedMove ++;
+			DEBUG("Up to %i repititions", giNumRepeatedMove);
+		}
+		else
+			giNumRepeatedMove = 0;
+
+		// TODO: Recurse once on this to determine what the other team will do
+		// Record that move, then check when the move is performed to see if we were right.
+
+		gLastMove = *Move;
+		gLastMove_Target = best_target;
+		gLastMove_Piece = best_p;
+		giTurnsSinceLastTake ++;
+	}
+
+	DEBUG("best_score = %i", best_score);
+
+	return best_score;
+}
+
+/**
+ * \brief 
+ */
+int GetScore(tPiece *This, tPiece *Target)
+{
+	tPlayerStats	*attacker, *defender;
+	int score;
+
+	if( This->Team == gMyColour ) {
+		defender = &gpCurrentGameState->Opponent;
+		attacker = &gpCurrentGameState->MyExposed;
+	}
+	else {
+		attacker = &gpCurrentGameState->Opponent;
+		defender = &gpCurrentGameState->MyExposed;
+	}
+
+	score = GetRawScore(This, Target);
+
+	if( This->Team == gMyColour )
+	{
+		switch( This->Rank )
+		{
+		case RANK_MARSHAL:	// Marshal has balls of steel if the spy and enemy marshal are dead
+			if( defender->nKilledRanks[RANK_MARSHAL] && defender->nKilledRanks[RANK_SPY] )
+				score *= 2;
+			break;
+		case RANK_GENERAL:	// General always attacks!
+			score *= 2;
+			break;
+		case RANK_SCOUT:
+			score = score * gpCurrentGameState->MyActual.nRanks[RANK_SCOUT] / MAX_RANK_COUNTS[RANK_SCOUT] + score;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return score;
+}
+
+int GetRawScore(tPiece *This, tPiece *Target)
+{
+	tPlayerStats	*this_team, *target_team;
+	
+	ASSERT( This->Team != Target->Team );
+
+	if( This->Team == gMyColour ) {
+		target_team = &gpCurrentGameState->Opponent;
+		this_team = &gpCurrentGameState->MyExposed;
+	}
+	else {
+		this_team = &gpCurrentGameState->Opponent;
+		target_team = &gpCurrentGameState->MyExposed;
+	}
+
+	// Both ranks known, used fixed rules
+	if( This->Rank != RANK_UNKNOWN && Target->Rank != RANK_UNKNOWN )
+	{
+		return GetOutcome(This->Rank, Target->Rank);
+	}
+	// If it's our move, and the guesses are valid, then use the guess
+	else if( This->Team == gMyColour
+		&& gpCurrentGameState->Opponent.bGuessValid
+		&& Target->GuessedRank != RANK_UNKNOWN
+		)
+	{
+		return GetOutcome(This->Rank, Target->GuessedRank);
+	}
+	else
+	{
+		 int	sum = 0;
+		 int	max = Target->bHasMoved ? RANK_SPY : RANK_FLAG;
+		 int	count = 0;
+
+		if( target_team->nIdentified >= target_team->nPieces ) {
+			DEBUG("%i >= %i, what the fsck",
+				target_team->nIdentified, target_team->nPieces);
+		}
+		ASSERT(target_team->nPieces > target_team->nIdentified);
+
+		for( int i = RANK_MARSHAL; i <= max; i ++ )
+		{
+			 int	n_unk = MAX_RANK_COUNTS[i] - (target_team->nRanks[i] + target_team->nKilledRanks[i]);
+			if( n_unk == 0 )
+				continue ;
+			ASSERT( n_unk > 0 );
+
+			// TODO: Fiddle with outcome score depending on respective ranks
+			sum += n_unk * GetOutcome(This->Rank, i);
+			count += n_unk;
+		}
+
+//		if( Target->bHasMoved )
+//			sum /= target_team->nPieces - target_team->nMoved;
+//		else
+//			sum /= target_team->nPieces - target_team->nIdentified;
+		sum /= count;
+
+		if( sum > OC_FLAG ) {
+			fprintf(stderr, "sum (%i) > OC_WIN (%i) -- nUnIdent=%i\n",
+				sum, OC_WIN, target_team->nPieces - target_team->nIdentified);
+			ASSERT( sum <= OC_FLAG );
+		}
+
+		return sum - ABS(sum) / 10;
+	}
+}
+
+static inline int GetOutcome(int Attacker, int Defender)
+{
+	if( Attacker == 0 )	return OC_UNK;
+	if( Defender == 0 )	return OC_UNK;
+
+	if( Defender == RANK_FLAG )
+		return OC_FLAG;
+
+	if( Attacker != RANK_MINER && Defender == RANK_BOMB )
+		return OC_LOSE;
+	if( Attacker == RANK_MINER && Defender == RANK_BOMB )
+		return OC_WIN;
+
+	if( Attacker == RANK_SPY && Defender == RANK_MARSHAL )
+		return OC_WIN;
+
+	if( Attacker == Defender )
+		return OC_DRAW;
+
+	if( Attacker < Defender )
+		return OC_WIN;
+	else
+		return OC_LOSE;
+}
+
+
+static inline int ABS(int Val)
+{
+	return Val < 0 ? -Val : Val;
+}
+
+static inline int RANGE(int Min, int Val, int Max)
+{
+	return Min <= Val && Val <= Max;
+}
+
diff --git a/agents/ramen/src/ai.o b/agents/ramen/src/ai.o
new file mode 100644
index 0000000000000000000000000000000000000000..39db28eb021287e1d2d121e2124522a45c0fc592
Binary files /dev/null and b/agents/ramen/src/ai.o differ
diff --git a/agents/ramen/src/ai.o.dep b/agents/ramen/src/ai.o.dep
new file mode 100644
index 0000000000000000000000000000000000000000..ed132fc89eaf4d5a644e5f5492d9dbb69731b990
--- /dev/null
+++ b/agents/ramen/src/ai.o.dep
@@ -0,0 +1 @@
+ai.o: ai.c interface.h ai_common.h
diff --git a/agents/ramen/src/ai_common.h b/agents/ramen/src/ai_common.h
new file mode 100644
index 0000000000000000000000000000000000000000..07936590005420c7bb7a2594d11cf828651d781a
--- /dev/null
+++ b/agents/ramen/src/ai_common.h
@@ -0,0 +1,108 @@
+/*
+ * UCC 2012 Programming Competition Entry
+ * - "Ramen"
+ *
+ * By John Hodge [TPG]
+ */
+#ifndef _AI_COMMON_H_
+#define _AI_COMMON_H_
+
+#include "interface.h"
+
+#define N_PIECES	40
+
+enum eRanks
+{
+	RANK_UNKNOWN,	//  0
+	RANK_MARSHAL,	//  1
+	RANK_GENERAL,	//  2
+	RANK_COLONEL,	//  3
+	RANK_MAJOR,	//  4
+	RANK_CAPTAIN,	//  5
+	RANK_LIEUTENANT,//  6
+	RANK_SERGEANT,	//  7
+	RANK_MINER,	//  8
+	RANK_SCOUT,	//  9
+	RANK_SPY,	// 10
+	RANK_BOMB,	// 11
+	RANK_FLAG,	// 12
+	N_RANKS
+};
+static const int MAX_RANK_COUNTS[N_RANKS] = {
+	40, 1, 1, 2, 3, 4, 4, 4, 5, 8, 1, 6, 1
+};
+static const char cRANK_CHARS[N_RANKS] = "#123456789sBF";
+static inline enum eRanks CharToRank(char ch)
+{
+	switch(ch)
+	{
+	case '1':	return RANK_MARSHAL;
+	case '2':	return RANK_GENERAL;
+	case '3':	return RANK_COLONEL;
+	case '4':	return RANK_MAJOR;
+	case '5':	return RANK_CAPTAIN;
+	case '6':	return RANK_LIEUTENANT;
+	case '7':	return RANK_SERGEANT;
+	case '8':	return RANK_MINER;
+	case '9':	return RANK_SCOUT;
+	case 's':	return RANK_SPY;
+	case 'B':	return RANK_BOMB;
+	case 'F':	return RANK_FLAG;
+	case '#':	return RANK_UNKNOWN;
+	default:
+		// Wut. Unkown
+		DEBUG("Unknown character '%c'", ch);
+		return RANK_UNKNOWN;
+	}
+}
+
+/**
+ */
+typedef struct sPiece
+{
+	 int	X, Y;
+	BOOL	bDead;
+	enum eRanks	Rank;	// -1 = unknown
+	BOOL	bHasMoved;
+	enum eColours	Team;
+	// TODO: Keep last moved
+	
+	BOOL	bExposed;	// Marks when the piece is known by the other team
+
+	 int	StartX, StartY;	// Used to save initial layout
+	enum eRanks	GuessedRank;	// Only used it bGuessValid is set
+} tPiece;
+
+typedef struct sPlayerStats
+{
+	enum eColours	Colour;
+	 int	nPieces;
+	 int	nMoved;
+	 int	nIdentified;
+	 int	nRanks[N_RANKS];
+	 int	nKilledRanks[N_RANKS];
+	tPiece	Pieces[N_PIECES];
+	BOOL	bGuessValid;
+} tPlayerStats;
+
+typedef struct sPieceRef
+{
+	char	Index;	// Index into tPlayerStats.Pieces
+	char	Team;	// 0 = Empty, 1 = Me, 2 = Opponent, 3 = Block
+} tPieceRef;
+
+typedef struct sGameState
+{
+	tPlayerStats	Opponent;
+	tPlayerStats	MyExposed;
+	tPlayerStats	MyActual;
+	tPieceRef	BoardState[];	// 
+} tGameState;
+
+// --- Database
+extern char	*DB_GetOpponentFile(const char *Opponent);
+extern void	DB_LoadGuesses(const char *DBFile, enum eColours Colour);
+extern void	DB_WriteBackInitialState(const char *DBFile, enum eColours Colour, tPiece *Pieces);
+
+#endif
+
diff --git a/agents/ramen/src/db.c b/agents/ramen/src/db.c
new file mode 100644
index 0000000000000000000000000000000000000000..a62d44aae6e6510ac53346530e24886fbd434737
--- /dev/null
+++ b/agents/ramen/src/db.c
@@ -0,0 +1,258 @@
+/*
+ * UCC 2012 Programming Competition Entry
+ * - "Ramen"
+ *
+ * By John Hodge [TPG]
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include "ai_common.h"
+#include <stdint.h>
+#include <string.h>
+
+#define TAG_BOARDSTATE	0x7342
+
+typedef struct sTag
+{
+	uint16_t	Tag;
+	uint16_t	Length;
+} tTag;
+
+typedef struct sSavedBoardState
+{
+	uint8_t 	W, H;
+	uint16_t	Count;
+	char	NormalisedBoard[];
+} tSavedBoardState;
+
+// === PROTOTYPES ===
+tSavedBoardState	*DB_int_ReadState(FILE *fp, off_t *offset);
+void	DB_int_AppendTag(FILE *fp, uint16_t Tag, uint16_t Size, void *Data);
+
+// === CODE ===
+char *DB_GetOpponentFile(const char *Opponent)
+{
+	uint32_t	checksum = 0;
+
+	{
+		 int	ofs = 0;
+		const char *str = Opponent;
+		while( *str )
+		{
+			checksum ^= *str << ofs;
+			str ++;
+			ofs += 5;
+			ofs %= 32 - 5;
+		}
+	}
+	
+	const char	*filename = NULL;
+	 int	filenamelen = 0;
+	{
+		const char *last_slash = NULL;
+		const char *last_dot = NULL;
+		const char *str = Opponent;
+		while( *str )
+		{
+			if(*str == '/')	last_slash = str;
+			if(*str == '.')	last_dot = str;
+			str ++;
+		}
+		filename = last_slash + 1;
+		if( last_slash > last_dot )
+			filenamelen = str - filename;
+		else
+			filenamelen = last_dot - filename;
+	}
+
+	int len = snprintf(NULL, 0, "%08x_%.*s.ramen", checksum, filenamelen, filename);
+	char *ret = malloc(len+1);
+	snprintf(ret, len+1, "%08x_%.*s.ramen", checksum, filenamelen, filename);
+//	fprintf(stderr, "DB File = '%s'\n", ret);
+	return ret;
+}
+
+void DB_LoadGuesses(const char *DBFile, enum eColours Colour)
+{
+	FILE	*fp;
+	off_t	offset = 0;
+	tSavedBoardState	*saved_board = NULL;
+
+	fp = fopen(DBFile, "r+");
+	if(!fp)	return ;
+
+	// Read board states, checking for a same state
+	while( (saved_board = DB_int_ReadState(fp, &offset)) )
+	{
+		if( saved_board->W != giBoardWidth )
+			continue ;
+		if( saved_board->H != giBoardHeight )
+			continue ;
+		break;
+	}
+
+	// TODO: Combine counts of how many times a state has been played
+
+	if( saved_board )
+	{
+		char	bs[giBoardWidth*4];
+		 int	ofs = 0;
+
+
+		if( Colour != COLOUR_RED )
+		{
+			ofs = giBoardHeight-4;
+			char	*bs2 = saved_board->NormalisedBoard;
+			memcpy(bs + giBoardWidth*0, bs2 + giBoardWidth*(4-1), giBoardWidth);
+			memcpy(bs + giBoardWidth*1, bs2 + giBoardWidth*(4-2), giBoardWidth);
+			memcpy(bs + giBoardWidth*2, bs2 + giBoardWidth*(4-3), giBoardWidth);
+			memcpy(bs + giBoardWidth*3, bs2 + giBoardWidth*(4-4), giBoardWidth);
+		}
+		else
+		{
+			memcpy(bs, saved_board->NormalisedBoard, giBoardWidth*4);
+		}
+//		for( int i = 0; i < 4; i ++ ) {
+//			fprintf(stderr, "%.*s\n", giBoardWidth, bs + giBoardWidth*i);
+//		}
+
+		// Set guessed ranks
+		for( int i = 0; i < giBoardWidth*4; i ++ )
+		{
+			tPiece	*p = GetPieceByPos(i % giBoardWidth, i/giBoardWidth + ofs );
+//			fprintf(stderr, "%c", bs[i]);
+//			if(i % giBoardWidth == giBoardWidth-1)
+//				fprintf(stderr, "\n");
+			if( bs[i] == '\0' && p )
+				break;
+			if( bs[i] != '\0' && !p )
+				break;
+			if( p )
+				p->GuessedRank = CharToRank(bs[i]);
+		}
+	}
+
+	fclose(fp);
+}
+
+void DB_WriteBackInitialState(const char *DBFile, enum eColours Colour, tPiece *Pieces)
+{
+	char	bs[giBoardHeight*giBoardWidth];
+	memset(bs, 0, sizeof(bs));
+
+	for( int i = 0; i < N_PIECES; i ++ )
+	{
+		if( Pieces[i].StartY < 0 )	continue ;
+		char	*bp = &bs[ Pieces[i].StartY*giBoardWidth + Pieces[i].StartX ];
+
+		if( *bp != '\0' )
+		{
+			// Oops?
+		}
+		else
+		{
+			*bp = cRANK_CHARS[ Pieces[i].Rank ];
+		}
+	}
+
+	// Normalise board to RED
+	if( Colour != COLOUR_RED )
+	{
+		memcpy(bs + giBoardWidth*0, bs + giBoardWidth*(giBoardHeight-1), giBoardWidth);
+		memcpy(bs + giBoardWidth*1, bs + giBoardWidth*(giBoardHeight-2), giBoardWidth);
+		memcpy(bs + giBoardWidth*2, bs + giBoardWidth*(giBoardHeight-3), giBoardWidth);
+		memcpy(bs + giBoardWidth*3, bs + giBoardWidth*(giBoardHeight-4), giBoardWidth);
+	}
+
+
+	off_t	offset;
+	tSavedBoardState	*saved_board;
+	FILE *fp = fopen(DBFile, "r+");
+	if( !fp ) {
+		fp = fopen(DBFile, "w");
+	}
+
+	// Read board states, checking for a same state
+	while( (saved_board = DB_int_ReadState(fp, &offset)) )
+	{
+//		fprintf(stderr, "DBG: %i == %i? and %i == %i\n",
+//			saved_board->W, giBoardWidth, saved_board->H, giBoardHeight
+//			);
+
+		if( saved_board->W != giBoardWidth )
+			continue ;
+		if( saved_board->H != giBoardHeight )
+			continue ;
+
+		BOOL	b_different = false;
+
+		for( int i = 0; i < 4*giBoardWidth; i ++ )
+		{
+			if( saved_board->NormalisedBoard[i] == '#' || bs[i] == '#' )
+				continue ;
+			if( saved_board->NormalisedBoard[i] != bs[i] ) {
+				fprintf(stderr, "DBG: '%c' != '%c'\n", saved_board->NormalisedBoard[i], bs[i]);
+				b_different = true;
+				break;
+			}
+		}
+
+		if( b_different )	continue ;
+
+		break;
+	}
+
+	if( saved_board )
+	{
+		saved_board->Count ++;
+		fseek(fp, offset, SEEK_SET);
+		// Merge
+		for( int i = 0; i < 4*giBoardWidth; i ++ )
+		{
+			if( saved_board->NormalisedBoard[i] == '#' )
+				saved_board->NormalisedBoard[i] = bs[i];
+		}
+		// Write back
+		fwrite(saved_board, sizeof(*saved_board) + giBoardWidth*4, 1, fp);
+	}
+	else
+	{
+		saved_board = malloc( sizeof(*saved_board) + giBoardWidth*4 );
+		saved_board->W = giBoardWidth;
+		saved_board->H = giBoardHeight;
+		saved_board->Count = 1;
+		memcpy(saved_board->NormalisedBoard, bs, 4*giBoardWidth);
+		DB_int_AppendTag(fp, TAG_BOARDSTATE, sizeof(*saved_board) + giBoardWidth*4, saved_board);
+	}
+	free(saved_board);
+
+	fclose(fp);
+}
+
+tSavedBoardState *DB_int_ReadState(FILE *fp, off_t *offset)
+{
+	tTag	tag;
+	tSavedBoardState	*ret = NULL;
+
+	do {
+		if( fread(&tag, sizeof(tag), 1, fp) != 1 )
+			break ;
+		if( tag.Tag == TAG_BOARDSTATE )
+		{
+			*offset = ftell(fp);
+			ret = malloc(tag.Length);
+			fread(ret, tag.Length, 1, fp);
+		}
+		fseek(fp, tag.Length, SEEK_CUR);
+	} while(!ret);
+
+	return ret;
+}
+
+void DB_int_AppendTag(FILE *fp, uint16_t Tag, uint16_t Size, void *Data)
+{
+	fseek(fp, 0, SEEK_END);
+	fwrite(&Tag, sizeof(uint16_t), 1, fp);
+	fwrite(&Size, sizeof(uint16_t), 1, fp);
+	fwrite(Data, Size, 1, fp);
+}
diff --git a/agents/ramen/src/db.o b/agents/ramen/src/db.o
new file mode 100644
index 0000000000000000000000000000000000000000..28ca7ce077ada44f31332078ce40fe09bd5b57cb
Binary files /dev/null and b/agents/ramen/src/db.o differ
diff --git a/agents/ramen/src/db.o.dep b/agents/ramen/src/db.o.dep
new file mode 100644
index 0000000000000000000000000000000000000000..f0eb37cb796678f91bdf82ae96d405391a5bf400
--- /dev/null
+++ b/agents/ramen/src/db.o.dep
@@ -0,0 +1 @@
+db.o: db.c ai_common.h interface.h
diff --git a/agents/ramen/src/interface.h b/agents/ramen/src/interface.h
new file mode 100644
index 0000000000000000000000000000000000000000..cb7e949595f85cbe149e0a58a6afcb3b9a71e984
--- /dev/null
+++ b/agents/ramen/src/interface.h
@@ -0,0 +1,70 @@
+/*
+ * UCC 2012 Programming Competition Entry
+ * - "Ramen"
+ *
+ * By John Hodge [TPG]
+ */
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#if ENABLE_DEBUG
+# define DEBUG(s, a...)	fprintf(stderr, "DEBUG: "s"\n" ,## a)
+#else
+# define DEBUG(...)	do{}while(0)
+#endif
+#define ASSERT(val)	do{if(!(val)){fprintf(stderr, "ASSERTION FAILED - " #val " at %s:%i\n", __FILE__, __LINE__);exit(-1);} }while(0)
+
+
+#define true	1
+#define false	0
+typedef char	BOOL;
+
+typedef struct sMove	tMove;
+
+enum eDirections
+{
+	DIR_INVAL,
+	DIR_LEFT,
+	DIR_RIGHT,
+	DIR_UP,
+	DIR_DOWN
+};
+
+enum eColours
+{
+	COLOUR_RED,
+	COLOUR_BLUE
+};
+
+enum eResult
+{
+	RESULT_INVAL,
+	RESULT_ILLEGAL,
+	RESULT_OK,
+	RESULT_KILL,
+	RESULT_DIES,
+	RESULT_BOTHDIE,
+	RESULT_VICTORY
+};
+
+struct sMove
+{
+	char	x, y;
+	enum eDirections	dir;	// eDirections
+	char	dist;
+
+	enum eResult	result;
+	char	attacker;
+	char	defender;
+};
+
+extern int	giBoardWidth;
+extern int	giBoardHeight;
+extern char	*gaBoardState;
+
+extern void	AI_Initialise(enum eColours Colour, const char *Opponent);
+extern void	AI_HandleMove(int bMyMove, const tMove *Move);
+extern void	AI_DoMove(tMove *MyMove);
+
+#endif
+
diff --git a/agents/ramen/src/main.c b/agents/ramen/src/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..a9a2d71ccfecfdd90d9f2d0496ae3c9f22082348
--- /dev/null
+++ b/agents/ramen/src/main.c
@@ -0,0 +1,240 @@
+/*
+ * UCC 2012 Programming Competition Entry
+ * - "Ramen"
+ *
+ * By John Hodge [TPG]
+ */
+#define ENABLE_DEBUG	0
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <regex.h>
+#include "interface.h"
+
+// === CONSTANTS ===
+static const char *DIR_NAMES[] = {"INVL", "LEFT", "RIGHT", "UP", "DOWN"};
+
+// === PROTOTYPES ===
+ int	main(int argc, char *argv[]);
+void	GetMove(char *line, tMove *Move);
+void	ReadBoardState(FILE *stream, char *dest);
+ int	RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
+void	CompileRegex(regex_t *regex, const char *pattern, int flags);
+
+// === GLOBALS ===
+regex_t	gRegex_move;
+regex_t	gRegex_res;
+ int	giBoardWidth;
+ int	giBoardHeight;
+char	*gaBoardState;
+
+// === CODE ===
+int main(int argc, char *argv[])
+{
+	setbuf(stdin, NULL);
+	setbuf(stdout, NULL);
+
+	// $X $Y $DIRECTION [$MULTIPLIER=1] $OUTCOME
+	CompileRegex(&gRegex_move, "([0-9]+) ([0-9]+) ([A-Z]+)( [0-9]+)? (.*)", REG_EXTENDED);
+	// (KILLS|DIES|BOTHDIE) $ATTACKER_RANK $DEFENDER_RANK
+	CompileRegex(&gRegex_res, "([A-Z_]+) (.) (.)", REG_EXTENDED);
+
+	{
+		 int	colour_id;
+		char	colour[6];
+		char	opponent[128];
+		fscanf(stdin, "%s %s %i %i", colour, opponent, &giBoardWidth, &giBoardHeight);
+
+		if( strcmp(colour, "RED") == 0 )
+			colour_id = COLOUR_RED;
+		else if( strcmp(colour, "BLUE") == 0 )
+			colour_id = COLOUR_BLUE;
+		else {
+			fprintf(stderr, "Oops... nutty manager, colour = %s\n", colour);
+			colour_id = COLOUR_RED;
+		}
+
+		DEBUG("colour=%i, opponent='%s', dims = %ix%i", colour_id, opponent, giBoardWidth, giBoardHeight);
+
+		AI_Initialise(colour_id, opponent);
+	}
+	
+	gaBoardState = malloc(giBoardWidth*giBoardHeight);
+
+	for( ;; )
+	{
+		tMove	mymove, opponent_move;
+		char	line[32];
+
+//		DEBUG("Waiting for move");
+		ASSERT( fgets(line, sizeof(line), stdin) != NULL );
+//		DEBUG("pm line = '%s'", line);
+
+		if( strcmp(line, "\n") == 0 )
+			continue ;
+
+		if( strcmp(line, "START\n") == 0 )
+		{
+//			DEBUG("New game");
+			ReadBoardState(stdin, gaBoardState);
+			// TODO: Check if this hasn't happened before
+			opponent_move.x = 0;
+			opponent_move.y = 0;
+			opponent_move.dist = 0;
+			opponent_move.dir = 0;
+		}
+		else if( strncmp(line, "QUIT", 4) == 0 )
+		{
+			// TODO: Result?
+			break ;
+		}
+		else if( strcmp(line, "VICTORY_FLAG\n") == 0 )
+		{
+			// I win!
+			break;
+		}
+		else
+		{
+//			DEBUG("GetMove");
+			GetMove(line, &opponent_move);
+//			DEBUG("Read board state");
+			ReadBoardState(stdin, gaBoardState);
+		}
+		DEBUG("Opposing move %i,%i dir %i dist %i",
+			opponent_move.x, opponent_move.y, opponent_move.dir, opponent_move.dist);
+
+		// Silly opponent, you lost
+		if( opponent_move.result == RESULT_VICTORY )
+			break;
+
+		// Determine move
+		AI_HandleMove(0, &opponent_move);
+		AI_DoMove(&mymove);
+		DEBUG("Chose move %i,%i %i %i", mymove.x, mymove.y, mymove.dir, mymove.dist);
+		printf("%i %i %s %i\n", mymove.x, mymove.y, DIR_NAMES[mymove.dir], mymove.dist);
+
+		// Get result of the move
+		ASSERT( fgets(line, sizeof(line), stdin) != NULL );
+//		DEBUG("res line = '%s'", line);
+//
+		GetMove(line, &mymove);
+		AI_HandleMove(1, &mymove);
+
+		// I WON!
+		if( mymove.result == RESULT_VICTORY )
+			break;
+
+//		DEBUG("Move over");
+	}
+
+	return 0;
+}
+
+void GetMove(char *line, tMove *Move)
+{
+	regmatch_t	matches[1+5];
+
+	// regex (\d+) (\d+) ([A-Z]*)(?: (\d+))?
+	RunRegex(&gRegex_move, line, 1+5, matches, "Move line");
+
+	char *xstr = line + matches[1].rm_so;
+	char *ystr = line + matches[2].rm_so;
+	char *dirstr = line + matches[3].rm_so;
+
+	Move->x = atoi(xstr);
+	Move->y = atoi(ystr);
+//	DEBUG("(%i,%i)", Move->x, Move->y);
+	// Direction
+	     if( strncmp(dirstr, "UP",    2) == 0 )
+		Move->dir = DIR_UP;
+	else if( strncmp(dirstr, "DOWN",  4) == 0 )
+		Move->dir = DIR_DOWN;
+	else if( strncmp(dirstr, "LEFT",  4) == 0 )
+		Move->dir = DIR_LEFT;
+	else if( strncmp(dirstr, "RIGHT", 5) == 0 )
+		Move->dir = DIR_RIGHT;
+	else {
+		fprintf(stderr, "Is the manager nuts? Dir = %.*s unk\n",
+			matches[3].rm_eo + matches[3].rm_so, dirstr
+			);
+		fprintf(stderr, "line = '%s'\n", line);
+		Move->dir = DIR_INVAL;
+	}
+	if( matches[4].rm_so >= 0 )
+		Move->dist = atoi(line + matches[4].rm_so + 1);
+	else
+		Move->dist = 1;
+	
+	// Outcome
+	char	*outcome = line + matches[5].rm_so;
+	if( strncmp(outcome, "OK", 2) == 0 )
+		Move->result = RESULT_OK;
+	else if( strncmp(outcome, "ILLEGAL", 7) == 0 )
+		Move->result = RESULT_ILLEGAL;
+	else if( strncmp(outcome, "VICTORY_FLAG", 12) == 0 )
+		Move->result = RESULT_VICTORY;
+	else if( strncmp(outcome, "VICTORY_ATTRITION", 17) == 0 )
+		Move->result = RESULT_VICTORY;
+	else
+	{
+		regmatch_t res_matches[3+1];
+		RunRegex(&gRegex_res, outcome, 3+1, res_matches, "Result portion");
+
+		char *res_str = outcome + res_matches[1].rm_so;
+		     if( strncmp(res_str, "KILLS ", 6) == 0 )
+			Move->result = RESULT_KILL;
+		else if( strncmp(res_str, "DIES ", 5) == 0 )
+			Move->result = RESULT_DIES;
+		else if( strncmp(res_str, "BOTHDIE ", 8) == 0 )
+			Move->result = RESULT_BOTHDIE;
+		else {
+			fprintf(stderr, "Is the manager nuts? Result = %.*s\n",
+				res_matches[1].rm_eo + res_matches[1].rm_so, res_str
+			       );
+			Move->result = RESULT_INVAL;
+		}
+
+		Move->attacker = *(outcome + res_matches[2].rm_so);
+		Move->defender = *(outcome + res_matches[3].rm_so);
+	}
+}
+
+void ReadBoardState(FILE *stream, char *dest)
+{
+	for( int i = 0; i < giBoardHeight; i ++ )
+	{
+		char	tmp[giBoardWidth+2];
+		fgets(tmp, sizeof(tmp), stream);
+		DEBUG("BS %.*s", giBoardWidth, tmp);
+		memcpy(dest+i*giBoardWidth, tmp, giBoardWidth);
+	}
+}
+
+int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
+{
+	 int	ret;
+	
+	ret = regexec(regex, string, nMatches, matches, 0);
+	if( ret ) {
+		size_t  len = regerror(ret, regex, NULL, 0);
+		char    errorStr[len];
+		regerror(ret, regex, errorStr, len);
+		fprintf(stderr, "string = '%s'\n", string);
+		fprintf(stderr, "%s\n%s", errorMessage, errorStr);
+		exit(-1);
+	}
+	
+	return ret;
+}
+
+void CompileRegex(regex_t *regex, const char *pattern, int flags)
+{
+	 int	ret = regcomp(regex, pattern, flags);
+	if( ret ) {
+		size_t	len = regerror(ret, regex, NULL, 0);
+		char    errorStr[len];
+		regerror(ret, regex, errorStr, len);
+		fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
+		exit(-1);
+	}
+}
diff --git a/agents/ramen/src/main.o b/agents/ramen/src/main.o
new file mode 100644
index 0000000000000000000000000000000000000000..2c66953147d1ee4471db422d6f0718eda04a5cdc
Binary files /dev/null and b/agents/ramen/src/main.o differ
diff --git a/agents/ramen/src/main.o.dep b/agents/ramen/src/main.o.dep
new file mode 100644
index 0000000000000000000000000000000000000000..b8323252d2747e357f4128e99fca8eef59b1130b
--- /dev/null
+++ b/agents/ramen/src/main.o.dep
@@ -0,0 +1 @@
+main.o: main.c interface.h
diff --git a/agents/vixen/basic_python.pyc b/agents/vixen/basic_python.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4b3cf003913e75339503e01db5cdfdb0d3151e3a
Binary files /dev/null and b/agents/vixen/basic_python.pyc differ
diff --git a/agents/vixen/path.pyc b/agents/vixen/path.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9d84840430f79c670f5d617c3c2934622c3f8582
Binary files /dev/null and b/agents/vixen/path.pyc differ