Пример #1
0
        public void LoadDatabaseTest()
        {
            UnitTestDatabase   database   = new UnitTestDatabase();
            SplatTagController controller = new SplatTagController(database);

            controller.Initialise();
            Assert.IsTrue(database.loadCalled);

            database.loadCalled = false;
            Team exampleTeam = new Team("Example Team", Builtins.ManualSource);

            exampleTeam.AddClanTag("e.g", Builtins.ManualSource, TagOption.Front);
            exampleTeam.AddDivision(new Division(1, DivType.DSB), Builtins.ManualSource);
            var TEAM_ID = exampleTeam.Id;

            database.expectedTeams = new List <Team> {
                exampleTeam
            };
            var PLAYER_NAME = "Example Name";

            database.expectedPlayers = new List <Player>
            {
                new Player(PLAYER_NAME, new Guid[] { TEAM_ID }, Builtins.ManualSource)
            };
            var PLAYER_ID = database.expectedPlayers[0].Id;

            controller.LoadDatabase();
            Assert.IsTrue(database.loadCalled);

            // Also check the player and team is now in the controller
            var players = controller.MatchPlayer(PLAYER_ID.ToString(), new MatchOptions {
                FilterOptions = FilterOptions.SlappId
            });

            Assert.IsNotNull(players.FirstOrDefault());
            var player = players[0];

            Assert.IsTrue(player.Name.Value == PLAYER_NAME);

            var teams = controller.MatchTeam(TEAM_ID.ToString(), new MatchOptions {
                FilterOptions = FilterOptions.SlappId
            });

            Assert.IsNotNull(teams.FirstOrDefault());
            var team = teams[0];

            Assert.IsTrue(team.CurrentDiv.Value == 1);
            Assert.IsTrue(team.CurrentDiv.DivType == DivType.DSB);

            // Verify getting the players for that team returns our player
            var playersForExampleTeam = controller.GetPlayersForTeam(exampleTeam);

            Assert.IsNotNull(playersForExampleTeam);
            Assert.IsTrue(playersForExampleTeam.Count == 1);
            Assert.IsTrue(playersForExampleTeam[0].player.Equals(player));
        }
