# HG changeset patch # User Josef 'Jeff' Sipek # Date 1727352173 14400 # Thu Sep 26 08:02:53 2024 -0400 # Node ID fbe2336fd2259942e48442ae57927f28af478221 # Parent ff9ed89a571150dc939b9cee4c31d574b6eaf5f7 WIP: xlua: switch NEQP script to county-based helper diff --git a/xlua/scripts/contests/NEQP.lua b/xlua/scripts/contests/NEQP.lua --- a/xlua/scripts/contests/NEQP.lua +++ b/xlua/scripts/contests/NEQP.lua @@ -28,127 +28,66 @@ [10000] = true, } --- DXCC entities that use states (+counties) instead of "DX" -local uscan_dxcc = { - [1] = true, -- Canada - [6] = true, -- Alaska - [110] = true, -- Hawaii - [291] = true, -- CONUS +local mode_points = { + ["PH"] = 1, + ["CW"] = 2, + ["DG"] = 2, } local raw_county_data = { -- NB: these are the CT multiplies starting 2024; previous years -- used counties instead of Regional Councils of Government - { 'CT', 'GBR', 'Greater Bridgeport' }, { 'CT', 'LCR', 'Lower CT River' }, - { 'CT', 'NAU', 'Naugatuck Valley' }, { 'CT', 'NOE', 'Northeastern' }, - { 'CT', 'NOW', 'Northwest Hills' }, { 'CT', 'SOE', 'Southeastern' }, - { 'CT', 'SOC', 'South Central' }, { 'CT', 'WES', 'Western' }, + { 291, 'CT', 'CTGBR', 'Greater Bridgeport' }, { 291, 'CT', 'CTLCR', 'Lower CT River' }, + { 291, 'CT', 'CTNAU', 'Naugatuck Valley' }, { 291, 'CT', 'CTNOE', 'Northeastern' }, + { 291, 'CT', 'CTNOW', 'Northwest Hills' }, { 291, 'CT', 'CTSOE', 'Southeastern' }, + { 291, 'CT', 'CTSOC', 'South Central' }, { 291, 'CT', 'CTWES', 'Western' }, - { 'MA', 'BAR', 'Barnstable' }, { 'MA', 'BER', 'Berkshire' }, - { 'MA', 'BRI', 'Bristol' }, { 'MA', 'DUK', 'Dukes' }, - { 'MA', 'ESS', 'Essex' }, { 'MA', 'FRA', 'Franklin' }, - { 'MA', 'HMD', 'Hampden' }, { 'MA', 'HMP', 'Hampshire' }, - { 'MA', 'MID', 'Middlesex' }, { 'MA', 'NAN', 'Nantucket' }, - { 'MA', 'NOR', 'Norfolk' }, { 'MA', 'PLY', 'Plymouth' }, - { 'MA', 'SUF', 'Suffolk' }, { 'MA', 'WOR', 'Worcester' }, + { 291, 'MA', 'MABAR', 'Barnstable' }, { 291, 'MA', 'MABER', 'Berkshire' }, + { 291, 'MA', 'MABRI', 'Bristol' }, { 291, 'MA', 'MADUK', 'Dukes' }, + { 291, 'MA', 'MAESS', 'Essex' }, { 291, 'MA', 'MAFRA', 'Franklin' }, + { 291, 'MA', 'MAHMD', 'Hampden' }, { 291, 'MA', 'MAHMP', 'Hampshire' }, + { 291, 'MA', 'MAMID', 'Middlesex' }, { 291, 'MA', 'MANAN', 'Nantucket' }, + { 291, 'MA', 'MANOR', 'Norfolk' }, { 291, 'MA', 'MAPLY', 'Plymouth' }, + { 291, 'MA', 'MASUF', 'Suffolk' }, { 291, 'MA', 'MAWOR', 'Worcester' }, - { 'ME', 'AND', 'Androscoggin' }, { 'ME', 'ARO', 'Aroostook' }, - { 'ME', 'CUM', 'Cumberland' }, { 'ME', 'FRA', 'Franklin' }, - { 'ME', 'HAN', 'Hancock' }, { 'ME', 'KEN', 'Kennebec' }, - { 'ME', 'KNO', 'Knox' }, { 'ME', 'LIN', 'Lincoln' }, - { 'ME', 'OXF', 'Oxford' }, { 'ME', 'PEN', 'Penobscot' }, - { 'ME', 'PIS', 'Piscataquis' }, { 'ME', 'SAG', 'Sagadahoc' }, - { 'ME', 'SOM', 'Somerset' }, { 'ME', 'WAL', 'Waldo' }, - { 'ME', 'WAS', 'Washington' }, { 'ME', 'YOR', 'York' }, + { 291, 'ME', 'MEAND', 'Androscoggin' }, { 291, 'ME', 'MEARO', 'Aroostook' }, + { 291, 'ME', 'MECUM', 'Cumberland' }, { 291, 'ME', 'MEFRA', 'Franklin' }, + { 291, 'ME', 'MEHAN', 'Hancock' }, { 291, 'ME', 'MEKEN', 'Kennebec' }, + { 291, 'ME', 'MEKNO', 'Knox' }, { 291, 'ME', 'MELIN', 'Lincoln' }, + { 291, 'ME', 'MEOXF', 'Oxford' }, { 291, 'ME', 'MEPEN', 'Penobscot' }, + { 291, 'ME', 'MEPIS', 'Piscataquis' }, { 291, 'ME', 'MESAG', 'Sagadahoc' }, + { 291, 'ME', 'MESOM', 'Somerset' }, { 291, 'ME', 'MEWAL', 'Waldo' }, + { 291, 'ME', 'MEWAS', 'Washington' }, { 291, 'ME', 'MEYOR', 'York' }, - { 'NH', 'BEL', 'Belknap' }, { 'NH', 'CAR', 'Carroll' }, - { 'NH', 'CHE', 'Cheshire' }, { 'NH', 'COO', 'Coos' }, - { 'NH', 'GRA', 'Grafton' }, { 'NH', 'HIL', 'Hillsborough' }, - { 'NH', 'MER', 'Merrimack' }, { 'NH', 'ROC', 'Rockingham' }, - { 'NH', 'STR', 'Strafford' }, { 'NH', 'SUL', 'Sullivan' }, + { 291, 'NH', 'NHBEL', 'Belknap' }, { 291, 'NH', 'NHCAR', 'Carroll' }, + { 291, 'NH', 'NHCHE', 'Cheshire' }, { 291, 'NH', 'NHCOO', 'Coos' }, + { 291, 'NH', 'NHGRA', 'Grafton' }, { 291, 'NH', 'NHHIL', 'Hillsborough' }, + { 291, 'NH', 'NHMER', 'Merrimack' }, { 291, 'NH', 'NHROC', 'Rockingham' }, + { 291, 'NH', 'NHSTR', 'Strafford' }, { 291, 'NH', 'NHSUL', 'Sullivan' }, - { 'RI', 'BRI', 'Bristol' }, { 'RI', 'KEN', 'Kent' }, - { 'RI', 'NEW', 'Newport' }, { 'RI', 'PRO', 'Providence' }, - { 'RI', 'WAS', 'Washington' }, + { 291, 'RI', 'RIBRI', 'Bristol' }, { 291, 'RI', 'RIKEN', 'Kent' }, + { 291, 'RI', 'RINEW', 'Newport' }, { 291, 'RI', 'RIPRO', 'Providence' }, + { 291, 'RI', 'RIWAS', 'Washington' }, - { 'VT', 'ADD', 'Addison' }, { 'VT', 'BEN', 'Bennington' }, - { 'VT', 'CAL', 'Caledonia' }, { 'VT', 'CHI', 'Chittenden' }, - { 'VT', 'ESS', 'Essex' }, { 'VT', 'FRA', 'Franklin' }, - { 'VT', 'GRA', 'Grand Isle' }, { 'VT', 'LAM', 'Lamoille' }, - { 'VT', 'ORA', 'Orange' }, { 'VT', 'ORL', 'Orleans' }, - { 'VT', 'RUT', 'Rutland' }, { 'VT', 'WAS', 'Washington' }, - { 'VT', 'WNH', 'Windham' }, { 'VT', 'WND', 'Windsor' }, + { 291, 'VT', 'VTADD', 'Addison' }, { 291, 'VT', 'VTBEN', 'Bennington' }, + { 291, 'VT', 'VTCAL', 'Caledonia' }, { 291, 'VT', 'VTCHI', 'Chittenden' }, + { 291, 'VT', 'VTESS', 'Essex' }, { 291, 'VT', 'VTFRA', 'Franklin' }, + { 291, 'VT', 'VTGRA', 'Grand Isle' }, { 291, 'VT', 'VTLAM', 'Lamoille' }, + { 291, 'VT', 'VTORA', 'Orange' }, { 291, 'VT', 'VTORL', 'Orleans' }, + { 291, 'VT', 'VTRUT', 'Rutland' }, { 291, 'VT', 'VTWAS', 'Washington' }, + { 291, 'VT', 'VTWNH', 'Windham' }, { 291, 'VT', 'VTWND', 'Windsor' }, } --- generate views of the county data -local map_county_name2abbrev = {} -local map_county_abbrev2name = {} -local new_england_states = {} - --- initialize county_{name2abbrev,abbrev2name} -for _, info in pairs(raw_county_data) do - local a2023 = info[2] .. info[1] - local a = info[1] .. info[2] - local n = info[1] .. info[3] - local s = info[1] - - assert(map_county_name2abbrev[n] == nil) - map_county_name2abbrev[n] = a - - -- old (2023) -style abbrevs for easier entry - assert(map_county_abbrev2name[a2023] == nil) - map_county_abbrev2name[a2023] = info - assert(map_county_abbrev2name[a] == nil) - map_county_abbrev2name[a] = info - - new_england_states[s] = true -end - --- dup checking -local dups = {} - -local function mkhash(qso) +local function duphash(qso, tx, rx) return string.format("%s-%u-%s-%s-%s", qso.rx.station_call, qso.tx.band, qso.tx.mode, - qso.additional['contest-tx'], - qso.additional['contest-rx']) + tx, + rx) end -local function county_name2abbrev(dxcc, state, county) - -- outside of US or Canada: DX - if uscan_dxcc[dxcc] == nil then - return "DX" - end - - -- outside of New England: state/province - if new_england_states[state] == nil then - return state - end - - -- inside New England: abbreviation - assert(county ~= nil) - - return map_county_name2abbrev[state .. county] -end - -local function county_abbrev2name(abbrev) - local info = map_county_abbrev2name[abbrev] - - -- inside New England: return state & county - if info ~= nil then - return info[1], info[3] - end - - -- outside of New England: state - if abbrev ~= nil and abbrev ~= "DX" then - return abbrev, nil - end - - -- outside of US - return nil, nil -end +local neqp = hlog.contest_helper.county(raw_county_data, { 1, 291 }, duphash) local function startup_fill_template(template) local done = false @@ -159,19 +98,30 @@ template.tx.dxcc = io.stdin:read() - if uscan_dxcc[template.tx.dxcc] then + if neqp:dxcc_use_state(template.tx.dxcc) then print("Enter state or province (e.g., MA):") - local tmp = io.stdin:read() - local state, county = county_abbrev2name(tmp) + local tmp = io.stdin:read():upper() + local state, county = neqp:abbrev2name(tmp) - if county ~= nil then + if tmp == "" or tmp == nil then + -- try again + elseif county ~= nil then template.tx.state = state template.tx.county = county done = true - elseif new_england_states[tmp] then + elseif neqp:state_use_county(template.tx.dxcc, tmp) then print("Enter county abbreviation (e.g., MID):") - state, county = county_abbrev2name(tmp .. io.stdin:read()) + + local tmp2 = io.stdin:read():upper() + + state, county = neqp:abbrev2name(tmp .. tmp2) + if county == nil then + -- fall back to just trying the last + -- input in case the user entered the + -- whole abbreviation (e.g., MAMID) + state, county = neqp:abbrev2name(tmp2) + end if county ~= nil then template.tx.state = state @@ -190,12 +140,6 @@ -- TODO: fill in qso.tx's {itu,cqz,country,continent} end -local function qso_done(qso) - if allowed_bands[qso.tx.band] then - dups[mkhash(qso)] = true - end -end - -- copy country data into QSO local function sync_zones(qso) local call = qso.rx.station_call @@ -235,7 +179,7 @@ qso.additional['contest-rx'] = value -- set state & county based on the exchange - local state, county = county_abbrev2name(value) + local state, county = neqp:abbrev2name(value) qso.rx.state = state qso.rx.county = county @@ -248,11 +192,115 @@ end return nil, { - ["DUP"] = dups[mkhash(qso)] and "red" or "black", + ["DUP"] = neqp:is_dup(qso) and "red" or "black", ["BAND"] = allowed_bands[qso.tx.band] and "black" or "red", } end +local function qexchange(side, ex) + local ok = true + local out = "" + + if side.rst == nil then + ok = false + out = out .. "59" + else + out = out .. side.rst + end + + if ex == nil then + ok = false + out = out .. " ??" + else + out = out .. " " .. ex + end + + return out, ok +end + +local function qpoints(qso, tx, rx) + assert(qso.tx.mode ~= nil) + + return mode_points[qso.tx.cabrillo_mode] +end + +local function qmult(qso, tx, rx) + local function in_ne(side) + if not neqp:dxcc_use_county(side.dxcc) and + not neqp:dxcc_use_state(side.dxcc) then + return false -- DX + end + + if not neqp:state_use_county(side.dxcc, side.state) then + return false -- state/province outside of New England + end + + return true -- inside New England + end + + if in_ne(qso.tx) then + -- We are in New England; we get to work anyone anywhere. + elseif in_ne(qso.rx) then + -- We are outside of New England and other station is in New + -- England, we get to work them. + else + -- We are outside of New England and the other station is + -- also outside of New England. + return nil + end + + -- At this point, we know that this contact involves at least one + -- New England station. + + if neqp:state_use_county(qso.rx.dxcc, qso.rx.state) then + -- Other station should have given us state & county. + local state, county = neqp:abbrev2name(rx) + + if county == nil then + return nil -- no/bad state & county code given + end + + return rx -- state & county code is valid + elseif neqp:dxcc_use_state(qso.rx.dxcc) then + -- Other station should have given us a state/province. + return rx -- TODO: sanity check state/province + else + -- Other station should have given us "DX". + if rx ~= "DX" then + return nil -- no/bad exchange + end + + return qso.rx.dxcc -- use DXCC as multiplier + end +end + +local function export(outfname) + local all + local station + local ops + local points + local mults + + all, station, ops, points, mults = neqp:export_prep(contest.id, contest.year, + qpoints, qmult, qexchange) + + 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" }, @@ -276,6 +324,8 @@ }, events = { + export = export, + -- called once on startup (after config's fill_template) startup = function(template) print("NEQP contest script") @@ -286,7 +336,7 @@ if qso.additional['contest-id'] == "NEQP" and qso.additional['contest-year'] == contest.year and allowed_bands[qso.tx.band] then - dups[mkhash(qso)] = true + neqp:qso_done(qso) end end end, @@ -299,23 +349,27 @@ local dxcc = qso.tx.dxcc local state = qso.tx.state local county = qso.tx.county - local cs = county_name2abbrev(dxcc, state, county) + local exch = neqp:name2abbrev(dxcc, state, county) -- qso.tx.rst = "59" -- we always send 59 -- qso.rx.rst = "59" -- we expect a 59 - qso.additional['contest-id'] = 'NEQP' - qso.additional['contest-year'] = contest.year - if cs == nil then + if exch == nil then contest.lcd("?") else - qso.additional['contest-tx'] = cs - contest.lcd(cs) + qso.additional['contest-id'] = 'NEQP' + qso.additional['contest-year'] = contest.year + qso.additional['contest-tx'] = exch + contest.lcd(exch) end end, -- called when QSO is complete & saved, just prior to a reset - qso_done = qso_done, + qso_done = function(qso) + if allowed_bands[qso.tx.band] then + neqp:qso_done(qso) + end + end, -- called whenever a contest field changes field_changed = field_changed,