Add the twelve contests left in the station's log

REF, the Ukrainian DX contest, the BARTG Sprint, ARRL 10M, Russian DX RTTY,
RDAC, YO DX HF, Oceania DX, IARU Region 1 Field Day, SARTG RTTY, All Asian and
SA 10. Every contest name in the log now opens, and every one of these but
Field Day scores its log contact for contact as N1MM did.

Most are written from N1MM's own class and checked against the log. Three
things the log settled that the class does not say plainly: ARRL 10M counts its
multipliers once per mode rather than once, the BARTG Sprint counts a continent
once in the contest while its countries and call areas count per band, and RDAC
counts nothing but the districts and scores nothing for a station that sends
none.

All Asian and SA 10 have no class in N1MM, which logs them from a .udc file, so
those two are written from the published rules and the log. docs/unfinished.md
lists what is still not settled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
2026-08-31 09:43:03 +00:00
co-authored by Claude Opus 5
parent 7d9bc05e9a
commit 264036f0f2
24 changed files with 1208 additions and 11 deletions
+79
View File
@@ -0,0 +1,79 @@
using Nonemm.Contests.Multipliers;
using Nonemm.Core;
using Nonemm.Core.Country;
namespace Nonemm.Contests.Rules;
/// The ARRL 10 metre contest. A CW contact is 4 points and a phone contact 2.
/// US, Canadian and Mexican stations send a state or province, everyone else a
/// serial number, and both the states and the countries are multipliers, each
/// counted once per mode: the contest is one band, and CW and phone count
/// separately.
public sealed class Arrl10Metre : Contest
{
public string Name => "ARRL10M";
public string DisplayName => "ARRL 10 Metre Contest";
public string CabrilloName => "ARRL-10";
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
[
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
new ExchangeField("S/P", ExchangeSlot.Section, ExchangeFieldKind.UsStateOrCanadianProvince),
new ExchangeField("Nr", ExchangeSlot.Exchange1, ExchangeFieldKind.Number),
];
public bool SkipsField(ExchangeField field, CountryLookup? their) => field.Slot switch
{
ExchangeSlot.Section => their is not null && !SendsState(their.Entity.PrimaryPrefix),
ExchangeSlot.Exchange1 => their is not null && SendsState(their.Entity.PrimaryPrefix),
_ => false,
};
public IReadOnlyList<string> MultiplierNames => ["States", "Countries"];
public DupeScope DupeScope => DupeScope.PerMode;
public bool HasSerialNumbers => true;
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Cw, ModeCategory.Phone];
public string SentExchangeFor(StationInfo me) =>
SendsState(me.CountryPrefix)
? me.State.Length > 0 ? me.State : me.Province
: "001";
public int PointsFor(QsoContext qso) => qso.ModeCategory == ModeCategory.Cw ? 4 : 2;
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
{
string mode = qso.ModeCategory.ToString();
string area = StatesAndProvinces.ArrlDxArea(qso.Qso.Section);
if (StatesAndProvinces.ArrlDxMultipliers.Contains(area))
{
return [new Multiplier(1, area, mode)];
}
return qso.CountryPrefix.Length == 0 ? [] : [new Multiplier(2, qso.CountryPrefix, mode)];
}
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
tally.Points * tally.TotalMultipliers;
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
new(
[
new CabrilloField(me.Callsign, 13),
new CabrilloField(qso.SentReport, 3),
new CabrilloField(SentExchangeFor(me), 6),
],
[
new CabrilloField(qso.Call.Text, 13),
new CabrilloField(qso.ReceivedReport, 3),
new CabrilloField(qso.Section.Length > 0 ? qso.Section : qso.Exchange1, 6),
]);
/// The states and provinces of the US, Canada and Mexico are the exchange;
/// the rest of the world sends a number.
private static bool SendsState(string countryPrefix) => countryPrefix is "K" or "VE" or "XE";
}