Пример #2
0
        public Source Load()
        {
            Debug.WriteLine("Loading " + tsvFile);
            string[] text = File.ReadAllLines(tsvFile);
            if (text.Length < 1)
            {
                throw new ArgumentException("TSV does not have any data. Check format of spreadsheet.");
            }

            // Build a map of the incoming data
            string headerRow = text[0];

            string[] columns         = headerRow.Split('\t').Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
            int      numberOfHeaders = columns.Length;

            PropertyEnum[] resolved = new PropertyEnum[numberOfHeaders];
            for (int i = 0; i < numberOfHeaders; ++i)
            {
                string header = columns[i].ToLowerInvariant()
                                .Replace("your ", "")
                                .Replace(" ", "")
                                .Replace("_", "")
                                .Replace(":", "")
                                .Replace("-", "")
                                .Replace("'s", "")
                                .Replace("teamcaptain", "captain")
                                .Replace("teammember", "player")
                ;
                if (string.IsNullOrWhiteSpace(header))
                {
                    Console.WriteLine($"Warning: Unable to resolve header, it is blank after processing: \"{header}\", (was \"{columns[i]}\")");
                    resolved[i] = PropertyEnum.UNKNOWN;
                }
                // Quick case
                else if (propertyValueStringMap.ContainsKey(header))
                {
                    resolved[i] = propertyValueStringMap[header];
                }
                else
                {
                    // Need to do some searching
                    int playerNum = 0;
                    if (header.Contains("captain"))
                    {
                        playerNum = 1;
                        header    = header.Replace("captain", "");
                    }
                    else
                    {
                        for (playerNum = 10; playerNum > 0; --playerNum)
                        {
                            if (header.Contains(playerNum.ToString()))
                            {
                                header = header.Replace(playerNum.ToString(), "");
                                break;
                            }
                        }
                    }

                    var matchedKey = propertyValueStringMap.Keys.FirstOrDefault(key => header.StartsWith(key));
                    if (matchedKey != null)
                    {
                        // For player num = 0 (not found), this will just return the appropriate header.
                        resolved[i] = (playerNum * (int)PropertyEnum.PlayerN_Offset) + propertyValueStringMap[matchedKey];
                    }
                    else if (playerNum == 0)
                    {
                        resolved[i] = PropertyEnum.UNKNOWN;
                        Console.WriteLine("Warning: Unable to resolve header: " + header);
                    }
                    else
                    {
                        resolved[i] = PropertyEnum.UNKNOWN;
                        Console.WriteLine("Warning: Unable to resolve header for player " + playerNum + ": " + header);
                    }
                }
            }

            // Now read the data...
            // Most tsv files will be team sheets, but for draft cups and verif they could be player records instead.
            // Each row therefore might be a single team with players, or a single player entry.
            List <Team>   teams   = new List <Team>();
            List <Player> players = new List <Player>();

            // From 1 as [0] is header row
            for (int lineIndex = 1; lineIndex < text.Length; ++lineIndex)
            {
                string line = text[lineIndex];
                if (!line.Contains('\t'))
                {
                    continue;
                }

                string[] cells = line.Split('\t').ToArray();

                // Warn if the values exceeds the number of defined headers (but don't bother if we're only one over and it's empty -- trailing tab)
                if (numberOfHeaders < cells.Length && (numberOfHeaders != cells.Length - 1 || !string.IsNullOrWhiteSpace(cells[cells.Length - 1])))
                {
                    Console.WriteLine($"Warning: Line {lineIndex} contains more cells in this row {cells.Length} than headers {numberOfHeaders}.");
                    Debug.WriteLine(line);
                }

                SortedDictionary <int, Player> rowPlayers = new SortedDictionary <int, Player>();
                Team t = new Team();

                for (int i = 0; i < cells.Length && i < numberOfHeaders; ++i)
                {
                    int          playerNum        = 0;
                    PropertyEnum resolvedProperty = resolved[i];
                    if (resolvedProperty > PropertyEnum.PlayerN_Offset)
                    {
                        playerNum        = (int)resolved[i] / (int)PropertyEnum.PlayerN_Offset;
                        resolvedProperty = (PropertyEnum)((int)resolved[i] % (int)PropertyEnum.PlayerN_Offset);
                    }

                    string value = cells[i];
                    if (string.IsNullOrWhiteSpace(value))
                    {
                        continue;
                    }

                    switch (resolvedProperty)
                    {
                    case PropertyEnum.UNKNOWN:
                    {
                        continue;
                    }

                    case PropertyEnum.Country:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);
                        p.Country = value;
                        break;
                    }

                    case PropertyEnum.DiscordId:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);
                        if (value.Length >= 14 && value.Length < 21)
                        {
                            // First, test is decimal (of length 17+)
                            bool isDecimal = value.Length >= 17 && value.All("0123456789".Contains);
                            if (isDecimal)
                            {
                                p.AddDiscordId(value, source);
                            }
                            // Otherwise test if we can get from a hex string (of length 14+ to give the correct 17 digit decimal)
                            else if (ulong.TryParse(value, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out ulong parsedId))
                            {
                                p.AddDiscordId(value, source);
                            }
                            else
                            {
                                Console.WriteLine($"Warning: DiscordId was specified ({lineIndex},{i}), but the value could not be parsed from a decimal or hex string. {value}.");
                                Debug.WriteLine(line);
                            }
                        }
                        else
                        {
                            Console.WriteLine($"Warning: DiscordId was specified ({lineIndex},{i}), but the value length does not fit into a discord id. {value}.");
                            Debug.WriteLine(line);
                        }
                        break;
                    }

                    case PropertyEnum.DiscordName:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);
                        if (Discord.DISCORD_NAME_REGEX.IsMatch(value))
                        {
                            p.AddDiscordUsername(value, source);
                        }
                        else if (FriendCode.TryParse(value, out FriendCode friendCode))
                        {
                            p.AddFCs(friendCode, source);
                            Console.WriteLine($"Warning: This value was declared as a Discord name but looks like a friend code. Bad data formatting? {value} on ({lineIndex},{i}).");
                        }
                        else
                        {
                            Console.WriteLine($"Warning: DiscordName was specified ({lineIndex},{i}), but the value was not in a Discord format of name#0000. {value}.");
                            Debug.WriteLine(line);
                        }
                        break;
                    }

                    case PropertyEnum.FC:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);
                        if (FriendCode.TryParse(value, out FriendCode friendCode))
                        {
                            p.AddFCs(friendCode, source);
                        }
                        else
                        {
                            Console.WriteLine($"Warning: FC was specified ({lineIndex},{i}), but the value was not in an FC format of 0000-0000-0000 or 0000 0000 0000. {value}.");
                            Debug.WriteLine(line);
                        }
                        break;
                    }

                    case PropertyEnum.Div:
                    {
                        t.AddDivision(new Division(value, divType, season), source);

                        if (divType == DivType.Unknown)
                        {
                            Console.WriteLine($"Warning: Div was specified ({lineIndex},{i}), but I don't know what type of division this file represents.");
                        }
                        break;
                    }

                    case PropertyEnum.LUTIDiv:
                    {
                        t.AddDivision(new Division(value, DivType.LUTI, season), source);
                        break;
                    }

                    case PropertyEnum.EBTVDiv:
                    {
                        t.AddDivision(new Division(value, DivType.EBTV, season), source);
                        break;
                    }

                    case PropertyEnum.DSBDiv:
                    {
                        t.AddDivision(new Division(value, DivType.DSB, season), source);
                        break;
                    }

                    case PropertyEnum.Timestamp:
                    {
                        // TODO - not supported right now. In future we could customise the source timestamp for this entry.
                        break;
                    }

                    case PropertyEnum.Name:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);

                        var playerName = value.Trim();
                        playerName = t.Tag?.StripFromPlayer(playerName) ?? playerName;
                        p.AddName(playerName, source);

                        if (FriendCode.TryParse(value, out FriendCode friendCode))
                        {
                            p.AddFCs(friendCode, source);
                            Console.WriteLine($"Warning: This value was declared as a name but looks like a friend code. Bad data formatting? {value} on ({lineIndex},{i}).");
                            Debug.WriteLine(line);
                        }
                        break;
                    }

                    case PropertyEnum.Role:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);
                        p.AddWeapons(value.Split(',').Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s)));
                        break;
                    }

                    case PropertyEnum.Pronouns:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);
                        p.PronounInformation.SetPronoun(value, source);
                        break;
                    }

                    case PropertyEnum.Tag:
                    {
                        t.AddClanTag(value, source);
                        break;
                    }

                    case PropertyEnum.TeamName:
                    {
                        t.AddName(value, source);
                        break;
                    }

                    case PropertyEnum.Twitter:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);
                        p.AddTwitter(value, source);
                        break;
                    }

                    case PropertyEnum.Twitch:
                    {
                        var p = GetCurrentPlayer(ref rowPlayers, playerNum, tsvFile);
                        p.AddTwitch(value, source);
                        break;
                    }

                    case PropertyEnum.Team_Offset:
                    case PropertyEnum.UnspecifiedPlayer_Offset:
                    case PropertyEnum.PlayerN_Offset:
                    default:
                    {
                        Console.WriteLine($"Warning: Unhandled header {resolvedProperty}. {value}. Line {lineIndex}:");
                        Debug.WriteLine(line);
                        break;
                    }
                    }
                }

                // End of the row, add the data.
                foreach (var pair in rowPlayers)
                {
                    // Don't add empty players
                    Player p = pair.Value;
                    if (p.Name.Equals(Builtins.UnknownPlayerName))
                    {
                        continue;
                    }
                    // Don't add the team to the player if it's not complete (e.g. single player record)
                    if (!t.Name.Equals(Builtins.UnknownTeamName))
                    {
                        p.TeamInformation.Add(t.Id, source);
                    }
                    players.Add(p);
                }

                // Don't bother adding the team if it has no players
                if (players.Count > 0)
                {
                    // Don't register a team if that information doesn't exist.
                    if (!t.Name.Equals(Builtins.UnknownTeamName))
                    {
                        // Recalculate the ClanTag layout
                        if (t.Tag != null)
                        {
                            t.Tag.CalculateTagOption(players[0].Name.Value);
                        }
                        else
                        {
                            ClanTag?newTag = ClanTag.CalculateTagFromNames(players.Select(p => p.Name.Value).ToArray(), source);

                            if (newTag != null)
                            {
                                t.AddClanTag(newTag);
                            }
                        }
                        teams.Add(t);
                    }
                }
            }

            source.Players = players.ToArray();
            source.Teams   = teams.ToArray();
            return(source);
        }
