WIP: xlua: switch CQ-WW-VHF to grid helper
1 files changed, 55 insertions(+), 138 deletions(-)

M xlua/scripts/contests/CQ-WW-VHF.lua
M xlua/scripts/contests/CQ-WW-VHF.lua +55 -138
@@ 1,5 1,5 @@ 
 --
--- Copyright (c) 2022-2024 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+-- Copyright (c) 2022-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

          
@@ 25,13 25,6 @@ local ALLOWED_BANDS = {
 	[6000] = 1,
 }
 
-local dup_table = {}
-local mults = {}
-
-for band, _ in pairs(ALLOWED_BANDS) do
-	dup_table[band] = {}
-end
-
 local function mkgrid(g)
 	if g == nil then
 		return ""

          
@@ 40,61 33,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))
-end
-
-local function duphash(qso)
-	local mgrid = mkgrid(qso.tx.grid)
-	local tgrid = mkgrid(qso.rx.grid)
-
+local function duphash(qso, tx, rx)
 	return string.format("%s-%s-%s-%s",
-		mgrid, qso.rx.station_call, tgrid, qso.tx.band)
+			     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 update_dup_table(qso)
-	local where = hash(qso.tx.grid, qso.rx.grid)
-	local mult = string.format("%u-%s", qso.tx.band, where)
-
-	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
-
-local function qso_done(qso)
-	if not ALLOWED_BANDS[qso.tx.band] then
-		return -- ignore out-of-band contacts
-	end
-
-	qso.additional['contest-id'] = 'CQ-WW-VHF'
-	qso.additional['contest-year'] = contest.year
-
-	update_dup_table(qso)
-end
+-- The object keeping all the state
+local vhf
 
 --
 -- Annunciator color

          
@@ 102,31 50,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
-
+-- 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, "")]
+	return color_warn
 end
 
 local function field_changed(qso, fields, field)

          
@@ 142,79 79,53 @@ local function field_changed(qso, fields
 		error(string.format("Unknown field '%s' encountered", field))
 	end
 
-	if not ALLOWED_BANDS[qso.tx.band] then
-		return -- ignore out-of-band contacts
-	end
-
 	return nil, {
-		["2m"] = is_dup(qso, 2000, "green", "magenta", "off"),
-		["6m"] = is_dup(qso, 6000, "green", "magenta", "off"),
-		["DUP"] = is_dup(qso, qso.tx.band, "black", "black", "red"),
+		["DUP"] = is_dup(qso, "black", "black", "red"),
+		["OOB"] = ALLOWED_BANDS[qso.tx.band] ~= nil and "black" or "red",
 	}
 end
 
-local function add_entry(all, dups, qso)
-	if qso.additional['contest-id'] ~= "CQ-WW-VHF" or
-	   qso.additional['contest-year'] ~= contest.year then
-		return
+local function qexchange(side, ex)
+	if ex == nil then
+		return "????", false
+	else
+		return mkgrid(ex), true
 	end
-
-	if qso.tx.band == nil then
-		print(string.format("Error: %s QSO lacks band info", qso.uuid))
-		return
-	end
-
-	assert(qso.tx.station_call ~= nil)
-
-	local dupkey = duphash(qso)
-
-	table.insert(all, qso)
-	dups[dupkey] = qso
 end
 
-local function mkexchange(side)
-	local ok = true
-	local out = mkgrid(side.grid)
+local function qpoints(qso, tx, rx)
+	return ALLOWED_BANDS[qso.tx.band]
+end
 
-	if out == "" then
-		ok = false
-		out = "??"
-	end
-
-	return out, ok
+local function qmult(qso, tx, rx)
+	return string.format("%u-%s-%s", qso.tx.band, mkgrid(tx), mkgrid(rx))
 end
 
 local function export(outfname)
-	local all = {}
-	local dups = {}
+	local all
+	local station
+	local ops
+	local points
+	local mults
 
-	for qso in hlog.index.history(true) do
-		add_entry(all, dups, qso)
-	end
+	all, station, ops, points, mults = vhf:export_prep(contest.id, contest.year,
+							   qpoints, qmult, qexchange)
 
-	local qpoints = 0
-	local mults = {}
-	for _, qso in pairs(dups) do
-		qpoints = qpoints + ALLOWED_BANDS[qso.tx.band]
-		mults[string.format("%s-%s-%s",
-			mkgrid(qso.tx.grid),
-			mkgrid(qso.rx.grid),
-			qso.tx.band)] = true
-	end
+	hlog.cabrillo.print_header(contest.id,
+				   station,
+				   ops:as_array(),
+				   points * mults:count_items())
 
-	local score = qpoints * hlog.utils.count_items(mults)
-
-	-- TODO: print cabrillo header
-
-	for _, qso in ipairs(all) do
-		local tx, txok = mkexchange(qso.tx, qso.additional['contest-tx'])
-		local rx, rxok = mkexchange(qso.rx, qso.additional['contest-rx'])
-		local xqso = not txok or not rxok
+	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
 
-	-- TODO: print cabrillo footer
+	hlog.cabrillo.print_footer()
 end
 
 return {

          
@@ 231,17 142,20 @@ return {
 	},
 
 	annunciators = {
-		{ 0, 31, "DUP", "black" },
-		{ 0, 35, "6m", "black" },
-		{ 0, 38, "2m", "black" },
+		{ 0, 31, "OOB", "black" },
+		{ 0, 35, "DUP", "black" },
 	},
 
 	events = {
+		export = export,
 		startup = function(template)
+			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'] == 'CQ-WW-VHF' and
 				   qso.additional['contest-year'] == contest.year then
-					update_dup_table(qso)
+					vhf:qso_load(qso)
 				end
 			end
 

          
@@ 255,11 169,14 @@ return {
 
 			template.tx.operator_call = op
 		end,
-		export = export,
 		qso_init = function(qso)
 			contest.lcd(mkgrid(qso.tx.grid))
 		end,
-		qso_done = qso_done,
+		qso_done = function(qso)
+			if ALLOWED_BANDS[qso.tx.band] then
+				vhf:qso_done(qso)
+			end
+		end,
 		field_changed = field_changed,
 	},
 }