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
83 lines
2.8 KiB
C#
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";
|
|
}
|