/* Contest Arbitrator v0.3.2 by Dmitry Gorokh, UR4MCK
This program is FREEWARE!
RU-QRP Wake-Up! Sprint contest evaluator
Changes:
18.02.2013: fixed contest period checking bug in wakeup_eval()
05.09.2011: in progress: 'allow_dupes' support added
17.06.2011: Strict evaluation added
*/
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <time.h>
#include <math.h>
#include "config.h"
#include "main.h"
#include "wakeup.h"
#include "cabrillo.h"
#include "report.h"
#define EARTH_RADIUS 6367.0 /* in km */
extern CONFIG_S config;
static CONTEST_CONFIG_S contest;
static void wakeup_dump_config(void);
static int is_dupe(HAM_S *me, QSO_S *my_qso);
static int set_round(QSO_S *qso);
static int valid_rst(char *rst, int mode_id);
static int valid_cnt(char *number, int previos);
static void copy_suffix(char *suffix, char *callsign);
static int calc_distance(char *location1, char *location2, FILE *ubn_fd);
static int is_mult(HAM_S *ham, QSO_S *qso);
static int gs2ll(int *lattitude, int *longitude, const char *grid);
static int check_band(HAM_S *ham, QSO_S *qso, unsigned int qso_num, FILE *ubn_fd);
static int check_mode(HAM_S *ham, QSO_S *qso, unsigned int qso_num, FILE *ubn_fd);
static int is_marked(QSO_S *qso, int *mark_id);
int wakeup_config(time_t start, time_t end, char *modes, char *bands, unsigned int rounds) {
int i, n, dt, flag;
char str[MAX_STR], *v, start_s[MAX_STR], end_s[MAX_STR];
DEBUG("==> wakeup_config(%#x, %#x, (%s), (%s), %u)\n", start, end, modes, bands, rounds);
/* Initialization */
memset(&contest, 0, sizeof(contest));
contest.id = CONTEST_ID_RUQRP_WAKEUP;
contest.start = start;
contest.end = end;
dt = contest.end - contest.start; /* Contest duration */
if (dt <= 0) {
/* Convert contest start & end time to strings */
strftime(start_s, MAX_STR, "%Y-%m-%d %H%M", gmtime(&start));
strftime(end_s, MAX_STR, "%Y-%m-%d %H%M", gmtime(&end));
out(NULL, "ERROR: Incorrect contest period: %s - %s\n", start_s, end_s);
return -2;
}
/* Parse modes list */
strcpy(str, modes);
v = strtok(str, LIST_DELIMITER);
n = 0;
while (v) {
/* Look for particular qso_mode id */
i = flag = 0;
while (mode_type[i].name) {
if (strcasecmp(mode_type[i].name, v) == 0) {
contest.mode[n] = i;
flag = 1;
break;
}
i++;
}
if (!flag) contest.mode[n] = 0; /* Unknown mode */
n++;
v = strtok(NULL, LIST_DELIMITER);
}
contest.mode_cnt = n;
/* Parse bands list */
strcpy(str, bands);
v = strtok(str, LIST_DELIMITER);
n = 0;
while (v) {
/* Look for particular band id */
i = flag = 0;
while (band_list[i].name) {
if (strcasecmp(band_list[i].name, v) == 0) {
contest.band[n] = i;
flag = 1;
break;
}
i++;
}
if (!flag) contest.band[n] = 0; /* Unknown band */
n++;
v = strtok(NULL, LIST_DELIMITER);
}
contest.band_cnt = n;
if (rounds > 0) {
/* Allocate rounds pointer structure */
if (!(contest.round = calloc(rounds, sizeof(ROUND_S*)))) {
out(NULL, "ERROR: out of memory\n");
return -1;
}
for (i = 0; i < rounds; i++) {
/* Allocate space for round structure */
if (!(contest.round[i] = calloc(1, sizeof(ROUND_S)))) {
out(NULL, "ERROR: out of memory\n");
return i - 1;
}
/* Fill rounds info */
contest.round[i]->num = i + 1;
if (i == 0) contest.round[0]->start = contest.start;
else contest.round[i]->start = contest.round[i - 1]->end + 1;
if (rounds > 1) contest.round[i]->end = contest.round[i]->start + dt / rounds - 1;
else contest.round[i]->end = contest.end;
}
contest.round_cnt = rounds;
}
#ifdef _DEBUG_
wakeup_dump_config();
#endif
DEBUG("<== wakeup_config(%#x, %#x, (%s), (%s), %u)\n", start, end, modes, bands, rounds);
return 0;
}
int wakeup_eval(BASE_S *base) {
int i, n, id, dt, dt2, cnt, prev_cnt, score, distance, mark;
unsigned int qso_num, errors, qso_ok;
char *my_call, prev_suffix[CALLSIGN_SIZE];
HAM_S *ham, *remote;
QSO_S *qso, *his_qso;
FILE *ubn_fd;
DEBUG("==> wakeup_eval(%p)\n", base);
out(NULL, "Wake-Up! Sprint contest specific cross-checking...\n\n");
n = base->ham_num; /* Total number of contest participants with logs */
for (i = 0; i < n; i++) {
out(NULL, "\n================================================================================================\n");
ham = base->ham[i];
my_call = ham->callsign;
/* Open UBN file */
if (!(ubn_fd = fopen(ham->ubn_file, "a"))) out(NULL, "WARNING: Unable to open UBN file: %s\n", ham->ubn_file);
qso = ham->qso;
qso_num = 1;
prev_cnt = errors = qso_ok = score = 0;
strcpy(prev_suffix, WAKEUP_INITIAL_SUFFIX);
mark = 0;
while (qso) {
/* Check is the judge's mark is present in this QSO */
if (ham->flags & HAM_FLAG_VALID_SIGNATURE) qso->marked = is_marked(qso, &mark);
if (qso->marked) {
qso->mark = mark; /* Store mark */
/* Check QSO mark valid/invalid */
if (mark & MARK_ID_QSO_INVALID) {
out(NULL, " INFO: %s, QSO #%u: Forcing QSO as INVALID\n", my_call, qso_num);
qso->checked = QSO_STATUS_CONTEST_ERROR;
qso->error_cnt++;
} else if (mark & MARK_ID_QSO_VALID) {
out(NULL, " INFO: %s, QSO #%u: Forcing QSO as VALID\n", my_call, qso_num);
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID;
qso_ok++;
}
/* Check multiplier mark valid/invalid */
if (mark & MARK_ID_MULT_INVALID) {
out(NULL, " INFO: %s, QSO #%u: Forcing multiplier as INVALID\n", my_call, qso_num);
qso->mult = 0;
} else if (mark & MARK_ID_MULT_VALID) {
out(NULL, " INFO: %s, QSO #%u: Forcing multiplier as VALID\n", my_call, qso_num);
qso->mult = 1;
} else if (!(mark & (MARK_ID_QSO_INVALID | MARK_ID_QSO_VALID | MARK_ID_MULT_INVALID | MARK_ID_MULT_VALID))) goto check_qso;
/* Calculate score if QSO have been forced as valid */
if (qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID) {
distance = -1;
if (qso->ham_id >= 0) {
remote = base->ham[qso->ham_id];
if ((his_qso = qso->his_qso)) {
/* Calculate distance between hams */
if (!ham->no_loc) {
if (strlen(ham->location) == 0) {
out(ubn_fd, "WARNING: %s: No WW-locator present in 'LOCATION:' tag in the log file\n", my_call);
ham->no_loc = 1; /* Mark an absence of QTH locator to not show a warning each time */
} else if (strlen(remote->location) > 0) {
distance = calc_distance(ham->location, remote->location, ubn_fd);
if (distance < 0) {
out(ubn_fd, "WARNING: %s <--> %s: Unable to calculate distance between locators: %s <--> %s\n", my_call, remote->callsign,
ham->location, remote->location);
distance = 0;
}
}
}
}
}
if (distance < 0) out(ubn_fd, "WARNING: %s, QSO #%u: Unable to calculate score (distance between hams)\n", my_call, qso_num);
else {
qso->score = distance;
score += distance;
}
}
} else {
check_qso: /* Check for allowed bands and modes */
if (check_band(ham, qso, qso_num, ubn_fd) && check_mode(ham, qso, qso_num, ubn_fd)) {
if (qso->checked == QSO_STATUS_GENERAL_XCHECK_OK || qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID) {
qso->score = 0; /* Reset score */
id = qso->ham_id;
if (id >= 0) {
if (id < n) {
remote = base->ham[id];
if ((his_qso = qso->his_qso)) {
/* Check for already existing errors on other side */
if (config.contest.strict_eval && (his_qso->checked == QSO_STATUS_CONTEST_XCHECK_FAIL || his_qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_SENT || \
his_qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_RECV)) {
out(ubn_fd, "ERROR: %s <--> %s, QSO #%u: Invalid exchange\n", my_call, remote->callsign, qso_num);
qso->checked = QSO_STATUS_CONTEST_XCHECK_FAIL;
qso->score = 0; /* Remove score */
qso->error_cnt++;
his_qso->checked = QSO_STATUS_CONTEST_XCHECK_FAIL;
his_qso->score = 0; /* Remove score */
his_qso->error_cnt++;
} else {
/* Perform cross-check */
/* Check if the timestamp is within a contest period */
if (qso->timestamp < contest.start || qso->timestamp >= contest.end) {
out(ubn_fd, "WARNING: %s <--> %s, QSO #%u: not within a contest period: %s", my_call, remote->callsign, qso_num,
asctime(gmtime(&qso->timestamp)));
qso->error_cnt++;
qso->checked = QSO_STATUS_CONTEST_XCHECK_FAIL;
} else {
/* Set round number */
set_round(qso);
/* Check dupes */
if (!config.contest.allow_dupes && is_dupe(ham, qso)) {
out(ubn_fd, "WARNING: %s <--> %s, QSO #%u: duplicate QSO\n", my_call, remote->callsign, qso_num);
qso->error_cnt++;
qso->checked = QSO_STATUS_CONTEST_XCHECK_FAIL;
} else {
/* Compare two timestamps with systematic difference in time correction */
dt = qso->timestamp - his_qso->timestamp;
dt2 = abs(dt - ham->systematic_dt - remote->systematic_dt);
dt = abs(dt);
if (dt > config.contest.max_dt && dt2 > config.contest.max_dt) {
out(ubn_fd, "WARNING: %s <--> %s, QSO #%u: not in the log (time difference >= %d min)\n", my_call, remote->callsign,
qso_num, dt > config.contest.max_dt ? dt / 60 : dt2 / 60);
qso->error_cnt++;
qso->checked = QSO_STATUS_CONTEST_XCHECK_FAIL;
} else {
/* Validate sent check numbers */
if (!config.contest.skip_rst_snt && !valid_rst(qso->check_sent[0], qso->mode_op)) { /* Validate sent RST */
out(ubn_fd, "WARNING: %s <--> %s, QSO #%u: sent RST is not valid (%s)\n", my_call, remote->callsign,
qso_num, qso->check_sent[0]);
qso->error_cnt++;
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_SENT;
} else if (!config.contest.skip_num_snt && (cnt = valid_cnt(qso->check_sent[1], prev_cnt)) < 0) { /* Validate incremental QSO counter */
out(ubn_fd, "WARNING: %s <--> %s, QSO #%u: sent number is not correct (%s). Must be %u\n", my_call, remote->callsign,
qso_num, qso->check_sent[1], prev_cnt + 1);
qso->error_cnt++;
prev_cnt = -cnt - 1;
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_SENT;
} else if (!config.contest.skip_chk_snt && strcasecmp(qso->check_sent[2], prev_suffix) != 0) { /* Validate previous QSO ham's callsign suffux */
out(ubn_fd, "WARNING: %s <--> %s, QSO #%u: sent check (suffix) is not correct ('%s'). Must be '%s'\n", my_call, remote->callsign,
qso_num, qso->check_sent[2], prev_suffix);
qso->error_cnt++;
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_SENT;
} else {
/* Validate received check numbers */
if (!config.contest.skip_rst_rcv && strcasecmp(qso->check_recv[0], his_qso->check_sent[0]) != 0) { /* Validate received RST */
out(ubn_fd, " ERROR: %s <--> %s, QSO #%u: received RST is not match ('%s'). Must be '%s'\n", my_call, remote->callsign,
qso_num, qso->check_recv[0], his_qso->check_sent[0]);
qso->error_cnt++;
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_RECV;
} else if (!config.contest.skip_num_rcv && strcasecmp(qso->check_recv[1], his_qso->check_sent[1]) != 0) { /* Validate incremental QSO counter */
out(ubn_fd, " ERROR: %s <--> %s, QSO #%u: received number is not match ('%s'). Must be '%s'\n", my_call, remote->callsign,
qso_num, qso->check_recv[1], his_qso->check_sent[1]);
qso->error_cnt++;
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_RECV;
} else if (!config.contest.skip_chk_rcv && strcasecmp(qso->check_recv[2], his_qso->check_sent[2]) != 0) { /* Validate suffix */
out(ubn_fd, " ERROR: %s <--> %s, QSO #%u: received check (suffix) is not match ('%s'). Must be '%s'\n", my_call, remote->callsign,
qso_num, qso->check_recv[2], his_qso->check_sent[2]);
qso->error_cnt++;
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_RECV;
} else {
/* Cross-check has passed */
/* Calculate distance between hams */
if (!ham->no_loc) {
if (strlen(ham->location) == 0) {
out(ubn_fd, "WARNING: %s: No WW-locator present in 'LOCATION:' tag in the log file\n", my_call);
ham->no_loc = 1; /* Mark an absence of QTH locator to not show a warning each time */
distance = 0;
} else if (strlen(remote->location) > 0) {
distance = calc_distance(ham->location, remote->location, ubn_fd);
if (distance < 0) {
out(ubn_fd, "WARNING: %s <--> %s: Unable to calculate distance between locators: %s <--> %s\n", my_call, remote->callsign,
ham->location, remote->location);
distance = 0;
}
} else distance = 0;
} else distance = 0;
qso->score = distance; /* Count score for this QSO as distance between WW-locators */
score += distance; /* Sum for the all valid QSOs */
/* Update own status */
if (qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID) qso->checked = QSO_STATUS_CONTEST_XCHECK_OK;
else qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID;
qso_ok++;
/* Update remote status */
if (!his_qso->error_cnt) {
/* Also mark his QSO as OK */
his_qso->score = distance;
if (his_qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID) his_qso->checked = QSO_STATUS_CONTEST_XCHECK_OK;
else his_qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID;
}
}
}
}
}
}
/* Update other side status if errors occurs here */
if (config.contest.strict_eval && (qso->checked == QSO_STATUS_CONTEST_XCHECK_FAIL || qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_SENT || \
qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_RECV)) {
his_qso->checked = QSO_STATUS_CONTEST_XCHECK_FAIL;
his_qso->score = 0; /* Remove score */
his_qso->error_cnt++;
}
}
}
} else {
out(NULL, "WARNING: %s: Incorrect ham_id (%d) in QSO #%u\n", qso->callsign, id, qso_num);
qso->error_cnt++;
}
} else if (id < -1) {
/* Look into unresolved list */
if (unresolved_get(id) >= config.contest.cfm_unresolved) {
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID;
qso_ok++;
} else qso->checked = QSO_STATUS_CONTEST_ERROR;
}
} else if (qso->checked == QSO_STATUS_CONTEST_XCHECK_OK) qso_ok++; /* Count already checked QSOs */
else if (qso->checked == QSO_STATUS_NOT_CHECKED && qso->ham_id < -1) {
/* Look into unresolved list */
if (unresolved_get(qso->ham_id) >= config.contest.cfm_unresolved)
qso->checked = QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID;
}
} else {
qso->checked = QSO_STATUS_CONTEST_ERROR;
qso->error_cnt++;
}
}
errors += qso->error_cnt;
/* Prepare for next QSO */
prev_cnt++; /* Increment QSO counter (sent number) */
copy_suffix(prev_suffix, qso->callsign); /* Save prefix for sent check */
qso = qso->next;
qso_num++;
}
if (score < 0) score = 0; /* Fail safe check for signed score */
ham->sum_score = (unsigned int)score;
if (errors) out(NULL, "------------------------------------------------------------------------------------------------\n");
out(NULL, " INFO: %s: %d QSOs are OK\n", my_call, qso_ok);
out(NULL, " INFO: %s: %u QSOs with errors or warnings\n", my_call, errors);
out(NULL, "================================================================================================\n");
/* Write statistics to UBN file */
report_add(ubn_fd, "Contest specific cross-check totals: %u errors and warnings\n\n", errors);
if (ubn_fd) fclose(ubn_fd);
}
/* Update multipliers */
n = base->ham_num;
for (i = 0; i < n; i++) {
ham = base->ham[i];
qso = ham->qso;
while (qso) {
if ((qso->checked == QSO_STATUS_CONTEST_XCHECK_OK || qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID) && \
!(qso->mark & MARK_ID_MULT_INVALID) && is_mult(ham, qso)) qso->mult = 1;
if (qso->mult == 1) ham->total_mult++; /* Count all mults (as well as forced by judge) */
qso = qso->next;
}
/* Also update total score */
ham->actual_score = ham->sum_score * ham->total_mult;
}
out(NULL, "\nContest specific cross-check completed\n\n");
DEBUG("<== wakeup_eval(%p)\n", base);
return 0;
}
int get_band_id(unsigned int freq) {
int i;
i = 0;
while (band_list[i].name) {
if (freq >= band_list[i].lo_range && freq <= band_list[i].hi_range) return band_list[i].id; /* Found */
i++;
}
return 0; /* Not found */
}
static void wakeup_dump_config(void) {
int i;
ROUND_S *r;
char start[MAX_STR], end[MAX_STR];
DEBUG("==> wakeup_dump_config()\n");
strcpy(start, asctime(gmtime(&contest.start)));
strcpy(end, asctime(gmtime(&contest.end)));
out(NULL, "\nWAKEUP: Contest configuration dump\n"
"----------------------------------------------\n");
out(NULL, " id = %d\n"
" start = %d %s"
" end = %d %s"
" round = %p\n",
contest.id, contest.start, start, contest.end, end, contest.round);
if (contest.mode_cnt > 0) {
out(NULL, " modes = ");
for (i = 0; i < contest.mode_cnt; i++) out(NULL, "%s ", get_mode(contest.mode[i]));
out(NULL, "\n");
}
if (contest.band_cnt > 0) {
out(NULL, " bands = ");
for (i = 0; i < contest.band_cnt; i++) out(NULL, "%s ", get_band(contest.band[i]));
out(NULL, "\n");
}
out(NULL, "----------------------------------------------\n");
if (contest.round) {
for (i = 0; i < contest.round_cnt; i++) {
if ((r = contest.round[i])) {
out(NULL, "\n *** Round #%d ***\n", r->num);
strcpy(start, asctime(gmtime(&(r->start))));
strcpy(end, asctime(gmtime(&(r->end))));
out(NULL, " start = %d %s"
" end = %d %s",
r->start, start, r->end, end);
}
}
out(NULL, "----------------------------------------------\n");
}
DEBUG("<== wakeup_dump_config()\n");
}
void wakeup_free(void) {
int i;
DEBUG("==> wakeup_free()\n");
/* Free rounds list */
if (contest.round) {
for (i = 0; i < contest.round_cnt; i++) free(contest.round[i]);
free(contest.round);
contest.round = NULL;
}
DEBUG("<== wakeup_free()\n");
}
static int is_dupe(HAM_S *me, QSO_S *my_qso) {
int his_id, band, mode, round;
QSO_S *qso;
DEBUG("==> is_dupe(%p, %p)\n", me, my_qso);
if (my_qso->dupe == 0) {
his_id = my_qso->ham_id;
band = get_band_id(my_qso->freq);
mode = my_qso->mode_op;
round = my_qso->round_num;
qso = me->qso;
while (qso && qso != my_qso) {
if (qso->ham_id == his_id && qso->dupe == 0 && \
get_band_id(qso->freq) == band && qso->mode_op == mode && qso->round_num == round) {
my_qso->dupe = 1;
my_qso->checked = QSO_STATUS_CONTEST_ERROR;
return 1; /* Found duplicate */
}
qso = qso->next;
}
} else return 1; /* Already is dupe */
DEBUG("<== is_dupe(%p, %p) = 0\n", me, my_qso);
return 0; /* No duplicates found */
}
static int set_round(QSO_S *qso) {
int i, round;
time_t timestamp;
ROUND_S *r;
DEBUG("==> set_round(%p)\n", qso);
round = 0;
if (contest.round) {
timestamp = qso->timestamp;
for (i = 0; i < contest.round_cnt; i++) {
r = contest.round[i];
if (timestamp >= r->start && timestamp <= r->end) {
round = r->num;
break;
}
}
}
qso->round_num = round;
DEBUG("<== set_round(%p) = %d\n", qso, round);
return round;
}
static int valid_rst(char *rst, int mode_id) {
int i, j, mode, valid, n;
char s[3];
valid = 0;
s[1] = s[2] = '\0';
for (i = 0; i < contest.mode_cnt; i++) {
mode = -1;
j = 0;
while (qso_mode[j].name) {
if (qso_mode[j].op_mode == contest.mode[i] && qso_mode[j].id == mode_id) {
mode = mode_type2id(mode_id);
break;
}
j++;
}
if (mode >= 0) break;
}
do {
if (mode == 1 || mode == 3) {
/* CW or Digital */
if (strlen(rst) == 3) {
/* Check 1'st number in RST */
s[0] = rst[0];
n = atoi(s);
if (n == 0 || n > 5) break; /* Not valid */
/* Check 2'nd & 3'rd numbers in RST */
s[0] = rst[1];
s[1] = rst[2];
n = atoi(s);
if (n < 11 || n > 99) break; /* Not valid */
valid = 1; /* RST is valid */
}
} else if (mode == 2) {
/* Phone */
if (strlen(rst) == 2) {
/* Check 1'st number in RST */
s[0] = rst[0];
n = atoi(s);
if (n == 0 || n > 5) break; /* Not valid */
/* Check 2'nd number in RST */
s[0] = rst[1];
n = atoi(s);
if (n == 0 || n > 9) break; /* Not valid */
valid = 1; /* RST is valid */
}
}
} while (0); /* dummy cycle */
return valid;
}
static int valid_cnt(char *number, int previous) {
int n;
n = atoi(number);
if (n > 0) {
if (n == previous + 1) return n; /* Valid */
return -n; /* Not valid */
}
return -previous - 1;
}
static void copy_suffix(char *suffix, char *callsign) {
int i, j, n;
char c;
j = 0;
n = strlen(callsign);
for (i = n - 1; i >= 0; i--) {
c = callsign[i];
if (c != '/') {
if (c >= '0' && c <= '9') {
if (j > 0) break; /* All done */
} else suffix[j++] = c; /* Copy next char from the end of callsign to suffix */
} else j = 0; /* Reset suffix if slash was found */
}
suffix[j] = '\0'; /* End of suffix string */
/* Note that suffix is in reversed order now */
if (j > 0) {
/* Reverse order of suffix chars */
for (i = 0; i < j / 2; i++) {
n = j - 1 - i;
c = suffix[n];
suffix[n] = suffix[i];
suffix[i] = c;
}
}
}
static int calc_distance(char *location1, char *location2, FILE *ubn_fd) {
int dist, lat1, lat2, lon1, lon2;
float lat1R, lat2R, lon1R, lon2R, dlatR, dlonR;
DEBUG("==> calc_distance(%s, %s, %p)\n", location1, location2, ubn_fd);
/* Convert grids into the lattitudes and longitudes (scaled to x100) */
if (gs2ll(&lat1, &lon1, location1) < 0) {
out(ubn_fd, "ERROR: Unable to convert QTH locator: '%s'\n", location1);
return -1;
}
if (gs2ll(&lat2, &lon2, location2) < 0) {
out(ubn_fd, "ERROR: Unable to convert QTH locator: '%s'\n", location2);
return -2;
}
/* Conversions */
lat1R = M_PI * (float)lat1 / 18000.0;
lon1R = M_PI * (float)lon1 / 18000.0;
lat2R = M_PI * (float)lat2 / 18000.0;
lon2R = M_PI * (float)lon2 / 18000.0;
dlatR = lat2R - lat1R;
dlonR = lon2R - lon1R;
/* Calculate Great Circle distance per Heversine formula */
dlatR = sinf(dlatR / 2);
dlonR = sinf(dlonR / 2);
dlatR = dlatR * dlatR + cosf(lat1R) * cosf(lat2R) * dlonR * dlonR;
dlonR = roundf(atan2f(sqrt(dlatR), sqrt(1 - dlatR)) * EARTH_RADIUS * 2);
dist = (int)dlonR;
DEBUG("<== calc_distance(%s, %s, %p) = %d\n", location1, location2, ubn_fd, dist);
return dist;
}
static int is_mult(HAM_S *ham, QSO_S *qso) {
int band_id, ham_id;
QSO_S *q;
if (qso->mult) return 0; /* No new mult if already is */
if (!config.contest.uniq_mult) band_id = get_band_id(qso->freq);
ham_id = qso->ham_id;
if (ham_id < -1 && unresolved_get(ham_id) < config.contest.cfm_unresolved) return 0; /* No mult */
q = ham->qso;
while (q) {
if (q->ham_id == ham_id && (q->checked == QSO_STATUS_CONTEST_XCHECK_OK || \
q->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID || q->marked)) {
if (!config.contest.uniq_mult) {
/* Allow multipliers by band */
if (q->mult && get_band_id(q->freq) == band_id) return 0; /* No mult */
} else {
/* Only unique multiplier */
if (q->mult) return 0; /* No mult */
}
return 1; /* Found mult */
}
q = q->next;
}
return 0; /* Mult was not found */
}
static int gs2ll(int *lattitude, int *longitude, const char *grid) {
/* Converts grid-square to lattitude & longitude (scaled to x100) */
char c1, c2, c5, c6;
signed char c3, c4;
int n, lat, lon;
DEBUG("==> gs2ll(%p, %p, '%s')\n", lattitude, longitude, grid);
if (grid && lattitude && longitude) {
/* Check for correct grid format */
n = strlen(grid);
if (n > 6) return -2; /* Grid string length is too big */
if (n < 2) return -3; /* Grid string length is too short */
/* Initialization */
c1 = c2 = c5 = c6 = '\0';
c3 = c4 = -1;
lat = lon = 0;
if (n >= 2) {
/* Get big square chars */
c1 = grid[0];
c2 = grid[1];
/* Convert to upper case */
if (c1 >= 'a' && c1 <= 'z') c1 += 'A' - 'a';
if (c2 >= 'a' && c2 <= 'z') c2 += 'A' - 'a';
/* Check for valid chars */
if (c1 < 'A' || c1 > 'Z' || c2 < 'A' || c2 > 'Z') return -4; /* Big square is incorrect */
}
if (n >= 4) {
/* Get grid numbers */
c3 = grid[2] - '0';
c4 = grid[3] - '0';
if (c3 < 0 || c3 > 9 || c4 < 0 || c4 > 9) return -5; /* Number in grid is incorrect */
}
if (n == 6) {
/* Get small square chars */
c5 = grid[4];
c6 = grid[5];
/* Convert to upper case */
if (c5 >= 'a' && c5 <= 'z') c5 += 'A' - 'a';
if (c6 >= 'a' && c6 <= 'z') c6 += 'A' - 'a';
/* Check for valid chars */
if (c5 < 'A' || c5 > 'Z' || c6 < 'A' || c6 > 'Z') return -6; /* Small square is incorrect */
}
if (c1 == '\0') return -7; /* Incorrect grid format */
/* Calculate lattitude and longitude (scaled to x100) */
lon = -18000 + (c1 - 'A') * 2000; /* Char 1 in big square */
lat = (c2 - 'A') * 1000 - 9000; /* Char 2 in big square */
if (c3 >= 0 && c4 >= 0) {
/* Add grid numbers */
lon += c3 * 200;
lat += c4 * 100;
if (c5 != '\0' && c6 != '\0') {
/* Add small square */
lon += (500 * (c5 - 'A') + 250) / 60;
lat += (250 * (c6 - 'A') + 125) / 60;
} else {
/* No small square present (only 4 chars in grid) */
/* Center of sub-square */
lon += 100;
lat += 50;
}
} else {
/* No grid numbers (only 2 chars in grid) */
/* Center of grid square */
lon += 1000;
lat += 500;
}
/* Result (scaled to x100) */
*lattitude = lat;
*longitude = lon;
} else return -1; /* NULL pointer */
DEBUG("<== gs2ll((%d), (%d), '%s')\n", *lattitude, *longitude, grid);
return 0; /* All OK */
}
static int check_band(HAM_S *ham, QSO_S *qso, unsigned int qso_num, FILE *ubn_fd) {
/* Return 1 if qso is within the permitted band list, 0 otherwise */
int i, b;
b = band(qso->freq);
for (i = 0; i < contest.band_cnt; i++)
if (contest.band[i] == b) return 1; /* Found. QSO band is allowed */
/* Used band is not allowed */
out(ubn_fd, "WARNING: %s <--> %s, QSO #%u: QSO band '%s' (%u kHz) is not allowed for this contest\n", ham->callsign, qso->callsign,
qso_num, get_band(b), qso->freq);
return 0;
}
static int check_mode(HAM_S *ham, QSO_S *qso, unsigned int qso_num, FILE *ubn_fd) {
/* Return 1 if qso is made with the permitted mode list, 0 otherwise */
int i, mode;
mode = mode_type2id(qso->mode_op);
for (i = 0; i < contest.mode_cnt; i++)
if (contest.mode[i] == mode) return 1; /* Found. QSO operation mode is allowed */
/* Used operation mode is not allowed */
out(ubn_fd, "WARNING: %s <--> %s, QSO #%u: Operating mode '%s' (%s) is not allowed for this contest\n", ham->callsign, qso->callsign,
qso_num, get_qso_mode(qso->mode_op), get_mode(mode));
return 0;
}
static int is_marked(QSO_S *qso, int *mark_id) {
int i, n, mark;
/* Search for the mark from the end of the check numbers list */
for (i = CHK_CNT - 1; i > 0; i--) {
n = strlen(qso->check_recv[i]);
if (n == 0) continue; /* Empty */
if (n > 2) return 0; /* Not marked: to many chars */
mark = 0;
while (n > 0) {
switch (qso->check_recv[i][n - 1]) {
case MARK_QSO_VALID:
mark |= MARK_ID_QSO_VALID;
break;
case MARK_QSO_INVALID:
mark |= MARK_ID_QSO_INVALID;
break;
case MARK_MULT_VALID:
mark |= MARK_ID_MULT_VALID;
break;
case MARK_MULT_INVALID:
mark |= MARK_ID_MULT_INVALID;
break;
}
n--;
}
if (mark) {
*mark_id = mark;
return 1; /* Marked */
} else break;
}
return 0; /* Not marked */
}