/// <summary> /// Parses a detailed summary entry into a scoreboard details object. /// </summary> /// <param name="dataEntries">The data.</param> protected virtual void ParseDetailedSummaryEntry(ScoreboardDetails details, string[] dataEntries) { var summary = new ScoreboardSummaryEntry(); details.Summary = summary; // ID, Division (labeled location, their bug), Location (labeled division, their bug), tier, scored image count, play time, score time, warnings, current score summary.TeamId = TeamId.Parse(dataEntries[0]); // [not in data, matched from categoryProvider] all service category summary.Category = _categoryProvider?.GetCategory(summary.TeamId); // tier and division if (Utilities.TryParseEnumSpaceless <Division>(dataEntries[2], out Division division)) { summary.Division = division; } summary.Location = dataEntries[1]; if (Enum.TryParse <Tier>(dataEntries[3], true, out Tier tier)) { summary.Tier = tier; } // number of images summary.ImageCount = int.Parse(dataEntries[4].Trim()); // times summary.PlayTime = Utilities.ParseHourMinuteSecondTimespan(dataEntries[5]); details.ScoreTime = Utilities.ParseHourMinuteSecondTimespan(dataEntries[6]); // warnings and total score string warnStr = dataEntries[7]; summary.Warnings |= warnStr.Contains("T") ? ScoreWarnings.TimeOver : 0; summary.Warnings |= warnStr.Contains("M") ? ScoreWarnings.MultiImage : 0; summary.TotalScore = double.Parse(dataEntries.Last().Trim()); }
private bool CategoryEquals(ScoreboardSummaryEntry team, string category) { if (team.Category == null) { return(category == null); } string teamCategory = team.Category.Trim().ToLowerInvariant(); category = category.Trim().ToLowerInvariant(); if (teamCategory.Equals(category)) { return(true); } if (teamCategory.StartsWith(category)) { // starts to get really soft from this comparison onwards return(true); } if (teamCategory.StartsWithWhereElement(c => c != ' ', category)) { return(true); } // last check /shrug return(teamCategory.Replace(" ", "").Contains(category.Replace(" ", ""))); }
protected string AbbreviateDivision(ScoreboardSummaryEntry team) { if (!team.Category.HasValue || team.Division != Division.AllService) { return(team.Division.ToConciseString()); } return("AS:" + CyberPatriot.Models.Serialization.ServiceCategoryExtensions.Abbreviate(team.Category.Value)); }
public string CreatePeerLeaderboardEmbed(TeamId teamId, CompleteScoreboardSummary scoreboard, IList <ScoreboardSummaryEntry> peerTeams, TimeZoneInfo timeZone = null, int topTeams = 3, int nearbyTeams = 5) { // var peerTeams = CompetitionLogic.GetPeerTeams(ScoreRetriever.Round, scoreboard, teamDetails.Summary); var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("**CyberPatriot Scoreboard**"); DateTimeOffset timestamp = timeZone == null ? scoreboard.SnapshotTimestamp : TimeZoneInfo.ConvertTime(scoreboard.SnapshotTimestamp, timeZone); stringBuilder.AppendFormat("*Competing against: {0} | As of: ", teamId); stringBuilder.AppendFormat("{0:g}", timestamp); stringBuilder.Append(' ').Append(timeZone == null ? "UTC" : TimeZoneNames.TZNames.GetAbbreviationsForTimeZone(timeZone.Id, "en-US").Generic).AppendLine("*"); stringBuilder.AppendLine("```bash"); // zero-based rank of the given team int pos = peerTeams.IndexOfWhere(team => team.TeamId == teamId); if (pos < nearbyTeams + topTeams + 1) { peerTeams.Take(nearbyTeams + pos + 1) .Select((team, i) => stringBuilder.AppendFormat("{8}{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", i + 1, team.TeamId, team.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore), team.Advancement.HasValue ? team.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", team.PlayTime), team.Warnings.ToConciseString(), team.Division.ToConciseString(), team.Tier, team.TeamId == teamId ? ">" : "#").AppendLine()) .Consume(); } else { peerTeams.Take(topTeams) .Select((team, i) => stringBuilder.AppendFormat("#{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", i + 1, team.TeamId, team.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore), team.Advancement.HasValue ? team.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", team.PlayTime), team.Warnings.ToConciseString(), team.Division.ToConciseString(), team.Tier).AppendLine()) .Consume(); stringBuilder.AppendLine("..."); peerTeams.Skip(pos - nearbyTeams) .Take(nearbyTeams) .Select((team, i) => stringBuilder.AppendFormat("#{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", i + pos - nearbyTeams + 1, team.TeamId, team.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore), team.Advancement.HasValue ? team.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", team.PlayTime), team.Warnings.ToConciseString(), team.Division.ToConciseString(), team.Tier).AppendLine()) .Consume(); ScoreboardSummaryEntry thisTeamDetails = peerTeams.Single(t => t.TeamId == teamId); stringBuilder.AppendFormat(">{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", pos + 1, thisTeamDetails.TeamId, thisTeamDetails.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(thisTeamDetails.TotalScore), thisTeamDetails.Advancement.HasValue ? thisTeamDetails.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", thisTeamDetails.PlayTime), thisTeamDetails.Warnings.ToConciseString(), thisTeamDetails.Division.ToConciseString(), thisTeamDetails.Tier).AppendLine(); // since pos and i are both zero-based, i + pos + 2 returns correct team rank for teams after given team peerTeams.Skip(pos + 1) .Take(nearbyTeams) .Select((team, i) => stringBuilder.AppendFormat("#{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", i + pos + 2, team.TeamId, team.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore), team.Advancement.HasValue ? team.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", team.PlayTime), team.Warnings.ToConciseString(), team.Division.ToConciseString(), team.Tier).AppendLine()) .Consume(); } stringBuilder.AppendLine("```"); if (scoreboard.OriginUri != null) { stringBuilder.AppendLine(scoreboard.OriginUri.ToString()); } return(stringBuilder.ToString()); }
public override ScoreboardFilterInfo GetPeerFilter(CompetitionRound round, ScoreboardSummaryEntry teamDetails) { if (teamDetails.Division == Division.MiddleSchool) { // middle school doesn't have tiers or categories return(new ScoreboardFilterInfo(Division.MiddleSchool, null)); } // open/service if ((teamDetails.Division == Division.Open && round > CompetitionRound.Round2) || (teamDetails.Division == Division.AllService && round == CompetitionRound.Round3)) { // In open past R2, tier matters, but that's it // In all service R3, category doesn't* matter, just tier // See issue #14 return(new ScoreboardFilterInfo(teamDetails.Division, teamDetails.Tier)); } // open/service, service: category matters; open: no tiers if (teamDetails.Division == Division.Open) { // unknown round - if our candidate team has a tier, filter by tier, otherwise return the whole division if (round == 0 && teamDetails.Tier != null) { return(new ScoreboardFilterInfo(teamDetails.Division, teamDetails.Tier)); } // either R1 or R2 // safe to return the whole division as a peer list return(new ScoreboardFilterInfo(Division.Open, null)); } // all-service round where category matters ("R0" we default to factoring in category) Tier?tierFilter = null; // filter by tier, where available if (round > CompetitionRound.Round2) { tierFilter = teamDetails.Tier; } // there might be some A.S. teams whose categories we don't know // they get treated as not-my-problem, that is, not part of my category // unknown category -> null -> no filtering on that, a good enough fallback return(new ScoreboardFilterInfo(Division.AllService, tierFilter, teamDetails.Category, null)); }
private string GetImageLeaderboardEntry(ScoreboardSummaryEntry team, ScoreboardImageDetails image, int friendlyIndex, bool useAbbreviatedDivision = false, string prefix = "#") { string divisionFormatString = useAbbreviatedDivision ? "{0,6}" : " {0,-10}"; string vulnPenString = new string(' ', 10); if (ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.VulnerabilityDisplay, image.VulnerabilitiesFound, image.VulnerabilitiesRemaining)) { vulnPenString = $"{image.VulnerabilitiesFound,5}v {image.Penalties,2}p"; } return($"{prefix}{friendlyIndex,-5}{team.TeamId,-7}{team.Location,4}" + string.Format(divisionFormatString, AbbreviateDivision(team)) + $"{team.Tier,10}" + $"{ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(image.Score),13}" + vulnPenString + $"{(ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.TimeDisplay, image.PlayTime) ? image.PlayTime.ToHoursMinutesSecondsString() : ""),7}" + $"{image.Warnings.ToConciseString(),4}"); }
protected virtual ScoreboardSummaryEntry ParseSummaryEntry(string[] dataEntries) { ScoreboardSummaryEntry summary = new ScoreboardSummaryEntry(); summary.TeamId = TeamId.Parse(dataEntries[0]); summary.Category = _categoryProvider?.GetCategory(summary.TeamId); summary.Location = dataEntries[1]; if (Utilities.TryParseEnumSpaceless <Division>(dataEntries[2], out Division division)) { summary.Division = division; } if (Enum.TryParse <Tier>(dataEntries[3]?.Trim(), true, out Tier tier)) { summary.Tier = tier; } summary.ImageCount = int.Parse(dataEntries[4]); summary.PlayTime = Utilities.ParseHourMinuteTimespan(dataEntries[5]); summary.TotalScore = int.Parse(dataEntries[7]); summary.Warnings |= dataEntries[6].Contains("T") ? ScoreWarnings.TimeOver : 0; summary.Warnings |= dataEntries[6].Contains("M") ? ScoreWarnings.MultiImage : 0; return(summary); }
protected override void ParseDetailedSummaryEntry(ScoreboardDetails details, string[] dataEntries) { var summary = new ScoreboardSummaryEntry(); details.Summary = summary; // ID, Division (labeled location, their bug), Location (labeled division, their bug), tier, scored img, play time, score time, current score, warn summary.TeamId = TeamId.Parse(dataEntries[0]); summary.Category = _categoryProvider?.GetCategory(summary.TeamId); if (Utilities.TryParseEnumSpaceless <Division>(dataEntries[1], out Division division)) { summary.Division = division; } summary.Location = dataEntries[2]; if (Enum.TryParse <Tier>(dataEntries[3], true, out Tier tier)) { summary.Tier = tier; } summary.ImageCount = int.Parse(dataEntries[4].Trim()); summary.PlayTime = Utilities.ParseHourMinuteTimespan(dataEntries[5]); string scoreTimeText = dataEntries[6]; // to deal with legacy scoreboards int scoreTimeIndOffset = 0; if (scoreTimeText.Contains(":")) { details.ScoreTime = Utilities.ParseHourMinuteTimespan(dataEntries[6]); } else { details.ScoreTime = summary.PlayTime; scoreTimeIndOffset = -1; } summary.TotalScore = int.Parse(dataEntries[7 + scoreTimeIndOffset].Trim()); string warnStr = dataEntries[8 + scoreTimeIndOffset]; summary.Warnings |= warnStr.Contains("T") ? ScoreWarnings.TimeOver : 0; summary.Warnings |= warnStr.Contains("M") ? ScoreWarnings.MultiImage : 0; }
public abstract IList <ScoreboardSummaryEntry> GetPeerTeams(CompetitionRound round, CompleteScoreboardSummary divisionScoreboard, ScoreboardSummaryEntry teamInfo);
public virtual string GetEffectiveDivisionDescriptor(ScoreboardSummaryEntry team) => team.Category ?? team.Division.ToStringCamelCaseToSpace();
ScoreboardFilterInfo ICompetitionRoundLogicService.GetPeerFilter(CompetitionRound round, ScoreboardSummaryEntry teamInfo) => underlyingService.GetPeerFilter(round, teamInfo);
string ICompetitionRoundLogicService.GetEffectiveDivisionDescriptor(ScoreboardSummaryEntry team) => underlyingService.GetEffectiveDivisionDescriptor(team);
async Task TeamPlacementChangeNotificationTimer(TimerStateWrapper state) { try { using (var databaseContext = _database.OpenContext <Models.Guild>(false)) using (var guildSettingEnumerator = databaseContext.FindAllAsync().GetEnumerator()) { CompleteScoreboardSummary masterScoreboard = null; Dictionary <TeamId, int> teamIdsToPeerIndexes = new Dictionary <TeamId, int>(); while (await guildSettingEnumerator.MoveNext().ConfigureAwait(false)) { Models.Guild guildSettings = guildSettingEnumerator.Current; if (guildSettings?.ChannelSettings == null || guildSettings.ChannelSettings.Count == 0) { return; } IGuild guild = _discord.GetGuild(guildSettings.Id); foreach (var chanSettings in guildSettings.ChannelSettings.Values) { if (chanSettings?.MonitoredTeams == null || chanSettings.MonitoredTeams.Count == 0) { continue; } IGuildChannel rawChan = await guild.GetChannelAsync(chanSettings.Id).ConfigureAwait(false); if (!(rawChan is ITextChannel chan)) { continue; } masterScoreboard = await _scoreRetriever.GetScoreboardAsync(ScoreboardFilterInfo.NoFilter).ConfigureAwait(false); foreach (TeamId monitored in chanSettings.MonitoredTeams) { int masterScoreboardIndex = masterScoreboard.TeamList.IndexOfWhere(scoreEntry => scoreEntry.TeamId == monitored); if (masterScoreboardIndex == -1) { continue; } // TODO efficiency: we're refiltering every loop iteration ScoreboardSummaryEntry monitoredEntry = masterScoreboard.TeamList[masterScoreboardIndex]; int peerIndex = masterScoreboard.Clone().WithFilter(_competitionLogic.GetPeerFilter(_scoreRetriever.Round, monitoredEntry)).TeamList.IndexOf(monitoredEntry); teamIdsToPeerIndexes[monitored] = peerIndex; // we've obtained all information, now compare to past data if (state.PreviousTeamListIndexes != null && state.PreviousTeamListIndexes.TryGetValue(monitored, out int prevPeerIndex)) { int indexDifference = peerIndex - prevPeerIndex; if (indexDifference != 0) { StringBuilder announceMessage = new StringBuilder(); announceMessage.Append("**"); announceMessage.Append(monitored); announceMessage.Append("**"); if (indexDifference > 0) { announceMessage.Append(" rose "); } else { announceMessage.Append(" fell "); indexDifference *= -1; } var teamDetails = await _scoreRetriever.GetDetailsAsync(monitored).ConfigureAwait(false); announceMessage.Append(Utilities.Pluralize("place", indexDifference)); announceMessage.Append(" to **"); announceMessage.Append(Utilities.AppendOrdinalSuffix(peerIndex + 1)); announceMessage.Append(" place**."); await chan.SendMessageAsync( announceMessage.ToString(), embed : _messageBuilder .CreateTeamDetailsEmbed( teamDetails, masterScoreboard, _competitionLogic.GetPeerFilter(_scoreRetriever.Round, teamDetails.Summary)) .Build()).ConfigureAwait(false); } } } } } state.PreviousTeamListIndexes = teamIdsToPeerIndexes; } } catch (Exception ex) { await _logService.LogApplicationMessageAsync(LogSeverity.Error, "Error in team monitor timer task", ex).ConfigureAwait(false); } }
public abstract ScoreboardFilterInfo GetPeerFilter(CompetitionRound round, ScoreboardSummaryEntry teamInfo);
public string CreatePeerLeaderboardEmbed(TeamId teamId, CompleteScoreboardSummary peerScoreboard, TimeZoneInfo timeZone = null, int topTeams = 3, int nearbyTeams = 5) { // var peerTeams = CompetitionLogic.GetPeerTeams(ScoreRetriever.Round, scoreboard, teamDetails.Summary); var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("**CyberPatriot Scoreboard**"); DateTimeOffset timestamp = timeZone == null ? peerScoreboard.SnapshotTimestamp : TimeZoneInfo.ConvertTime(peerScoreboard.SnapshotTimestamp, timeZone); stringBuilder.AppendFormat("*Competing against: {0} | As of: ", teamId); stringBuilder.AppendFormat("{0:g}", timestamp); stringBuilder.Append(' ').Append(timeZone == null ? "UTC" : TimeZoneNames.TZNames.GetAbbreviationsForTimeZone(timeZone.Id, "en-US").Generic).AppendLine("*"); var peerTeams = peerScoreboard.TeamList; stringBuilder.AppendFormat("*{0} competes in: {1} Division", teamId, peerScoreboard.Filter.Division.ToStringCamelCaseToSpace()); if (peerScoreboard.Filter.Category.HasValue) { stringBuilder.AppendFormat(", {0}", peerScoreboard.Filter.Category.Value.ToCanonicalName()); } if (peerScoreboard.Filter.Tier.HasValue) { stringBuilder.AppendFormat(", {0} Tier", peerScoreboard.Filter.Tier.Value); } if (peerScoreboard.Filter.Location != null) { stringBuilder.AppendFormat(", {0}", peerScoreboard.Filter.Location); } stringBuilder.AppendLine("*"); bool conciseDivision = !peerTeams.Any(x => x.Category != null); stringBuilder.AppendLine("```bash"); // zero-based rank of the given team int pos = peerTeams.IndexOfWhere(team => team.TeamId == teamId); if (pos < nearbyTeams + topTeams + 1) { peerTeams.Take(nearbyTeams + pos + 1) .Select((team, i) => stringBuilder.AppendLine(GetTeamLeaderboardEntry(team, i + 1, useAbbreviatedDivision: conciseDivision, prefix: team.TeamId == teamId ? ">" : "#"))) .Consume(); } else { peerTeams.Take(topTeams) .Select((team, i) => stringBuilder.AppendLine(GetTeamLeaderboardEntry(team, i + 1, useAbbreviatedDivision: conciseDivision))) .Consume(); stringBuilder.AppendLine("..."); peerTeams.Skip(pos - nearbyTeams) .Take(nearbyTeams) .Select((team, i) => stringBuilder.AppendLine(GetTeamLeaderboardEntry(team, i + pos - nearbyTeams + 1, useAbbreviatedDivision: conciseDivision))) .Consume(); ScoreboardSummaryEntry thisTeamDetails = peerTeams.Single(t => t.TeamId == teamId); stringBuilder.AppendLine(GetTeamLeaderboardEntry(thisTeamDetails, pos + 1, useAbbreviatedDivision: conciseDivision, prefix: ">")); // since pos and i are both zero-based, i + pos + 2 returns correct team rank for teams after given team peerTeams.Skip(pos + 1) .Take(nearbyTeams) .Select((team, i) => stringBuilder.AppendLine(GetTeamLeaderboardEntry(team, i + pos + 2, useAbbreviatedDivision: conciseDivision))) .Consume(); } stringBuilder.AppendLine("```"); if (peerScoreboard.OriginUri != null) { stringBuilder.AppendLine(peerScoreboard.OriginUri.ToString()); } return(stringBuilder.ToString()); }
public virtual TeamDetailRankingInformation GetRankingInformation(CompetitionRound round, CompleteScoreboardSummary divisionScoreboard, ScoreboardSummaryEntry teamInfo) { divisionScoreboard = divisionScoreboard.Clone().WithFilter(teamInfo.Division, null); // may be equal to division scoreboard, that's fine var tierScoreboard = divisionScoreboard.Clone().WithFilter(teamInfo.Division, teamInfo.Tier); var peers = GetPeerTeams(round, divisionScoreboard, teamInfo); var summaryComparer = BuildSummaryComparer(teamInfo.TeamId); return(new TeamDetailRankingInformation() { TeamId = teamInfo.TeamId, Peers = peers, PeerIndex = peers.IndexOfWhere(summaryComparer), PeerCount = peers.Count, DivisionIndex = divisionScoreboard.TeamList.IndexOfWhere(summaryComparer), DivisionCount = divisionScoreboard.TeamList.Count, TierIndex = tierScoreboard.TeamList.IndexOfWhere(summaryComparer), TierCount = tierScoreboard.TeamList.Count }); }
private string GetTeamLeaderboardEntry(ScoreboardSummaryEntry team, int friendlyIndex, bool useAbbreviatedDivision = false, string prefix = "#", bool showAdvancement = true) { string divisionFormatString = useAbbreviatedDivision ? "{0,6}" : " {0,-10}"; return($"{prefix}{friendlyIndex,-5}{team.TeamId,-7}{team.Location,4}" + string.Format(divisionFormatString, AbbreviateDivision(team)) + $"{team.Tier,10}{ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore),16}{((showAdvancement && team.Advancement.HasValue) ? team.Advancement.Value.ToConciseString() : (ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.TimeDisplay, team.PlayTime) ? team.PlayTime.ToHoursMinutesSecondsString() : "")),10}{team.Warnings.ToConciseString(),4}"); }
public override IList <ScoreboardSummaryEntry> GetPeerTeams(CompetitionRound round, CompleteScoreboardSummary divisionScoreboard, ScoreboardSummaryEntry teamDetails) { // make a clone because we'll mutate this later divisionScoreboard = divisionScoreboard.Clone().WithFilter(teamDetails.Division, null); if (teamDetails.Division == Division.MiddleSchool) { // middle school doesn't have tiers or categories return(divisionScoreboard.TeamList); } // open/service if ((teamDetails.Division == Division.Open && round > CompetitionRound.Round2) || (teamDetails.Division == Division.AllService && round == CompetitionRound.Round3)) { // In open past R2, tier matters, but that's it // In all service R3, category doesn't* matter, just tier // See issue #14 return(divisionScoreboard.WithFilter(teamDetails.Division, teamDetails.Tier).TeamList); } // open/service, service: category matters; open: no tiers if (teamDetails.Division == Division.Open) { // unknown round - if our candidate team has a tier, filter by tier, otherwise return the whole division if (round == 0 && teamDetails.Tier != null) { return(divisionScoreboard.WithFilter(teamDetails.Division, teamDetails.Tier).TeamList); } // either R1 or R2 // safe to return the whole division as a peer list return(divisionScoreboard.TeamList); } // all-service round where category matters ("R0" we default to factoring in category) // filter by tier, where available if (round > CompetitionRound.Round2) { divisionScoreboard.WithFilter(Division.AllService, teamDetails.Tier); } // just need to filter the list by category if (teamDetails.Category == null) { // silent fail return(divisionScoreboard.TeamList); } // there might be some A.S. teams whose categories we don't know // they get treated as not-my-problem, that is, not part of my category return(divisionScoreboard.TeamList.Where(t => t.Category == teamDetails.Category).ToIList()); }
public virtual string GetEffectiveDivisionDescriptor(ScoreboardSummaryEntry team) => team.Category.HasValue ? CyberPatriot.Models.Serialization.ServiceCategoryExtensions.ToCanonicalName(team.Category.Value) : team.Division.ToStringCamelCaseToSpace();