namespace Nonemm.Session; /// What the entry window looks like when Enter is pressed, which is all ESM /// needs to know to decide what to send. public sealed record EsmSituation { public bool IsRunning { get; init; } public bool HasCall { get; init; } /// The station has been worked before on this band and mode. public bool IsDupe { get; init; } /// Every exchange box holds something the contest accepts. public bool HasExchange { get; init; } /// Our call has gone out for this station: F4 while searching, F5 and the /// exchange while running. public bool CallSent { get; init; } public bool ExchangeSent { get; init; } /// N1MM's "big gun" switch: while searching, send the call once and then be /// ready to copy the exchange, rather than calling again. public bool SendsCallOnce { get; init; } /// N1MM's "work dupes when running", which it recommends: a station that /// calls in again is worked rather than told it is in the log. public bool WorksDupes { get; init; } } /// The function keys Enter sends, in order, and whether the contact is logged /// afterwards. No keys and no logging means Enter does nothing, which is what /// happens when a dupe calls in while searching. public sealed record EsmAction(IReadOnlyList Keys, bool Logs) { public static readonly EsmAction Nothing = new([], false); public bool IsNothing => Keys.Count == 0 && !Logs; } /// Enter sends the message the contact has got to. The table is N1MM's, from /// its function-key documentation: the state of the callsign and exchange /// boxes, and whether the station is running or searching, decide what goes /// out. public static class Esm { /// Which function key is which. N1MM's layout, and the one the entry /// window's buttons are labelled with. public const int CallCq = 0; public const int Exchange = 1; public const int EndQso = 2; public const int MyCall = 3; public const int HisCall = 4; public const int WorkedBefore = 5; public const int Again = 7; public static EsmAction Decide(EsmSituation now) => now.IsRunning ? Running(now) : Searching(now); private static EsmAction Running(EsmSituation now) { if (!now.HasCall) { return new EsmAction([CallCq], false); } if (now.HasExchange) { return now.ExchangeSent ? new EsmAction([EndQso], true) : new EsmAction([HisCall, Exchange], false); } if (now.IsDupe && !now.WorksDupes) { return new EsmAction([WorkedBefore], false); } return now.CallSent ? new EsmAction([Again], false) : new EsmAction([HisCall, Exchange], false); } private static EsmAction Searching(EsmSituation now) { if (!now.HasCall) { return new EsmAction([MyCall], false); } if (now.HasExchange) { return now.ExchangeSent ? new EsmAction([], true) : new EsmAction([Exchange], true); } if (now.IsDupe) { // a station already in the log is not called again return EsmAction.Nothing; } return now.CallSent && now.SendsCallOnce ? new EsmAction([Again], false) : new EsmAction([MyCall], false); } }