Пример #3
0
        public void FinaliseTeamsTest()
        {
            const string T1_STRING = "cafef00d";
            const string T2_STRING = "deadb33f";
            const string T4_STRING = "b335";

            Team t1 = new Team("Shared Name", new Source("t1", DateTime.Now.AddDays(1)));

            t1.AddBattlefyId(T1_STRING, Builtins.ManualSource);

            Team t2 = new Team("TeamB", new Source("t2", DateTime.Now.AddDays(2)));

            t2.AddBattlefyId(T2_STRING, Builtins.ManualSource);

            // t3 -> t2
            Team t3 = new Team("AnotherTeam", new Source("t3", DateTime.Now.AddDays(3)));

            t3.AddBattlefyId(T2_STRING, Builtins.ManualSource);

            // t4 -> t1
            Team t4 = new Team("Shared Name", new Source("t4", DateTime.Now.AddDays(4)));

            t4.AddBattlefyId(T4_STRING, Builtins.ManualSource);

            // t5 should NOT merge into t1 because it has no players in common despite sharing a name.
            Team t5 = new Team("Shared Name", new Source("t5", DateTime.UtcNow.AddDays(-1)));

            t5.AddDivision(new Division(5, DivType.LUTI, "FirstSeason"), t5.Sources[0]);

            // t6 -> t5
            Team t6 = new Team("Shared Name", new Source("t6", DateTime.UtcNow));

            t6.AddDivision(new Division(4, DivType.LUTI, "LaterSeason"), t6.Sources[0]);

            Player p1 = new Player("username", new[] { t1.Id, t2.Id, t4.Id }, new Source("p1"));

            p1.AddBattlefyInformation("user", "user", "p1id", Builtins.ManualSource);
            Assert.IsNotNull(t1.BattlefyPersistentTeamId);
            p1.AddBattlefyInformation("slug", "user", "p1id2", Builtins.ManualSource);

            Player p2 = new Player("player", new[] { t1.Id, t2.Id, t4.Id }, new Source("p2"));
            Player p3 = new Player("another player", new[] { t1.Id, t2.Id, t4.Id }, new Source("p3"));
            Player p4 = new Player("player 4", new[] { t6.Id, t5.Id }, new Source("p4"));
            Player p5 = new Player("player 5", new[] { t6.Id, t5.Id }, new Source("p5"));

            // Perform the merge
            var players = new List <Player>()
            {
                p1, p2, p3, p4, p5
            };
            var teams = new List <Team>()
            {
                t1, t2, t3, t4, t5, t6
            };

            DumpSourceable("Players before merge:", players);
            DumpSourceable("Teams before merge:", teams);
            var result = Merger.FinaliseTeams(players, teams);

            DumpSourceable("Players after merge:", players);
            DumpSourceable("Teams after merge:", teams);

            Assert.IsTrue(result.ContainsKey(t6.Id), "Expected t6 to be merged");
            Assert.AreEqual(t5.Id, result[t6.Id], "Expected t6 to be merged --> t5");
            Assert.AreEqual(4, t5.CurrentDiv.Value, "Expected t6 to be merged --> t5 (Div should now be 4, not 5)");

            Assert.IsTrue(result.ContainsKey(t4.Id), "Expected t4 to be merged");
            Assert.AreEqual(t1.Id, result[t4.Id], "Expected t4 to be merged --> t1");
            Assert.IsTrue(t1.BattlefyPersistentTeamIdInformation.Contains(T4_STRING), "Expected t4 to be merged --> t1 (Battlefy Id should have merged)");

            Assert.IsTrue(result.ContainsKey(t3.Id), "Expected t3 to be merged");
            Assert.AreEqual(t2.Id, result[t3.Id], "Expected t3 to be merged --> t2");
            Assert.AreEqual <string>("AnotherTeam", t2.Name.Value, "Expected t3 to be merged --> t2 (Names should have merged)");

            Assert.AreEqual(3, result.Count, "3 teams should have been merged.");
            Assert.AreEqual(3, teams.Count, "3 teams should be left.");

            // Fix the players.
            Merger.CorrectTeamIdsForPlayers(players, result);
            Assert.AreEqual(p1.CurrentTeam, t1.Id, "Expected p1's current team to still be team 1");
            Assert.AreEqual(2, p1.TeamInformation.Count, $"Expected p1's number of teams to now be 2, actually: {IdsToString(p1.TeamInformation.GetAllTeamsUnordered())}");
            Assert.AreEqual(1, p4.TeamInformation.Count, $"Expected p4's number of teams to now be 1, actually: {IdsToString(p4.TeamInformation.GetAllTeamsUnordered())}");
            Assert.AreEqual(p4.CurrentTeam, t5.Id, "Expected p4's current team to now be t5");
            Assert.AreEqual(1, p5.TeamInformation.Count, $"Expected p5's number of teams to now be 1, actually: {IdsToString(p5.TeamInformation.GetAllTeamsUnordered())}");
            Assert.AreEqual(p5.CurrentTeam, t5.Id, "Expected p5's current team to now be t5");
        }
