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