using System; using System.Collections.Generic; using System.IO; using RidR.Exceptions; using RidR.Extensions; namespace RidR.Protocol { /// /// The type of the message recieved from the server. /// public enum ServerMessageType { /// /// The server Acknowledged command: /// ACK /// Ack, /// /// The server Not Acknowledged command: /// NAK /// Nak, /// /// The TEAM command is sent by the server after team registration: /// TEAM [TeamCount] [TeamNumber] /// Team, /// /// The Info command is sent by the server to inform the client of the game rules: /// INFO [RoundTime] [SightRadius] [ChargeTime] [ShootTime] [StunTime] /// Info, /// /// The Map command is sent when the server wants to send the game map to the client: /// MAP [Width] [Height] [ElementCount] /// Map, /// /// The StartPos command is sent to inform the client of all the posible spawn location: /// STARTPOS [ElementCount] /// StartPos, /// /// The False command is sent when the client requested an invalid starting position: /// FALSE /// False, /// /// The Disqualified command is sent when the client has been disqualified by the server: /// DISQUALIFIED /// Disqualified, /// /// The Start command is sent when the game begins: /// START [MoveCode] /// Start, /// /// The GameState command is sent when the server informs the client of the current game state: /// GAMESTATE [ElementCount] [MoveCode] /// GameState, /// /// The Robot command is part of the gamestate command, informing the client of a robot position: /// ROBOT [BotCode] [xPos] [yPos] [Orientation] /// Robot, /// /// The Laser command is part of the gamestate command, informing the client of a laser beam: /// LASER [TeamID] [xBegin] [xEnd] [yBegin] [yEnd] [ActiveTime] /// Laser, /// /// The Crystal command is part of a gamestate command, informing the client of a new score: /// Crystal [TeamID] [xCrystal] [yCrystal] /// Crystal, /// /// The Hit command is part of a gamestate command, informing the client that he has been hit: /// HIT [RemainingStun] /// Hit, /// /// The Win command is sent when a team has won the current game: /// WIN [WinningTeamID] /// Win, /// /// The Score command is sent after the game has ended to inform the client of the final score: /// SCORE [ScoreTeam1] [ScoreTeam2] [ScoreTeam3] [ScoreTeam4] /// Score, /// /// The GameTime command is sent after the game has ended and informs the client of the total game time: /// GAMETIME [GameTime] /// GameTime, /// /// The First to Score command is sent after the game has ended and informs the clients of the first team to score: /// FIRSTTOSCORE [TeamID] [GameTime] /// FirstToScore, /// /// The Robot stat command shows statistics on the bots: /// ROBOTSTAT [RobotCode] [MsgPerMin] [DistanceTraveled] [LasersFired] [BotsHit] [BeenHit] /// RobotStats, /// /// The command is not a RidR protocol command. /// Unknown } /// /// This class provides static methodes to help you format client messages and interpret server messages. /// public static class ClientProtocolHelper { #region Reading Server Messages /// /// Reads the message and determines the command. /// /// The message string /// The arguments passed with the message /// Returns if the message was formated correctly /// The type of the message public static ServerMessageType GetMessageType(string message, out string[] args, out bool valid) { if (!String.IsNullOrWhiteSpace(message)) { string[] split = message.Split(); args = split.RemoveAt(0); switch (split[0]) { case CommonProtocol.ACK: valid = true; return ServerMessageType.Ack; case CommonProtocol.NAK: valid = true; return ServerMessageType.Nak; case CommonProtocol.TEAM: valid = args.Length == 2; return ServerMessageType.Team; case CommonProtocol.INFO: valid = args.Length == 5; return ServerMessageType.Info; case CommonProtocol.MAP: valid = args.Length == 3; return ServerMessageType.Map; case CommonProtocol.STARTPOS: valid = args.Length == 1; return ServerMessageType.StartPos; case CommonProtocol.FALSE: valid = true; return ServerMessageType.False; case CommonProtocol.DISQUALIFIED: valid = true; return ServerMessageType.Disqualified; case CommonProtocol.START: valid = args.Length == 1; return ServerMessageType.Start; case CommonProtocol.GAMESTATE: valid = args.Length == 2; return ServerMessageType.GameState; case CommonProtocol.ROBOT: valid = args.Length == 4; return ServerMessageType.Robot; case CommonProtocol.LASER: valid = args.Length == 6; return ServerMessageType.Laser; case CommonProtocol.CRYSTAL: valid = args.Length == 3; return ServerMessageType.Crystal; case CommonProtocol.HIT: valid = args.Length == 1; return ServerMessageType.Hit; case CommonProtocol.WIN: valid = args.Length == 1; return ServerMessageType.Win; case CommonProtocol.SCORE: valid = args.Length == 4; return ServerMessageType.Score; case CommonProtocol.GAMETIME: valid = args.Length == 1; return ServerMessageType.GameTime; case CommonProtocol.FIRSTTOSCORE: valid = args.Length == 2; return ServerMessageType.FirstToScore; case CommonProtocol.ROBOTSTAT: valid = args.Length == 6; return ServerMessageType.RobotStats; } } args = new string[] { message }; valid = false; return ServerMessageType.Unknown; } /// /// Interprets the arguments of a TEAM command. /// /// Argument list for the TEAM command /// The number of teams /// The number of this team /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadTeamCommand(string[] args, out int teamCount, out int teamNumber) { try { teamCount = int.Parse(args[0]); teamNumber = int.Parse(args[1]); } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of an INFO command. /// /// Argument list for the INFO command /// Total time of this round in seconds /// The radius in which clients can see /// The amount of time it takes to charge 10 points of energy in milliseconds /// The amount of time a laser persists in milliseconds /// The amount of time a bot is stunned when hit in milliseconds /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadInfoCommand(string[] args, out int roundTime, out int sightRadius, out int chargeTime, out int shootTime, out int stunTime) { try { roundTime = int.Parse(args[0]); sightRadius = int.Parse(args[1]); chargeTime = int.Parse(args[2]); shootTime = int.Parse(args[3]); stunTime = int.Parse(args[4]); } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a MAP command. /// /// Argument list for the MAP command /// The input stream from which to read the map /// The current game map /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadMapCommand(string[] args, StreamReader inputReader, out TileType[,] map) { try { int width = int.Parse(args[0]); int height = int.Parse(args[1]); int msgCount = int.Parse(args[2]); map = new TileType[width, height]; for (int i = 0; i < msgCount; ++i) { string[] split = inputReader.ReadLine().Split(); TileType tile; switch (split[0]) { case "R": tile = TileType.Rift; break; case "W": tile = TileType.Wall; break; case "C": tile = TileType.Crystal; break; default: tile = TileType.Floor; break; } int x = int.Parse(split[1]); int y = int.Parse(split[2]); map[x, y] = tile; } } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a STARTPOS command. /// /// Argument list for the STARTPOS command /// The input stream from which to read the position list /// A list of possible start position sets /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadStartPosCommand(string[] args, StreamReader inputReader, out IList startList) { try { int count = int.Parse(args[0]); startList = new StartPositionSet[count]; for (int i = 0; i < count * 3; ++i) { string[] split; split = inputReader.ReadLine().Split(); int index = int.Parse(split[0]) - 1; Position p = new Position(int.Parse(split[1]), int.Parse(split[2])); StartPositionSet set = startList[index]; set.PositionID = index + 1; if (set.P1.IsEmpty) { set.P1 = p; } else if (set.P2.IsEmpty) { set.P2 = p; } else { set.P3 = p; } startList[index] = set; } } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a START command. /// /// Argument list for the START command /// The first move code for this bot /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadStartCommand(string[] args, out int moveCode) { try { moveCode = int.Parse(args[0]); } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a GAMESTATE command. /// /// Argument list for the GAMESTATE command /// The stream from which to read the game state /// The move code for the bot's next move /// A list of all the robots visable to the current bot /// A list of laser ranges which fall within the current bot's sight radius /// A list of new scores since the last gamestate update /// A list of hits on the current bot /// remaining timeout in milliseconds this bot has incurred due to laser hits /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadGameStateCommand(string[] args, StreamReader inputReader, out int moveCode, out IList visableRobots, out IList visableLasers, out IList scores, out int timeout) { try { visableRobots = new List(); visableLasers = new List(); scores = new List(); timeout = 0; int count = int.Parse(args[0]); moveCode = int.Parse(args[1]); for (int i = 0; i < count; ++i) { string[] split = inputReader.ReadLine().Split(); switch (split[0]) { case CommonProtocol.ROBOT: { Position p = new Position(int.Parse(split[2]), int.Parse(split[3])); Orientation o = (Orientation)int.Parse(split[4]); RobotInfo r = new RobotInfo(split[1], p, o); visableRobots.Add(r); break; } case CommonProtocol.LASER: { int id = int.Parse(split[1]); Position s = new Position(int.Parse(split[2]), int.Parse(split[4])); Position e = new Position(int.Parse(split[3]), int.Parse(split[5])); LaserRange l = new LaserRange(); l.ActiveTime = int.Parse(split[6]); l.StartPoint = s; l.EndPoint = e; visableLasers.Add(l); break; } case CommonProtocol.CRYSTAL: { ScoreEvent s = new ScoreEvent(); s.TeamId = int.Parse(split[1]); Position p = new Position(int.Parse(split[2]), int.Parse(split[3])); s.Position = p; scores.Add(s); break; } case CommonProtocol.HIT: { timeout = Math.Max(timeout, int.Parse(split[1])); break; } } } } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a WIN command. /// /// Argument list for the WIN command /// ID of the winning team /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadWinCommand(string[] args, out int winningTeamID) { try { winningTeamID = int.Parse(args[0]); } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a SCORE command. /// /// Argument list for the SCORE command /// Final score of team 1 /// Final score of team 2 /// Final score of team 3 /// Final score of team 4 /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadFinalScoreCommand(string[] args, out int scoreTeam1, out int scoreTeam2, out int scoreTeam3, out int scoreTeam4) { try { scoreTeam1 = int.Parse(args[0]); scoreTeam2 = int.Parse(args[1]); scoreTeam3 = int.Parse(args[2]); scoreTeam4 = int.Parse(args[3]); } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a GAMETIME command. /// /// Argument list for the GAMETIME command /// Amount of time this round lasted in ms public static void ReadGameTimeCommand(string[] args, out int gameTime) { try { gameTime = int.Parse(args[0]); } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a FIRSTTOSCORE command. /// /// Argument list for the FIRSTTOSCORE command /// ID of the team /// Amount of time it took to score in ms /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadFirstToScoreCommand(string[] args, out int teamID, out int gameTime) { try { teamID = int.Parse(args[0]); gameTime = int.Parse(args[1]); } catch { throw new ProtocolException(); } } /// /// Interprets the arguments of a ROBOTSTAT command. /// /// Argument list for the ROBOTSTAT command /// The robot ID /// Messges per minute /// Distance traveled by this bot /// The amount of lasers fired by this bot /// Amount of bots hit by this bot's lasers /// Amount of times this bot was hit by a laser /// Thrown when the args parameter does not conform to the RidR protocol. public static void ReadRobotStatCommand(string[] args, out int robotCode, out float msgPerMin, out int distanceTraveled, out int lasersFired, out int botsHit, out int beenHit) { try { robotCode = int.Parse(args[0]); msgPerMin = float.Parse(args[1]); distanceTraveled = int.Parse(args[2]); lasersFired = int.Parse(args[3]); botsHit = int.Parse(args[4]); beenHit = int.Parse(args[5]); } catch { throw new ProtocolException(); } } #endregion #region Format Client Messages /// /// Formats a PLAY command. /// /// The team name /// The team role public static string FormatPlayCommand(string TeamName, TeamRole role) { return CommonProtocol.PLAY + " " + TeamName + " " + (int)role; } /// /// Formats a STARTPOS command. /// /// Argument list for the Start Position command. /// The ID of the start position set /// The position /// The desired starting orientation public static string FormatStartPosCommand(int PositionID, Position position, Orientation orientation) { return CommonProtocol.STARTPOS + " " + PositionID + " " + position.ToString() + " " + (int)orientation; } /// /// Formats a MOVE command. /// /// The desired move /// The move code public static string FormatMoveCommand(Move move, int moveCode) { return CommonProtocol.MOVE + " " + (int)move + " " + moveCode; } /// /// Formats a SHOOT command. /// /// The desired range /// The move code public static string FormatShootCommand(int range, int moveCode) { return CommonProtocol.SHOOT + " " + range + " " + moveCode; } #endregion } }