From: Sam Moore Date: Sat, 28 Apr 2012 10:20:32 +0000 (+0800) Subject: [PRELIMINARY ROUND 1] X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=3b7e8befa3c75a9ac0aac03dc527c6637dd5fcd7;p=progcomp2012.git [PRELIMINARY ROUND 1] Credits: -------- celcius - David Gow (sulix) ramen - John Hodge (tpg) peternlewis - Peter N Lewis Samples: basic_python basic_cpp (NOT COMPETING) basic_java (NOT COMPETING) asmodeus vixen hunter All samples are provided by Sam Moore The preliminary round is worth 10%; scores in simulate.py have been weighted accordingly Good luck! --- diff --git a/agents/celsius/celsius.py b/agents/celsius/celsius.py new file mode 100755 index 0000000..cc71b90 --- /dev/null +++ b/agents/celsius/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/celsius/info b/agents/celsius/info new file mode 100644 index 0000000..aedb008 --- /dev/null +++ b/agents/celsius/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/peternlewis/Makefile b/agents/peternlewis/Makefile new file mode 100644 index 0000000..e7e0bc1 --- /dev/null +++ b/agents/peternlewis/Makefile @@ -0,0 +1,25 @@ +#Makefile for basic_cpp +# Sample C++ Stratego AI +# UCC Programming Competition 2012 + +CPP = g++ -Wall -pedantic -g +OBJ = peternlewis.o + +BIN = peternlewis + +$(BIN) : $(OBJ) + $(CPP) -o $(BIN) $(OBJ) + +%.o : %.cpp %.h + $(CPP) -c $< + +clean : + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + +#Cleans up all backup files +clean_full: + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + $(RM) *.*~ + $(RM) *~ + + diff --git a/agents/peternlewis/__MACOSX/._peternlewis b/agents/peternlewis/__MACOSX/._peternlewis new file mode 100644 index 0000000..633840e Binary files /dev/null and b/agents/peternlewis/__MACOSX/._peternlewis differ diff --git a/agents/peternlewis/__MACOSX/peternlewis/._.DS_Store b/agents/peternlewis/__MACOSX/peternlewis/._.DS_Store new file mode 100644 index 0000000..321346b Binary files /dev/null and b/agents/peternlewis/__MACOSX/peternlewis/._.DS_Store differ diff --git a/agents/peternlewis/__MACOSX/peternlewis/._Makefile b/agents/peternlewis/__MACOSX/peternlewis/._Makefile new file mode 100644 index 0000000..6adb72e Binary files /dev/null and b/agents/peternlewis/__MACOSX/peternlewis/._Makefile differ diff --git a/agents/peternlewis/__MACOSX/peternlewis/._info b/agents/peternlewis/__MACOSX/peternlewis/._info new file mode 100644 index 0000000..633840e Binary files /dev/null and b/agents/peternlewis/__MACOSX/peternlewis/._info differ diff --git a/agents/peternlewis/__MACOSX/peternlewis/._peternlewis.cpp b/agents/peternlewis/__MACOSX/peternlewis/._peternlewis.cpp new file mode 100644 index 0000000..d2c90af Binary files /dev/null and b/agents/peternlewis/__MACOSX/peternlewis/._peternlewis.cpp differ diff --git a/agents/peternlewis/__MACOSX/peternlewis/._peternlewis.h b/agents/peternlewis/__MACOSX/peternlewis/._peternlewis.h new file mode 100644 index 0000000..cb3e53f Binary files /dev/null and b/agents/peternlewis/__MACOSX/peternlewis/._peternlewis.h differ diff --git a/agents/peternlewis/info b/agents/peternlewis/info new file mode 100644 index 0000000..7b0886b --- /dev/null +++ b/agents/peternlewis/info @@ -0,0 +1,4 @@ +peternlewis +Peter N Lewis +C++ +Based on my MacTech 1997 cahellnge winning algorithm, updated only enough to comply with the UCC challenge API. diff --git a/agents/peternlewis/peternlewis b/agents/peternlewis/peternlewis new file mode 100755 index 0000000..b63237f Binary files /dev/null and b/agents/peternlewis/peternlewis differ diff --git a/agents/peternlewis/peternlewis.cpp b/agents/peternlewis/peternlewis.cpp new file mode 100755 index 0000000..b8666f7 --- /dev/null +++ b/agents/peternlewis/peternlewis.cpp @@ -0,0 +1,1708 @@ +#define ASSERTIONS 1 +#define DEBUG_RULES 0 +#define DEBUG 0 +#define SAVE_OUTPUT 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; // two of two, yay! + +typedef bool Boolean; +typedef unsigned long UInt32; + +#include "peternlewis.h" + +const char *gPieceCharacters = kPieceCharacters; +#if SAVE_OUTPUT +ofstream gOutputFile; +#endif + +template +inline std::string to_string (const T& t) +{ + std::stringstream ss; + ss << t; + return ss.str(); +} + + +/* + Author: Peter N Lewis + + Originally: Submitted in 1997 to MacTech Programming Challenge and won. + + + Assumptions: + Only time we spend thinking is counted against out 10 seconds (not time in GetMove/ReportMove) + + Method: + Basically we keep track of the board and what we know and what they know. + Each opponent piece has a bit map associated with it describing what pieces it could be. + As we see more pieces, the bit map is culled. If the piece moves, the bomb & flag bits are removed. + If we've seen all Scouts (for example), then the Scout bit is removed from all remaining pieces. + If all but one bit is remvoed, then we know what the piece is. + + At each turn, we simply apply a sequence of actions (listed below) and take the first action that works. + + It does very little in the way of lookahead (it plans out a path, but doesn't remember it and + doesn't take it to account any movement by the opposition) + + It keeps a CRC of recent board positions (since the last strike) and doesn't replay any boards + (we want to win, not draw!). + + If we exceed 10 seconds thinking time, we resign. Not that this is particularly likely, + in the games I tried, it spend less than half a second total. + + Optimizations: + None. + + Comment: + It actually plays a half decent game! The end game is not as good as I'd like, but time is up! +*/ + +/* +USE SPY + If our spy is next to their 1, kill it + +DEFEND AGAINST SPY + if we have seen the spy, ignore this case + + If an unknown piece is next to the 1, then + run, attack, have another piece attack, or ignore depending on a table + +ATTACK WEAKER + If a known piece is next to a weaker known piece, attack it + except if it places that piece in a dangerous location + +EXPLORE ATTACK + If a 6,7,9 is next to an unknown piece, attack it + +RETREAT + If a known piece is next to a stronger known piece, run away + (preferably towards something that can kill it + or if it's lowly, towards an unknown piece) + +SCOUT + Try advancing scouts rapidly + +ATTACK DISTANT + If a known piece is distant, but a clear path leads a slightly better piece towards it, advance the better piece + (includes miners) + +EXPLORE DISTANT + Try exploring (advance lowly pieces towards unknown pieces) + +ATTACK KNOWN WITH SAME DISTANT + If a known piece can be attacked by a known identical piece, attack it + +FIND FLAG + When few unmoved pieces remain, start assuming they are bombs/flags + +MOVE FORWARD + Move any piece we can forward + +MOVE + Move any piece we can + +RESIGN + Give up +*/ + +static void Output( string what ) +{ +#if SAVE_OUTPUT + gOutputFile << "<< " << what << "\n"; +#endif + cout << what << "\n"; +} + +static void SaveInput( string what ) +{ +#if SAVE_OUTPUT + gOutputFile << ">> " << what << "\n"; +#endif +} + +static void Log( string what ) +{ +#if SAVE_OUTPUT + gOutputFile << what << "\n"; +#endif + cerr << what << "\n"; +} + +static void Debug( string what ) +{ +#if SAVE_OUTPUT + gOutputFile << what << "\n"; +#endif +#if DEBUG + cerr << what << "\n"; +#endif +} + +static void Assert( short must, string what ) +{ + if ( !must ) { +#if ASSERTIONS + Log( string("Assert failed! ") + what + "\n" ); +#endif + } +} + +std::vector gLineBuffer; + +enum { + kNoNothing = 0x00001FFE, + kStationaryBits = ((1 << kBomb) | (1 << kFlag)) +}; + +enum { + kRepeatedBoards = 1000 +}; + +typedef struct Square { + PlayerColor color; + PieceRank rank; + UInt32 possibilities; +} Square; + +typedef Square OurBoard[kBoardSize][kBoardSize]; + +typedef int Counts[kFlag+1]; + +typedef UInt32 BoardPossibilities[kBoardSize][kBoardSize]; + +typedef struct Storage { + OurBoard board; + Counts our_pieces; + Counts their_pieces; + Boolean do_getmove; + Boolean victory; + Square blankSquare; + PlayerColor playerColor; + PlayerColor theirColor; + BoardPossibilities dangers; + BoardPossibilities known_dangers; + UInt32 repeated_board[kRepeatedBoards]; + UInt32 repeated_board_count; +} Storage, *StoragePtr; + +static void DumpBoard( StoragePtr storage ) +{ +#if DEBUG + Debug( "DumpBoard:" ); + for ( int row = 0; row < kBoardSize; row++ ) { + string line; + for ( int col = 0; col < kBoardSize; col++ ) { + PieceRank rank = storage->board[row][col].rank; + if ( kMarshall <= rank && rank <= kFlag ) { + char left, right; + if ( storage->board[row][col].color == kRed ) { + left = '{'; + right = '}'; + } else { + left = '['; + right = ']'; + } + line += left; + line += gPieceCharacters[rank]; + line += right; + line += ' '; + } else { + line += ' '; + if ( rank == kEmpty ) { + line += '.'; + } else if ( rank == kWater ) { + line += '*'; + } else if ( rank == kMoved ) { + line += 'M'; + } else if ( rank == kAddForRankish ) { + line += '%'; + } else if ( rank == kUnknown ) { + line += '?'; + } else { + line += '@'; + } + line += ' '; + line += ' '; + } + } + Debug( line ); + } + Debug( "" ); +#endif +} + +static PieceRank HelperGetRank(char token) +{ + for ( unsigned int ii=0; ii <= strlen(gPieceCharacters); ++ii ) { + if (gPieceCharacters[ii] == token) { + return (PieceRank)(ii); + } + } + return kUnknown; +} + +/** + * Tokenise a string + */ +static int HelperTokenise(std::vector & buffer, std::string & str, char split = ' ') +{ + buffer.clear(); + string token = ""; + for (unsigned int x = 0; x < str.size(); ++x) + { + if (str[x] == split && token.size() > 0) + { + buffer.push_back(token); + token = ""; + } + if (str[x] != split) + token += str[x]; + } + if (token.size() > 0) + buffer.push_back(token); + return buffer.size(); +} + +/** + * Convert string to integer + */ +static int HelperInteger(std::string & fromStr) +{ + stringstream s(fromStr); + int result = 0; + s >> result; + return result; +} + +/** + * Read in a line from stdin + */ +static void HelperReadLine() +{ + std::string buffer = ""; + for (char c = cin.get(); c != '\n' && cin.good(); c = cin.get()) { + buffer += c; + } + SaveInput( buffer ); + HelperTokenise( gLineBuffer, buffer ); + if ( gLineBuffer.size() == 0 ) { + Log( "No tokens on line" ); + exit( 99 ); + } + if ( gLineBuffer.size() == 0 ) { + Log( "No tokens on line" ); + exit( 99 ); + } + if ( gLineBuffer[0] == "QUIT" || gLineBuffer[0] == "NO_MOVE") { + Log( "QUIT token found" ); + exit( 99 ); + } +} + +static void HelperJunkLine( int lines = 1 ) +{ + while ( lines > 0 ) { + HelperReadLine(); + lines--; + } +} + +enum MoveOutcome { + kMR_OK, + kMR_Kills, + kMR_Dies, + kMR_BothDie +}; + +static void HelperReadMove( StoragePtr storage, PiecePosition& from, PiecePosition& to, MoveOutcome& result, PieceRank& attacker, PieceRank& defender ) +{ + HelperReadLine(); + if ( gLineBuffer.size() < 4 ) { + Log( "Less than 4 tokens" ); + exit( 99 ); + } + from.col = HelperInteger( gLineBuffer[0] ); + from.row = HelperInteger( gLineBuffer[1] ); + int move = HelperInteger( gLineBuffer[3] ); + if ( move == 0 ) { + gLineBuffer.insert( gLineBuffer.begin()+3, "1" ); + move = 1; + } + to = from; + std::string dir = gLineBuffer[2]; + if (dir == "UP") { + to.row -= move; + } else if (dir == "DOWN") { + to.row += move; + } else if (dir == "LEFT") { + to.col -= move; + } else if (dir == "RIGHT") { + to.col += move; + } else { + Log( "DIRECTION ERROR" ); + exit( 99 ); + } + attacker = kUnknown; + defender = kUnknown; + std::string res = gLineBuffer[4]; + if ( res == "ILLEGAL" ) { + Log( "Opponent (hopefully) made ILLEGAL move" ); + exit( 99 ); + } else if ( res == "OK" ) { + result = kMR_OK; + } else { + if ( res == "KILLS" ) { + result = kMR_Kills; + } else if ( res == "DIES" ) { + result = kMR_Dies; + } else if ( res == "BOTHDIE" ) { + result = kMR_BothDie; + } else { + Log( string("Unknown move result ") + res ); + exit( 99 ); + } + attacker = HelperGetRank( gLineBuffer[5][0] ); + defender = HelperGetRank( gLineBuffer[6][0] ); + } + +} + +#ifdef false + +static const char *board_setup_1[4] = { // 1 = Marshal, ..., 9 = Scout, : = Spy, ; = Bomb, < = Flag + "8;<;77;6;7", + "75;586896;", + "6989954893", + "943159:249", +}; + +static const char *bobs_board_setup[4] = { // 1 = Marshal, ..., 9 = Scout, : = Spy, ; = Bomb, < = Flag + "<8;645:1;6", + "8277984893", + ";;5756;7;8", + "9949359969", +}; + +#endif + +static const char *board_setup[4] = { // 1 = Marshal, ..., 9 = Scout, : = Spy, ; = Bomb, < = Flag +// "8;<;67;;77", + "8;<;67;7;7", + "48;3862;89", + "6359954865", + "997159:499", +}; + +static const char *start_piece_counts = "0112344458161"; + +static int dR[4] = { 1, 0, -1, 0 }; +static int dC[4] = { 0, -1, 0, 1 }; + +#if ASSERTIONS + +static void AssertValidBoard( StoragePtr storage ) +{ + int piece; + int count1 = 0; + int count2 = 0; + int row, col; + + for ( piece = kMarshall; piece <= kFlag; piece++ ) { + count1 += storage->their_pieces[piece]; + } + + for ( row = 0; row < kBoardSize; row++ ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( storage->board[row][col].color == storage->theirColor + && storage->board[row][col].rank == kUnknown ) { + count2++; + } + } + } + + Assert( count1 == count2, "count1 == count2" ); +} + +#else + +#define AssertValidBoard( storage ) + +#endif + +static void PositionPieces( + StoragePtr storage, /* 1MB of preinitialized storage for your use */ + PlayerColor playerColor, /* you play red or blue, with red playing first */ + Board *theBoard /* provide the initial position of your pieces */ +) +{ + int row, our_row, their_row, col, board_col; + PlayerColor theirColor; + int piece; + Boolean reverse = (time( NULL ) & 1) != 0; + + Assert( strlen(board_setup[0]) == kBoardSize, "strlen(board_setup[0]) == kBoardSize" ); + Assert( strlen(board_setup[1]) == kBoardSize, "strlen(board_setup[1]) == kBoardSize" ); + Assert( strlen(board_setup[2]) == kBoardSize, "strlen(board_setup[2]) == kBoardSize" ); + Assert( strlen(board_setup[3]) == kBoardSize, "strlen(board_setup[3]) == kBoardSize" ); + + for ( row = 0; row <= 3; row++ ) { + if ( playerColor == kRed ) { + our_row = row; + their_row = (kBoardSize-1)-row; + theirColor = kBlue; + } else { + their_row = row; + our_row = (kBoardSize-1)-row; + theirColor = kRed; + } + for ( col = 0; col < 10; col++ ) { + board_col = reverse ? (kBoardSize-1) - col : col; + (*theBoard)[our_row][col].thePieceRank = (PieceRank) (board_setup[row][board_col] - '0'); + (*theBoard)[our_row][col].thePieceColor = playerColor; + + storage->board[our_row][col].color = playerColor; + storage->board[our_row][col].rank = (*theBoard)[our_row][col].thePieceRank; + storage->board[our_row][col].possibilities = kNoNothing; + + storage->board[their_row][col].color = theirColor; + storage->board[their_row][col].rank = kUnknown; + storage->board[their_row][col].possibilities = kNoNothing; + } + } + + for ( row = 4; row <= 5; row++ ) { + for( col = 0; col < kBoardSize; col++ ) { + storage->board[row][col].color = (PlayerColor)kNoColor; + storage->board[row][col].rank = (PieceRank) ((col/2 % 2 == 1) ? kWater : kEmpty); + storage->board[row][col].possibilities = 0; + } + } + + for ( piece = kMarshall; piece <= kFlag; piece++ ) { + storage->our_pieces[piece] = start_piece_counts[piece] - '0'; + storage->their_pieces[piece] = start_piece_counts[piece] - '0'; + } + + storage->do_getmove = (playerColor == kBlue); + storage->victory = false; + storage->blankSquare = storage->board[4][0]; + storage->playerColor = playerColor; + storage->theirColor = playerColor == kRed ? kBlue : kRed; + storage->repeated_board_count = 0; + + AssertValidBoard( storage ); +} + +static void Learn( StoragePtr storage, Boolean them, int row, int col, PieceRank rank ) +{ + Boolean gotall; + PlayerColor thiscolor; + int r, c; + + if ( storage->board[row][col].rank == kUnknown ) { + + if ( rank == kMoved ) { + UInt32 possibilities = storage->board[row][col].possibilities; + possibilities &= ~kStationaryBits; + + if ( (possibilities & (possibilities-1)) == 0 ) { // only one bit on! Now we know! + int newrank; + newrank = 0; + while ( (possibilities & 1) == 0 ) { + possibilities >>= 1; + newrank++; + } + rank = (PieceRank)newrank; + } else { + storage->board[row][col].possibilities = possibilities; + } + } + + if ( rank != kMoved ) { + storage->board[row][col].rank = rank; + storage->board[row][col].possibilities = (1 << rank); + if ( them ) { + gotall = --storage->their_pieces[rank] == 0; + } else { + gotall = --storage->our_pieces[rank] == 0; + } + if ( gotall ) { + thiscolor = storage->board[row][col].color; + for ( r = 0; r < kBoardSize; r++ ) { + for ( c = 0; c < kBoardSize; c++ ) { + if ( storage->board[r][c].rank == kUnknown + && storage->board[r][c].color == thiscolor ) { + UInt32 possibilities = storage->board[r][c].possibilities; + possibilities &= ~ (1 << rank); + storage->board[r][c].possibilities = possibilities; + if ( (possibilities & (possibilities-1)) == 0 ) { // only one bit on! + int newrank; + newrank = 0; + while ( (possibilities & 1) == 0 ) { + possibilities >>= 1; + newrank++; + } + Learn( storage, them, r, c, (PieceRank)newrank ); + } + } + } + } + } + } + } else { + Assert( rank == kMoved || storage->board[row][col].rank == rank, "rank == kMoved || storage->board[row][col].rank == rank" ); + } +} + +static void HandleTheirMove( StoragePtr storage, const PiecePosition moveFrom, const PiecePosition moveTo, Boolean moveStrike, const MoveResult moveResult ) +{ + Assert( moveResult.legalMove, "moveResult.legalMove" ); // They must have made a legal move or we would not be called + Assert( !moveResult.victory, "!moveResult.victory" ); // If they won we would not be called + if ( moveStrike ) { + Learn( storage, true, moveFrom.row, moveFrom.col, moveResult.rankOfAttacker.thePieceRank ); + Learn( storage, false, moveTo.row, moveTo.col, moveResult.rankOfDefender.thePieceRank ); + if ( moveResult.attackerRemoved && moveResult.defenderRemoved ) { + storage->board[moveFrom.row][moveFrom.col] = storage->blankSquare; + storage->board[moveTo.row][moveTo.col] = storage->blankSquare; + } else if ( moveResult.attackerRemoved ) { +// if ( storage->board[moveTo.row][moveTo.col].rank == kBomb ) { + storage->board[moveFrom.row][moveFrom.col] = storage->blankSquare; +// } else { +// storage->board[moveFrom.row][moveFrom.col] = storage->board[moveTo.row][moveTo.col]; +// storage->board[moveTo.row][moveTo.col] = storage->blankSquare; +// } + } else { + Assert( moveResult.defenderRemoved, "moveResult.defenderRemoved" ); + storage->board[moveTo.row][moveTo.col] = storage->board[moveFrom.row][moveFrom.col]; + storage->board[moveFrom.row][moveFrom.col] = storage->blankSquare; + } + } else { + storage->board[moveTo.row][moveTo.col] = storage->board[moveFrom.row][moveFrom.col]; + storage->board[moveFrom.row][moveFrom.col] = storage->blankSquare; + if ( abs(moveTo.row - moveFrom.row) + abs(moveTo.col - moveFrom.col) > 1 ) { + Learn( storage, true, moveTo.row, moveTo.col, kScout ); + } else { + Learn( storage, true, moveTo.row, moveTo.col, (PieceRank)kMoved ); + } + } + + AssertValidBoard( storage ); +} + +static Boolean FindPiece( StoragePtr storage, PlayerColor color, PieceRank rank, int *row, int *col ) +{ + int r, c; + + for ( r = 0; r < kBoardSize; r++ ) { + for( c = 0; c < kBoardSize; c++ ) { + if ( storage->board[r][c].color == color + && storage->board[r][c].rank == rank ) { + *row = r; + *col = c; + return true; + } + } + } + return false; +} + +static Boolean IsOnBoardWeak( int row, int col ) +{ + return 0 <= row && row < kBoardSize && 0 <= col && col < kBoardSize; +} + +static Boolean IsOnBoard( int row, int col ) +{ + if ( 0 <= row && row < kBoardSize && 0 <= col && col < kBoardSize ) { + if ( row <= 3 || row >= 6 ) { + return true; + } + if ( col <= 1 || col >= 8 ) { + return true; + } + if ( 4 <= col && col <= 5 ) { + return true; + } + } + return false; +} + +static Boolean IsColorPiece( StoragePtr storage, int row, int col, PlayerColor color ) +{ + Assert( IsOnBoardWeak( row, col ), "IsOnBoardWeak( row, col )" ); + return storage->board[row][col].color == color; +} + +static Boolean IsOurPiece( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoardWeak( row, col ), "IsOnBoardWeak( row, col )" ); + return storage->board[row][col].color == storage->playerColor; +} + +static Boolean IsTheirPiece( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoardWeak( row, col ), "IsOnBoardWeak( row, col )" ); + return storage->board[row][col].color == storage->theirColor; +} + +static Boolean IsUnknownPiece( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoardWeak( row, col ), "IsOnBoardWeak( row, col )" ); + return storage->board[row][col].rank == kUnknown; +} + +static Boolean IsRankPiece( StoragePtr storage, int row, int col, PieceRank rank ) +{ + Assert( IsOnBoardWeak( row, col ), "IsOnBoardWeak( row, col )" ); + return storage->board[row][col].rank == rank; +} + +static Boolean IsEmptySquare( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoardWeak( row, col ), "IsOnBoardWeak( row, col )" ); + return storage->board[row][col].rank == (PieceRank)kEmpty; +} + +static Boolean IsWaterSquare( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoardWeak( row, col ), "IsOnBoardWeak( row, col )" ); + return storage->board[row][col].rank == (PieceRank)kWater; +} + +static Boolean IsLowlyRank( PieceRank rank ) +{ + return kCaptain <= rank && rank <= kScout && rank != kMiner; +} + +static Boolean IsLowlyPiece( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoard( row, col ), "IsOnBoard( row, col )" ); + return IsLowlyRank( storage->board[row][col].rank ); +} + +static Boolean IsMovedPiece( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoard( row, col ), "IsOnBoard( row, col )" ); + return (storage->board[row][col].possibilities & kStationaryBits) == 0; +} + +static Boolean IsRevealedPiece( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoard( row, col ), "IsOnBoard( row, col )" ); + Assert( IsOurPiece( storage, row, col ), "IsOurPiece( storage, row, col )" ); + UInt32 possibilities = storage->board[row][col].possibilities; + return ( (possibilities & (possibilities-1)) == 0 ); +} + +static int CountAdjacentUnknownPieces( StoragePtr storage, PlayerColor color, int row, int col ) +{ + int d; + int unknowns = 0; + + for ( d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + if ( IsOnBoard( r, c ) && IsColorPiece( storage, r, c, color ) && IsUnknownPiece( storage, r, c ) ) { + unknowns++; + } + } + + return unknowns; +} + +static const char *defend_spy_table = "RARROAOORARRRARRXAXAOAOOXAXAXAXA"; +// Run/Attack/Other/Nothing, >1 unknown:other:danger:moved + +static Boolean LowlyCanAttack( StoragePtr storage, int row, int col, int *otherRow, int *otherCol ) +{ + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + if ( IsOnBoard( r, c ) + && IsOurPiece( storage, r, c ) + && IsLowlyPiece( storage, r, c ) ) { + *otherRow = r; + *otherCol = c; + return true; + } + } + return false; +} + +static void UpdateDangerPossibilities( StoragePtr storage ) +{ + int row, col; + + for ( row = 0; row < kBoardSize; row++ ) { + for( col = 0; col < kBoardSize; col++ ) { + storage->dangers[row][col] = 0; + storage->known_dangers[row][col] = 0; + } + } + + for ( row = 0; row < kBoardSize; row++ ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsTheirPiece( storage, row, col ) ) { + UInt32 possibilities = (storage->board[row][col].possibilities & ~kStationaryBits); + UInt32 known_possibilities = 0; + + if ( storage->board[row][col].rank != kUnknown ) { + known_possibilities = possibilities; + } + + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + if ( IsOnBoard( r, c ) ) { + storage->dangers[r][c] |= possibilities; + storage->known_dangers[r][c] |= known_possibilities; + } + } + + } + } + } + +} + +static UInt32 GetDangerPossibilities( StoragePtr storage, int row, int col ) +{ + Assert( IsOnBoard( row, col ), "IsOnBoard( row, col )" ); + return storage->dangers[row][col]; +} + +static Boolean PossibilitiesCouldKill( PieceRank rank, UInt32 possibilities ) +{ + if ( (possibilities & ~kStationaryBits) == 0 ) { + return false; + } + + switch ( rank ) { + case kFlag: + return true; + case kBomb: + return (possibilities & (1 << kMiner)) != 0; + case kMarshall: + return (possibilities & ((1 << kMarshall) + (1<< kSpy))) != 0; + default: + return (possibilities & ((1 << (rank+1)) - 1)) != 0; + } +} + +static Boolean PossibilitiesCouldKillSafely( PieceRank rank, UInt32 possibilities ) +{ + if ( (possibilities & ~kStationaryBits) == 0 ) { + return false; + } + + switch ( rank ) { + case kFlag: + return true; + case kBomb: + return (possibilities & (1 << kMiner)) != 0; + case kMarshall: + return (possibilities & ((1<< kSpy))) != 0; + default: + return (possibilities & ((1 << rank) - 1)) != 0; + } +} + +static Boolean WillKillPossibilities( PieceRank rank, UInt32 possibilities ) +{ + Assert( possibilities != 0, "possibilities != 0" ); + + switch ( rank ) { + case kFlag: + return false; + case kBomb: + return false; + case kMiner: + return (possibilities & ~((1 << kScout) + (1 << kBomb) + (1 << kFlag))) == 0; + case kSpy: + return (possibilities & ~(1 << kMarshall)) == 0; + default: + return (possibilities & (((1 << (rank + 1)) - 1) + (1 << kBomb))) == 0; + } +} + +static Boolean WillKillOrSuicidePossibilities( PieceRank rank, UInt32 possibilities ) +{ + Assert( possibilities != 0, "possibilities != 0" ); + + switch ( rank ) { + case kFlag: + return false; + case kBomb: + return false; + case kMiner: + return (possibilities & ~((1 << kScout) + (1 << kMiner) + (1 << kBomb) + (1 << kFlag))) == 0; + case kSpy: + return (possibilities & ~((1 << kMarshall) + (1 << kSpy))) == 0; + default: + return (possibilities & (((1 << rank) - 1) + (1 << kBomb))) == 0; + } +} + +static Boolean WillPossibilitiesKill( UInt32 possibilities, PieceRank rank ) +{ + Assert( possibilities != 0, "possibilities != 0" ); + possibilities &= ~kStationaryBits; + if ( possibilities == 0 ) { + return false; + } + + switch ( rank ) { + case kFlag: + return true; + case kBomb: + return possibilities == (1 << kMiner); + default: + return (possibilities & ~((1 << (rank+1))-1)) == 0; + } +} + +static Boolean FindSafeSquare( StoragePtr storage, int row, int col, int *safeRow, int *safeCol ) +{ + Assert( IsOnBoard( row, col ), "IsOnBoard( row, col )" ); + + PieceRank rank = storage->board[row][col].rank; + int doff = (storage->playerColor == kBlue ? 0 : 2); // Try backwards first + + for ( int d = 0; d < 4; d++ ) { + int dr = dR[(d + doff) % 4]; + int dc = dC[(d + doff) % 4]; + int r = row + dr; + int c = col + dc; + + while ( IsOnBoard( r, c ) && IsEmptySquare( storage, r, c ) ) { + if ( !PossibilitiesCouldKill( rank, GetDangerPossibilities( storage, r, c ) ) ) { + *safeRow = r; + *safeCol = c; + return true; + } + if ( rank != kScout ) { + break; + } + r += dr; + c += dc; + } + } + return false; +} + +static void CountEnemies( StoragePtr storage, int row, int col, int *knowns, int *unknowns ) +{ + *knowns = 0; + *unknowns = 0; + + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + if ( IsOnBoard( r, c ) && IsTheirPiece( storage, r, c ) ) { + if ( storage->board[r][c].rank == kUnknown ) { + *unknowns += 1; + } else { + *knowns += 1; + } + } + } +} + +/* +static Boolean CanRun( StoragePtr storage, int row, int col, int *runRow, int *runCol ) +{ + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[(d + (storage->playerColor == kBlue ? 0 : 2)) % 4]; // Try backwards first + int c = col + dC[(d + (storage->playerColor == kBlue ? 0 : 2)) % 4]; + + if ( IsOnBoard( r, c ) && (storage->board[r][c].rank == kEmpty) ) { + *runRow = r; + *runCol = c; + return true; + } + } + return false; +} +*/ + +static Boolean FindSafePath( StoragePtr storage, Boolean very_safe, Boolean suicide_ok, int from_row, int from_col, int to_row, int to_col, int *best_path, int *first_row, int *first_col ) +{ + Assert( IsOurPiece( storage, from_row, from_col ), "IsOurPiece( storage, from_row, from_col )" ); + + PieceRank rank = storage->board[from_row][from_col].rank; + BoardPossibilities *dangers = very_safe ? &storage->dangers : &storage->known_dangers; + + if ( abs( from_row - to_row ) + abs( from_col - to_col ) > *best_path ) { + return false; + } + + if ( abs( from_row - to_row ) + abs( from_col - to_col ) == 1 ) { + *best_path = 0; + *first_row = to_row; + *first_col = to_col; + return true; + } + + int path_length_to[kBoardSize][kBoardSize]; + PiecePosition que[kBoardSize * kBoardSize]; + int que_start = 0; + int que_fin = 0; + int que_next_len = 0; + int current_len = 0; + int row, col; + + for ( row = 0; row < kBoardSize; row++ ) { + for( col = 0; col < kBoardSize; col++ ) { + path_length_to[row][col] = -1; + } + } + + que[que_fin].row = from_row; + que[que_fin].col = from_col; + path_length_to[from_row][from_col] = 0; + que_fin++; + que_next_len = que_fin; + + while ( que_fin > que_start ) { + row = que[que_start].row; + col = que[que_start].col; + que_start++; + + for ( int d = 0; d < 4; d++ ) { + int dr = dR[d]; + int dc = dC[d]; +// scout moves NYI + int r = row + dr; + int c = col + dc; + + if ( IsOnBoard( r, c ) && path_length_to[r][c] == -1 + && IsEmptySquare( storage, r, c ) ) { + if ( suicide_ok + ? !PossibilitiesCouldKillSafely( rank, (*dangers)[r][c] ) + : !PossibilitiesCouldKill( rank, (*dangers)[r][c] ) ) { + path_length_to[r][c] = current_len + 1; + if ( abs( to_row - r ) + abs( to_col - c ) == 1 ) { + *best_path = current_len + 1; + while ( current_len > 0 ) { + for ( int d = 0; d < 4; d++ ) { + int backr = r + dR[d]; + int backc = c + dC[d]; + + if ( path_length_to[backr][backc] == current_len ) { + r = backr; + c = backc; + break; + } + } + current_len--; + } + *first_row = r; + *first_col = c; + return true; + } + que[que_fin].row = r; + que[que_fin].col = c; + que_fin++; + } else { + path_length_to[r][c] = 1000; // Cant go here + } + } + } + + if ( que_start == que_next_len ) { + que_next_len = que_fin; + current_len++; + } + } + + return false; +} + +static UInt32 CalcBoardCRC( StoragePtr storage, int from_row, int from_col, int to_row, int to_col ) +{ + Assert( !IsOnBoard( from_row, from_col ) || IsOurPiece( storage, from_row, from_col ), "!IsOnBoard( from_row, from_col ) || IsOurPiece( storage, from_row, from_col )" ); + Assert( !IsOnBoard( to_row, to_col ) || IsEmptySquare( storage, to_row, to_col ), "!IsOnBoard( to_row, to_col ) || IsEmptySquare( storage, to_row, to_col )" ); + + UInt32 result = 0; + + int row, col; + int rankish; + + for ( row = 0; row < kBoardSize; row++ ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( row == from_row && col == from_col ) { + rankish = 0; + } else if ( row == to_row && col == to_col ) { + rankish = storage->board[from_row][from_col].rank; + } else if ( IsEmptySquare( storage, row, col ) || IsWaterSquare( storage, row, col ) ) { + rankish = 0; + } else if ( IsOurPiece( storage, row, col ) ) { + rankish = storage->board[row][col].rank; + } else { + rankish = storage->board[row][col].rank + kAddForRankish; + } + result += rankish; // Hmm, not a very good CRC + result = result * 11 + (result >> 25); + } + } + + return result; +} + +static Boolean OKMove( StoragePtr storage, int from_row, int from_col, int to_row, int to_col ) +{ + if ( IsTheirPiece( storage, to_row, to_col ) ) { + return true; + } + + UInt32 crc = CalcBoardCRC( storage, from_row, from_col, to_row, to_col ); + for ( UInt32 i = 0; i < storage->repeated_board_count; i++ ) { + if ( crc == storage->repeated_board[i] ) { + return false; + } + } + return true; +} + +static void AppendRepeatedBoard( StoragePtr storage ) +{ + UInt32 crc = CalcBoardCRC( storage, -1, -1, -1, -1 ); + + if ( storage->repeated_board_count == kRepeatedBoards ) { + storage->repeated_board_count--; + memcpy( &storage->repeated_board[0], &storage->repeated_board[1], storage->repeated_board_count * sizeof(storage->repeated_board[0]) ); + } + storage->repeated_board[storage->repeated_board_count++] = crc; +} + +#if DEBUG_RULES + #define RETURN( x ) Log( x ); return +#else + #define RETURN( x ) return +#endif + +static void FigureOutOurMove( StoragePtr storage, PiecePosition *moveFrom, PiecePosition *moveTo ) +{ + int ourRow, ourCol, theirRow, theirCol, row, col, runRow, runCol; + int rowFirst = storage->playerColor == kRed ? 0 : kBoardSize - 1; + int rowLast = storage->playerColor == kRed ? kBoardSize - 1 : 0; + int rowAdd = storage->playerColor == kRed ? 1 : -1; + int bestUnknowns; + int bestPath; + int thisPath; + + UpdateDangerPossibilities( storage ); + +// USE SPY + if ( FindPiece( storage, storage->theirColor, kMarshall, &theirRow, &theirCol ) + && FindPiece( storage, storage->playerColor, kSpy, &ourRow, &ourCol ) + && abs( theirRow - ourRow ) + abs( theirCol - ourCol ) == 1 ) { + moveFrom->row = ourRow; + moveFrom->col = ourCol; + moveTo->row = theirRow; + moveTo->col = theirCol; + RETURN( "USE SPY" ); + } + +// DEFEND AGAINST SPY + if (storage->their_pieces[kSpy] > 0) { + if ( FindPiece( storage, storage->playerColor, kMarshall, &ourRow, &ourCol ) ) { + int unknowns = CountAdjacentUnknownPieces( storage, storage->theirColor, ourRow, ourCol ); + + if ( unknowns ) { + int base_index = 0; + Boolean canrun = FindSafeSquare( storage, ourRow, ourCol, &runRow, &runCol ); + if ( !canrun ) { + base_index += 16; + } + if ( unknowns > 1 ) { + base_index += 8; + } + + for ( int d = 0; d < 4; d++ ) { + int r = ourRow + dR[d]; + int c = ourCol + dC[d]; + int otherRow, otherCol; + + if ( IsOnBoard( r, c ) + && IsTheirPiece( storage, r, c ) + && IsUnknownPiece( storage, r, c ) ) { + int index = base_index; + if ( LowlyCanAttack( storage, r, c, &otherRow, &otherCol ) ) { + index += 4; + } + if ( CountAdjacentUnknownPieces( storage, storage->theirColor, r, c ) > 0 ) { + index += 2; + } + if ( IsMovedPiece( storage, r, c ) ) { + index += 1; + } + + if ( defend_spy_table[index] == 'A' ) { // Attack + moveFrom->row = ourRow; + moveFrom->col = ourCol; + moveTo->row = r; + moveTo->col = c; + RETURN( "DEFEND AGAINST SPY 1" ); + } else if ( defend_spy_table[index] == 'O' ) { // Attack + moveFrom->row = otherRow; + moveFrom->col = otherCol; + moveTo->row = r; + moveTo->col = c; + RETURN( "DEFEND AGAINST SPY 2" ); + } + } + } + + if ( canrun && OKMove( storage, ourRow, ourCol, runRow, runCol ) ) { + moveFrom->row = ourRow; + moveFrom->col = ourCol; + moveTo->row = runRow; + moveTo->col = runCol; + RETURN( "DEFEND AGAINST SPY 3" ); + } + // Give up! Next ruleƉ + } + } + } + +// ATTACK WEAKER + for ( row = rowFirst; 0 <= row && row < kBoardSize; row += rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsTheirPiece( storage, row, col ) ) { + UInt32 enemy = storage->board[row][col].possibilities; + UInt32 danger = GetDangerPossibilities( storage, row, col ); + + int bestDir = -1; + Boolean isBestRevealed = true; + PieceRank bestRank = kUnknown; + + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + if ( IsOnBoard( r, c ) && IsOurPiece( storage, r, c ) ) { + if ( !PossibilitiesCouldKill( storage->board[r][c].rank, danger ) ) { + if ( WillKillPossibilities( storage->board[r][c].rank, enemy ) ) { + Boolean thisRevealed = IsRevealedPiece( storage, r, c ); + if ( isBestRevealed || !thisRevealed ) { + if ( bestDir == -1 || (storage->board[r][c].rank > bestRank) ) { + bestDir = d; + bestRank = storage->board[r][c].rank; + isBestRevealed = thisRevealed; + } + } + } + } + } + } + if ( bestDir != -1 ) { + moveFrom->row = row + dR[bestDir]; + moveFrom->col = col + dC[bestDir]; + moveTo->row = row; + moveTo->col = col; + RETURN( "ATTACK WEAKER" ); + } + } + } + } + +// EXPLORE ATTACK + for ( int rnk = kScout; rnk >= kMarshall; rnk-- ) { + PieceRank rank = (PieceRank) rnk; + if ( IsLowlyRank( rank ) ) { + + for ( row = rowLast; 0 <= row && row < kBoardSize; row -= rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsOurPiece( storage, row, col ) + && IsRankPiece( storage, row, col, rank ) ) { + + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + if ( IsOnBoard( r, c ) + && IsTheirPiece( storage, r, c ) + && IsRankPiece( storage, r, c, kUnknown ) ) { + moveFrom->row = row; + moveFrom->col = col; + moveTo->row = r; + moveTo->col = c; + RETURN( "EXPLORE ATTACK" ); + } + } + } + } + } + + } + } + +// RETREAT + for ( row = rowLast; 0 <= row && row < kBoardSize; row -= rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsOurPiece( storage, row, col ) + && IsMovedPiece( storage, row, col ) ) { + + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + if ( IsOnBoard( r, c ) + && IsTheirPiece( storage, r, c ) + && WillPossibilitiesKill( storage->board[r][c].possibilities, storage->board[row][col].rank ) ) { + bestPath = 1000; + for ( int to_row = rowLast; 0 <= to_row && to_row < kBoardSize; to_row -= rowAdd ) { + for( int to_col = 0; to_col < kBoardSize; to_col++ ) { + thisPath = bestPath; + if ( IsTheirPiece( storage, to_row, to_col ) + && (IsRankPiece( storage, to_row, to_col, kUnknown ) + || WillKillPossibilities( storage->board[row][col].rank, storage->board[to_row][to_col].possibilities )) + && FindSafePath( storage, false, true, row, col, to_row, to_col, &thisPath, &runRow, &runCol ) + && OKMove( storage, row, col, runRow, runCol ) ) { + bestPath = thisPath; + moveFrom->row = row; + moveFrom->col = col; + moveTo->row = runRow; + moveTo->col = runCol; + } + } + } + if ( bestPath < 1000 ) { + RETURN( "RETREAT" ); + } + } + } + } + } + } + +// SCOUT + bestUnknowns = 0; + + for ( row = rowLast; 0 <= row && row < kBoardSize; row -= rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsOurPiece( storage, row, col ) + && IsRankPiece( storage, row, col, kScout ) ) { + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + while ( IsOnBoard( r, c ) && IsEmptySquare( storage, r, c ) ) { + + int knowns, unknowns; + CountEnemies( storage, r, c, &knowns, &unknowns ); + if ( knowns == 0 && unknowns > bestUnknowns && OKMove( storage, row, col, r, c ) ) { + bestUnknowns = unknowns; + ourRow = row; + ourCol = col; + runRow = r; + runCol = c; + } + r += dR[d]; + c += dC[d]; + } + } + } + } + } + + if ( bestUnknowns > 0 ) { + moveFrom->row = ourRow; + moveFrom->col = ourCol; + moveTo->row = runRow; + moveTo->col = runCol; + RETURN( "SCOUT" ); + } + +// ATTACK DISTANT + + bestPath = 1000; + + for ( row = rowFirst; 0 <= row && row < kBoardSize; row += rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsTheirPiece( storage, row, col ) ) { + UInt32 possibilities = storage->board[row][col].possibilities; + UInt32 danger = GetDangerPossibilities( storage, row, col ); + + if ( (possibilities & ((1 << kBomb) | (1 << kMarshall))) != ((1 << kBomb) | (1 << kMarshall)) ) { + for ( int r = rowFirst; 0 <= r && r < kBoardSize; r += rowAdd ) { + for( int c = 0; c < kBoardSize; c++ ) { + if ( IsOurPiece( storage, r, c ) ) { + if ( WillKillPossibilities( storage->board[r][c].rank, possibilities ) ) { + if ( storage->board[r][c].rank >= kCaptain || !PossibilitiesCouldKill( storage->board[r][c].rank, danger ) ) { + thisPath = bestPath; + if ( FindSafePath( storage, true, false, r, c, row, col, &thisPath, &runRow, &runCol ) ) { + if ( OKMove( storage, r, c, runRow, runCol ) ) { + bestPath = thisPath; + moveFrom->row = r; + moveFrom->col = c; + moveTo->row = runRow; + moveTo->col = runCol; + } + } + } + } + } + } + } + } + + } + } + } + + if ( bestPath < 1000 ) { + RETURN( "ATTACK DISTANT" ); + } + +// EXPLORE DISTANT + + bestPath = 1000; + + for ( row = rowFirst; 0 <= row && row < kBoardSize; row += rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsTheirPiece( storage, row, col ) && storage->board[row][col].rank == kUnknown ) { + + for ( int r = rowFirst; 0 <= r && r < kBoardSize; r += rowAdd ) { + for( int c = 0; c < kBoardSize; c++ ) { + if ( IsOurPiece( storage, r, c ) && IsLowlyPiece( storage, r, c ) ) { + thisPath = bestPath; + if ( FindSafePath( storage, false, true, r, c, row, col, &thisPath, &runRow, &runCol ) ) { + if ( OKMove( storage, r, c, runRow, runCol ) ) { + bestPath = thisPath; + moveFrom->row = r; + moveFrom->col = c; + moveTo->row = runRow; + moveTo->col = runCol; + } + } + } + } + } + + } + } + } + + if ( bestPath < 1000 ) { + RETURN( "EXPLORE DISTANT" ); + } + +// ATTACK KNOWN WITH SAME DISTANT + + bestPath = 1000; + + for ( row = rowFirst; 0 <= row && row < kBoardSize; row += rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsTheirPiece( storage, row, col ) ) { + UInt32 possibilities = storage->board[row][col].possibilities; + + if ( (possibilities & ((1 << kBomb) | (1 << kMarshall))) != ((1 << kBomb) | (1 << kMarshall)) ) { + for ( int r = rowFirst; 0 <= r && r < kBoardSize; r += rowAdd ) { + for( int c = 0; c < kBoardSize; c++ ) { + if ( IsOurPiece( storage, r, c ) ) { + if ( WillKillOrSuicidePossibilities( storage->board[r][c].rank, possibilities ) ) { + thisPath = bestPath; + if ( FindSafePath( storage, true, true, r, c, row, col, &thisPath, &runRow, &runCol ) ) { + if ( OKMove( storage, r, c, runRow, runCol ) ) { + bestPath = thisPath; + moveFrom->row = r; + moveFrom->col = c; + moveTo->row = runRow; + moveTo->col = runCol; + } + } + } + } + } + } + } + + } + } + } + + if ( bestPath < 1000 ) { + RETURN( "ATTACK KNOWN WITH SAME DISTANT" ); + } + +// FIND FLAG +// NYI + +// MOVE FORWARD + + for ( row = rowLast; 0 <= row && row < kBoardSize; row -= rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsOurPiece( storage, row, col ) ) { + PieceRank rank = storage->board[row][col].rank; + if ( rank != kBomb && rank != kFlag ) { + int r = row + rowAdd; + if ( IsOnBoard( r, col ) && !IsOurPiece( storage, r, col ) && OKMove( storage, row, col, r, col ) ) { + moveFrom->row = row; + moveFrom->col = col; + moveTo->row = r; + moveTo->col = col; + RETURN( "MOVE FORWARD" ); + } + } + } + } + } + +// MOVE + + for ( row = rowLast; 0 <= row && row < kBoardSize; row -= rowAdd ) { + for( col = 0; col < kBoardSize; col++ ) { + if ( IsOurPiece( storage, row, col ) ) { + PieceRank rank = storage->board[row][col].rank; + if ( rank != kBomb && rank != kFlag ) { + + for ( int d = 0; d < 4; d++ ) { + int r = row + dR[d]; + int c = col + dC[d]; + + if ( IsOnBoard( r, c ) && !IsOurPiece( storage, r, c ) && OKMove( storage, row, col, r, c ) ) { + moveFrom->row = row; + moveFrom->col = col; + moveTo->row = r; + moveTo->col = c; + RETURN( "MOVE" ); + } + } + } + } + } + } + +// RESIGN + moveFrom->row = -1; + moveFrom->col = -1; + moveTo->row = -1; + moveTo->col = -1; + RETURN( "RESIGN" ); + +} + +static void HandleOurMove( StoragePtr storage, PiecePosition moveFrom, PiecePosition moveTo, const MoveResult moveResult ) +{ + Boolean moveStrike; + + if ( IsOnBoard( moveTo.row, moveTo.col ) ) { + moveStrike = storage->board[moveTo.row][moveTo.col].color != kNoColor; + } else { + moveStrike = false; + } + + if ( moveResult.victory ) { // We Win! :-) + storage->victory = true; + } else if ( !moveResult.legalMove ) { // We Lose! :-( + } else { + if ( moveStrike ) { + storage->repeated_board_count = 0; + Learn( storage, true, moveTo.row, moveTo.col, moveResult.rankOfDefender.thePieceRank ); + Learn( storage, false, moveFrom.row, moveFrom.col, moveResult.rankOfAttacker.thePieceRank ); + + if ( moveResult.attackerRemoved && moveResult.defenderRemoved ) { + storage->board[moveFrom.row][moveFrom.col] = storage->blankSquare; + storage->board[moveTo.row][moveTo.col] = storage->blankSquare; + } else if ( moveResult.attackerRemoved ) { +// if ( storage->board[moveTo.row][moveTo.col].rank == kBomb ) { + storage->board[moveFrom.row][moveFrom.col] = storage->blankSquare; +// } else { +// storage->board[moveFrom.row][moveFrom.col] = storage->board[moveTo.row][moveTo.col]; +// storage->board[moveTo.row][moveTo.col] = storage->blankSquare; +// } + } else { + Assert( moveResult.defenderRemoved, "moveResult.defenderRemoved" ); + storage->board[moveTo.row][moveTo.col] = storage->board[moveFrom.row][moveFrom.col]; + storage->board[moveFrom.row][moveFrom.col] = storage->blankSquare; + } + + } else { + if ( abs( moveTo.row - moveFrom.row ) + abs( moveTo.col - moveFrom.col ) > 1 ) { + Assert( storage->board[moveFrom.row][moveFrom.col].rank == kScout, "storage->board[moveFrom.row][moveFrom.col].rank == kScout" ); + Learn( storage, false, moveFrom.row, moveFrom.col, kScout ); + } else { + Learn( storage, false, moveFrom.row, moveFrom.col, (PieceRank)kMoved ); + } + storage->board[moveTo.row][moveTo.col] = storage->board[moveFrom.row][moveFrom.col]; + storage->board[moveFrom.row][moveFrom.col] = storage->blankSquare; + } + AppendRepeatedBoard( storage ); + } + + AssertValidBoard( storage ); +} + +/* +Boolean MakeAMove( + StoragePtr storage, / * 1MB of storage from PositionPieces * / + PlayerColor playerColor, / * you play red or blue, with red playing first * / + GetOpponentMove *GetMove, / * callback used to find about opponents last move * / + ReportYourMove *ReportMove / * callback used to make a move * / +) +{ + if ( storage->do_getmove ) { + HandleTheirMove( storage, *GetMove ); + } + storage->do_getmove = true; + + HandleOurMove( storage, *ReportMove ); + + return storage->victory; +} +*/ + +// Code to map UCC Challenge to MacTech Challenge + +static PlayerColor ReadPlayerLine() +{ + HelperReadLine(); + + std::string colourStr = gLineBuffer[0]; + + if ( colourStr == "RED" ) { + return kRed; + } else if ( colourStr == "BLUE" ) { + return kBlue; + } else { + Log( string("What color? ") + colourStr ); + exit( 99 ); + } +} + +static PlayerColor OtherPlayerColor( PlayerColor player ) +{ + return (player == kRed) ? kBlue : kRed; +} + +int main(int argc, char ** argv) +{ + srand(time(NULL)); + cin.rdbuf()->pubsetbuf(NULL, 0); + cout.rdbuf()->pubsetbuf(NULL, 0); + cout.setf(std::ios::unitbuf); +#if SAVE_OUTPUT + gOutputFile.open( (string("/tmp/peternlewis-output-") + to_string(time( NULL )) + "-" + to_string(rand() & 0xFFFF) + ".log").c_str() ); + gOutputFile.setf(std::ios::unitbuf); +#endif + + Storage storage; + Board board; + + PlayerColor player = ReadPlayerLine(); + + PositionPieces( &storage, player, &board ); + if ( player == kRed ) { + for ( int r = 0; r <= 3; r++ ) { + string line; + for ( int c = 0; c < kBoardSize; c++ ) { + line += gPieceCharacters[board[r][c].thePieceRank]; + } + Output( line ); + } + } else { + for ( int r = kBoardSize - 4; r < kBoardSize; r++ ) { + string line; + for ( int c = 0; c < kBoardSize; c++ ) { + line += gPieceCharacters[board[r][c].thePieceRank]; + } + Output( line ); + } + } + + bool expectStart = (player == kRed); + while ( 1 ) { + Debug( "LOOP" ); + if ( expectStart ) { + HelperReadLine(); + expectStart = false; + } else { + PiecePosition from; + PiecePosition to; + MoveOutcome result; + PieceRank attacker; + PieceRank defender; + HelperReadMove( &storage, from, to, result, attacker, defender ); + Debug( to_string(from.col) + "," + to_string(from.row) + " -> " + to_string(to.col) + "," + to_string(to.row) ); + MoveResult moveResult; + moveResult.rankOfAttacker.thePieceRank = attacker; + moveResult.rankOfAttacker.thePieceColor = OtherPlayerColor( player ); + moveResult.rankOfDefender.thePieceRank = defender; + moveResult.rankOfDefender.thePieceColor = player; + moveResult.attackerRemoved = (result == kMR_Dies) || (result == kMR_BothDie); + moveResult.defenderRemoved = (result == kMR_Kills) || (result == kMR_BothDie); + moveResult.victory = false; + moveResult.legalMove = true; + HandleTheirMove( &storage, from, to, (result != kMR_OK), moveResult ); + } + HelperJunkLine( kBoardSize ); + DumpBoard( &storage ); + + PiecePosition moveFrom; + PiecePosition moveTo; + + FigureOutOurMove( &storage, &moveFrom, &moveTo ); + Debug( to_string(moveFrom.col) + ',' + to_string(moveFrom.row) + " -> " + to_string(moveTo.col) + ',' + to_string(moveTo.row) ); + if ( moveFrom.row < 0 ) { + Output( "SURRENDER" ); + exit(EXIT_SUCCESS); + } + std::string dir; + int move; + if ( moveTo.col > moveFrom.col ) { + dir = "RIGHT"; + move = moveTo.col - moveFrom.col; + } else if ( moveTo.col < moveFrom.col ) { + dir = "LEFT"; + move = moveFrom.col - moveTo.col; + } else if ( moveTo.row < moveFrom.row ) { + dir = "UP"; + move = moveFrom.row - moveTo.row; + } else if ( moveTo.row > moveFrom.row ) { + dir = "DOWN"; + move = moveTo.row - moveFrom.row; + } + Output( to_string(moveFrom.col) + ' ' + to_string(moveFrom.row) + ' ' + dir + ' ' + to_string(move) ); + { + PiecePosition from; + PiecePosition to; + MoveOutcome result; + PieceRank attacker; + PieceRank defender; + HelperReadMove( &storage, from, to, result, attacker, defender ); + MoveResult moveResult; + moveResult.rankOfAttacker.thePieceRank = attacker; + moveResult.rankOfAttacker.thePieceColor = player; + moveResult.rankOfDefender.thePieceRank = defender; + moveResult.rankOfDefender.thePieceColor = OtherPlayerColor( player ); + moveResult.attackerRemoved = (result == kMR_Dies) || (result == kMR_BothDie); + moveResult.defenderRemoved = (result == kMR_Kills) || (result == kMR_BothDie); + moveResult.victory = false; + moveResult.legalMove = true; + HandleOurMove( &storage, from, to, moveResult ); + DumpBoard( &storage ); + } + } + + exit(EXIT_SUCCESS); + return 0; +} diff --git a/agents/peternlewis/peternlewis.h b/agents/peternlewis/peternlewis.h new file mode 100755 index 0000000..9b12e8c --- /dev/null +++ b/agents/peternlewis/peternlewis.h @@ -0,0 +1,71 @@ +#ifndef __LL_CHALLENGE__ +#define __LL_CHALLENGE__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define kBoardSize 10 + +typedef enum { kUnknown=0, + kMarshall=1,kGeneral,kColonel,kMajor,kCaptain, + kLieutenant,kSergeant,kMiner,kScout,kSpy, + kBomb,kFlag, + + kEmpty, + kWater, + kMoved, // fake rank for moved pieces + kAddForRankish // add this in for enemies when calculating the CRC +} PieceRank; + +#define kPieceCharacters "0123456789sBF.+MA" + +typedef enum {kNoColor, kRed, kBlue} PlayerColor; + +typedef struct PieceType { + PieceRank thePieceRank; /* rank of a piece */ + PlayerColor thePieceColor; /* color of a piece */ +} PieceType; + +typedef PieceType Board[kBoardSize][kBoardSize]; +/* Used to provide test code with board configuration. Red starts + in rows 0..3, Blue starts in rows 6..9 */ +/* Squares [4][2], [4][3], [4][6], [4][7] and + [5][2], [5][3], [5][6], [5][7] are water and cannot + be occupied */ + +typedef struct PiecePosition { + long row; /* 0..9 */ + long col; /* 0..9 */ +} PiecePosition; + +typedef struct MoveResult { + PieceType rankOfAttacker; + /* after a strike, returns identity of attacker */ + PieceType rankOfDefender; + /* after a strike, returns identity of defender */ + Boolean attackerRemoved; + /* true after a strike against a piece of equal or greater rank, + or against a bomb when the attacker is not a Miner */ + Boolean defenderRemoved; + /* true after a strike by a piece of equal or greater rank, + or against a bomb when the attacker is a Miner, + or against a Marshall by a Spy */ + Boolean victory; + /* true after a strike against the Flag */ + Boolean legalMove; + /* true unless you + - move into an occupied square, or + - move or strike in a direction other than forward, backward, or sideways, or + - move more than one square (except Scouts), or + - move a Bomb or a Flag, + - move into Water, or + - strike a square not occupied by an opponent, or + - make any other illegal move */ +} MoveResult; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/agents/peternlewis/peternlewis.o b/agents/peternlewis/peternlewis.o new file mode 100644 index 0000000..7654f1a Binary files /dev/null and b/agents/peternlewis/peternlewis.o differ diff --git a/agents/ramen/Makefile b/agents/ramen/Makefile new file mode 100644 index 0000000..06068b3 --- /dev/null +++ b/agents/ramen/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/ai.c b/agents/ramen/ai.c new file mode 100644 index 0000000..6625d24 --- /dev/null +++ b/agents/ramen/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 +#include +#include +#include +#include +#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/ai_common.h b/agents/ramen/ai_common.h new file mode 100644 index 0000000..0793659 --- /dev/null +++ b/agents/ramen/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/db.c b/agents/ramen/db.c new file mode 100644 index 0000000..a62d44a --- /dev/null +++ b/agents/ramen/db.c @@ -0,0 +1,258 @@ +/* + * UCC 2012 Programming Competition Entry + * - "Ramen" + * + * By John Hodge [TPG] + */ +#include +#include +#include "ai_common.h" +#include +#include + +#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/info b/agents/ramen/info new file mode 100644 index 0000000..43dd0fc --- /dev/null +++ b/agents/ramen/info @@ -0,0 +1,4 @@ +ramen +John Hodge +C +Fool me once, shame on you. Fool me twice, shame on me. diff --git a/agents/ramen/interface.h b/agents/ramen/interface.h new file mode 100644 index 0000000..cb7e949 --- /dev/null +++ b/agents/ramen/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/main.c b/agents/ramen/main.c new file mode 100644 index 0000000..a9a2d71 --- /dev/null +++ b/agents/ramen/main.c @@ -0,0 +1,240 @@ +/* + * UCC 2012 Programming Competition Entry + * - "Ramen" + * + * By John Hodge [TPG] + */ +#define ENABLE_DEBUG 0 +#include +#include +#include +#include +#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/judge/simulator/simulate.py b/judge/simulator/simulate.py index b055945..d8db775 100755 --- a/judge/simulator/simulate.py +++ b/judge/simulator/simulate.py @@ -51,7 +51,7 @@ if len(sys.argv) >= 5: #Score dictionary - Tuple is of the form: (end score, other score, other result) where end is the player on whose turn the result occurs, other is the other player, other result indicates what to record the outcome as for the other player. -scores = {"VICTORY":(3,1, "DEFEAT"), "DEFEAT":(1,3, "VICTORY"), "SURRENDER":(0,3, "VICTORY"), "DRAW":(2,2, "DRAW"), "DRAW_DEFAULT":(1,1, "DRAW_DEFAULT"), "ILLEGAL":(-1,2, "DEFAULT"), "DEFAULT":(2,-1, "ILLEGAL"), "BOTH_ILLEGAL":(-1,-1, "BOTH_ILLEGAL"), "INTERNAL_ERROR":(0,0, "INTERNAL_ERROR"), "BAD_SETUP":(0,0,"BAD_SETUP")} +scores = {"VICTORY":(0.3,0.1, "DEFEAT"), "DEFEAT":(0.1,0.3, "VICTORY"), "SURRENDER":(0,0.3, "VICTORY"), "DRAW":(0.2,0.2, "DRAW"), "DRAW_DEFAULT":(0.1,0.1, "DRAW_DEFAULT"), "ILLEGAL":(-0.1,0.2, "DEFAULT"), "DEFAULT":(0.2,-0.1, "ILLEGAL"), "BOTH_ILLEGAL":(-0.1,-0.1, "BOTH_ILLEGAL"), "INTERNAL_ERROR":(0,0, "INTERNAL_ERROR"), "BAD_SETUP":(0,0,"BAD_SETUP")} #Verbose - print lots of useless stuff about what you are doing (kind of like matches talking on irc...)