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