Let the published .udc files score the contests they are written for
I wrote SA 10 and OK/OM DX SSB in code after two failed attempts at the download. The download works — the page posts a nonce with the form — and the files say I got both contests wrong. SA 10 is deleted from the code. Its file scores the station's log to the contact, where the code had the wrong Cabrillo name and counted its multipliers once in the contest rather than once per mode. OK/OM DX now covers the CW running only, which is what N1MM's class is for and what N1MM's own documentation says: the SSB running has different rules and a pair of .udc files, one for each side. The CW rules go back to the class, so Czechia and Slovakia are one side of the contest again. With the SSB files in place both of the station's SSB logs score to the contact. Reading those files turned up four things in the .udc reader: a file whose extension is upper case was passed over, `Country` was not read as a multiplier source, `OtherCountry` was not a points condition, and a section multiplier counted the serial numbers the other side of a contest sends. All Asian stays in code — N1MM has a class for it after all — with the Cabrillo name and the band table it uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
@@ -52,4 +52,21 @@ public class N1mmContestNamesTests
|
||||
[Fact]
|
||||
public void HasFindsAContestByTheNameN1mmWrote() =>
|
||||
Assert.True(Registry().Has("CQWPXRTTY"));
|
||||
|
||||
/// Published `.udc` files come with the extension in either case, and a
|
||||
/// case-sensitive file system will not match one glob for both.
|
||||
[Fact]
|
||||
public void AUdcFileIsReadWhateverTheCaseOfItsExtension()
|
||||
{
|
||||
string folder = Directory.CreateTempSubdirectory().FullName;
|
||||
File.WriteAllText(Path.Combine(folder, "Loud.UDC"), "[Contest]\nName=LOUD\n");
|
||||
File.WriteAllText(Path.Combine(folder, "quiet.udc"), "[Contest]\nName=QUIET\n");
|
||||
|
||||
ContestRegistry registry = ContestRegistry.FromFolder(folder, out IReadOnlyList<string> problems);
|
||||
|
||||
Assert.Empty(problems);
|
||||
Assert.True(registry.Has("LOUD"));
|
||||
Assert.True(registry.Has("QUIET"));
|
||||
Directory.Delete(folder, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,23 @@ namespace Nonemm.Contests.Tests;
|
||||
|
||||
public class OkOmDxTests
|
||||
{
|
||||
private static readonly Contest Ssb = new OkOmDx(ModeCategory.Phone);
|
||||
private static readonly Contest Cw = new OkOmDx();
|
||||
|
||||
private static ContestLog LogFor(StationInfo me) => new(Ssb, me, TestLog.CountryFile);
|
||||
private static ContestLog LogFor(StationInfo me) => new(Cw, me, TestLog.CountryFile);
|
||||
|
||||
private static Qso FromOkOm(string call, string county, double kilohertz = 14_025) =>
|
||||
TestLog.Contact(call, kilohertz, mode: Modes.Usb, section: county);
|
||||
TestLog.Contact(call, kilohertz, section: county);
|
||||
|
||||
private static Qso FromDx(string call, string number, double kilohertz = 14_025) =>
|
||||
TestLog.Contact(call, kilohertz, mode: Modes.Usb, exchange: number);
|
||||
TestLog.Contact(call, kilohertz, exchange: number);
|
||||
|
||||
/// The CW running counts Czechia and Slovakia as one side of the contest:
|
||||
/// two points either way. The SSB running splits them, and it has its own
|
||||
/// pair of .udc files.
|
||||
[Theory]
|
||||
[InlineData("OM3XYZ", 2)]
|
||||
[InlineData("OK1XYZ", 3)]
|
||||
public void AtHomeCzechiaAndSlovakiaAreTwoCountries(string call, int expected) =>
|
||||
[InlineData("OK1XYZ", 2)]
|
||||
public void EitherSideOfTheContestIsTwoPointsAtHome(string call, int expected) =>
|
||||
Assert.Equal(expected, LogFor(TestLog.Slovakia).Judge(FromOkOm(call, "NOV")).Points);
|
||||
|
||||
[Theory]
|
||||
@@ -36,40 +39,39 @@ public class OkOmDxTests
|
||||
public void AStationOutsideScoresTenForAnOkOmContact(string call, int expected) =>
|
||||
Assert.Equal(expected, LogFor(TestLog.Germany).Judge(FromOkOm(call, "NOV")).Points);
|
||||
|
||||
/// An OK or OM operator counts the prefixes it works and nothing for the
|
||||
/// other side of the contest.
|
||||
[Fact]
|
||||
public void CountriesAndCountiesBothCountOncePerBand()
|
||||
public void AnOkOmOperatorCountsPrefixes()
|
||||
{
|
||||
ContestLog log = LogFor(TestLog.Slovakia);
|
||||
Assert.Equal(
|
||||
[1, 2],
|
||||
log.Judge(FromOkOm("OM3XYZ", "NOV")).NewMultipliers.Select(m => m.Index).ToList());
|
||||
log.Add(FromOkOm("OM3XYZ", "NOV"));
|
||||
Assert.Empty(log.Judge(FromOkOm("OM4XYZ", "NOV")).NewMultipliers);
|
||||
Assert.Single(log.Judge(FromOkOm("OM4XYZ", "PAR")).NewMultipliers);
|
||||
Assert.Equal("IK2", log.Judge(FromDx("IK2XYZ", "014")).NewMultipliers.Single().Value);
|
||||
Assert.Empty(log.Judge(FromOkOm("OM3XYZ", "NOV")).NewMultipliers);
|
||||
}
|
||||
|
||||
/// A station outside counts the counties instead.
|
||||
[Fact]
|
||||
public void AStationOutsideOkOmBringsItsCountryAndNoCounty() =>
|
||||
Assert.Equal(
|
||||
[1],
|
||||
LogFor(TestLog.Slovakia).Judge(FromDx("IK2XYZ", "014")).NewMultipliers
|
||||
.Select(m => m.Index)
|
||||
.ToList());
|
||||
public void EveryoneElseCountsCounties()
|
||||
{
|
||||
ContestLog log = LogFor(TestLog.Germany);
|
||||
Assert.Equal("NOV", log.Judge(FromOkOm("OM3XYZ", "NOV")).NewMultipliers.Single().Value);
|
||||
Assert.Empty(log.Judge(FromDx("IK2XYZ", "014")).NewMultipliers);
|
||||
}
|
||||
|
||||
/// The entry window walks the county box for an OK or OM station and the
|
||||
/// serial box for everyone else.
|
||||
[Fact]
|
||||
public void OnlyOneOfTheTwoBoxesIsWalked()
|
||||
{
|
||||
IReadOnlyList<ExchangeField> fields = Ssb.ExchangeFieldsFor(TestLog.Slovakia);
|
||||
IReadOnlyList<ExchangeField> fields = Cw.ExchangeFieldsFor(TestLog.Slovakia);
|
||||
CountryLookup? okom = TestLog.CountryFile.Find("OM3XYZ");
|
||||
CountryLookup? dx = TestLog.CountryFile.Find("IK2XYZ");
|
||||
|
||||
Assert.Equal(
|
||||
[ExchangeSlot.ReceivedReport, ExchangeSlot.Exchange1],
|
||||
fields.Where(f => !Ssb.SkipsField(f, dx)).Select(f => f.Slot).ToList());
|
||||
fields.Where(f => !Cw.SkipsField(f, dx)).Select(f => f.Slot).ToList());
|
||||
Assert.Equal(
|
||||
[ExchangeSlot.ReceivedReport, ExchangeSlot.Section],
|
||||
fields.Where(f => !Ssb.SkipsField(f, okom)).Select(f => f.Slot).ToList());
|
||||
fields.Where(f => !Cw.SkipsField(f, okom)).Select(f => f.Slot).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,12 @@ public class SmallContestTests
|
||||
.Judge(TestLog.Contact("K1ABC", mode: Modes.Rtty, section: "K1"))
|
||||
.NewMultipliers.Select(m => m.Index).ToList());
|
||||
|
||||
/// 160 metres pays 3, 80 and 10 metres 2, and the rest 1.
|
||||
[Theory]
|
||||
[InlineData(14_150, 1)]
|
||||
[InlineData(7_050, 2)]
|
||||
[InlineData(3_650, 3)]
|
||||
[InlineData(7_050, 1)]
|
||||
[InlineData(3_650, 2)]
|
||||
[InlineData(1_830, 3)]
|
||||
public void AllAsianScoresByBand(double kilohertz, int expected) =>
|
||||
Assert.Equal(
|
||||
expected,
|
||||
@@ -47,16 +49,6 @@ public class SmallContestTests
|
||||
.Judge(TestLog.Contact("IK2XYZ", mode: Modes.Usb, exchange: "46"))
|
||||
.Points);
|
||||
|
||||
[Theory]
|
||||
[InlineData("PY2XYZ", 4)]
|
||||
[InlineData("IK2XYZ", 2)]
|
||||
public void SouthAmericaTenPaysMoreForSouthAmerica(string call, int expected) =>
|
||||
Assert.Equal(
|
||||
expected,
|
||||
LogFor(new SouthAmerica10())
|
||||
.Judge(TestLog.Contact(call, 28_400, mode: Modes.Usb, section: "11"))
|
||||
.Points);
|
||||
|
||||
[Theory]
|
||||
[InlineData("YO3XYZ", 8)]
|
||||
[InlineData("DL9XYZ", 1)]
|
||||
|
||||
@@ -261,4 +261,35 @@ public class UdcScoringTests
|
||||
Assert.True(Contest("ZoneType=IARU").UsesItuZones);
|
||||
Assert.False(Contest("ZoneType=CQ").UsesItuZones);
|
||||
}
|
||||
|
||||
/// The received exchange of a contest where one side sends a section and
|
||||
/// the other a serial number lands in the same column, and only the
|
||||
/// section counts. N1MM checks it against the contest's section list; the
|
||||
/// lists are not held here, so an exchange that is all digits is taken for
|
||||
/// a serial number.
|
||||
[Fact]
|
||||
public void ASerialNumberIsNoSectionMultiplier()
|
||||
{
|
||||
UserDefinedContest contest = Contest("MultSqlString=Section", "IsMultPer=1");
|
||||
Assert.Empty(LogFor(contest).Judge(TestLog.Contact("IK2XYZ", section: "058")).NewMultipliers);
|
||||
Assert.Single(LogFor(contest).Judge(TestLog.Contact("OM3XYZ", section: "NOV")).NewMultipliers);
|
||||
}
|
||||
|
||||
/// `Country` is what a published file writes where the editor writes
|
||||
/// `CountryPrefix`.
|
||||
[Fact]
|
||||
public void CountryIsTheSameSourceAsCountryPrefix() =>
|
||||
Assert.Equal(
|
||||
"I",
|
||||
LogFor(Contest("MultSqlString=Country", "IsMultPer=4"))
|
||||
.Judge(TestLog.Contact("IK2XYZ"))
|
||||
.NewMultipliers.Single().Value);
|
||||
|
||||
[Fact]
|
||||
public void OtherCountryIsEveryCountryButMine()
|
||||
{
|
||||
UserDefinedContest contest = Contest("PointsPerContact=MyCountry, 0, OtherCountry, 2");
|
||||
Assert.Equal(0, Points(contest, TestLog.Contact("DL9XYZ")));
|
||||
Assert.Equal(2, Points(contest, TestLog.Contact("JA1XYZ")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user