WIP: xlua: switch ARRL-VHF-{JAN,JUN,SEP} to grid helper
1 files changed, 94 insertions(+), 95 deletions(-)

M xlua/scripts/contests/ARRL-VHF-SEP.lua
M xlua/scripts/contests/ARRL-VHF-SEP.lua +94 -95
@@ 1,5 1,5 @@ 
 --
--- Copyright (c) 2022 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+-- Copyright (c) 2022,2024-2025 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
 --
 -- Permission is hereby granted, free of charge, to any person obtaining a copy
 -- of this software and associated documentation files (the "Software"), to deal

          
@@ 20,6 20,9 @@ 
 -- SOFTWARE.
 --
 
+local MIN_FREQ = 50000000 -- Hz; lowest allowed freq in contest
+local MAX_FREQ_STEPS = 1300000000 -- Hz; highest freq which doesn't get max qso points
+
 local BAND_POINTS = {
 	["ARRL-VHF-JAN"] = {
 		[6000] = 1,

          
@@ 28,7 31,7 @@ local BAND_POINTS = {
 		[700] = 2,
 		[330] = 4,
 		[230] = 4,
-		-- TODO: 8 points for even higher bands
+		["HIGHER"] = 8,
 	},
 	["ARRL-VHF-JUN"] = {
 		[6000] = 1,

          
@@ 37,7 40,7 @@ local BAND_POINTS = {
 		[700] = 2,
 		[330] = 3,
 		[230] = 3,
-		-- TODO: 4 points for even higher bands
+		["HIGHER"] = 4,
 	},
 	["ARRL-VHF-SEP"] = {
 		[6000] = 1,

          
@@ 46,13 49,10 @@ local BAND_POINTS = {
 		[700] = 2,
 		[330] = 3,
 		[230] = 3,
-		-- TODO: 4 points for even higher bands
+		["HIGHER"] = 4,
 	},
 }
 
-local dup_table = {}
-local mults = {}
-
 local function mkgrid(g)
 	if g == nil then
 		return ""

          
@@ 61,49 61,16 @@ local function mkgrid(g)
 	return g:sub(1,4)
 end
 
-local function hash(mgrid, tgrid)
-	return string.format("%s-%s", mkgrid(mgrid), mkgrid(tgrid))
+local function duphash(qso, tx, rx)
+	return string.format("%s-%s-%s-%s",
+			     qso.rx.station_call,
+			     qso.tx.band,
+			     mkgrid(tx),
+			     mkgrid(rx))
 end
 
-local function check(band, who, mgrid, tgrid)
-	local where = hash(mgrid, tgrid)
-	local info = dup_table[band][who]
-
-	if info == nil then
-		return 1 -- callsign not seen before on this band ==> not dup
-	end
-
-	if info[where] then
-		return 3 -- exact match ==> dup
-	end
-
-	for cur, _ in pairs(info) do
-		if cur:find(where, 1, true) == 1 then
-			return 2 -- where is a prefix of cur ==> maybe dup
-		end
-	end
-
-	return 1 -- no prefix found ==> not dup
-end
-
-local function qso_done(qso)
-	if not BAND_POINTS[contest.id][qso.tx.band] then
-		return -- ignore out-of-band contacts
-	end
-
-	qso.additional['contest-id'] = contest.id
-	qso.additional['contest-year'] = contest.year
-
-	local where = hash(qso.tx.grid, qso.rx.grid)
-	local mult = string.format("%u-%s", qso.tx.band, mkgrid(qso.rx.grid))
-
-	if dup_table[qso.tx.band][qso.rx.station_call] == nil then
-		dup_table[qso.tx.band][qso.rx.station_call] = {}
-	end
-	dup_table[qso.tx.band][qso.rx.station_call][where] = true
-
-	mults[mult] = true
-end
+-- The object keeping all the state
+local vhf
 
 --
 -- Annunciator color

          
@@ 111,47 78,20 @@ end
 -- There are three colors we can display: "not a dup", "maybe a dup", and
 -- "definitely a dup".  When we have full information (i.e., callsign and
 -- grid), it is easy to check and return either "not a dup" or "definitely a
--- dup".  Things get more complicated when we don't have all the info.  In
--- general, we try to do a prefix match with the entered grid square.  This
--- allows us to indicate whether or not there is some chance of a dup.
-local function is_dup(qso, band, color_none, color_warn, color_dup)
-	local colors = { color_none, color_warn, color_dup }
-	local who = qso.rx.station_call
-	local where = qso.rx.grid
-
-	--
-	-- Out of band contacts cannot be dups.  Instead of using
-	-- color_none, we simply hide them.
-	--
-	if not BAND_POINTS[contest.id][qso.tx.band] then
-		return "black"
-	end
-
+-- dup".  When we don't have full information (i.e., 4-digit grid), we just
+-- warn that it may be a duplicate.
+local function is_dup(qso, color_none, color_warn, color_dup)
 	--
 	-- complete information
 	--
-	if where ~= nil and where:len() >= 4 then
-		local colors = { color_none, nil, color_dup }
-
-		return colors[check(band, who, qso.tx.grid, where)]
+	if qso.rx.grid ~= nil and qso.rx.grid:len() >= 4 then
+		return vhf:is_dup(qso) and color_dup or color_none
 	end
 
 	--
 	-- incomplete information
 	--
-	if where ~= nil then
-		return colors[check(band, who, qso.tx.grid, where)]
-	end
-
-	return colors[check(band, who, qso.tx.grid, "")]
-end
-
-local function is_oob(qso)
-	if BAND_POINTS[contest.id][qso.tx.band] then
-		return "black"
-	else
-		return "red"
-	end
+	return color_warn
 end
 
 local function field_changed(qso, fields, field)

          
@@ 168,15 108,67 @@ local function field_changed(qso, fields
 	end
 
 	return nil, {
-		["2m"] = is_dup(qso, 2000, "green", "magenta", "off"),
-		["6m"] = is_dup(qso, 6000, "green", "magenta", "off"),
-		["70cm"] = is_dup(qso, 700, "green", "magenta", "off"),
-		["23cm"] = is_dup(qso, 230, "green", "magenta", "off"),
-		["DUP"] = is_dup(qso, qso.tx.band, "black", "black", "red"),
-		["OOB"] = is_oob(qso),
+		["DUP"] = is_dup(qso, "black", "black", "red"),
+		["OOB"] = qso.tx.freq >= MIN_FREQ and "black" or "red",
 	}
 end
 
+local function qexchange(side, ex)
+	if ex == nil then
+		return "????", false
+	else
+		return mkgrid(ex), true
+	end
+end
+
+local function qpoints(qso, tx, rx)
+	if qso.tx.freq <= MAX_FREQ_STEPS then
+		return BAND_POINTS[contest.id][qso.tx.band]
+	else
+		return BAND_POINTS[contest.id]["HIGHER"]
+	end
+end
+
+local function qmult(qso, tx, rx)
+	return string.format("%u-%s", qso.tx.band, mkgrid(rx))
+end
+
+local function export(outfname)
+	local all
+	local station
+	local ops
+	local points
+	local mults
+
+	all, station, ops, points, mults = vhf:export_prep(contest.id, contest.year,
+							   qpoints, qmult, qexchange)
+
+	if station:sub(-2) == "/R" then
+		-- rovers need a +1 mult for each grid visited
+		for _, rec in ipairs(all) do
+			local qso = rec[1]
+
+			mults:add(string.format("visited-%s", mkgrid(qso.tx.grid)))
+		end
+	end
+
+	hlog.cabrillo.print_header(contest.id,
+				   station,
+				   ops:as_array(),
+				   points * mults:count_items())
+
+	for _, rec in ipairs(all) do
+		local qso = rec[1]
+		local tx = rec[2]
+		local rx = rec[3]
+		local xqso = not rec[4]
+
+		hlog.cabrillo.print_qso(qso, tx, rx, xqso)
+	end
+
+	hlog.cabrillo.print_footer()
+end
+
 return {
 	labels = {
 		{ 0,  0, "St. Call" },

          
@@ 193,23 185,30 @@ return {
 	annunciators = {
 		{ 0, 31, "OOB", "black" },
 		{ 0, 35, "DUP", "black" },
-		{ 0, 39, "6m", "black" },
-		{ 0, 42, "2m", "black" },
-		{ 0, 45, "70cm", "black" },
-		{ 0, 50, "23cm", "black" },
-		-- TODO: more band indicators?
 	},
 
 	events = {
+		export = export,
 		startup = function()
-			for band, _ in pairs(BAND_POINTS[contest.id]) do
-				dup_table[band] = {}
+			vhf = hlog.contest_helper.grid(contest.id, contest.year, duphash)
+
+			-- load previous contacts from history
+			for qso in hlog.index.history(true) do
+				if qso.additional['contest-id'] == contest.id and
+				   qso.additional['contest-year'] == contest.year and
+				   qso.tx.freq >= MIN_FREQ then
+					vhf:qso_load(qso)
+				end
 			end
 		end,
 		qso_init = function(qso)
 			contest.lcd(mkgrid(qso.tx.grid))
 		end,
-		qso_done = qso_done,
+		qso_done = function(qso)
+			if qso.tx.freq >= MIN_FREQ then
+				vhf:qso_done(qso)
+			end
+		end,
 		field_changed = field_changed,
 	},
 }