shell.c (17045B)
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 UINT64_C(1024) 13 #define SOLUTIONS_BUFFER_SIZE UINT64_C(500000) 14 #define MAX_PATH_LENGTH UINT64_C(10000) 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_NISSTYPE "-nisstype" 27 #define FLAG_MINMOVES "-m" 28 #define FLAG_MAXMOVES "-M" 29 #define FLAG_OPTIMAL "-O" 30 #define FLAG_MAXSOLUTIONS "-n" 31 #define FLAG_THREADS "-t" 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_nisstype; 54 unsigned minmoves; 55 unsigned maxmoves; 56 unsigned optimal; 57 unsigned maxsolutions; 58 unsigned threads; 59 } args_t; 60 61 static int64_t compose_exec(args_t *); 62 static int64_t inverse_exec(args_t *); 63 static int64_t applymoves_exec(args_t *); 64 static int64_t applytrans_exec(args_t *); 65 static int64_t frommoves_exec(args_t *); 66 static int64_t convert_exec(args_t *); 67 static int64_t randomcube_exec(args_t *); 68 static int64_t datasize_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 help_exec(args_t *); 74 75 static int parse_args(int, char **, args_t *); 76 static bool parse_uint(char *, unsigned *); 77 78 static bool set_cube(int, char **, args_t *); 79 static bool set_cube_perm(int, char **, args_t *); 80 static bool set_str_command(int, char **, args_t *); 81 static bool set_str_cube(int, char **, args_t *); 82 static bool set_str_format(int, char **, args_t *); 83 static bool set_str_format_in(int, char **, args_t *); 84 static bool set_str_format_out(int, char **, args_t *); 85 static bool set_str_moves(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_nisstype(int, char **, args_t *); 89 static bool set_minmoves(int, char **, args_t *); 90 static bool set_maxmoves(int, char **, args_t *); 91 static bool set_optimal(int, char **, args_t *); 92 static bool set_maxsolutions(int, char **, args_t *); 93 static bool set_threads(int, char **, args_t *); 94 static bool set_id(int, char **, args_t *); 95 96 static uint64_t rand64(void); 97 98 #define OPTION(N, A, S) { .name = N, .nargs = A, .set = S } 99 struct { 100 char *name; 101 int nargs; 102 bool (*set)(int, char **, args_t *); 103 } options[] = { 104 OPTION(FLAG_CUBE, 1, set_cube), 105 OPTION(FLAG_PERM, 1, set_cube_perm), 106 OPTION(FLAG_COMMAND, 1, set_str_command), 107 OPTION(FLAG_STR_CUBE, 1, set_str_cube), 108 OPTION(FLAG_FORMAT, 1, set_str_format), 109 OPTION(FLAG_FORMAT_IN, 1, set_str_format_in), 110 OPTION(FLAG_FORMAT_OUT, 1, set_str_format_out), 111 OPTION(FLAG_MOVES, 1, set_str_moves), 112 OPTION(FLAG_TRANS, 1, set_str_trans), 113 OPTION(FLAG_SOLVER, 1, set_str_solver), 114 OPTION(FLAG_NISSTYPE, 1, set_str_nisstype), /* TODO: more args ? */ 115 OPTION(FLAG_MINMOVES, 1, set_minmoves), 116 OPTION(FLAG_MAXMOVES, 1, set_maxmoves), 117 OPTION(FLAG_OPTIMAL, 1, set_optimal), 118 OPTION(FLAG_MAXSOLUTIONS, 1, set_maxsolutions), 119 OPTION(FLAG_THREADS, 1, set_threads), 120 OPTION(NULL, 0, NULL) 121 }; 122 123 #define COMMAND(N, S, D, E) { .name = N, .syn = S, .desc = D, .exec = E } 124 struct { 125 char *name; 126 char *syn; 127 char *desc; 128 int64_t (*exec)(args_t *); 129 } commands[] = { 130 /* TODO: add synopsis and description here */ 131 COMMAND( 132 "compose", 133 "compose " FLAG_CUBE " CUBE " FLAG_PERM " PERM", 134 "Apply on CUBE the permutation defined by PERM. " 135 INFO_CUBEFORMAT("CUBE and PERM"), 136 compose_exec 137 ), 138 COMMAND( 139 "inverse", 140 "inverse " FLAG_CUBE " CUBE ", 141 "Compute the inverse of the given CUBE. " 142 INFO_CUBEFORMAT("CUBE"), 143 inverse_exec 144 ), 145 COMMAND( 146 "applymoves", 147 "applymoves " FLAG_CUBE " CUBE " FLAG_MOVES " MOVES", 148 "Apply the given MOVES to the given CUBE. " 149 INFO_CUBEFORMAT("CUBE") " " INFO_MOVESFORMAT, 150 applymoves_exec 151 ), 152 COMMAND( 153 "applytrans", 154 "applytrans " FLAG_CUBE " CUBE " FLAG_TRANS " TRANS", 155 "Apply the single transformation TRANS to the given CUBE. " 156 INFO_CUBEFORMAT("CUBE") " " INFO_TRANSFORMAT, 157 applytrans_exec 158 ), 159 COMMAND( 160 "frommoves", 161 "frommoves " FLAG_MOVES " MOVES", 162 "Return the cube obtained by applying the given MOVES " 163 "to a solved cube. " INFO_MOVESFORMAT, 164 frommoves_exec 165 ), 166 COMMAND( 167 "convert", 168 "convert " FLAG_STR_CUBE " CUBESTR " 169 FLAG_FORMAT_IN " FORMAT_IN " FLAG_FORMAT_OUT " FORMAT_OUT", 170 "Convert the cube described by CUBESTR from FORMAT_IN to " 171 "FORMAT_OUT." 172 INFO_FORMATS " " 173 "CUBESTR must be a valid cube in the FORMAT_IN format.", 174 convert_exec 175 ), 176 COMMAND( 177 "randomcube", 178 "randomcube", 179 "Returns a random cube in B32 format.", 180 randomcube_exec 181 ), 182 COMMAND( 183 "datasize", 184 "datasize " FLAG_SOLVER " SOLVER", 185 "Return the size in bytes of the data table used by " 186 "SOLVER when called with the given OPTIONS.", 187 datasize_exec 188 ), 189 COMMAND( 190 "gendata", 191 "gendata " FLAG_SOLVER " SOLVER", 192 "Generate the data table used by " 193 "SOLVER when called with the given OPTIONS.", 194 gendata_exec 195 ), 196 COMMAND( 197 "solve", 198 "solve " FLAG_SOLVER " SOLVER" 199 "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] " 200 FLAG_CUBE " CUBE" 201 FLAG_THREADS " T", 202 "Solve the given CUBE using SOLVER, " 203 "using at least n and at most N moves, and T threads. " 204 INFO_CUBEFORMAT("CUBE"), 205 solve_exec 206 ), 207 COMMAND( 208 "solve_scramble", 209 "solve_scramble " FLAG_SOLVER " SOLVER" 210 "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] " 211 FLAG_MOVES " MOVES", 212 "Solve the given SCRAMBLE using SOLVER, " 213 "using at least n and at most N moves. " 214 INFO_MOVESFORMAT, 215 solve_scramble_exec 216 ), 217 COMMAND( 218 "countmoves", 219 "countmoves " FLAG_MOVES " MOVES", 220 "Count the given MOVES in HTM. " 221 INFO_MOVESFORMAT, 222 countmoves_exec 223 ), 224 COMMAND( 225 "help", 226 "help [" FLAG_COMMAND " COMMAND]", 227 "If no COMMAND is specified, prints some generic information " 228 "and the list of commands. Otherwise it prints detailed " 229 "information about the specified COMMAND.", 230 help_exec 231 ), 232 COMMAND(NULL, NULL, NULL, NULL) 233 }; 234 235 char *tablepaths[] = { 236 "tables/", 237 "", 238 NULL 239 }; 240 241 static uint64_t 242 rand64(void) 243 { 244 uint64_t i, ret; 245 246 for (i = 0, ret = 0; i < 64; i++) 247 ret |= (uint64_t)(rand() % 2) << i; 248 249 return ret; 250 } 251 252 static int64_t 253 compose_exec(args_t *args) 254 { 255 char result[22]; 256 int64_t ret; 257 258 ret = nissy_compose(args->cube, args->cube_perm, result); 259 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 260 printf("%s\n", result); 261 262 return ret; 263 } 264 265 static int64_t 266 inverse_exec(args_t *args) 267 { 268 char result[22]; 269 int64_t ret; 270 271 ret = nissy_inverse(args->cube, 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 applymoves_exec(args_t *args) 280 { 281 char result[22]; 282 int64_t ret; 283 284 ret = nissy_applymoves(args->cube, args->str_moves, result); 285 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 286 printf("%s\n", result); 287 288 return ret; 289 } 290 291 static int64_t 292 applytrans_exec(args_t *args) 293 { 294 char result[22]; 295 int64_t ret; 296 297 ret = nissy_applytrans(args->cube, args->str_trans, result); 298 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 299 printf("%s\n", result); 300 301 return ret; 302 } 303 304 static int64_t 305 frommoves_exec(args_t *args) 306 { 307 sprintf(args->cube, NISSY_SOLVED_CUBE); 308 return applymoves_exec(args); 309 } 310 311 static int64_t 312 convert_exec(args_t *args) 313 { 314 char result[PRINTCUBE_BUFFER_SIZE]; 315 int64_t ret; 316 317 ret = nissy_convert(args->str_format_in, args->str_format_out, 318 args->str_cube, PRINTCUBE_BUFFER_SIZE, result); 319 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 320 printf("%s\n", result); 321 322 return ret; 323 } 324 325 static int64_t 326 randomcube_exec(args_t *args) 327 { 328 char result[PRINTCUBE_BUFFER_SIZE]; 329 int64_t ret, ep, eo, cp, co; 330 331 ep = rand64(); 332 eo = rand64(); 333 cp = rand64(); 334 co = rand64(); 335 ret = nissy_getcube(ep, eo, cp, co, "fix", result); 336 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 337 printf("%s\n", result); 338 339 return ret; 340 } 341 342 static int64_t 343 datasize_exec(args_t *args) 344 { 345 int64_t ret; 346 347 ret = nissy_datasize(args->str_solver); 348 if (ret < 0) 349 fprintf(stderr, "Unknown error (make sure solver is valid)\n"); 350 printf("%" PRId64 "\n", ret); 351 352 return ret; 353 } 354 355 static int64_t 356 gendata_exec(args_t *args) 357 { 358 int i; 359 FILE *file; 360 char *buf, path[MAX_PATH_LENGTH]; 361 int64_t ret, size; 362 size_t written; 363 364 /* TODO: should give warning if overwriting existing file */ 365 for (i = 0; tablepaths[i] != NULL; i++) { 366 strcpy(path, tablepaths[i]); 367 strcat(path, args->str_solver); 368 file = fopen(path, "wb"); 369 if (file != NULL) 370 break; 371 } 372 373 if (tablepaths[i] == NULL) { 374 fprintf(stderr, "Cannot write data to file\n"); 375 fclose(file); 376 return -2; 377 } 378 379 size = nissy_datasize(args->str_solver); 380 381 if (size < 0) { 382 fprintf(stderr, 383 "Unknown error in retrieving data size" 384 "(make sure solver is valid)\n"); 385 fclose(file); 386 return -3; 387 } 388 389 buf = malloc(size); 390 391 ret = nissy_gendata(args->str_solver, size, buf); 392 if (ret < 0) { 393 fprintf(stderr, "Unknown error in generating data\n"); 394 fclose(file); 395 free(buf); 396 return -4; 397 } 398 if (ret != size) { 399 fprintf(stderr, "Unknown error: unexpected data size " 400 "got %" PRId64 ", expected %" PRId64 ")\n", ret, size); 401 fclose(file); 402 free(buf); 403 return -5; 404 } 405 406 written = fwrite(buf, size, 1, file); 407 fclose(file); 408 free(buf); 409 410 if (written != 1) { 411 fprintf(stderr, 412 "Error: data was generated correctly, but could not be " 413 "written to file (generated %" PRId64 " bytes, written " 414 "%zu)\n", size, written); 415 return -6; 416 } 417 418 fprintf(stderr, "Data written to %s\n", path); 419 420 return 0; 421 } 422 423 static int64_t 424 solve_exec(args_t *args) 425 { 426 int i; 427 uint8_t nissflag; 428 FILE *file; 429 char *buf, solutions[SOLUTIONS_BUFFER_SIZE], path[MAX_PATH_LENGTH]; 430 long long stats[NISSY_SIZE_SOLVE_STATS]; 431 int64_t ret, gendata_ret, size; 432 size_t read; 433 434 nissflag = NISSY_NISSFLAG_NORMAL; /* TODO: parse str_nisstype */ 435 436 for (i = 0; tablepaths[i] != NULL; i++) { 437 strcpy(path, tablepaths[i]); 438 strcat(path, args->str_solver); 439 file = fopen(path, "rb"); 440 if (file != NULL) 441 break; 442 } 443 444 if (tablepaths[i] == NULL) { 445 fprintf(stderr, 446 "Cannot read data file, " 447 "generating it (this can take a while)\n"); 448 gendata_ret = gendata_exec(args); 449 if (gendata_ret) 450 return gendata_ret; 451 } 452 453 /* Ugh, this is not elegant TODO */ 454 if (file == NULL) { 455 for (i = 0; tablepaths[i] != NULL; i++) { 456 strcpy(path, tablepaths[i]); 457 strcat(path, args->str_solver); 458 file = fopen(path, "rb"); 459 if (file != NULL) 460 break; 461 } 462 } 463 464 if (tablepaths[i] == NULL) { 465 fprintf(stderr, "Error: data file not found\n"); 466 fclose(file); 467 return -1; 468 } 469 470 size = nissy_datasize(args->str_solver); 471 buf = malloc(size); 472 read = fread(buf, size, 1, file); 473 fclose(file); 474 if (read != 1) { 475 fprintf(stderr, "Error reading data from file: " 476 "fread() returned %zu instead of 1 when attempting to" 477 "read %" PRId64 " bytes from file %s\n", read, size, path); 478 return -2; 479 } 480 481 ret = nissy_solve( 482 args->cube, args->str_solver, nissflag, args->minmoves, 483 args->maxmoves, args->maxsolutions, args->optimal, args->threads, 484 size, buf, SOLUTIONS_BUFFER_SIZE, solutions, stats); 485 486 free(buf); 487 488 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 489 fprintf(stderr, "No solutions found\n"); 490 else 491 printf("%s", solutions); 492 493 return 0; 494 } 495 496 static int64_t 497 solve_scramble_exec(args_t *args) 498 { 499 nissy_applymoves(NISSY_SOLVED_CUBE, args->str_moves, args->cube); 500 501 return solve_exec(args); 502 } 503 504 static int64_t 505 countmoves_exec(args_t *args) 506 { 507 long long count; 508 509 count = nissy_countmoves(args->str_moves); 510 511 if (count >= 0) 512 printf("%lld\n", count); 513 514 return count >= 0 ? 0 : count; 515 } 516 517 static int64_t 518 help_exec(args_t *args) 519 { 520 int i; 521 522 if (args->str_command == NULL || args->str_command[0] == '\0') { 523 printf("This is a rudimentary shell for the H48 library.\n"); 524 printf("Available commands and usage:\n\n"); 525 for (i = 0; commands[i].name != NULL; i++) 526 printf("%-15s%s\n", commands[i].name, commands[i].syn); 527 printf("\nUse 'help -command COMMAND' for more information.\n"); 528 } else { 529 for (i = 0; commands[i].name != NULL; i++) 530 if (!strcmp(args->str_command, commands[i].name)) 531 break; 532 if (commands[i].name == NULL) { 533 printf("Unknown command %s\n", args->str_command); 534 return 1; 535 } 536 printf("Command %s\n\n", commands[i].name); 537 printf("Synopsis: %s\n\n", commands[i].syn); 538 printf("Description: %s\n", commands[i].desc); 539 } 540 541 return 0; 542 } 543 544 static int 545 parse_args(int argc, char **argv, args_t *args) 546 { 547 int i, j, n; 548 549 *args = (args_t) { 550 .command_index = -1, 551 .cube = "", 552 .cube_perm = "", 553 .str_cube = "", 554 .str_format = "", 555 .str_format_in = "", 556 .str_format_out = "", 557 .str_moves = "", 558 .str_trans = "", 559 .str_solver = "", 560 .str_nisstype = "", 561 .minmoves = 0, 562 .maxmoves = 20, 563 .optimal = -1, 564 .maxsolutions = 1, 565 .threads = 0, 566 }; 567 568 if (argc == 0) { 569 printf("No command given\n"); 570 return 1; 571 } 572 573 for (i = 0; commands[i].name != NULL; i++) { 574 if (!strcmp(argv[0], commands[i].name)) { 575 args->command_index = i; 576 break; 577 } 578 } 579 580 if (commands[i].name == NULL) { 581 fprintf(stderr, "Unknown command %s\n", argv[0]); 582 return 1; 583 } 584 585 for (i = 1; i < argc; i++) { 586 for (j = 0; options[j].name != NULL; j++) { 587 n = argc - i - 1; 588 if (strcmp(argv[i], options[j].name)) 589 continue; 590 if (n < options[j].nargs) { 591 fprintf(stderr, 592 "Too few arguments for option %s\n", 593 options[j].name); 594 return 1; 595 } 596 if (!options[j].set(n, argv+i+1, args)) { 597 fprintf(stderr, 598 "Error parsing arguments for option %s\n", 599 options[j].name); 600 return 1; 601 } 602 i += options[j].nargs; 603 break; 604 } 605 if (options[j].name == NULL) { 606 fprintf(stderr, "Unknown option %s\n", argv[i]); 607 return 1; 608 } 609 } 610 611 return 0; 612 } 613 614 bool 615 parse_uint(char *argv, unsigned *result) 616 { 617 *result = strtol(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_nisstype(int argc, char **argv, args_t *args) 707 { 708 args->str_nisstype = argv[0]; 709 710 return true; 711 } 712 713 static bool 714 set_minmoves(int argc, char **argv, args_t *args) 715 { 716 return parse_uint(argv[0], &args->minmoves); 717 } 718 719 static bool 720 set_maxmoves(int argc, char **argv, args_t *args) 721 { 722 return parse_uint(argv[0], &args->maxmoves); 723 } 724 725 static bool 726 set_optimal(int argc, char **argv, args_t *args) 727 { 728 return parse_uint(argv[0], &args->optimal); 729 } 730 731 static bool 732 set_maxsolutions(int argc, char **argv, args_t *args) 733 { 734 return parse_uint(argv[0], &args->maxsolutions); 735 } 736 737 static bool 738 set_threads(int argc, char **argv, args_t *args) 739 { 740 return parse_uint(argv[0], &args->threads); 741 } 742 743 void 744 log_stderr(const char *str, ...) 745 { 746 va_list args; 747 748 va_start(args, str); 749 vfprintf(stderr, str, args); 750 va_end(args); 751 } 752 753 int 754 main(int argc, char **argv) 755 { 756 int parse_error; 757 args_t args; 758 759 srand(time(NULL)); 760 nissy_setlogger(log_stderr); 761 762 parse_error = parse_args(argc-1, argv+1, &args); 763 if (parse_error) 764 return parse_error; 765 766 return (int)commands[args.command_index].exec(&args); 767 }