Act on the .udc session, off-time and band-change settings
Four settings were read by nothing. MultipleSessions = 0000/30 runs the contest in half-hour sessions from 00:00 UTC. The session is part of the dupe key, so a station worked in an earlier one may be worked again, and ResetMultsEverySession and ResetSNEverySession start the multipliers and the serial numbers over. DupeQSOMinutesAgo is the other way back to a station: after so many minutes rather than in the next session. The log holds the time of the last contact per dupe key instead of only the key, and the setting's IgnoreBand and ThisMode decide the scope, because a file that uses it turns DupeType off. MinimumOffTime drives the time on and time off the score summary now shows under the score, and beside them are the band changes made against the allowance for a contest that limits them. N1MM's countdown for the minutes you have to stay on a new band needs its info window and is not here. UdcFile.Flag now takes 1 and 0 as well as True and False, which is what these settings are written as and what N1MM reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
@@ -12,7 +12,7 @@ public sealed class ContestLog
|
||||
private readonly StationInfo me;
|
||||
private readonly CountryFile? countries;
|
||||
private readonly List<Qso> qsos = [];
|
||||
private readonly HashSet<string> workedKeys = new(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, DateTime> workedKeys = new(StringComparer.Ordinal);
|
||||
private readonly HashSet<string> claimedMultipliers = new(StringComparer.Ordinal);
|
||||
private ScoreTally tally = new();
|
||||
|
||||
@@ -52,14 +52,14 @@ public sealed class ContestLog
|
||||
{
|
||||
return new Verdict(false, contest.PointsFor(context), []);
|
||||
}
|
||||
if (contest.DupeScope != DupeScope.Never && workedKeys.Contains(DupeKey(candidate)))
|
||||
if (IsWorked(candidate))
|
||||
{
|
||||
return Verdict.Dupe;
|
||||
}
|
||||
List<Multiplier> newOnes = [];
|
||||
foreach (Multiplier multiplier in contest.MultipliersFor(context))
|
||||
{
|
||||
if (!claimedMultipliers.Contains(MultiplierKey(multiplier)))
|
||||
if (!claimedMultipliers.Contains(MultiplierKey(multiplier, candidate)))
|
||||
{
|
||||
newOnes.Add(multiplier);
|
||||
}
|
||||
@@ -114,10 +114,22 @@ public sealed class ContestLog
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
public bool IsWorked(Qso candidate) =>
|
||||
contest.DupeScope != DupeScope.Never
|
||||
&& contest.IsContact(candidate)
|
||||
&& workedKeys.Contains(DupeKey(candidate));
|
||||
/// A station worked before, under the contest's dupe rule. A contest with
|
||||
/// a dupe window — N1MM's `DupeQSOMinutesAgo` — lets the same station be
|
||||
/// worked again once the window has passed.
|
||||
public bool IsWorked(Qso candidate)
|
||||
{
|
||||
if (contest.DupeScope == DupeScope.Never || !contest.IsContact(candidate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!workedKeys.TryGetValue(DupeKey(candidate), out DateTime last))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return contest.DupeWindow is not { } window
|
||||
|| (candidate.TimestampUtc - last).Duration() < window;
|
||||
}
|
||||
|
||||
/// Every contact with this call, newest first.
|
||||
public IReadOnlyList<Qso> WorkedBefore(string call) =>
|
||||
@@ -159,10 +171,10 @@ public sealed class ContestLog
|
||||
{
|
||||
return;
|
||||
}
|
||||
workedKeys.Add(DupeKey(qso));
|
||||
workedKeys[DupeKey(qso)] = qso.TimestampUtc;
|
||||
foreach (Multiplier multiplier in verdict.NewMultipliers)
|
||||
{
|
||||
claimedMultipliers.Add(MultiplierKey(multiplier));
|
||||
claimedMultipliers.Add(MultiplierKey(multiplier, qso));
|
||||
tally.AddMultiplier(multiplier);
|
||||
}
|
||||
}
|
||||
@@ -197,9 +209,13 @@ public sealed class ContestLog
|
||||
DupeScope.PerBandAndMode => $"{call}|{qso.Band?.Name}|{qso.Mode.Category}",
|
||||
_ => call,
|
||||
};
|
||||
// a station worked in an earlier session may be worked again
|
||||
key = $"{key}|{contest.SessionOf(qso.TimestampUtc)}";
|
||||
return extra.Length == 0 ? key : $"{key}|{extra}";
|
||||
}
|
||||
|
||||
private static string MultiplierKey(Multiplier multiplier) =>
|
||||
$"{multiplier.Index}|{multiplier.Value}|{multiplier.Scope}";
|
||||
private string MultiplierKey(Multiplier multiplier, Qso qso) =>
|
||||
contest.ResetsMultipliersEachSession
|
||||
? $"{multiplier.Index}|{multiplier.Value}|{multiplier.Scope}|{contest.SessionOf(qso.TimestampUtc)}"
|
||||
: $"{multiplier.Index}|{multiplier.Value}|{multiplier.Scope}";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user