Finally.
BLERGH.
--- /dev/null
+#!/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
+
--- /dev/null
+celsius.py
+David Gow
+python
+Generates a heatmap of the board, and uses this to control pieces.
--- /dev/null
+
+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)
+
+-include $(DEPFILES)
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+ai.o: ai.c interface.h ai_common.h
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+db.o: db.c ai_common.h interface.h
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+main.o: main.c interface.h