A file is written to; it does not write, score or name anything. Five comments and doc lines said otherwise, two of them older than today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
296 lines
11 KiB
C#
296 lines
11 KiB
C#
using Nonemm.Contests.Udc;
|
|
using Nonemm.Core;
|
|
|
|
namespace Nonemm.Contests.Tests;
|
|
|
|
/// The scoring settings a `.udc` file can carry: the points conditions, the
|
|
/// points multipliers, the bonus, and where a multiplier comes from.
|
|
public class UdcScoringTests
|
|
{
|
|
private static UserDefinedContest Contest(params string[] settings) =>
|
|
new(UdcFile.Parse("[Contest]\nName=SCORETEST\n" + string.Join("\n", settings)));
|
|
|
|
private static ContestLog LogFor(
|
|
UserDefinedContest contest,
|
|
StationInfo? me = null,
|
|
ContestEntry? entry = null) =>
|
|
new(contest, me ?? TestLog.Germany, TestLog.CountryFile, entry);
|
|
|
|
private static int Points(UserDefinedContest contest, Qso qso, StationInfo? me = null) =>
|
|
LogFor(contest, me).Judge(qso).Points;
|
|
|
|
[Theory]
|
|
[InlineData("CT", 5)]
|
|
[InlineData("MA", 1)]
|
|
public void SectIsMatchesTheReceivedSection(string section, int expected) =>
|
|
Assert.Equal(
|
|
expected,
|
|
Points(
|
|
Contest("PointsPerContact=SectIs_CT, 5, OtherContinent, 1"),
|
|
TestLog.Contact("K1ABC", section: section)));
|
|
|
|
[Fact]
|
|
public void ExchIsNumSeparatesANumberFromAWord() =>
|
|
Assert.Equal(
|
|
3,
|
|
Points(
|
|
Contest("PointsPerContact=ExchIsNum, 3, ExchIsNotNum, 7"),
|
|
TestLog.Contact("K1ABC", exchange: "129")));
|
|
|
|
[Fact]
|
|
public void ExchIsEmptyScoresTheContactWithNoExchange() =>
|
|
Assert.Equal(
|
|
2,
|
|
Points(Contest("PointsPerContact=ExchIs_Empty, 2, ExchIsNum, 9"), TestLog.Contact("K1ABC")));
|
|
|
|
[Fact]
|
|
public void AStarScoresTheNumberInTheExchangeBox() =>
|
|
Assert.Equal(
|
|
42,
|
|
Points(Contest("PointsPerContact=Exchange, *"), TestLog.Contact("K1ABC", exchange: "42")));
|
|
|
|
[Fact]
|
|
public void MyCqZoneScoresAStationInMyOwnZone() =>
|
|
Assert.Equal(
|
|
1,
|
|
Points(Contest("PointsPerContact=MyCQZone, 1, OtherContinent, 4"), TestLog.Contact("DL9XYZ")));
|
|
|
|
/// `SAMECONTINENT+160` is the same continent on one band only.
|
|
[Fact]
|
|
public void AContinentConditionCanCarryABand()
|
|
{
|
|
UserDefinedContest contest = Contest("PointsPerContact=SameContinent+160, 6, SameContinent, 2");
|
|
Assert.Equal(6, Points(contest, TestLog.Contact("IK2XYZ", 1_830)));
|
|
Assert.Equal(2, Points(contest, TestLog.Contact("IK2XYZ")));
|
|
}
|
|
|
|
[Fact]
|
|
public void PointsMultByBandScalesWhatTheContactScores()
|
|
{
|
|
UserDefinedContest contest = Contest("PointsPerContact=3", "PointsMultByBand=160M, 3, 20M, 1");
|
|
Assert.Equal(9, Points(contest, TestLog.Contact("IK2XYZ", 1_830)));
|
|
Assert.Equal(3, Points(contest, TestLog.Contact("IK2XYZ")));
|
|
}
|
|
|
|
[Fact]
|
|
public void PointsMultAtTimeGmtScalesInsideItsWindow()
|
|
{
|
|
UserDefinedContest contest = Contest("PointsPerContact=2", "PointsMultAtTimeGMT=2300, 0300, 2");
|
|
DateTime midnight = new(2026, 5, 30, 0, 30, 0, DateTimeKind.Utc);
|
|
Assert.Equal(4, Points(contest, TestLog.Contact("IK2XYZ", at: midnight)));
|
|
Assert.Equal(2, Points(contest, TestLog.Contact("IK2XYZ")));
|
|
}
|
|
|
|
[Fact]
|
|
public void BonusPointsAreAddedToWhatTheContactScores() =>
|
|
Assert.Equal(
|
|
26,
|
|
Points(
|
|
Contest("PointsPerContact=1", "BonusPoints=IK2XYZ, 25"),
|
|
TestLog.Contact("IK2XYZ")));
|
|
|
|
/// A contest can pay the bonus for a new multiplier, so the multiplier has
|
|
/// to be worked out before the points are.
|
|
[Fact]
|
|
public void BonusPointsCanBePaidForANewMultiplier()
|
|
{
|
|
UserDefinedContest contest = Contest(
|
|
"PointsPerContact=1",
|
|
"MultSqlString=CountryPrefix",
|
|
"IsMultPer=1",
|
|
"BonusPoints=IsMult1, 10");
|
|
ContestLog log = LogFor(contest);
|
|
Assert.Equal(11, log.Judge(TestLog.Contact("IK2XYZ")).Points);
|
|
log.Add(TestLog.Contact("IK2XYZ"));
|
|
Assert.Equal(1, log.Judge(TestLog.Contact("IK3XYZ")).Points);
|
|
}
|
|
|
|
[Fact]
|
|
public void ADistanceTableScoresByTheRangeTheContactFallsIn()
|
|
{
|
|
UserDefinedContest contest = Contest("PointsPerContact=0/100/7");
|
|
StationInfo me = TestLog.Germany with { GridSquare = "JO60" };
|
|
Qso near = TestLog.Contact("DL9XYZ") with { GridSquare = "JO60" };
|
|
Qso far = TestLog.Contact("JA1XYZ") with { GridSquare = "PM95" };
|
|
Assert.Equal(7, Points(contest, near, me));
|
|
Assert.True(Points(contest, far, me) > 1_000);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("MultSqlString=2LPREFIX", "IK")]
|
|
[InlineData("MultSqlString=LastLetter", "Z")]
|
|
[InlineData("MultSqlString=EU_Country", "I")]
|
|
public void TheMultiplierValueComesFromTheSource(string setting, string expected)
|
|
{
|
|
UserDefinedContest contest = Contest(setting, "IsMultPer=4");
|
|
Verdict verdict = LogFor(contest).Judge(TestLog.Contact("IK2XYZ"));
|
|
Assert.Equal(expected, Assert.Single(verdict.NewMultipliers).Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void ACountryOnAnotherContinentBringsNoContinentMultiplier() =>
|
|
Assert.Empty(
|
|
LogFor(Contest("MultSqlString=EU_Country", "IsMultPer=4"))
|
|
.Judge(TestLog.Contact("JA1XYZ"))
|
|
.NewMultipliers);
|
|
|
|
[Fact]
|
|
public void CountMultOnlyForTakesAContinentAsWellAsACountry()
|
|
{
|
|
UserDefinedContest contest = Contest(
|
|
"MultSqlString=CountryPrefix",
|
|
"IsMultPer=4",
|
|
"CountMultOnlyFor=EU");
|
|
Assert.Single(LogFor(contest).Judge(TestLog.Contact("IK2XYZ")).NewMultipliers);
|
|
Assert.Empty(LogFor(contest).Judge(TestLog.Contact("JA1XYZ")).NewMultipliers);
|
|
}
|
|
|
|
[Fact]
|
|
public void DoNotCountMultOnlyForLeavesThoseStationsOut() =>
|
|
Assert.Empty(
|
|
LogFor(Contest("MultSqlString=CountryPrefix", "IsMultPer=4", "DoNotCountMultOnlyFor=JA"))
|
|
.Judge(TestLog.Contact("JA1XYZ"))
|
|
.NewMultipliers);
|
|
|
|
[Fact]
|
|
public void DoNotCountMeAsMultLeavesOutMyOwnCountry() =>
|
|
Assert.Empty(
|
|
LogFor(Contest("MultSqlString=CountryPrefix", "IsMultPer=4", "DoNotCountMeAsMult=True"))
|
|
.Judge(TestLog.Contact("DL9XYZ"))
|
|
.NewMultipliers);
|
|
|
|
[Fact]
|
|
public void MultMultSaysWhatAMultiplierIsWorth()
|
|
{
|
|
ContestLog log = LogFor(Contest(
|
|
"PointsPerContact=1",
|
|
"MultSqlString=CountryPrefix",
|
|
"IsMultPer=4",
|
|
"MultMult=2"));
|
|
log.Add(TestLog.Contact("JA1XYZ"));
|
|
log.Add(TestLog.Contact("IK2XYZ"));
|
|
Assert.Equal(8, log.TotalScore);
|
|
}
|
|
|
|
/// A multiplier weighted zero is still worked, and adds nothing.
|
|
[Fact]
|
|
public void MultMultZeroLeavesTheScoreAtThePoints()
|
|
{
|
|
ContestLog log = LogFor(Contest(
|
|
"PointsPerContact=3",
|
|
"MultSqlString=CountryPrefix",
|
|
"IsMultPer=4",
|
|
"MultMult=0"));
|
|
log.Add(TestLog.Contact("JA1XYZ"));
|
|
Assert.Equal(3, log.TotalScore);
|
|
}
|
|
|
|
[Fact]
|
|
public void PowerMultScalesTheWholeScoreByTheEntryCategory()
|
|
{
|
|
UserDefinedContest contest = Contest("PointsPerContact=2", "PowerMult=QRP, 3, LP, 2, HP, 1");
|
|
ContestLog qrp = LogFor(contest, entry: new ContestEntry { PowerCategory = "QRP" });
|
|
ContestLog low = LogFor(contest, entry: new ContestEntry { PowerCategory = "LOW" });
|
|
qrp.Add(TestLog.Contact("JA1XYZ"));
|
|
low.Add(TestLog.Contact("JA1XYZ"));
|
|
Assert.Equal(6, qrp.TotalScore);
|
|
Assert.Equal(4, low.TotalScore);
|
|
}
|
|
|
|
/// `PointsMultByCategory` is about the station being worked, not the entry:
|
|
/// N1MM scales only a callsign signing QRP.
|
|
[Fact]
|
|
public void PointsMultByCategoryScalesAStationSigningQrp()
|
|
{
|
|
UserDefinedContest contest = Contest("PointsPerContact=2", "PointsMultByCategory=/QRP, 3");
|
|
Assert.Equal(6, Points(contest, TestLog.Contact("JA1XYZ/QRP")));
|
|
Assert.Equal(2, Points(contest, TestLog.Contact("JA1XYZ")));
|
|
}
|
|
|
|
[Fact]
|
|
public void MyExchangeComparesWhatThisStationSends()
|
|
{
|
|
UserDefinedContest contest = Contest("PointsPerContact=MyExchange, 1, OtherContinent, 5");
|
|
ContestEntry entry = new() { SentExchange = "14" };
|
|
Assert.Equal(
|
|
1,
|
|
LogFor(contest, entry: entry).Judge(TestLog.Contact("JA1XYZ", section: "14")).Points);
|
|
Assert.Equal(
|
|
5,
|
|
LogFor(contest, entry: entry).Judge(TestLog.Contact("JA1XYZ", section: "15")).Points);
|
|
}
|
|
|
|
[Fact]
|
|
public void NumMultsCapsHowManyAreCounted()
|
|
{
|
|
UserDefinedContest contest = Contest(
|
|
"MultSqlString=CountryPrefix",
|
|
"MultSqlString2=Continent",
|
|
"IsMultPer=4",
|
|
"NumMults=1");
|
|
Assert.Single(LogFor(contest).Judge(TestLog.Contact("JA1XYZ")).NewMultipliers);
|
|
}
|
|
|
|
[Fact]
|
|
public void DupeSqlStringMakesASecondSectionADifferentContact()
|
|
{
|
|
UserDefinedContest contest = Contest("DupeType=1", "DupeSqlString=1");
|
|
ContestLog log = LogFor(contest);
|
|
log.Add(TestLog.Contact("K1ABC", section: "CT"));
|
|
Assert.False(log.Judge(TestLog.Contact("K1ABC", section: "MA")).IsDupe);
|
|
Assert.True(log.Judge(TestLog.Contact("K1ABC", section: "CT")).IsDupe);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsWorkableLeavesOutTheStationsTheContestDoesNotCount()
|
|
{
|
|
UserDefinedContest contest = Contest(
|
|
"PointsPerContact=3",
|
|
"MultSqlString=CountryPrefix",
|
|
"IsMultPer=4",
|
|
"IsWorkable=EUonly");
|
|
Verdict outside = LogFor(contest).Judge(TestLog.Contact("JA1XYZ"));
|
|
Assert.Equal(0, outside.Points);
|
|
Assert.Empty(outside.NewMultipliers);
|
|
Assert.Equal(3, Points(contest, TestLog.Contact("IK2XYZ")));
|
|
}
|
|
|
|
[Fact]
|
|
public void ZoneTypeIaruPutsTheContestOnItuZones()
|
|
{
|
|
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);
|
|
}
|
|
|
|
/// Published files hold `Country` in this key; the UDC editor puts
|
|
/// `CountryPrefix` there.
|
|
[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")));
|
|
}
|
|
}
|