public void Write(Stream stream)
        {
            // Game length
            stream.WriteInt(LengthInSeconds);

            // Game version, 8 bytes, ASCII
            stream.WriteString(GameVersion, 8, Encoding.ASCII);

            // Date and time, 8 bytes
            stream.WriteLong(DateAndTime.ToBinary());
            // SawCompletion, 1 byte
            stream.WriteBool(SawCompletion);
            // Number of players, 1 byte
            stream.WriteByte(Convert.ToByte(GetPlayerCount()));
            // Average FPS, 4 bytes
            stream.WriteInt(AverageFPS);
            // Map name, 128 bytes (64 chars), Unicode
            stream.WriteString(MapName, 128);
            // Game mode, 64 bytes (32 chars), Unicode
            stream.WriteString(GameMode, 64);
            // Unique game ID, 4 bytes
            stream.WriteInt(GameID);
            // Whether game options were valid for earning a star, 1 byte
            stream.WriteBool(IsValidForStar);

            // Write player info
            for (int i = 0; i < GetPlayerCount(); i++)
            {
                PlayerStatistics ps = GetPlayer(i);
                ps.Write(stream);
            }
        }
        public void AddPlayer(string name, bool isLocal, bool isAI, bool isSpectator,
                              int side, int team, int color, int aiLevel)
        {
            PlayerStatistics ps = new PlayerStatistics(name, isLocal, isAI, isSpectator,
                                                       side, team, color, aiLevel);

            Players.Add(ps);
        }
 public void AddPlayer(PlayerStatistics ps)
 {
     Players.Add(ps);
 }
        public int GetSkirmishRankForDefaultMap(string mapName, int requiredPlayerCount)
        {
            List <MatchStatistics> matches = new List <MatchStatistics>();

            // Filter out unfitting games
            foreach (MatchStatistics ms in Statistics)
            {
                if (ms.SawCompletion &&
                    ms.MapName == mapName &&
                    ms.Players.Count == requiredPlayerCount)
                {
                    matches.Add(ms);
                }
            }

            int rank = -1;

            foreach (MatchStatistics ms in matches)
            {
                // TODO This code turned out pretty ugly, should design it better

                PlayerStatistics localPlayer = ms.Players.Find(p => p.IsLocalPlayer);

                if (localPlayer == null || !localPlayer.Won)
                {
                    continue;
                }

                int[] teamMemberCounts   = new int[5];
                int   lowestEnemyAILevel = 2;
                int   highestAllyAILevel = 0;

                teamMemberCounts[localPlayer.Team]++;

                bool allowContinue = true;

                for (int i = 0; i < ms.Players.Count; i++)
                {
                    PlayerStatistics ps = ms.GetPlayer(i);

                    if (ps.IsLocalPlayer)
                    {
                        continue;
                    }

                    if (!ps.IsAI)
                    {
                        // We're looking for Skirmish games, so skip all matches
                        // that have more than 1 human player
                        allowContinue = false;
                        break;
                    }

                    teamMemberCounts[ps.Team]++;

                    if (ps.Team > 0 && ps.Team == localPlayer.Team)
                    {
                        if (ps.AILevel > highestAllyAILevel)
                        {
                            highestAllyAILevel = ps.AILevel;
                        }
                    }
                    else
                    {
                        if (ps.AILevel < lowestEnemyAILevel)
                        {
                            lowestEnemyAILevel = ps.AILevel;
                        }
                    }
                }

                if (!allowContinue)
                {
                    continue;
                }

                if (lowestEnemyAILevel < highestAllyAILevel)
                {
                    // Check that the player's AI allies weren't stronger
                    continue;
                }

                if (localPlayer.Team > 0)
                {
                    // Check that all teams had an equal number of players

                    int allyCount = teamMemberCounts[localPlayer.Team];
                    int lowestEnemyTeamMemberCount = Int32.MaxValue;

                    for (int i = 1; i < 5; i++)
                    {
                        if (teamMemberCounts[i] > 0 && i != localPlayer.Team)
                        {
                            if (teamMemberCounts[i] < lowestEnemyTeamMemberCount)
                            {
                                lowestEnemyTeamMemberCount = teamMemberCounts[i];
                            }
                        }
                    }

                    if (lowestEnemyTeamMemberCount == Int32.MaxValue || lowestEnemyTeamMemberCount < allyCount)
                    {
                        // The human player either had more allies than one of
                        // the enemy teams or the enemies weren't allied at all
                        continue;
                    }
                }

                if (rank < lowestEnemyAILevel)
                {
                    rank = lowestEnemyAILevel;

                    if (rank == 2)
                    {
                        return(rank); // Best possible rank
                    }
                }
            }

            return(rank);
        }
        public bool HasWonMapInPvP(string mapName, string gameMode, int requiredPlayerCount)
        {
            List <MatchStatistics> matches = new List <MatchStatistics>();

            foreach (MatchStatistics ms in Statistics)
            {
                if (!ms.SawCompletion)
                {
                    continue;
                }

                if (ms.MapName != mapName)
                {
                    continue;
                }

                if (ms.GameMode != gameMode)
                {
                    continue;
                }

                if (ms.Players.Count(ps => !ps.WasSpectator) != requiredPlayerCount)
                {
                    continue;
                }

                if (ms.Players.Find(ps => ps.IsAI) != null)
                {
                    continue;
                }

                PlayerStatistics localPlayer = ms.Players.Find(p => p.IsLocalPlayer);

                if (localPlayer == null)
                {
                    continue;
                }

                if (localPlayer.WasSpectator)
                {
                    continue;
                }

                int[] teamMemberCounts = new int[5];

                ms.Players.FindAll(ps => !ps.WasSpectator).ForEach(ps => teamMemberCounts[ps.Team]++);

                if (localPlayer.Team > 0)
                {
                    int lowestEnemyTeamMemberCount = int.MaxValue;

                    for (int i = 1; i < 5; i++)
                    {
                        if (i != localPlayer.Team && teamMemberCounts[i] > 0)
                        {
                            if (teamMemberCounts[i] < lowestEnemyTeamMemberCount)
                            {
                                lowestEnemyTeamMemberCount = teamMemberCounts[i];
                            }
                        }
                    }

                    if (lowestEnemyTeamMemberCount > teamMemberCounts[localPlayer.Team])
                    {
                        continue;
                    }

                    return(true);
                }

                if (ms.Players.Count(ps => !ps.WasSpectator) > 1)
                {
                    return(true);
                }
            }

            return(false);
        }
        int GetRankForCoopMatch(MatchStatistics ms)
        {
            PlayerStatistics localPlayer = ms.Players.Find(p => p.IsLocalPlayer);

            if (localPlayer == null || !localPlayer.Won)
            {
                return(-1);
            }

            if (ms.Players.Find(p => p.WasSpectator) != null)
            {
                return(-1); // Don't allow matches with spectators
            }
            if (ms.Players.Count(p => !p.IsAI && p.Team != localPlayer.Team) > 0)
            {
                return(-1); // Don't allow matches with human players who were on a different team
            }
            if (ms.Players.Find(p => p.Team == 0) != null)
            {
                return(-1); // Matches with non-allied players are discarded
            }
            if (ms.Players.All(ps => ps.Team == localPlayer.Team))
            {
                return(-1); // Discard matches that had no enemies
            }
            int[] teamMemberCounts   = new int[5];
            int   lowestEnemyAILevel = 2;
            int   highestAllyAILevel = 0;

            for (int i = 0; i < ms.Players.Count; i++)
            {
                PlayerStatistics ps = ms.GetPlayer(i);

                teamMemberCounts[ps.Team]++;

                if (!ps.IsAI)
                {
                    continue;
                }

                if (ps.Team > 0 && ps.Team == localPlayer.Team)
                {
                    if (ps.AILevel > highestAllyAILevel)
                    {
                        highestAllyAILevel = ps.AILevel;
                    }
                }
                else
                {
                    if (ps.AILevel < lowestEnemyAILevel)
                    {
                        lowestEnemyAILevel = ps.AILevel;
                    }
                }
            }

            if (lowestEnemyAILevel < highestAllyAILevel)
            {
                // Check that the player's AI allies weren't stronger
                return(-1);
            }

            // Check that all teams had at least as many players
            // as the local player's team
            int allyCount = teamMemberCounts[localPlayer.Team];

            for (int i = 1; i < 5; i++)
            {
                if (i == localPlayer.Team)
                {
                    continue;
                }

                if (teamMemberCounts[i] > 0)
                {
                    if (teamMemberCounts[i] < allyCount)
                    {
                        return(-1);
                    }
                }
            }

            return(lowestEnemyAILevel);
        }
        public void AddMatchAndSaveDatabase(bool addMatch, MatchStatistics ms)
        {
            if (ms.LengthInSeconds < 60)
            {
                Logger.Log("Skipping adding match to statistics because the game was cancelled.");
                return;
            }

            if (addMatch)
            {
                Statistics.Add(ms);
                GameAdded?.Invoke(this, EventArgs.Empty);
            }

            if (!File.Exists(gamePath + SCORE_FILE_PATH))
            {
                CreateDummyFile();
            }

            Logger.Log("Writing game info to statistics file.");

            using (FileStream fs = File.Open(gamePath + SCORE_FILE_PATH, FileMode.Open, FileAccess.ReadWrite))
            {
                fs.Position = 4; // First 4 bytes after the version mean the amount of games
                byte[] writeBuffer = BitConverter.GetBytes(Statistics.Count);
                fs.Write(writeBuffer, 0, 4);

                fs.Position = fs.Length;

                // Game length
                writeBuffer = BitConverter.GetBytes(ms.LengthInSeconds);
                fs.Write(writeBuffer, 0, 4);
                // Game version, 8 bytes, ASCII
                writeBuffer = Encoding.ASCII.GetBytes(ms.GameVersion);
                if (writeBuffer.Length != 8)
                {
                    // If the game version's byte representation is not 8 bytes,
                    // let's resize the array
                    byte[] temp = writeBuffer;
                    writeBuffer = new byte[8];
                    for (int i = 0; i < temp.Length && i < writeBuffer.Length; i++)
                    {
                        writeBuffer[i] = temp[i];
                    }
                }
                fs.Write(writeBuffer, 0, 8);
                // Date and time, 8 bytes
                writeBuffer = BitConverter.GetBytes(ms.DateAndTime.ToBinary());
                fs.Write(writeBuffer, 0, 8);
                // SawCompletion, 1 byte
                fs.WriteByte(Convert.ToByte(ms.SawCompletion));
                // Number of players, 1 byte
                fs.WriteByte(Convert.ToByte(ms.GetPlayerCount()));
                // Average FPS, 4 bytes
                fs.Write(BitConverter.GetBytes(ms.AverageFPS), 0, 4);
                // Map name, 128 bytes (64 chars), Unicode
                writeBuffer = Encoding.Unicode.GetBytes(ms.MapName);
                if (writeBuffer.Length != 128)
                {
                    // If the map name's byte representation is shorter than 128 bytes,
                    // let's resize the array
                    byte[] temp = writeBuffer;
                    writeBuffer = new byte[128];
                    if (temp.Length < 129)
                    {
                        for (int i = 0; i < temp.Length; i++)
                        {
                            writeBuffer[i] = temp[i];
                        }
                    }
                    else
                    {
                        for (int i = 0; i < 128; i++)
                        {
                            writeBuffer[i] = temp[i];
                        }
                    }
                }
                fs.Write(writeBuffer, 0, 128);

                // Game mode, 64 bytes (32 chars), Unicode
                writeBuffer = Encoding.Unicode.GetBytes(ms.GameMode);
                if (writeBuffer.Length != 64)
                {
                    // If the game mode's byte representation is shorter than 64 bytes,
                    // let's resize the array
                    byte[] temp = writeBuffer;
                    writeBuffer = new byte[64];
                    for (int i = 0; i < temp.Length; i++)
                    {
                        writeBuffer[i] = temp[i];
                    }
                }
                fs.Write(writeBuffer, 0, 64);

                // Unique game ID, 4 bytes
                fs.Write(BitConverter.GetBytes(ms.GameID), 0, 4);

                // Write player info
                for (int i = 0; i < ms.GetPlayerCount(); i++)
                {
                    PlayerStatistics ps = ms.GetPlayer(i);
                    // 1 byte for economy
                    fs.WriteByte(Convert.ToByte(ps.Economy));
                    // 1 byte for IsAI
                    fs.WriteByte(Convert.ToByte(ps.IsAI));
                    // 1 byte for IsLocalPlayer
                    fs.WriteByte(Convert.ToByte(ps.IsLocalPlayer));
                    // 4 bytes for kills
                    fs.Write(BitConverter.GetBytes(ps.Kills), 0, 4);
                    // 4 bytes for losses
                    fs.Write(BitConverter.GetBytes(ps.Losses), 0, 4);
                    // Name takes 32 bytes
                    writeBuffer = Encoding.Unicode.GetBytes(ps.Name);
                    if (writeBuffer.Length != 32)
                    {
                        // If the name's byte presentation is shorter than 32 bytes,
                        // let's resize the array
                        byte[] temp = writeBuffer;
                        writeBuffer = new byte[32];
                        for (int j = 0; j < temp.Length; j++)
                        {
                            writeBuffer[j] = temp[j];
                        }
                    }
                    fs.Write(writeBuffer, 0, 32);
                    // 1 byte for SawEnd
                    fs.WriteByte(Convert.ToByte(ps.SawEnd));
                    // 4 bytes for Score
                    fs.Write(BitConverter.GetBytes(ps.Score), 0, 4);
                    // 1 byte for Side
                    fs.WriteByte(Convert.ToByte(ps.Side));
                    // 1 byte for Team
                    fs.WriteByte(Convert.ToByte(ps.Team));
                    // 1 byte color Color
                    fs.WriteByte(Convert.ToByte(ps.Color));
                    // 1 byte for WasSpectator
                    fs.WriteByte(Convert.ToByte(ps.WasSpectator));
                    // 1 byte for Won
                    fs.WriteByte(Convert.ToByte(ps.Won));
                    // 1 byte for AI level
                    fs.WriteByte(Convert.ToByte(ps.AILevel));
                }
            }

            Logger.Log("Finished writing statistics.");
        }
        private void ReadDatabase(string filePath, double versionDouble)
        {
            try
            {
                using (FileStream fs = File.OpenRead(filePath))
                {
                    fs.Position = 4;           // Skip version
                    byte[] readBuffer = new byte[128];
                    fs.Read(readBuffer, 0, 4); // First 4 bytes following the version mean the amount of games
                    int gameCount = BitConverter.ToInt32(readBuffer, 0);

                    for (int i = 0; i < gameCount; i++)
                    {
                        MatchStatistics ms = new MatchStatistics();

                        // First 4 bytes of game info is the length in seconds
                        fs.Read(readBuffer, 0, 4);
                        int lengthInSeconds = BitConverter.ToInt32(readBuffer, 0);
                        ms.LengthInSeconds = lengthInSeconds;
                        // Next 8 are the game version
                        fs.Read(readBuffer, 0, 8);
                        ms.GameVersion = System.Text.Encoding.ASCII.GetString(readBuffer, 0, 8);
                        // Then comes the date and time, also 8 bytes
                        fs.Read(readBuffer, 0, 8);
                        long dateData = BitConverter.ToInt64(readBuffer, 0);
                        ms.DateAndTime = DateTime.FromBinary(dateData);
                        // Then one byte for SawCompletion
                        fs.Read(readBuffer, 0, 1);
                        ms.SawCompletion = Convert.ToBoolean(readBuffer[0]);
                        // Then 1 byte for the amount of players
                        fs.Read(readBuffer, 0, 1);
                        int playerCount = readBuffer[0];
                        if (versionDouble > 1.01)
                        {
                            // 4 bytes for average FPS
                            fs.Read(readBuffer, 0, 4);
                            ms.AverageFPS = BitConverter.ToInt32(readBuffer, 0);
                        }

                        int mapNameLength = 64;

                        if (versionDouble > 1.03)
                        {
                            mapNameLength = 128;
                        }

                        // Map name, 64 or 128 bytes of Unicode depending on version
                        fs.Read(readBuffer, 0, mapNameLength);
                        ms.MapName = Encoding.Unicode.GetString(readBuffer).Replace("\0", "");

                        // Game mode, 64 bytes
                        fs.Read(readBuffer, 0, 64);
                        ms.GameMode = Encoding.Unicode.GetString(readBuffer, 0, 64).Replace("\0", "");

                        if (versionDouble > 1.02)
                        {
                            // Unique game ID, 32 bytes (int32)
                            fs.Read(readBuffer, 0, 4);
                            ms.GameID = BitConverter.ToInt32(readBuffer, 0);
                        }

                        // Player info comes right after the general match info
                        for (int j = 0; j < playerCount; j++)
                        {
                            PlayerStatistics ps = new PlayerStatistics();

                            // Economy is between 0 and 100, so it takes only one byte
                            fs.Read(readBuffer, 0, 1);
                            ps.Economy = readBuffer[0];
                            // IsAI is a bool, so obviously one byte
                            fs.Read(readBuffer, 0, 1);
                            ps.IsAI = Convert.ToBoolean(readBuffer[0]);
                            // IsLocalPlayer is also a bool
                            fs.Read(readBuffer, 0, 1);
                            ps.IsLocalPlayer = Convert.ToBoolean(readBuffer[0]);
                            // Kills take 4 bytes
                            fs.Read(readBuffer, 0, 4);
                            ps.Kills = BitConverter.ToInt32(readBuffer, 0);
                            // Losses also take 4 bytes
                            fs.Read(readBuffer, 0, 4);
                            ps.Losses = BitConverter.ToInt32(readBuffer, 0);
                            // 32 bytes for the name
                            fs.Read(readBuffer, 0, 32);
                            ps.Name = System.Text.Encoding.Unicode.GetString(readBuffer, 0, 32);
                            ps.Name = ps.Name.Replace("\0", String.Empty);
                            // 1 byte for SawEnd
                            fs.Read(readBuffer, 0, 1);
                            ps.SawEnd = Convert.ToBoolean(readBuffer[0]);
                            // 4 bytes for Score
                            fs.Read(readBuffer, 0, 4);
                            ps.Score = BitConverter.ToInt32(readBuffer, 0);
                            // 1 byte for Side
                            fs.Read(readBuffer, 0, 1);
                            ps.Side = readBuffer[0];
                            // 1 byte for Team
                            fs.Read(readBuffer, 0, 1);
                            ps.Team = readBuffer[0];
                            if (versionDouble > 1.02)
                            {
                                // 1 byte for Color
                                fs.Read(readBuffer, 0, 1);
                                ps.Color = readBuffer[0];
                            }
                            // 1 byte for WasSpectator
                            fs.Read(readBuffer, 0, 1);
                            ps.WasSpectator = Convert.ToBoolean(readBuffer[0]);
                            // 1 byte for Won
                            fs.Read(readBuffer, 0, 1);
                            ps.Won = Convert.ToBoolean(readBuffer[0]);
                            // 1 byte for AI level
                            fs.Read(readBuffer, 0, 1);
                            ps.AILevel = readBuffer[0];

                            ms.AddPlayer(ps);
                        }

                        if (ms.Players.Find(p => p.IsLocalPlayer && !p.IsAI) == null)
                        {
                            continue;
                        }

                        Statistics.Add(ms);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log("Adding match to statistics failed! Message: " + ex.Message);
            }
        }
        public int GetSkirmishRankForDefaultMap(string mapName, int requiredPlayerCount)
        {
            List <MatchStatistics> matches = new List <MatchStatistics>();

            // Filter out unfitting games
            foreach (MatchStatistics ms in Statistics)
            {
                if (ms.SawCompletion &&
                    ms.IsValidForStar &&
                    ms.MapName == mapName &&
                    ms.Players.Count == requiredPlayerCount &&
                    ms.Players.Count(p => !p.IsAI) == 1)
                {
                    matches.Add(ms);
                }
            }

            int rank = -1;

            foreach (MatchStatistics ms in matches)
            {
                // TODO This code turned out pretty ugly, should design it better

                PlayerStatistics localPlayer = ms.Players.Find(p => p.IsLocalPlayer);

                if (localPlayer == null || !localPlayer.Won)
                {
                    continue;
                }

                int[] teamMemberCounts   = new int[5];
                int   lowestEnemyAILevel = 2;
                int   highestAllyAILevel = 0;

                for (int i = 0; i < ms.Players.Count; i++)
                {
                    PlayerStatistics ps = ms.GetPlayer(i);

                    teamMemberCounts[ps.Team]++;

                    if (ps.IsLocalPlayer)
                    {
                        continue;
                    }

                    if (ps.Team > 0 && ps.Team == localPlayer.Team)
                    {
                        if (ps.AILevel > highestAllyAILevel)
                        {
                            highestAllyAILevel = ps.AILevel;
                        }
                    }
                    else
                    {
                        if (ps.AILevel < lowestEnemyAILevel)
                        {
                            lowestEnemyAILevel = ps.AILevel;
                        }
                    }
                }

                if (lowestEnemyAILevel < highestAllyAILevel)
                {
                    // Check that the player's AI allies weren't stronger
                    continue;
                }

                if (localPlayer.Team > 0)
                {
                    // Check that all teams had at least as many players as the human player's team

                    int  allyCount = teamMemberCounts[localPlayer.Team];
                    bool pass      = true;

                    for (int i = 1; i < 5; i++)
                    {
                        if (i == localPlayer.Team)
                        {
                            continue;
                        }

                        if (teamMemberCounts[i] > 0)
                        {
                            if (teamMemberCounts[i] < allyCount)
                            {
                                // The enemy team has fewer players than the player's team
                                pass = false;
                                break;
                            }
                        }
                    }

                    if (!pass)
                    {
                        continue;
                    }

                    // Check that there is a team other than the players' team that is at least as large
                    pass = false;
                    for (int i = 1; i < 5; i++)
                    {
                        if (i == localPlayer.Team)
                        {
                            continue;
                        }

                        if (teamMemberCounts[i] >= allyCount)
                        {
                            pass = true;
                            break;
                        }
                    }

                    if (!pass)
                    {
                        continue;
                    }
                }

                if (rank < lowestEnemyAILevel)
                {
                    rank = lowestEnemyAILevel;

                    if (rank == 2)
                    {
                        return(rank); // Best possible rank
                    }
                }
            }

            return(rank);
        }