From 3b7e8befa3c75a9ac0aac03dc527c6637dd5fcd7 Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Sat, 28 Apr 2012 18:20:32 +0800 Subject: [PATCH] [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! --- agents/celsius/celsius.py | 539 ++++++ agents/celsius/info | 4 + agents/peternlewis/Makefile | 25 + agents/peternlewis/__MACOSX/._peternlewis | Bin 0 -> 229 bytes .../__MACOSX/peternlewis/._.DS_Store | Bin 0 -> 82 bytes .../__MACOSX/peternlewis/._Makefile | Bin 0 -> 171 bytes .../peternlewis/__MACOSX/peternlewis/._info | Bin 0 -> 229 bytes .../__MACOSX/peternlewis/._peternlewis.cpp | Bin 0 -> 167 bytes .../__MACOSX/peternlewis/._peternlewis.h | Bin 0 -> 171 bytes agents/peternlewis/info | 4 + agents/peternlewis/peternlewis | Bin 0 -> 159242 bytes agents/peternlewis/peternlewis.cpp | 1708 +++++++++++++++++ agents/peternlewis/peternlewis.h | 71 + agents/peternlewis/peternlewis.o | Bin 0 -> 268304 bytes agents/ramen/Makefile | 25 + agents/ramen/ai.c | 874 +++++++++ agents/ramen/ai_common.h | 108 ++ agents/ramen/db.c | 258 +++ agents/ramen/info | 4 + agents/ramen/interface.h | 70 + agents/ramen/main.c | 240 +++ judge/simulator/simulate.py | 2 +- 22 files changed, 3931 insertions(+), 1 deletion(-) create mode 100755 agents/celsius/celsius.py create mode 100644 agents/celsius/info create mode 100644 agents/peternlewis/Makefile create mode 100644 agents/peternlewis/__MACOSX/._peternlewis create mode 100644 agents/peternlewis/__MACOSX/peternlewis/._.DS_Store create mode 100644 agents/peternlewis/__MACOSX/peternlewis/._Makefile create mode 100644 agents/peternlewis/__MACOSX/peternlewis/._info create mode 100644 agents/peternlewis/__MACOSX/peternlewis/._peternlewis.cpp create mode 100644 agents/peternlewis/__MACOSX/peternlewis/._peternlewis.h create mode 100644 agents/peternlewis/info create mode 100755 agents/peternlewis/peternlewis create mode 100755 agents/peternlewis/peternlewis.cpp create mode 100755 agents/peternlewis/peternlewis.h create mode 100644 agents/peternlewis/peternlewis.o create mode 100644 agents/ramen/Makefile create mode 100644 agents/ramen/ai.c create mode 100644 agents/ramen/ai_common.h create mode 100644 agents/ramen/db.c create mode 100644 agents/ramen/info create mode 100644 agents/ramen/interface.h create mode 100644 agents/ramen/main.c 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 0000000000000000000000000000000000000000..633840e786bbae3b20ae2dbc8220e656a5c5d607 GIT binary patch literal 229 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@n#?f@t*=Q5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#Lccq?xChq^22K2PdW_7G+wSIa@dy qxVky%x;dH{=$e?g8R$Vqox1Ojhs@R)|o50+1L3ClDI}u>uf-_(4F-09OIxU;zLoY6T$x literal 0 HcmV?d00001 diff --git a/agents/peternlewis/__MACOSX/peternlewis/._Makefile b/agents/peternlewis/__MACOSX/peternlewis/._Makefile new file mode 100644 index 0000000000000000000000000000000000000000..6adb72ec537e12b7d90a1ae5cb0b7773f90043a8 GIT binary patch literal 171 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aU~Fg_^W}KA;dKz1VrNojv*mIP+5?U zIY7*hrkW8UE}op9tCv_%kdvwxl3G#XnwOlPl9`tdR1g~CrfXqsXl!C+Xl`j?0swGa B6-xjB literal 0 HcmV?d00001 diff --git a/agents/peternlewis/__MACOSX/peternlewis/._info b/agents/peternlewis/__MACOSX/peternlewis/._info new file mode 100644 index 0000000000000000000000000000000000000000..633840e786bbae3b20ae2dbc8220e656a5c5d607 GIT binary patch literal 229 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@n#?f@t*=Q5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#Lccq?xChq^22K2PdW_7G+wSIa@dy qxVky%x;dH{=$e?g8R$Vqox1Ojhs@R)|o50+1L3ClDI}aVZdk_{)KqA;dKz#5vs4mB9hT!v-8f zLV}>;ARTjnm>W$UBSKs}IX_n~v7jI)RWBs9qQo^XIX@*cFCC~LH!(RguOz=X!`c7< DyVn$Vqox1Ojhs@R)|o50+1L3ClDI}aU~Fg_^W}KA;dKz#5vs4mB9hT!v-8f zLV}>;ARTjnm>*3YBSKs}IX_n~v7jI)RWBs9qQo^XIX@*cFCC~LG{jBU!rIW-#K_Rx H(!vA)5&IUi literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b63237ff14e5cb54a07d72dbb26686b7f7713725 GIT binary patch literal 159242 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 z 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 +#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 0000000000000000000000000000000000000000..7654f1ac5aade64b88decebc935635d11429af8e GIT binary patch literal 268304 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�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

)snDtqg;K88cccSLl~gJzM1B= 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 literal 0 HcmV?d00001 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...) -- 2.20.1