/**
* @file
* Code to read files of strategic moves and put them in a
* simpler form intended to be sorted.
* Last Modified: Sat Dec 27 09:17:15 PST 2014
* @author Kevin O'Gorman
**/
/*
* Copyright 2012-2014 Kevin O'Gorman <kogorman@gmail.com>.
* Distributed under the GNU General Public License.
*
* This file is part of libqubist, the library of functions for playing
* 4x4x4 tic-tac-toe, also known as Qubic, and by some other names
* outside the USA.
*
* Libqubist is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2.
*
* Libqubist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* @file
* Code to read files of strategic moves and put them in a
* simpler form intended to be sorted. The input must contain the moves, one per line,
* after a line that begins with 7 hyphens. There must be nothing else after this line.
*
* NOTE: Oren's strategic moves are saved on Google Docs at
* https://drive.google.com/file/d/0B6pbHEZND52eMzlzWWRCWFdrTm8/edit?usp=sharing
* You just place it in the current directory, and Qubist will read it in when it starts.
* You can also develop your own file, but it has to have the name "qubic.dictionary" for
* it to be accepted.
*
* Oren's file describes its own format. This code also accepts "-" characters as explicit
* empty cells, which is more convenient in these days of cheap storage.
*
* You can check you have the right file with any of these checksums:
* <pre>
* MD5SUM: 9b456c0256ad962ba767872cae426da1
* sha1sum: f166e5f03747692f2dfca3d938497e2631c74289
* sha224sum: 59ea2fc9de3e7c5f3fd33f62a269cb37c1ddd055fc7782324faf5941
* sha256sum: 38d9b7820fd97269968e6eea32c6448712d0551e66c1f2ed1ce47f74f3069499
* sha384sum: d601ac40226c2896ac6348dadf8c40f6c0c551a68f572bc8f320d63c616050f0384c62f70916e4af45f379671603d52
* sha512sum: cfe3ffc274a391754e35042f688de2d4d52e2588c965bb73087ac90d2bb1ece3a886b3c635b9d45257618861e702abf231a71f1624b361d3e8dedf005a68bf93
* </pre>
*
* TODO: accept and process multiple strategic moves per position, possibly with depth-of-win
* information.
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>
#undef NDEBUG
#define NDEBUG
#include <assert.h>
#define BUFL 1024
#include "moves.h"
/* Private data */
static move_t *qs_allstrats = NULL; /**< array of all strategic moves */
static int qs_strats_allocated = 0; /**< used-count within qs_allstrats */
static int qs_strats_count = 0; /**< allocated size of qs_allstrats */
static int qs_id = 0; /**< assigns sequential IDs to strategic moves */
static move_t *qs_strat_census_tabs[65] = {NULL}; /**< pointers (tabs) to census-groups in qs_allstrats */
/* Getter function. Also avoids compiler quibbles about array length. */
move_t *
qb_get_stratpos(int i)
{
return qs_strat_census_tabs[i];
}
/* Getter function. Also avoids compiler quibbles about array length. */
static int qs_max_strat_census = -1; /**< the current maximum census */
int
qb_get_max_census(void)
{
return qs_max_strat_census;
}
/* getter function. Also avoids compiler quibbles about array length. */
int
qb_get_strat_count(int census)
{
assert(0 <= census && census <= 64);
if (census > qs_max_strat_census) return 0;
return qs_strat_census_tabs[census+1] - qs_strat_census_tabs[census];
}
#define STRAT_CHUNK 3000 /**< How the collection grows */
/**
* Ensure adequate space in qs_allstrats.
*/
static void
makespace(void)
{
if (qs_strats_count + 1 >= qs_strats_allocated) {
qs_strats_allocated += STRAT_CHUNK;
qs_allstrats = (move_t*)realloc(qs_allstrats, qs_strats_allocated * sizeof(move_t));
if (!qs_allstrats) {
perror("realloc");
exit(EXIT_FAILURE);
}
}
}
/**
* Read strategic positions and set up the global arrays and variables.
*/
static void
qs_readStrats(FILE *stream) {
char buf[BUFL];
char *endptr;
/* Read and ignore everything up to a line that begins with at least 7 hypens. */
while (fgets(buf, BUFL, stream) != NULL) {
if (strncmp(buf, "-------", 7) == 0) {
break;
}
}
if (feof(stream) || ferror(stream)) {
fprintf(stderr, " *** ERROR 1: error reading strategic moves.\n");
exit(EXIT_FAILURE);
}
/* read actual strategic positions until the end of file */
while (fgets(buf, BUFL, stream) != NULL) {
int wt = 0;
int cur = 0;
int pos = 0;
int n;
char ch;
makespace();
/* read the position part */
while ((ch = buf[cur++]) != ' ' && isprint(ch)) {
/* always looking at an x, an o, - */
qs_allstrats[qs_strats_count].posn[pos++] = ch;
if (ch == 'x' || ch == 'o') wt++;
/* scan a number if present, making sure not to treat a '-' as beginning a number */
if (isdigit(buf[cur])) {
n = strtol(buf + cur, &endptr, 10);
cur = endptr - buf;
while (n) {
qs_allstrats[qs_strats_count].posn[pos++] = '-';
n--;
}
}
}
if (ch != ' ') {
fprintf(stderr, " *** ERROR 2: error reading strategic moves.\n");
exit(EXIT_FAILURE);
}
while (pos < 64) {
qs_allstrats[qs_strats_count].posn[pos++] = '-';
}
qs_allstrats[qs_strats_count].posn[64] = '\0';
qs_allstrats[qs_strats_count].census = wt;
qs_allstrats[qs_strats_count].move = strtol(buf + cur, &endptr, 10);
qs_allstrats[qs_strats_count].id = ++qs_id;
qs_strats_count++;
}
if (ferror(stream)) {
fprintf(stderr, " *** ERROR 4: error reading strategic moves.\n");
exit(EXIT_FAILURE);
}
}
/**
* Comparison routine for use with qsort.
*/
int
compare_strats(const void *a, const void *b)
{
int diff;
diff = ((move_t*)a)->census - ((move_t*)b)->census;
if (diff) return diff;
return strncmp(((move_t*)a)->posn, ((move_t*)b)->posn, 64);
}
/**
* Finalize the reading of strategic positions, sorting by census then position,
* then using realloc() to discard any unused positions.
*/
static void
qs_finalize_strats(void)
{
int i;
int c;
if (qs_strats_allocated > 0) {
/* finalize, sort and resize the qs_allstrats list of strategic moves */
qs_allstrats[qs_strats_count].census = 65;
qs_allstrats[qs_strats_count].id = 999999999;
qs_strats_count++;
qs_strats_allocated = qs_strats_count;
qsort(qs_allstrats, qs_strats_count, sizeof(move_t), (int (*)(const void*,const void*))compare_strats);
qs_allstrats = (move_t*)realloc(qs_allstrats, qs_strats_allocated * sizeof(move_t));
qs_max_strat_census = qs_allstrats[qs_strats_count-2].census;
/* Create tabs into the list, organized by census */
qs_strat_census_tabs[0] = qs_allstrats;
for (c = 0, i = 1; i < qs_strats_count; i++) {
while (c != qs_allstrats[i].census) {
assert(c < qs_allstrats[i].census);
c++;
qs_strat_census_tabs[c] = qs_allstrats + i;
}
}
}
}
/**
* Set up the table of strategic positions and moves. This comes from all of the
* files with names ending in .dictionary.
*/
void
qb_initialize_strats(void)
{
DIR *dir;
FILE *stream;
struct dirent* entry;
int len;
dir = opendir(".");
if (!dir) {
perror("opendir");
exit(EXIT_FAILURE);
}
while ((entry = readdir(dir))) {
len = strlen(entry->d_name);
if (len > 11 && !strcmp(".dictionary", entry->d_name + len - 11)) {
stream = fopen(entry->d_name, "r");
qs_readStrats(stream);
fclose(stream);
}
}
closedir(dir);
qs_finalize_strats();
}
#if 0 /* For unit testing only */
/**
* The usual main function. In this case it's all of the compile-moves program.
* @param argc a count of the fields on the command-line.
* @param argv an array of pointers to those fields.
* @return 0 for success; any result in the range 1-255 on failure.
*/
int
main(int argc, char *argv[])
{
int i;
qb_initialize_strats();
for (i = 0; i < 32; i++) {
printf("%2d: %d\n", i, qb_get_strat_count(i));
}
printf("Max census of any strategic move: %d\n", qb_get_max_census());
return EXIT_SUCCESS;
}
#endif
/* vim: set expandtab autoindent shiftround nosmartindent tabstop=8 softtabstop=2 shiftwidth=2: */