Write N1MM's own Contest table rows for the built-in contests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 03:47:29 +00:00
co-authored by Claude Sonnet 5.5
parent 79c65c92a7
commit 393b73f87a
6 changed files with 143 additions and 67 deletions
+1 -1
View File
@@ -153,7 +153,7 @@ public sealed partial class AppSession
scoreSent = DateTime.UtcNow;
ContestInstance instance = Logging.Instance;
StationInfo me = Settings.Station.ToStationInfo();
IReadOnlyList<string> multiplierNames = N1mmMultiplierNames.For(Logging.Contest, me);
IReadOnlyList<string> multiplierNames = N1mmContestRows.MultiplierNames(Logging.Contest, me);
List<ScoreReportRow> rows = ScoreBreakdown.For(Logging.Log.Qsos, Logging.Contest)
.Select(r => new ScoreReportRow(r.Band, r.Mode, r.Qsos, r.Multipliers, r.Points))
.ToList();
+74
View File
@@ -0,0 +1,74 @@
using Nonemm.Core;
namespace Nonemm.Contests;
/// What N1MM keeps in its `Contest` table for one contest. `Multipliers` are
/// the internal names such as `ZN` and `Sect`, without the `N/A` entries.
public sealed record N1mmContestRow(
string DisplayName,
string CabrilloName,
string Mode,
int DupeType,
IReadOnlyList<string> Multipliers,
int Period,
int PointsPerContact,
string CabrilloVersion);
/// The `Contest` table rows of the built-in contests, taken from a log N1MM
/// wrote. N1MM reads this table when it opens a log, so a log written here
/// carries N1MM's values rather than ones derived from our rules. The score
/// broadcast names its multiplier types from the same rows.
public static class N1mmContestRows
{
private static readonly Dictionary<string, N1mmContestRow> Known =
new(StringComparer.OrdinalIgnoreCase)
{
["CQWWCW"] = new("CQ WorldWide CW", "CQ-WW-CW", "CW", 2, ["ZN", "CountryPrefix"], 2, 3, "3.0"),
["CQWWSSB"] = new("CQ WorldWide SSB", "CQ-WW-SSB", "SSB", 2, ["ZN", "CountryPrefix"], 2, 3, "3.0"),
["CQWWRTTY"] = new("CQ WorldWide RTTY", "CQ-WW-RTTY", "RTTY", 2, ["ZN", "CountryPrefix", "Sect"], 2, 3, "3.0"),
["CQWPXCW"] = new("CQ WPX CW", "CQ-WPX-CW", "CW", 2, ["WPXPrefix"], 2, 2, "3.0"),
["CQWPXSSB"] = new("CQ WPX SSB", "CQ-WPX-SSB", "SSB", 2, ["WPXPrefix"], 2, 2, "3.0"),
["CQWPXRTTY"] = new("CQ WPX RTTY", "CQ-WPX-RTTY", "RTTY", 2, ["WPXPrefix"], 2, 2, "3.0"),
["ARRLDXCW"] = new("ARRL DX CW", "ARRL-DX-CW", "CW", 2, ["Sect"], 2, 3, "3.0"),
["ARRLDXSSB"] = new("ARRL DX SSB", "ARRL-DX-SSB", "SSB", 2, ["Sect"], 2, 3, "3.0"),
["IARU"] = new("Internatl Amateur Radio Union HF Contest", "IARU-HF", "Both", 3, ["ZN", "HQ"], 2, 1, "3.0"),
["SSCW"] = new("ARRL Sweepstakes CW (November)", "ARRL-SS-CW", "CW", 1, ["Sect"], 2, 2, "3.0"),
["SSSSB"] = new("ARRL Sweepstakes SSB (November)", "ARRL-SS-SSB", "SSB", 1, ["Sect"], 2, 2, "3.0"),
["WAECW"] = new("Worked All Europe - WAE CW", "DARC-WAEDC-CW", "CW", 3, ["CountryPrefix"], 1, 1, "3.0"),
["WAESSB"] = new("Worked All Europe - WAE SSB", "DARC-WAEDC-SSB", "SSB", 3, ["CountryPrefix"], 1, 1, "3.0"),
["WAERTTY"] = new("Worked All Europe - WAE RTTY", "DARC-WAEDC-RTTY", "RTTY", 3, ["CountryPrefix"], 1, 1, "3.0"),
["ARRLRTTY"] = new("ARRL RTTY Roundup (January)", "ARRL-RTTY", "RTTY", 2, ["Sect", "CountryPrefix"], 1, 1, "3.0"),
["NAQPCW"] = new("North American QSO Parties - CW", "NAQP-CW", "CW", 2, ["Sect", "NA"], 1, 1, "3.0"),
["NAQPSSB"] = new("North American QSO Parties - SSB", "NAQP-SSB", "SSB", 2, ["Sect", "NA"], 1, 1, "3.0"),
["NAQPRTTY"] = new("North American QSO Parties - RTTY", "NAQP-RTTY", "RTTY", 2, ["Sect", "NA"], 1, 1, "3.0"),
["OKOMDX"] = new("Czech and Slovak Republics DX", "OK-OM-DX", "CW", 2, ["Sect"], 1, 1, "3.0"),
["YOTA"] = new("YOTA Contest", "YOTA", "BOTH", 3, ["Exchange"], 2, 0, "3.0"),
["IOTA"] = new("RSGB Islands On The Air Contest", "RSGB-IOTA", "Both", 3, ["Sect"], 1, 1, "3.0"),
["EUDXC"] = new("EU DX Contest (not UDC)", "EUDXC", "Both", 3, ["Sect", "CountryPrefix"], 1, 1, "3.0"),
["CQ160CW"] = new("CQ World-Wide 160 Meter DX Contest - CW", "CQ-160-CW", "CW", 1, ["StateProvince", "CountryPrefix"], 2, 1, "3.0"),
["CQ160SSB"] = new("CQ World-Wide 160 Meter DX Contest - Phone", "CQ-160-SSB", "SSB", 1, ["StateProvince", "CountryPrefix"], 2, 1, "3.0"),
["XERTTY"] = new("Mexico RTTY Contest", "XE-RTTY", "RTTY", 2, ["Countryprefix", "Sect"], 1, 1, "3.0"),
["REFCW"] = new("French REF DX contest - CW", "REF-CW", "CW", 2, ["Sect", "CountryPrefix"], 1, 1, "3.0"),
["REFSSB"] = new("French REF DX contest - SSB", "REF-SSB", "SSB", 2, ["Sect", "CountryPrefix"], 1, 1, "3.0"),
["UKRAINDX"] = new("UKRAINIAN DX", "UKRAINIAN-DX", "BOTH", 3, ["CountryPrefix", "Sect"], 1, 1, "3.0"),
["BARTGRTTYS"] = new("BARTG RTTY Sprint contest (January)", "BARTG-SPRINT", "RTTY", 2, ["CountryPrefix", "Sect", "Continent"], 2, 1, "3.0"),
["ARRL10M"] = new("ARRL 10 Meter Contest", "ARRL-10", "Both", 3, ["StateProvince", "CountryPrefix"], 2, 1, "3.0"),
["RUSDXRTTY"] = new("Russian DX RTTY", "RADIO-WW-RTTY", "RTTY", 2, ["CountryPrefix", "Sect"], 1, 1, "3.0"),
["RDAC"] = new("Russian District Award Contest", "RDAC", "Both", 3, ["CountryPrefix", "Sect"], 1, 1, "3.0"),
["YOHFDX"] = new("Romanian Amateur Radio Federation - YO DX HF", "YO-DX-HF", "Both", 3, ["CountryPrefix", "Sect"], 1, 1, "3.0"),
["OCEANIACW"] = new("Oceania DX CW", "Oceania-DX-CW", "CW", 2, ["WPXPrefix"], 2, 2, "3.0"),
["OCEANIASSB"] = new("Oceania DX SSB", "Oceania-DX-SSB", "SSB", 2, ["WPXPrefix"], 2, 2, "3.0"),
["FDREG1"] = new("Internatl Amateur Radio Union, Region 1 Field Day", "FIELDDAY-REGION-1", "Both", 2, ["CountryPrefix"], 1, 1, "3.0"),
["SARTGRTTY"] = new("Scandinavian Amateur Radio, WorldWide RTTY", "SARTG-RTTY", "RTTY", 2, ["CountryPrefix", "Sect"], 2, 1, "3.0"),
["ALLASIACW"] = new("All Asian DX contest CW", "AADX-CW", "CW", 2, ["WPXPrefix"], 2, 1, "3.0"),
["ALLASIASSB"] = new("All Asian DX contest SSB", "AADX-SSB", "SSB", 2, ["WPXPrefix"], 2, 1, "3.0"),
["DX"] = new("General Logging", "DX", "Both", 3, ["CountryPrefix"], 32000, 1, "3.0"),
};
public static N1mmContestRow? Find(string name) =>
Known.TryGetValue(name, out N1mmContestRow? row) ? row : null;
/// A contest not in the table gets the names its rules give.
public static IReadOnlyList<string> MultiplierNames(Contest contest, StationInfo me) =>
Find(contest.Name)?.Multipliers ?? contest.MultiplierNamesFor(me);
}
@@ -1,62 +0,0 @@
using Nonemm.Core;
namespace Nonemm.Contests;
/// The multiplier names N1MM keeps in the `Contest` table, such as `ZN`,
/// `CountryPrefix` and `Sect`. The score broadcast names its multiplier types
/// from them, and a contest's own `MultiplierNamesFor` holds the names the
/// score window shows instead (`Zones`, `Countries`). Taken from a log N1MM
/// wrote, without the `N/A` entries.
public static class N1mmMultiplierNames
{
private static readonly Dictionary<string, IReadOnlyList<string>> Known =
new(StringComparer.OrdinalIgnoreCase)
{
["CQWWCW"] = ["ZN", "CountryPrefix"],
["CQWWSSB"] = ["ZN", "CountryPrefix"],
["CQWWRTTY"] = ["ZN", "CountryPrefix", "Sect"],
["CQWPXCW"] = ["WPXPrefix"],
["CQWPXSSB"] = ["WPXPrefix"],
["CQWPXRTTY"] = ["WPXPrefix"],
["ARRLDXCW"] = ["Sect"],
["ARRLDXSSB"] = ["Sect"],
["IARU"] = ["ZN", "HQ"],
["SSCW"] = ["Sect"],
["SSSSB"] = ["Sect"],
["WAECW"] = ["CountryPrefix"],
["WAESSB"] = ["CountryPrefix"],
["WAERTTY"] = ["CountryPrefix"],
["ARRLRTTY"] = ["Sect", "CountryPrefix"],
["NAQPCW"] = ["Sect", "NA"],
["NAQPSSB"] = ["Sect", "NA"],
["NAQPRTTY"] = ["Sect", "NA"],
["OKOMDX"] = ["Sect"],
["YOTA"] = ["Exchange"],
["IOTA"] = ["Sect"],
["EUDXC"] = ["Sect", "CountryPrefix"],
["CQ160CW"] = ["StateProvince", "CountryPrefix"],
["CQ160SSB"] = ["StateProvince", "CountryPrefix"],
["XERTTY"] = ["Countryprefix", "Sect"],
["REFCW"] = ["Sect", "CountryPrefix"],
["REFSSB"] = ["Sect", "CountryPrefix"],
["UKRAINDX"] = ["CountryPrefix", "Sect"],
["BARTGRTTYS"] = ["CountryPrefix", "Sect", "Continent"],
["ARRL10M"] = ["StateProvince", "CountryPrefix"],
["RUSDXRTTY"] = ["CountryPrefix", "Sect"],
["RDAC"] = ["CountryPrefix", "Sect"],
["YOHFDX"] = ["CountryPrefix", "Sect"],
["OCEANIACW"] = ["WPXPrefix"],
["OCEANIASSB"] = ["WPXPrefix"],
["FDREG1"] = ["CountryPrefix"],
["SARTGRTTY"] = ["CountryPrefix", "Sect"],
["ALLASIACW"] = ["WPXPrefix"],
["ALLASIASSB"] = ["WPXPrefix"],
["DX"] = ["CountryPrefix"],
};
/// A contest not in the table gets the names its rules give.
public static IReadOnlyList<string> For(Contest contest, StationInfo me) =>
Known.TryGetValue(contest.Name, out IReadOnlyList<string>? names)
? names
: contest.MultiplierNamesFor(me);
}
+20 -2
View File
@@ -5,12 +5,30 @@ using Nonemm.Storage;
namespace Nonemm.Session;
/// Turns a contest's rules into the definition row N1MM keeps in the `Contest`
/// table, which it reads when it opens a log.
/// table, which it reads when it opens a log. A built-in contest takes N1MM's
/// own row; any other is worked out from its rules.
public static class ContestDefinitions
{
public static ContestDefinition For(Contest contest, StationInfo me)
{
IReadOnlyList<string> mults = N1mmMultiplierNames.For(contest, me);
if (N1mmContestRows.Find(contest.Name) is { } row)
{
return new ContestDefinition
{
Name = contest.Name,
DisplayName = row.DisplayName,
CabrilloName = row.CabrilloName,
Mode = row.Mode,
DupeType = row.DupeType,
Multiplier1Name = NameAt(row.Multipliers, 0),
Multiplier2Name = NameAt(row.Multipliers, 1),
Multiplier3Name = NameAt(row.Multipliers, 2),
Period = row.Period,
PointsPerContact = row.PointsPerContact,
CabrilloVersion = row.CabrilloVersion,
};
}
IReadOnlyList<string> mults = contest.MultiplierNamesFor(me);
return new ContestDefinition
{
Name = contest.Name,