Paint a WAE station by the traffic left with it, as N1MM does

N1MM has four colours for a station in WAE rather than the usual three, and
the manual and the RTTY tutorial both spell them out. Red is a new
multiplier, as everywhere else. Green is a new station with QTC traffic
still to exchange, which takes the more-than-one-multiplier colour because
WAE has no second multiplier to use it. Black is a station already worked
that still has traffic left — worth working for the QTC points alone — and
it is a colour of its own, N1MM's "Color of a QTC callsign in the WAE
contest". Grey is a dupe with nothing left, the same as in other contests.

The verdict carries how much traffic is left with the station, so the
callsign box, the check window, the bandmap and everything else that colours
a station say the same thing. The contest works the number out: ten with a
station on the other side of the contest, none with one on our own
continent, and none once the ten have gone. Every other contest answers
nought. The log counts the QTC rows per station as they are indexed rather
than counting them again for each station judged, because the bandmap judges
every spot it holds on every pass.

The verdict line says it in words as well, so a dupe reads `dupe · 7 QTCs
left` rather than `dupe`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
This commit is contained in:
2026-09-10 20:03:32 +00:00
co-authored by Claude Opus 5
parent f24cab0ae5
commit 2079bf3f59
8 changed files with 136 additions and 8 deletions
+19 -2
View File
@@ -13,6 +13,11 @@ public sealed class ContestLog
private readonly CountryFile? countries;
private readonly List<Qso> qsos = [];
private readonly Dictionary<string, DateTime> workedKeys = new(StringComparer.Ordinal);
/// How many QTC lines have gone with each station, either way. It is kept
/// here rather than counted when a station is judged, because the bandmap
/// judges every spot it holds on every pass.
private readonly Dictionary<string, int> trafficWith = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> claimedMultipliers = new(StringComparer.Ordinal);
private readonly Dictionary<ScoreKey, ScoreTally> rows = [];
private ScoreTally tally = new();
@@ -56,6 +61,10 @@ public sealed class ContestLog
public int TotalScore => contest.TotalScore(tally, Entry);
/// How many QTC lines have already gone with this station, either way.
public int TrafficWith(string call) =>
trafficWith.TryGetValue(call.Trim(), out int count) ? count : 0;
/// What logging this contact right now would do, without logging it.
public Verdict Judge(Qso candidate)
{
@@ -68,9 +77,10 @@ public sealed class ContestLog
{
return new Verdict(false, contest.PointsFor(context), []);
}
int trafficLeft = contest.TrafficLeft(context, TrafficWith(candidate.Call.Text));
if (IsWorked(candidate))
{
return Verdict.Dupe;
return Verdict.Dupe with { TrafficLeft = trafficLeft };
}
List<Multiplier> newOnes = [];
foreach (Multiplier multiplier in contest.MultipliersFor(context))
@@ -83,7 +93,11 @@ public sealed class ContestLog
// a contest can pay a bonus for a new multiplier, so the flags have to
// be set before the points are worked out
QsoContext scored = ContextFor(ApplyMultiplierFlags(candidate, newOnes));
return new Verdict(false, IsContestMode(candidate) ? contest.PointsFor(scored) : 0, newOnes);
return new Verdict(
false,
IsContestMode(candidate) ? contest.PointsFor(scored) : 0,
newOnes,
trafficLeft);
}
/// Adds the contact and returns it with points and multiplier flags filled in.
@@ -234,6 +248,8 @@ public sealed class ContestLog
}
if (!contest.IsContact(qso))
{
string station = qso.Call.Text.Trim();
trafficWith[station] = trafficWith.GetValueOrDefault(station) + 1;
return;
}
workedKeys[DupeKey(qso)] = qso.TimestampUtc;
@@ -270,6 +286,7 @@ public sealed class ContestLog
private void Rebuild()
{
workedKeys.Clear();
trafficWith.Clear();
claimedMultipliers.Clear();
rows.Clear();
tally = new ScoreTally();