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