The entry window had a fixed set of boxes with a Name and Comment box beside the exchange. N1MM lays out one row of boxes per contest and nothing else, so this does the same: general logging keeps Name and Comment, and a contest gets the boxes it exchanges. Two new members on `Contest` carry the cases where the boxes and the log columns are not the same list: - `FromExchange1` spreads what was typed in one box across the log columns. Where the other station sends a serial or a district code depending on where it is, N1MM gives it one box and sorts the value into the number, section or zone column from there, so the score and the Cabrillo file read it from the same column N1MM does. - `LogFieldsFor` gives the log window its own column list. Sweepstakes takes the whole exchange in one box and logs it in four columns; `SweepstakesExchange` parses `72 A 74 SCV` in any order, reading from the last word back as N1MM does. The sent serial is now marked as not required and space steps over it along with the two report boxes, because nobody types the number going out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
165 lines
6.1 KiB
C#
165 lines
6.1 KiB
C#
using Nonemm.Contests.Multipliers;
|
|
using Nonemm.Core;
|
|
using Nonemm.Core.Country;
|
|
|
|
namespace Nonemm.Contests.Rules;
|
|
|
|
/// CQ World Wide DX, CW, SSB and RTTY. Points by continent, multipliers are
|
|
/// zones and countries counted once per band. The country list is DXCC plus
|
|
/// WAE, which is what `wl_cty.dat` holds.
|
|
///
|
|
/// RTTY is a different contest wearing the same name: the exchange carries the
|
|
/// state or province as well as the zone, that is a third multiplier, and every
|
|
/// contact scores — a contact inside your own country counts 1 rather than 0.
|
|
public sealed class CqWorldWide : Contest
|
|
{
|
|
private readonly ModeCategory mode;
|
|
|
|
public CqWorldWide(ModeCategory mode) => this.mode = mode;
|
|
|
|
public string Name => mode switch
|
|
{
|
|
ModeCategory.Phone => "CQWWSSB",
|
|
ModeCategory.Digital => "CQWWRTTY",
|
|
_ => "CQWWCW",
|
|
};
|
|
|
|
public string DisplayName => $"CQ World Wide DX {ModeLabel()}";
|
|
|
|
public string CabrilloName => mode switch
|
|
{
|
|
ModeCategory.Cw => "CQ-WW-CW",
|
|
ModeCategory.Phone => "CQ-WW-SSB",
|
|
_ => "CQ-WW-RTTY",
|
|
};
|
|
|
|
/// The state or province box is not required: a station outside the US and
|
|
/// Canada sends `DX` there, and holding up the log for it would cost more
|
|
/// than it is worth.
|
|
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) => IsRtty
|
|
?
|
|
[
|
|
new ExchangeField("Snt", ExchangeSlot.SentReport, ExchangeFieldKind.Report, IsRequired: false),
|
|
new ExchangeField("Rcv", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
|
new ExchangeField("CQ-Zone", ExchangeSlot.Zone, ExchangeFieldKind.CqZone),
|
|
new ExchangeField("State/RAC", ExchangeSlot.Section, ExchangeFieldKind.UsStateOrCanadianProvince, IsRequired: false),
|
|
]
|
|
:
|
|
[
|
|
new ExchangeField("Snt", ExchangeSlot.SentReport, ExchangeFieldKind.Report, IsRequired: false),
|
|
new ExchangeField("Rcv", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
|
new ExchangeField("CQ-Zone", ExchangeSlot.Zone, ExchangeFieldKind.CqZone),
|
|
];
|
|
|
|
/// On RTTY the exchange carries a state or province, but only from the US
|
|
/// and Canada; the box is stepped over for everybody else. N1MM's entry
|
|
/// window does the same, and the box is still there to type in.
|
|
public bool SkipsField(ExchangeField field, CountryLookup? their) =>
|
|
IsRtty
|
|
&& field.Slot == ExchangeSlot.Section
|
|
&& their is not null
|
|
&& their.Entity.PrimaryPrefix is not ("K" or "VE");
|
|
|
|
public IReadOnlyList<string> MultiplierNames =>
|
|
IsRtty ? ["Zones", "Countries", "States"] : ["Zones", "Countries"];
|
|
|
|
public DupeScope DupeScope => DupeScope.PerBand;
|
|
|
|
public bool HasSerialNumbers => false;
|
|
|
|
public IReadOnlyList<ModeCategory> Modes => [mode];
|
|
|
|
/// The report is fixed for the whole contest, so the sent exchange is the
|
|
/// zone alone, which is also what N1MM stores. RTTY sends the state or
|
|
/// province after it, and `DX` from anywhere else.
|
|
public string SentExchangeFor(StationInfo me) =>
|
|
IsRtty ? $"{me.CqZone} {HomeOf(me)}" : me.CqZone.ToString();
|
|
|
|
/// A maritime mobile station counts for no country, and still scores by
|
|
/// the continent it is operating from, which is what N1MM logs.
|
|
public int PointsFor(QsoContext qso)
|
|
{
|
|
if (IsRtty)
|
|
{
|
|
return qso.IsSameCountry && qso.Qso.Call.CountsForEntity ? 1
|
|
: qso.IsSameContinent ? 2
|
|
: 3;
|
|
}
|
|
if (qso.IsSameCountry && qso.Qso.Call.CountsForEntity)
|
|
{
|
|
return 0;
|
|
}
|
|
if (!qso.IsSameContinent)
|
|
{
|
|
return 3;
|
|
}
|
|
return qso.Continent == "NA" ? 2 : 1;
|
|
}
|
|
|
|
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
|
{
|
|
string band = qso.Band?.Name ?? "";
|
|
List<Multiplier> found = [];
|
|
if (qso.Qso.Zone > 0)
|
|
{
|
|
found.Add(new Multiplier(1, qso.Qso.Zone.ToString(), band));
|
|
}
|
|
found.Add(new Multiplier(2, qso.CountryPrefix, band));
|
|
string home = qso.Qso.Section.Trim().ToUpperInvariant();
|
|
if (IsRtty && StatesAndProvinces.CqWwRttyAreas.Contains(home))
|
|
{
|
|
found.Add(new Multiplier(3, home, band));
|
|
}
|
|
return found;
|
|
}
|
|
|
|
public int TotalScore(ScoreTally tally, ContestEntry entry) => tally.Points * tally.TotalMultipliers;
|
|
|
|
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) => IsRtty
|
|
? new CabrilloExchange(
|
|
[
|
|
new CabrilloField(me.Callsign, 13),
|
|
new CabrilloField(qso.SentReport, 3),
|
|
new CabrilloField(me.CqZone.ToString(), 3),
|
|
new CabrilloField(HomeOf(me), 6),
|
|
],
|
|
[
|
|
new CabrilloField(qso.Call.Text, 13),
|
|
new CabrilloField(qso.ReceivedReport, 3),
|
|
new CabrilloField(qso.Zone.ToString(), 3),
|
|
new CabrilloField(Home(qso.Section), 6),
|
|
])
|
|
: new CabrilloExchange(
|
|
[
|
|
new CabrilloField(me.Callsign, 13),
|
|
new CabrilloField(qso.SentReport, 3),
|
|
new CabrilloField(me.CqZone.ToString(), 6),
|
|
],
|
|
[
|
|
new CabrilloField(qso.Call.Text, 13),
|
|
new CabrilloField(qso.ReceivedReport, 3),
|
|
new CabrilloField(qso.Zone.ToString(), 6),
|
|
]);
|
|
|
|
private bool IsRtty => mode == ModeCategory.Digital;
|
|
|
|
/// What goes in the state column: the state or province, and `DX` for
|
|
/// everywhere else, which is what the contest asks a DX station to send.
|
|
private static string HomeOf(StationInfo me) =>
|
|
Home(me.State.Length > 0 ? me.State : me.Province);
|
|
|
|
private static string Home(string text)
|
|
{
|
|
string value = text.Trim().ToUpperInvariant();
|
|
return StatesAndProvinces.IsStateOrProvince(value) ? value : "DX";
|
|
}
|
|
|
|
private string ModeLabel() => mode switch
|
|
{
|
|
ModeCategory.Cw => "CW",
|
|
ModeCategory.Phone => "SSB",
|
|
_ => "RTTY",
|
|
};
|
|
|
|
}
|