static void Main(string[] args) { Console.Clear(); Console.WriteLine("Battle City AI, Copyright (c) 1985 U.S. Robots and Mechanical Men, Inc."); #if DEBUG Console.WriteLine("Mode: DEBUG"); #else Console.WriteLine("Mode: RELEASE"); #endif Console.WriteLine(Environment.NewLine + "Welcome, scalvin."); string botname = "ctf"; Console.BufferHeight = 4096; Console.WindowWidth = 132; Console.WindowHeight = 40; Debug.Listeners.Add(new TextWriterTraceListener(System.Console.Out)); var endpoint = "http://localhost:7070/Challenge/ChallengeService"; if (args.Length > 0) { endpoint = args[0]; } if (args.Length > 1) { botname = args[1].ToLower(); } System.ServiceModel.BasicHttpBinding binding = new System.ServiceModel.BasicHttpBinding(); binding.MaxReceivedMessageSize = Settings.MAX_SOAP_MSG_SIZE; System.ServiceModel.EndpointAddress remoteAddress = new System.ServiceModel.EndpointAddress(endpoint); client = new ChallengeService.ChallengeClient(binding, remoteAddress); try { ChallengeService.board result = client.login(); board = new Board(result.states); board.endGamePoint = result.endGamePoint; // Set up the clock, and add all the tasks that should execute each cycle. clock = new Clock(Settings.SYNC_TICK, Settings.SYNC_DELTA_STEP_LO); // At the start of each cycle, process all events and prune the game trees. clock.AddTask(0, handleNewTick); // Some time through the cycle, post a preliminary move as backup. clock.AddTask(Settings.SCHEDULE_EARLY_MOVE, postEarlyMove); // Just before the end of the cycle, post the final best move found. clock.AddTask(Settings.SCHEDULE_FINAL_MOVE, postFinalMove); // In the first tick, the server status is read, and the clock is started based // on the reported millisecondsToNextTick. ChallengeService.game status = client.getStatus(); board.Update(status); Console.Title = board.playerName; Debug.Listeners.Add(new TextWriterTraceListener(board.playerName + ".log")); switch (botname) { case "random": bot = new AI_Random(board, client); break; case "aggro": bot = new AI_Aggro(board, client); break; case "ctf": bot = new AI_CTF(board, client); break; default: bot = new AI_Random(board, client); break; } Console.WriteLine("Launching AI '{0}'" + Environment.NewLine, botname); clock.Start(status.millisecondsToNextTick + Settings.SYNC_INITIAL_DELAY); Debug.Flush(); } catch (Exception e) { Debug.WriteLine(e); } }
public void Update(ChallengeService.game status) { if (playerName == null) // First time update is called { playerName = status.playerName; if (status.players[0].name == status.playerName) { playerID = 0; } else if (status.players[1].name == status.playerName) { playerID = 1; } else { throw new ArgumentException("Player '{0}' not found in player list.", status.playerName); } playerBase.x = status.players[playerID][email protected]; playerBase.y = status.players[playerID][email protected]; opponentBase.x = status.players[opponentID][email protected]; opponentBase.y = status.players[opponentID][email protected]; if ((status.players[playerID].bullets == null) && (status.players[opponentID].bullets == null)) { Debug.WriteLine("No bullets in play yet."); } else { Debug.WriteLine("WARNING: bullets already in play!"); } int i = 0; Debug.WriteLine(""); Debug.WriteLine("Player base is at ({0},{1}).", playerBase.x, playerBase.y); Debug.WriteLine("Opponent base is at ({0},{1}).", opponentBase.x, opponentBase.y); foreach (ChallengeService.unit u in status.players[playerID].units) { playerTank[i++] = new Tank(u.x, u.y, u.direction, u.id); Debug.WriteLine("Player tank ID {0} starts at ({1},{2}), facing {3}.", u.id, u.x, u.y, u.direction); } i = 0; foreach (ChallengeService.unit u in status.players[opponentID].units) { opponentTank[i++] = new Tank(u.x, u.y, u.direction, u.id); Debug.WriteLine("Opponent tank ID {0} starts at ({1},{2}), facing {3}.", u.id, u.x, u.y, u.direction); } } // Update tank positions foreach (Tank t in playerTank) { bool wasDestroyed = t.destroyed; t.destroyed = true; ChallengeService.unit[] serverUnit = status.players[playerID].units; if ((serverUnit.Length > 0) && (serverUnit[0] != null) && (serverUnit[0].id == t.id)) { t.x = serverUnit[0].x; t.y = serverUnit[0].y; t.direction = serverUnit[0].direction; t.destroyed = false; } else if ((serverUnit.Length > 1) && (serverUnit[1] != null) && (serverUnit[1].id == t.id)) { t.x = serverUnit[1].x; t.y = serverUnit[1].y; t.direction = serverUnit[1].direction; t.destroyed = false; } else { if (!wasDestroyed) { Debug.WriteLine("Player tank (id={0}) destroyed!", t.id); } } } foreach (Tank t in opponentTank) { bool wasDestroyed = t.destroyed; t.destroyed = true; ChallengeService.unit[] serverUnit = status.players[opponentID].units; if ((serverUnit.Length > 0) && (serverUnit[0] != null) && (serverUnit[0].id == t.id)) { t.x = serverUnit[0].x; t.y = serverUnit[0].y; t.direction = serverUnit[0].direction; t.destroyed = false; } else if ((serverUnit.Length > 1) && (serverUnit[1] != null) && (serverUnit[1].id == t.id)) { t.x = serverUnit[1].x; t.y = serverUnit[1].y; t.direction = serverUnit[1].direction; t.destroyed = false; } else { if (!wasDestroyed) { Debug.WriteLine("Opponent tank (id={0}) destroyed!", t.id); } } } // Update bullet positions, and capture new ones. foreach (KeyValuePair <int, Bullet> bullet in playerBullet) { // For each bullet that we're tracking, clear its updated status. If it doesn't get updated // in the latest status report, we know that it's been destroyed. bullet.Value.updated = false; } if (status.players[playerID].bullets != null) { foreach (ChallengeService.bullet b in status.players[playerID].bullets) { Bullet myBullet; if (playerBullet.TryGetValue(b.id, out myBullet)) { // This is a bullet we know. Update its position. myBullet.x = b.x; myBullet.y = b.y; if (myBullet.direction != b.direction) { Debug.WriteLine("ERROR: Player bullet #{0} changed direction from {1} to {2}!", myBullet.direction, b.direction); } myBullet.updated = true; } else { // This is a bullet that has just been fired. Create a new entry, and find out who its daddy is. Bullet newBullet = new Bullet(b.x, b.y, b.direction, b.id); int ownerX, ownerY; switch (b.direction) { case ChallengeService.direction.DOWN: ownerX = b.x; ownerY = b.y - 3; break; case ChallengeService.direction.LEFT: ownerX = b.x + 3; ownerY = b.y; break; case ChallengeService.direction.RIGHT: ownerX = b.x - 3; ownerY = b.y; break; case ChallengeService.direction.UP: ownerX = b.x; ownerY = b.y + 3; break; default: ownerX = b.x; ownerY = b.y; Debug.WriteLine("ERROR: Player bullet #{0} created without firing direction.", b.direction); break; } if (!playerTank[0].destroyed && (playerTank[0].x == ownerX) && (playerTank[0].y == ownerY)) { newBullet.owner = playerTank[0]; playerTank[0].bullet = newBullet; } else if (!playerTank[1].destroyed && (playerTank[1].x == ownerX) && (playerTank[1].y == ownerY)) { newBullet.owner = playerTank[1]; playerTank[1].bullet = newBullet; } else if ((Math.Abs(playerTank[0].x - ownerX) <= 2) && (Math.Abs(playerTank[0].y - ownerY) <= 2)) { Debug.WriteLine("WARNING: Player bullet #{0} created too far from closest active tank; taking the best guess.", b.id); newBullet.owner = playerTank[0]; playerTank[0].bullet = newBullet; } else if ((Math.Abs(playerTank[1].x - ownerX) <= 2) && (Math.Abs(playerTank[1].y - ownerY) <= 2)) { Debug.WriteLine("WARNING: Player bullet #{0} created too far from closest active tank; taking the best guess.", b.id); newBullet.owner = playerTank[1]; playerTank[1].bullet = newBullet; } // REFACTOR: We're actually violating DRY here, since we're keeping two separate lists of bullets // (one inside the player/opponent bullet lists, another inside the tank objects themselves. It // could perhaps be cleaner just to store the bullet objects in the tank object. playerBullet[b.id] = newBullet; } } } List <int> destroyedBullets = new List <int>(); foreach (KeyValuePair <int, Bullet> bullet in playerBullet) { if (bullet.Value.updated == false) { // This bullet has not been updated this round, so it must have been destroyed. // Remove the tank's reference to it. if (bullet.Value.owner != null) { bullet.Value.owner.bullet = null; } // We can't remove it from the dictionary inside the loop, so take note of the destroyed bullets. destroyedBullets.Add(bullet.Key); } } foreach (int key in destroyedBullets) { playerBullet.Remove(key); } foreach (KeyValuePair <int, Bullet> bullet in opponentBullet) { // For each bullet that we're tracking, clear its updated status. If it doesn't get updated // in the latest status report, we know that it's been destroyed. bullet.Value.updated = false; } if (status.players[opponentID].bullets != null) { foreach (ChallengeService.bullet b in status.players[opponentID].bullets) { Bullet myBullet; if (opponentBullet.TryGetValue(b.id, out myBullet)) { // This is a bullet we know. Update its position. myBullet.x = b.x; myBullet.y = b.y; if (myBullet.direction != b.direction) { Debug.WriteLine("ERROR: Opponent bullet #{0} changed direction from {1} to {2}!", myBullet.direction, b.direction); } myBullet.updated = true; } else { // This is a bullet that has just been fired. Create a new entry, and find out who its daddy is. Bullet newBullet = new Bullet(b.x, b.y, b.direction, b.id); int ownerX, ownerY; switch (b.direction) { case ChallengeService.direction.DOWN: ownerX = b.x; ownerY = b.y - 3; break; case ChallengeService.direction.LEFT: ownerX = b.x + 3; ownerY = b.y; break; case ChallengeService.direction.RIGHT: ownerX = b.x - 3; ownerY = b.y; break; case ChallengeService.direction.UP: ownerX = b.x; ownerY = b.y + 3; break; default: ownerX = b.x; ownerY = b.y; Debug.WriteLine("ERROR: Opponent bullet #{0} created without firing direction.", b.direction); break; } if (!opponentTank[0].destroyed && (opponentTank[0].x == ownerX) && (opponentTank[0].y == ownerY)) { newBullet.owner = opponentTank[0]; opponentTank[0].bullet = newBullet; } else if (!opponentTank[1].destroyed && (opponentTank[1].x == ownerX) && (opponentTank[1].y == ownerY)) { newBullet.owner = opponentTank[1]; opponentTank[1].bullet = newBullet; } else if ((Math.Abs(opponentTank[0].x - ownerX) <= 2) && (Math.Abs(opponentTank[0].y - ownerY) <= 2)) { Debug.WriteLine("WARNING: Opponent bullet #{0} created too far from closest active tank; taking the best guess.", b.id); newBullet.owner = opponentTank[0]; opponentTank[0].bullet = newBullet; } else if ((Math.Abs(opponentTank[1].x - ownerX) <= 2) && (Math.Abs(opponentTank[1].y - ownerY) <= 2)) { Debug.WriteLine("WARNING: Opponent bullet #{0} created too far from closest active tank; taking the best guess.", b.id); newBullet.owner = opponentTank[1]; opponentTank[1].bullet = newBullet; } opponentBullet[b.id] = newBullet; } } } destroyedBullets = new List <int>(); foreach (KeyValuePair <int, Bullet> bullet in opponentBullet) { if (bullet.Value.updated == false) { // This bullet has not been updated this round, so it must have been destroyed. // Remove the tank's reference to it. if (bullet.Value.owner != null) { bullet.Value.owner.bullet = null; } // We can't remove it from the dictionary inside the loop, so take note of the destroyed bullets. destroyedBullets.Add(bullet.Key); } } foreach (int key in destroyedBullets) { opponentBullet.Remove(key); } if (status.events == null) { // Debug.WriteLine("No events."); return; } else { // Debug.WriteLine(state.events.ToString()); } if (status.events.blockEvents != null) { foreach (ChallengeService.blockEvent e in status.events.blockEvents) { board[e.point.x][e.point.y] = e.newState; Debug.WriteLine("Block at ({0},{1}) changed to {2}.", e.point.x, e.point.y, e.newState); } } if (status.events.unitEvents != null) { foreach (ChallengeService.unitEvent e in status.events.unitEvents) { // These don't seem to do anything, so just ignore it. Debug.WriteLine("WARNING: UNIT EVENT: {0}", e.unit); } } }