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 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 MultiplierNames => IsRtty ? ["Zones", "Countries", "States"] : ["Zones", "Countries"]; public DupeScope DupeScope => DupeScope.PerBand; public bool HasSerialNumbers => false; public IReadOnlyList 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 MultipliersFor(QsoContext qso) { string band = qso.Band?.Name ?? ""; List 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", }; }