h48

A prototype for an optimal Rubik's cube solver, work in progress.
git clone https://git.tronto.net/h48
Download | Log | Files | Refs | README | LICENSE

shell.c (16909B)


      1 #include <inttypes.h>
      2 #include <errno.h>
      3 #include <stdarg.h>
      4 #include <stdbool.h>
      5 #include <stdio.h>
      6 #include <stdlib.h>
      7 #include <string.h>
      8 #include <time.h>
      9 
     10 #include "src/nissy.h"
     11 
     12 #define PRINTCUBE_BUFFER_SIZE 1024 /* Should be enough */
     13 #define SOLUTIONS_BUFFER_SIZE 500000 /* Should be enough */
     14 #define MAX_PATH_LENGTH 10000 /* Should be enough */
     15 
     16 #define _flag_cube         "-cube"
     17 #define _flag_perm         "-perm"
     18 #define _flag_command      "-command"
     19 #define _flag_str_cube     "-cubestr"
     20 #define _flag_format       "-format"
     21 #define _flag_format_in    "-fin"
     22 #define _flag_format_out   "-fout"
     23 #define _flag_moves        "-moves"
     24 #define _flag_trans        "-trans"
     25 #define _flag_solver       "-solver"
     26 #define _flag_options      "-options"
     27 #define _flag_nisstype     "-nisstype"
     28 #define _flag_minmoves     "-m"
     29 #define _flag_maxmoves     "-M"
     30 #define _flag_optimal      "-O"
     31 #define _flag_maxsolutions "-n"
     32 
     33 #define _info_cubeformat(cube) cube " must be given in B32 format."
     34 #define _info_movesformat "The accepted moves are U, D, R, L, F and B, " \
     35     "optionally followed by a 2, a ' or a 3."
     36 #define _info_transformat "The transformation must be given in the format " \
     37     "(rotation|mirrored) (2 letters), for exmple " \
     38     "'rotation UF' or 'mirrored BL'."
     39 #define _info_formats "The available formats are H48, B32 and SRC."
     40 
     41 typedef struct {
     42 	int command_index;
     43 	char cube[22];
     44 	char cube_perm[22];
     45 	char *str_command;
     46 	char *str_cube;
     47 	char *str_format;
     48 	char *str_format_in;
     49 	char *str_format_out;
     50 	char *str_moves;
     51 	char *str_trans;
     52 	char *str_solver;
     53 	char *str_options; /* TODO: remove, use only solver */
     54 	char *str_nisstype; /* TODO: remove, use flags */
     55 	int8_t minmoves;
     56 	int8_t maxmoves;
     57 	int8_t optimal;
     58 	int64_t maxsolutions;
     59 } args_t;
     60 
     61 static void print_cube_result(int64_t, char [static 22]);
     62 static void print_str_result(int64_t, char *);
     63 
     64 static int64_t compose_exec(args_t *);
     65 static int64_t inverse_exec(args_t *);
     66 static int64_t applymoves_exec(args_t *);
     67 static int64_t applytrans_exec(args_t *);
     68 static int64_t frommoves_exec(args_t *);
     69 static int64_t convert_exec(args_t *);
     70 static int64_t randomcube_exec(args_t *);
     71 static int64_t datasize_exec(args_t *);
     72 static int64_t gendata_exec(args_t *);
     73 static int64_t solve_exec(args_t *);
     74 static int64_t help_exec(args_t *);
     75 
     76 static int parse_args(int, char **, args_t *);
     77 static bool parse_int8(char *, int8_t *);
     78 static bool parse_int64(char *, int64_t *);
     79 
     80 static bool set_cube(int, char **, args_t *);
     81 static bool set_cube_perm(int, char **, args_t *);
     82 static bool set_str_command(int, char **, args_t *);
     83 static bool set_str_cube(int, char **, args_t *);
     84 static bool set_str_format(int, char **, args_t *);
     85 static bool set_str_format_in(int, char **, args_t *);
     86 static bool set_str_format_out(int, char **, args_t *);
     87 static bool set_str_moves(int, char **, args_t *);
     88 static bool set_str_trans(int, char **, args_t *);
     89 static bool set_str_solver(int, char **, args_t *);
     90 static bool set_str_options(int, char **, args_t *);
     91 static bool set_str_nisstype(int, char **, args_t *);
     92 static bool set_minmoves(int, char **, args_t *);
     93 static bool set_maxmoves(int, char **, args_t *);
     94 static bool set_optimal(int, char **, args_t *);
     95 static bool set_maxsolutions(int, char **, args_t *);
     96 static bool set_id(int, char **, args_t *);
     97 
     98 static uint64_t rand64(void);
     99 
    100 #define OPTION(N, A, S) { .name = N, .nargs = A, .set = S }
    101 struct {
    102 	char *name;
    103 	int nargs;
    104 	bool (*set)(int, char **, args_t *);
    105 } options[] = {
    106 	OPTION(_flag_cube, 1, set_cube),
    107 	OPTION(_flag_perm, 1, set_cube_perm),
    108 	OPTION(_flag_command, 1, set_str_command),
    109 	OPTION(_flag_str_cube, 1, set_str_cube),
    110 	OPTION(_flag_format, 1, set_str_format),
    111 	OPTION(_flag_format_in, 1, set_str_format_in),
    112 	OPTION(_flag_format_out, 1, set_str_format_out),
    113 	OPTION(_flag_moves, 1, set_str_moves),
    114 	OPTION(_flag_trans, 1, set_str_trans),
    115 	OPTION(_flag_solver, 1, set_str_solver),
    116 	OPTION(_flag_options, 1, set_str_options), /* TODO: remove, use only solver */
    117 	OPTION(_flag_nisstype, 1, set_str_nisstype), /* TODO: remove, use flags */
    118 	OPTION(_flag_minmoves, 1, set_minmoves),
    119 	OPTION(_flag_maxmoves, 1, set_maxmoves),
    120 	OPTION(_flag_optimal, 1, set_optimal),
    121 	OPTION(_flag_maxsolutions, 1, set_maxsolutions),
    122 	OPTION(NULL, 0, NULL)
    123 };
    124  
    125 #define COMMAND(N, S, D, E) { .name = N, .syn = S, .desc = D, .exec = E }
    126 struct {
    127 	char *name;
    128 	char *syn;
    129 	char *desc;
    130 	int64_t (*exec)(args_t *);
    131 } commands[] = {
    132 /* TODO: add synopsis and description here */
    133 	COMMAND(
    134 		"compose",
    135 		"compose " _flag_cube " CUBE " _flag_perm " PERM",
    136 		"Apply on CUBE the permutation defined by PERM. "
    137 		_info_cubeformat("CUBE and PERM"),
    138 		compose_exec
    139 	),
    140 	COMMAND(
    141 		"inverse",
    142 		"inverse " _flag_cube " CUBE ",
    143 		"Compute the inverse of the given CUBE. "
    144 		_info_cubeformat("CUBE"),
    145 		inverse_exec
    146 	),
    147 	COMMAND(
    148 		"applymoves",
    149 		"applymoves " _flag_cube " CUBE " _flag_moves " MOVES",
    150 		"Apply the given MOVES to the given CUBE. "
    151 		_info_cubeformat("CUBE") " " _info_movesformat,
    152 		applymoves_exec
    153 	),
    154 	COMMAND(
    155 		"applytrans",
    156 		"applytrans " _flag_cube " CUBE " _flag_trans " TRANS",
    157 		"Apply the single transformation TRANS to the given CUBE. "
    158 		_info_cubeformat("CUBE") " " _info_transformat,
    159 		applytrans_exec
    160 	),
    161 	COMMAND(
    162 		"frommoves",
    163 		"frommoves " _flag_moves " MOVES",
    164 		"Return the cube obtained by applying the given MOVES "
    165 		"to a solved cube. " _info_movesformat,
    166 		frommoves_exec
    167 	),
    168 	COMMAND(
    169 		"convert",
    170 		"convert " _flag_str_cube " CUBESTR "
    171 		_flag_format_in " FORMAT_IN " _flag_format_out " FORMAT_OUT",
    172 		"Convert the cube described by CUBESTR from FORMAT_IN to "
    173 		"FORMAT_OUT."
    174 		_info_formats " "
    175 		"CUBESTR must be a valid cube in the FORMAT_IN format.",
    176 		convert_exec
    177 	),
    178 	COMMAND(
    179 		"randomcube",
    180 		"randomcube",
    181 		"Returns a random cube in B32 format.",
    182 		randomcube_exec
    183 	),
    184 	COMMAND(
    185 		"datasize",
    186 		"datasize " _flag_solver " SOLVER " _flag_options " OPTIONS",
    187 		"Return the size in bytes of the data table used by "
    188 		"SOLVER when called with the given OPTIONS.",
    189 		datasize_exec
    190 	),
    191 	COMMAND(
    192 		"gendata",
    193 		"gendata " _flag_solver " SOLVER " _flag_options " OPTIONS",
    194 		"Generate the data table used by "
    195 		"SOLVER when called with the given OPTIONS.",
    196 		gendata_exec
    197 	),
    198 	COMMAND(
    199 		"solve",
    200 		"solve " _flag_solver " SOLVER " _flag_options " OPTIONS "
    201 		"[" _flag_minmoves " n] [" _flag_maxmoves " N] "
    202 		_flag_cube " CUBE",
    203 		"Solve the given CUBE using SOLVER with the given OPTIONS, "
    204 		"using at least n and at most N moves. "
    205 		_info_cubeformat("CUBE"),
    206 		solve_exec
    207 	),
    208 	COMMAND(
    209 		"help",
    210 		"help [" _flag_command " COMMAND]",
    211 		"If no COMMAND is specified, prints some generic information "
    212 		"and the list of commands. Otherwise it prints detailed "
    213 		"information about the specified COMMAND.",
    214 		help_exec
    215 	),
    216 	COMMAND(NULL, NULL, NULL, NULL)
    217 };
    218 
    219 char *tablepaths[] = {
    220 	"tables/",
    221 	"",
    222 	NULL
    223 };
    224 
    225 static uint64_t
    226 rand64(void)
    227 {
    228 	uint64_t i, ret;
    229 
    230 	for (i = 0, ret = 0; i < 64; i++)
    231 		ret |= (uint64_t)(rand() % 2) << i;
    232 
    233 	return ret;
    234 }
    235 
    236 static void
    237 print_cube_result(int64_t ret, char result[static 22])
    238 {
    239 	switch (ret) {
    240 	case 0:
    241 		break;
    242 	case 1:
    243 		fprintf(stderr, "Warning: resulting cube not solvable\n");
    244 		break;
    245 	case 2: /* Fallthrough */
    246 	default:
    247 		fprintf(stderr, "Unknown error (result is inconsistent)\n");
    248 	}
    249 
    250 	printf("%s\n", result);
    251 }
    252 
    253 static void
    254 print_str_result(int64_t ret, char *result)
    255 {
    256 	switch (ret) {
    257 	case 0:
    258 		break;
    259 	default:
    260 		fprintf(stderr, "Unknown error\n");
    261 	}
    262 
    263 	printf("%s\n", result);
    264 }
    265 
    266 static int64_t
    267 compose_exec(args_t *args)
    268 {
    269 	char result[22];
    270 	int64_t ret;
    271 
    272 	ret = nissy_compose(args->cube, args->cube_perm, result);
    273 	print_cube_result(ret, result);
    274 
    275 	return ret;
    276 }
    277 
    278 static int64_t
    279 inverse_exec(args_t *args)
    280 {
    281 	char result[22];
    282 	int64_t ret;
    283 
    284 	ret = nissy_inverse(args->cube, result);
    285 	print_cube_result(ret, result);
    286 
    287 	return ret;
    288 }
    289 
    290 static int64_t
    291 applymoves_exec(args_t *args)
    292 {
    293 	char result[22];
    294 	int64_t ret;
    295 
    296 	ret = nissy_applymoves(args->cube, args->str_moves, result);
    297 	print_cube_result(ret, result);
    298 
    299 	return ret;
    300 }
    301 
    302 static int64_t
    303 applytrans_exec(args_t *args)
    304 {
    305 	char result[22];
    306 	int64_t ret;
    307 
    308 	ret = nissy_applytrans(args->cube, args->str_trans, result);
    309 	print_cube_result(ret, result);
    310 
    311 	return ret;
    312 }
    313 
    314 static int64_t
    315 frommoves_exec(args_t *args)
    316 {
    317 	char result[22];
    318 	int64_t ret;
    319 
    320 	ret = nissy_frommoves(args->str_moves, result);
    321 	print_cube_result(ret, result);
    322 
    323 	return ret;
    324 }
    325 
    326 static int64_t
    327 convert_exec(args_t *args)
    328 {
    329 	char result[PRINTCUBE_BUFFER_SIZE];
    330 	int64_t ret;
    331 
    332 	ret = nissy_convert(
    333 	    args->str_format_in, args->str_format_out, args->str_cube, result);
    334 	print_str_result(ret, result);
    335 
    336 	return ret;
    337 }
    338 
    339 static int64_t
    340 randomcube_exec(args_t *args)
    341 {
    342 	char result[PRINTCUBE_BUFFER_SIZE];
    343 	int64_t ret, ep, eo, cp, co;
    344 
    345 	ep = rand64();
    346 	eo = rand64();
    347 	cp = rand64();
    348 	co = rand64();
    349 	ret = nissy_getcube(ep, eo, cp, co, "fix", result);
    350 	print_str_result(ret, result);
    351 
    352 	return ret;
    353 }
    354 
    355 static int64_t
    356 datasize_exec(args_t *args)
    357 {
    358 	int64_t ret;
    359 
    360 	ret = nissy_datasize(args->str_solver, args->str_options);
    361 	if (ret < 0)
    362 		fprintf(stderr, "Unknown error (make sure solver is valid)\n");
    363 	printf("%" PRId64 "\n", ret);
    364 
    365 	return ret;
    366 }
    367 
    368 static int64_t
    369 gendata_exec(args_t *args)
    370 {
    371 	int i;
    372 	FILE *file;
    373 	char *buf, path[MAX_PATH_LENGTH];
    374 	int64_t ret, size;
    375 	size_t written;
    376 
    377 	/* TODO: should give warning if overwriting existing file */
    378 	for (i = 0; tablepaths[i] != NULL; i++) {
    379 		strcpy(path, tablepaths[i]);
    380 		strcat(path, args->str_solver);
    381 		file = fopen(path, "wb");
    382 		if (file != NULL)
    383 			break;
    384 	}
    385 
    386 	if (tablepaths[i] == NULL) {
    387 		fprintf(stderr, "Cannot write data to file\n");
    388 		fclose(file);
    389 		return -2;
    390 	}
    391 
    392 	size = nissy_datasize(args->str_solver, args->str_options);
    393 
    394 	if (size < 0) {
    395 		fprintf(stderr,
    396 		    "Unknown error in retrieving data size"
    397 		    "(make sure solver is valid)\n");
    398 		fclose(file);
    399 		return -3;
    400 	}
    401 
    402 	buf = malloc(size);
    403 
    404 	ret = nissy_gendata(args->str_solver, args->str_options, buf);
    405 	if (ret < 0) {
    406 		fprintf(stderr, "Unknown error in generating data\n");
    407 		fclose(file);
    408 		free(buf);
    409 		return -4;
    410 	}
    411 	if (ret != size) {
    412 		fprintf(stderr, "Unknown error: unexpected data size "
    413 		    "got %" PRId64 ", expected %" PRId64 ")\n", ret, size);
    414 		fclose(file);
    415 		free(buf);
    416 		return -5;
    417 	}
    418 
    419 	written = fwrite(buf, size, 1, file);
    420 	fclose(file);
    421 	free(buf);
    422 
    423 	if (written != 1) {
    424 		fprintf(stderr,
    425 		    "Error: data was generated correctly, but could not be "
    426 		    "written to file (generated %" PRId64 " bytes, written "
    427 		    "%zu)\n", size, written);
    428 		return -6;
    429 	}
    430 
    431 	fprintf(stderr, "Data written to %s\n", path);
    432 
    433 	return 0;
    434 }
    435 
    436 static int64_t
    437 solve_exec(args_t *args)
    438 {
    439 	int i;
    440 	FILE *file;
    441 	char *buf, solutions[SOLUTIONS_BUFFER_SIZE], path[MAX_PATH_LENGTH];
    442 	int64_t ret, gendata_ret, size;
    443 	size_t read;
    444 
    445 	for (i = 0; tablepaths[i] != NULL; i++) {
    446 		strcpy(path, tablepaths[i]);
    447 		strcat(path, args->str_solver);
    448 		file = fopen(path, "rb");
    449 		if (file != NULL)
    450 			break;
    451 	}
    452 
    453 	if (tablepaths[i] == NULL) {
    454 		fprintf(stderr,
    455 		    "Cannot read data file, "
    456 		    "generating it (this can take a while)\n");
    457 		gendata_ret = gendata_exec(args);
    458 		if (gendata_ret)
    459 			return gendata_ret;
    460 	}
    461 
    462 	/* Ugh, this is not elegant TODO */
    463 	if (file == NULL) {
    464 		for (i = 0; tablepaths[i] != NULL; i++) {
    465 			strcpy(path, tablepaths[i]);
    466 			strcat(path, args->str_solver);
    467 			file = fopen(path, "rb");
    468 			if (file != NULL)
    469 				break;
    470 		}
    471 	}
    472 
    473 	if (tablepaths[i] == NULL) {
    474 		fprintf(stderr, "Error: data file not found\n");
    475 		fclose(file);
    476 		return -1;
    477 	}
    478 
    479 	size = nissy_datasize(args->str_solver, args->str_options);
    480 	buf = malloc(size);
    481 	read = fread(buf, size, 1, file);
    482 	fclose(file);
    483 	if (read != 1) {
    484   		fprintf(stderr, "Error reading data from file: "
    485 		    "fread() returned %zu instead of 1 when attempting to"
    486 		    "read %" PRId64 " bytes from file %s\n", read, size, path);
    487 		return -2;
    488 	}
    489 
    490 	ret = nissy_solve(
    491 	    args->cube, args->str_solver, args->str_options, args->str_nisstype,
    492 	    args->minmoves, args->maxmoves, args->maxsolutions, args->optimal,
    493 	    buf, solutions);
    494 
    495 	free(buf);
    496 
    497 	if (ret == 0)
    498 		fprintf(stderr, "No solutions found\n");
    499 	else
    500 		printf("%s", solutions);
    501 
    502 	return 0;
    503 }
    504 
    505 static int64_t
    506 help_exec(args_t *args)
    507 {
    508 	int i;
    509 
    510 	if (args->str_command == NULL || args->str_command[0] == '\0') {
    511 		printf("This is a rudimentary shell for the H48 library.\n");
    512 		printf("Available commands and usage:\n\n");
    513 		for (i = 0; commands[i].name != NULL; i++)
    514 			printf("%-15s%s\n", commands[i].name, commands[i].syn);
    515 		printf("\nUse 'help COMMAND' for more information.\n");
    516 	} else {
    517 		for (i = 0; commands[i].name != NULL; i++)
    518 			if (!strcmp(args->str_command, commands[i].name))
    519 				break;
    520 		if (commands[i].name == NULL) {
    521 			printf("Unknown command %s\n", args->str_command);
    522 			return 1;
    523 		}
    524 		printf("Command %s\n\n", commands[i].name);
    525 		printf("Synopsis: %s\n\n", commands[i].syn);
    526 		printf("Description: %s\n", commands[i].desc);
    527 	}
    528 
    529 	return 0;
    530 }
    531 
    532 static int
    533 parse_args(int argc, char **argv, args_t *args)
    534 {
    535 	int i, j, n;
    536 
    537 	*args = (args_t) {
    538 		.command_index = -1,
    539 		.cube = "",
    540 		.cube_perm = "",
    541 		.str_cube = "",
    542 		.str_format = "",
    543 		.str_format_in = "",
    544 		.str_format_out = "",
    545 		.str_moves = "",
    546 		.str_trans = "",
    547 		.str_solver = "",
    548 		.str_options = "",
    549 		.str_nisstype = "",
    550 		.minmoves = 0,
    551 		.maxmoves = 20,
    552 		.optimal = -1,
    553 		.maxsolutions = 1,
    554 	};
    555 
    556 	if (argc == 0) {
    557 		printf("No command given\n");
    558 		return 1;
    559 	}
    560 
    561 	for (i = 0; commands[i].name != NULL; i++) {
    562 		if (!strcmp(argv[0], commands[i].name)) {
    563 			args->command_index = i;
    564 			break;
    565 		}
    566 	}
    567 
    568 	if (commands[i].name == NULL) {
    569 		fprintf(stderr, "Unknown command %s\n", argv[0]);
    570 		return 1;
    571 	}
    572 
    573 	for (i = 1; i < argc; i++) {
    574 		for (j = 0; options[j].name != NULL; j++) {
    575 			n = argc - i - 1;
    576 			if (strcmp(argv[i], options[j].name))
    577 				continue;
    578 			if (n < options[j].nargs) {
    579 				fprintf(stderr,
    580 				    "Too few arguments for option %s\n",
    581 				    options[j].name);
    582 				return 1;
    583 			}
    584 			if (!options[j].set(n, argv+i+1, args)) {
    585 				fprintf(stderr,
    586 				    "Error parsing arguments for option %s\n",
    587 				    options[j].name);
    588 				return 1;
    589 			}
    590 			i += options[j].nargs;
    591 			break;
    592 		}
    593 		if (options[j].name == NULL) {
    594 			fprintf(stderr, "Unknown option %s\n", argv[i]);
    595 			return 1;
    596 		}
    597 	}
    598 
    599 	return 0;
    600 }
    601 
    602 bool
    603 parse_int8(char *argv, int8_t *result)
    604 {
    605 	bool noerror;
    606 	int64_t n;
    607 
    608 	noerror = parse_int64(argv, &n);
    609 	*result = (int8_t)n;
    610 
    611 	return noerror && n >= INT8_MIN && n <= INT8_MAX;
    612 }
    613 
    614 bool
    615 parse_int64(char *argv, int64_t *result)
    616 {
    617 	*result = strtoll(argv, NULL, 10);
    618 
    619 	/* TODO: figure out how errno works and use it */
    620 	return true;
    621 }
    622 
    623 static bool
    624 set_cube(int argc, char **argv, args_t *args)
    625 {
    626 	memcpy(args->cube, argv[0], 22);
    627 	args->cube[21] = 0;
    628 
    629 	return true;
    630 }
    631 
    632 static bool
    633 set_cube_perm(int argc, char **argv, args_t *args)
    634 {
    635 	memcpy(args->cube_perm, argv[0], 22);
    636 	args->cube_perm[21] = 0;
    637 
    638 	return true;
    639 }
    640 
    641 static bool
    642 set_str_command(int argc, char **argv, args_t *args)
    643 {
    644 	args->str_command = argv[0];
    645 
    646 	return true;
    647 }
    648 
    649 static bool
    650 set_str_cube(int argc, char **argv, args_t *args)
    651 {
    652 	args->str_cube = argv[0];
    653 
    654 	return true;
    655 }
    656 
    657 static bool
    658 set_str_format(int argc, char **argv, args_t *args)
    659 {
    660 	args->str_format = argv[0];
    661 
    662 	return true;
    663 }
    664 
    665 static bool
    666 set_str_format_in(int argc, char **argv, args_t *args)
    667 {
    668 	args->str_format_in = argv[0];
    669 
    670 	return true;
    671 }
    672 
    673 static bool
    674 set_str_format_out(int argc, char **argv, args_t *args)
    675 {
    676 	args->str_format_out = argv[0];
    677 
    678 	return true;
    679 }
    680 
    681 static bool
    682 set_str_moves(int argc, char **argv, args_t *args)
    683 {
    684 	args->str_moves = argv[0];
    685 
    686 	return true;
    687 }
    688 
    689 static bool
    690 set_str_trans(int argc, char **argv, args_t *args)
    691 {
    692 	args->str_trans = argv[0];
    693 
    694 	return true;
    695 }
    696 
    697 static bool
    698 set_str_solver(int argc, char **argv, args_t *args)
    699 {
    700 	args->str_solver = argv[0];
    701 
    702 	return true;
    703 }
    704 
    705 static bool
    706 set_str_options(int argc, char **argv, args_t *args)
    707 {
    708 	args->str_options = argv[0];
    709 
    710 	return true;
    711 }
    712 
    713 static bool
    714 set_str_nisstype(int argc, char **argv, args_t *args)
    715 {
    716 	args->str_nisstype = argv[0];
    717 
    718 	return true;
    719 }
    720 
    721 static bool
    722 set_minmoves(int argc, char **argv, args_t *args)
    723 {
    724 	return parse_int8(argv[0], &args->minmoves);
    725 }
    726 
    727 static bool
    728 set_maxmoves(int argc, char **argv, args_t *args)
    729 {
    730 	return parse_int8(argv[0], &args->maxmoves);
    731 }
    732 
    733 static bool
    734 set_optimal(int argc, char **argv, args_t *args)
    735 {
    736 	return parse_int8(argv[0], &args->optimal);
    737 }
    738 
    739 static bool
    740 set_maxsolutions(int argc, char **argv, args_t *args)
    741 {
    742 	return parse_int64(argv[0], &args->maxsolutions);
    743 }
    744 
    745 void
    746 log_stderr(const char *str, ...)
    747 {
    748 	va_list args;
    749 
    750 	va_start(args, str);
    751 	vfprintf(stderr, str, args);
    752 	va_end(args);
    753 }
    754 
    755 int
    756 main(int argc, char **argv)
    757 {
    758 	int parse_error;
    759 	args_t args;
    760 
    761 	srand(time(NULL));
    762 	nissy_setlogger(log_stderr);
    763 
    764 	parse_error = parse_args(argc-1, argv+1, &args);
    765 	if (parse_error)
    766 		return parse_error;
    767 
    768 	return (int)commands[args.command_index].exec(&args);
    769 }