Files
Nonemm/src/Nonemm.Contests/Rules/AllAsian.cs
T
erikbrockoandClaude Opus 5 e0f307968e Mark a dupe in the contact type column, as N1MM does
N1MM writes a contact type on every contact it logs: `D` for a station
already worked, `N` for one the entry may not work at all, and empty for an
ordinary contact. Nothing here wrote the column, so a dupe counted as a
contact worth no points instead of dropping out of the score, and the two
places that read the column did nothing: the QTC pile offered dupes that
N1MM leaves out, and the statistics window could not leave dupes out either.

The marking is in one place, `ContestLog.ApplyVerdict`, and every contest is
marked the same way. What changes between contests is what counts as a dupe
and which stations are workable, and both come from the contest, as they do
in N1MM's `Contact.IsDupe`. No contest module in N1MM marks differently. Our
own callsign is not workable either, which is what N1MM adds on top of the
contest rule.

Two details follow N1MM as well: QTC traffic keeps an empty column, because
its `IsDupe` only marks a row while the program is logging contacts; and a
mark that came with the contact stands, because N1MM marks a contact when it
logs or edits one and never clears marks in a pass over the log. Editing a
contact clears the column so it is worked out again, which is what N1MM does
at the same point: an edit can move a contact to a band where it is no
longer a dupe.

A dupe is now out of the score altogether — contacts, points and multipliers
— which is N1MM's `ScoreClass.AddContact`. The QSO count still counts WAE
QTC rows as contacts; that is the other half of the scoring audit's item 14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-10 19:38:57 +00:00

83 lines
2.8 KiB
C#

using Nonemm.Core;
namespace Nonemm.Contests.Rules;
/// The JARL All Asian DX contest. Only contacts with Asia count for a station
/// outside it. The exchange is the operator's age, the points go by band, and
/// each prefix worked counts once per band. An Asian station scores three
/// times what a station outside Asia scores for the same contact.
public sealed class AllAsian : Contest
{
private readonly ModeCategory mode;
public AllAsian(ModeCategory mode) => this.mode = mode;
public string Name => mode == ModeCategory.Cw ? "ALLASIACW" : "ALLASIASSB";
public string DisplayName => $"All Asian DX {ModeLabel()}";
public string CabrilloName => mode == ModeCategory.Cw ? "AADX-CW" : "AADX-SSB";
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
[
new ExchangeField("Snt", ExchangeSlot.SentReport, ExchangeFieldKind.Report, IsRequired: false),
new ExchangeField("Rcv", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
new ExchangeField("Age", ExchangeSlot.Exchange1, ExchangeFieldKind.Number) { Width = 3 },
];
public IReadOnlyList<string> MultiplierNames => ["Prefixes"];
public DupeScope DupeScope => DupeScope.PerBand;
public bool HasSerialNumbers => false;
public IReadOnlyList<ModeCategory> Modes => [mode];
public string SentExchangeFor(StationInfo me) => "";
public int PointsFor(QsoContext qso)
{
if (!IsWorkable(qso))
{
return 0;
}
return PointsOn(qso.Band) * (qso.Me.Continent == "AS" ? 3 : 1);
}
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso) =>
IsWorkable(qso) && qso.Qso.Call.WpxPrefix() is { } prefix
? [new Multiplier(1, prefix, qso.Band?.Name ?? "")]
: [];
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(entry.SentExchange, 3),
],
[
new CabrilloField(qso.Call.Text, 13),
new CabrilloField(qso.ReceivedReport, 3),
new CabrilloField(qso.Exchange1, 3),
]);
/// One side of every contact has to be in Asia.
public bool IsWorkable(QsoContext qso) =>
qso.Continent == "AS" || qso.Me.Continent == "AS";
/// 160 metres is worth 3, 80 and 10 metres 2, and every other band 1.
private static int PointsOn(Band? band) => band?.Name switch
{
"160M" => 3,
"80M" or "10M" => 2,
null => 0,
_ => 1,
};
private string ModeLabel() => mode == ModeCategory.Cw ? "CW" : "SSB";
}