Пример #4
0
        public void SerializeTeam()
        {
            var sources      = new Dictionary <string, Source>();
            var oldestSource = new Source("old_source", DateTime.Now.AddDays(-6));
            var newestSource = new Source("new_source", DateTime.Now);

            sources.Add(newestSource.Id, newestSource);
            sources.Add(oldestSource.Id, oldestSource);

            Team team = new Team();

            team.AddBattlefyId("2teamid2", oldestSource);
            team.AddBattlefyId("1teamid1", newestSource);

            team.AddClanTag("old", oldestSource);
            team.AddClanTag("new", newestSource);

            team.AddDivision(new Division(10, DivType.DSB, "S9"), oldestSource);
            team.AddDivision(new Division(8, DivType.LUTI, "SX"), newestSource);

            team.AddName("team2", oldestSource);
            team.AddName("team1", newestSource);

            string json = Serialize(team);

            Console.WriteLine(nameof(SerializeTeam) + ": ");
            Console.WriteLine(json);
            Team deserialized = Deserialize <Team>(json, sources);

            var battlefy = deserialized.BattlefyPersistentTeamIdInformation.GetItemsOrdered();

            Assert.AreEqual(2, battlefy.Count, "Unexpected number of team battlefy slugs");
            Assert.AreEqual("1teamid1", battlefy[0].Value, "Slug [0] unexpected handle");
            Assert.AreEqual("2teamid2", battlefy[1].Value, "Slug [1] unexpected handle");
            Assert.AreEqual("new_source", battlefy[0].Sources[0].Name, "Slug [0] unexpected source");
            Assert.AreEqual("old_source", battlefy[1].Sources[0].Name, "Slug [1] unexpected source");

            var clanTags = deserialized.ClanTagInformation.GetItemsOrdered();

            Assert.AreEqual(2, clanTags.Count, "Unexpected number of clanTags");
            Assert.AreEqual("new", clanTags[0].Value, "clanTags[0] Unexpected Value");
            Assert.AreEqual("old", clanTags[1].Value, "clanTags[1] Unexpected Value");
            Assert.AreEqual("new_source", clanTags[0].Sources[0].Name, "clanTags[0] unexpected source");
            Assert.AreEqual("old_source", clanTags[1].Sources[0].Name, "clanTags[1] unexpected source");

            var divisions = deserialized.DivisionInformation.GetDivisionsOrdered();

            Assert.AreEqual(2, divisions.Count, "Unexpected number of divisions");
            // First - most recent
            Assert.AreEqual(8, divisions[0].Value, "Unexpected Value");
            Assert.AreEqual(DivType.LUTI, divisions[0].DivType, "Unexpected DivType");
            Assert.AreEqual("SX", divisions[0].Season, "Unexpected Season");
            // Next
            Assert.AreEqual(10, divisions[1].Value, "Unexpected Value");
            Assert.AreEqual(DivType.DSB, divisions[1].DivType, "Unexpected DivType");
            Assert.AreEqual("S9", divisions[1].Season, "Unexpected Season");

            var names = deserialized.NamesInformation.GetItemsOrdered();

            Assert.AreEqual(2, names.Count, "Unexpected number of team names");
            Assert.AreEqual("team1", names[0].Value, "Names [0] unexpected handle");
            Assert.AreEqual("team2", names[1].Value, "Names [1] unexpected handle");
            Assert.AreEqual("new_source", names[0].Sources[0].Name, "Names [0] unexpected source");
            Assert.AreEqual("old_source", names[1].Sources[0].Name, "Names [1] unexpected source");
        }