// alpha beta with iterative deepening private static string PerformNearMove(Path shortestPath) { int depth = 0; float time = 0; float score, bestScore; GameState gs = new GameState(); Point p = new Point(); // default to something that won't kill us - server sometimes // runs out of time WAY early resulting in no time to perform alpha beta // iterations string bestMove = Map.MOVES[0]; //PerformFoolishRandomMove(); DateTime lastAlphaBeta; List<string> ties = new List<string>(); string[] moves = new string[4]; float[] scores = new float[4]; // 0 north, 1 south, 2 east, 3 west scores[0]=3; scores[1]=2; scores[2]=1; scores[3]=0; // used to adjust time estimate for next depth so we don't go over time limit float timebase = ((float)Map.Width() * (float)Map.Height()) / (15f * 15f); while(Duration() + time < (TIME_LIMIT - TIME_THRESHOLD) && depth <= 12) { score = int.MinValue; bestScore = int.MinValue; depth++; // order moves by previous iterations scores // TODO this really does nothing. Cache of game states is needed // for quick eval retrival and move ordering int length = scores.Length; bool swapped = true; moves[0] = "North"; moves[1] = "South"; moves[2] = "East"; moves[3] = "West"; while(swapped) { swapped = false; for(int b=0; b<length - 1; b++) { if (scores[b] < scores[b+1]) { string tmp = moves[b]; float ftmp = scores[b]; moves[b] = moves[b+1]; scores[b] = scores[b+1]; moves[b+1] = tmp; scores[b+1] = ftmp; swapped = true; } } length -= 1; //Console.Error.WriteLine("best:" + best + " score:" + scores[best]); } for(int i=0; i<moves.Length; i++) { string move = moves[i]; p.X = Map.MyX(); p.Y = Map.MyY(); p.MoveInDirection(move); if (!Map.IsWall(p.X, p.Y)) { // negate since starting with opponents moves lastAlphaBeta = DateTime.Now; score = -AlphaBeta(gs.ApplyMoveToMeAndCreate(move), depth, -int.MaxValue, -(-int.MaxValue), false); // estimate time for next depth TimeSpan ts = DateTime.Now - lastAlphaBeta; time = (float)ts.Milliseconds * (depth * timebase); } else { score = int.MinValue; } //Console.Error.WriteLine("alphabeta:" + move + ":" + score + " depth:" + depth); if (score > bestScore) { bestMove = move; bestScore = score; ties.Clear(); ties.Add(bestMove); } else if (score == bestScore) { ties.Add(move); } // track score string temp = move.Substring(0, 1).ToUpper(); int firstChar = (int)temp[0]; switch(firstChar) { case 'N': scores[0] = score; break; case 'S': scores[1] = score; break; case 'E': scores[2] = score; break; case 'W': scores[3] = score; break; } } depth++; } List<string> secondaryTies = new List<string>(); // break ties if (ties.Count > 1) { bestScore = int.MinValue; foreach(string move in ties) { //Console.Error.WriteLine("alpha tie break:" + move); p.X = Map.MyX(); p.Y = Map.MyY(); p.MoveInDirection(move); if (Map.IsWall(p.X, p.Y)) { continue; } Territory room = new Territory(gs.ApplyMoveToMeAndCreate(move)); room.DetermineTerritories(); score = (float)room.GetMySize() - (float)room.GetOpponentSize(); //Console.Error.WriteLine("alpha tie break:" + move + ":" + score); if (score > bestScore) { bestScore = score; bestMove = move; secondaryTies.Clear(); secondaryTies.Add(move); } else if (score == bestScore) { secondaryTies.Add(move); } } } // kinda lame, but need another tie breaker...quick and dirty if (secondaryTies.Count > 1) { bestScore = int.MinValue; foreach(string move in ties) { if (shortestPath != null) { if (move.Equals(shortestPath.direction)) { bestMove = shortestPath.direction; break; } } p.X = Map.MyX(); p.Y = Map.MyY(); p.MoveInDirection(move); if (Map.IsWall(p.X, p.Y)) { continue; } score = -GetEuclideanOpponentDistance(p.X, p.Y); //Console.Error.WriteLine("alpha tie break:" + move + ":" + score); if (score > bestScore) { bestScore = score; bestMove = move; } } } return bestMove; }
// Evaluation function for alpha-beta pruning / minimax private static float EvaluateMove(GameState gs) { if (gs.IsMyWin()) { return MAX_MAP_SIZE; } if (gs.IsOpponentWin()) { return -MAX_MAP_SIZE; } if (gs.IsDraw()) { // return 0 } Territory room = new Territory(gs); room.DetermineTerritories(); int mySize = room.GetMySize(); int opponentSize = room.GetOpponentSize(); int size = room.GetMySize() - room.GetOpponentSize(); //Console.Error.WriteLine(String.Format("my room:{0} other room:{1}",mySize,opponentSize)); return (float)size; }