shell.c (15540B)
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 } else { 288 printf("%" PRId64 "\n%s\n", ret, buf); 289 ret = 0; 290 } 291 292 return ret; 293 } 294 295 static int64_t 296 gendata_exec(args_t *args) 297 { 298 int i; 299 FILE *file; 300 char path[MAX_PATH_LENGTH], dataid[NISSY_SIZE_DATAID]; 301 unsigned char *buf; 302 int64_t ret, size; 303 size_t written; 304 305 size = nissy_solverinfo(args->str_solver, dataid); 306 307 if (size < 0) { 308 fprintf(stderr, "gendata: unknown solver %s\n", 309 args->str_solver); 310 return -3; 311 } 312 313 /* TODO: should give warning if overwriting existing file */ 314 for (i = 0; tablepaths[i] != NULL; i++) { 315 strcpy(path, tablepaths[i]); 316 strcat(path, dataid); 317 file = fopen(path, "wb"); 318 if (file != NULL) 319 break; 320 } 321 322 if (tablepaths[i] == NULL) { 323 fprintf(stderr, "Cannot write data to file\n"); 324 fclose(file); 325 return -2; 326 } 327 328 buf = malloc(size); 329 330 ret = nissy_gendata(args->str_solver, size, buf); 331 if (ret < 0) { 332 fprintf(stderr, "Unknown error in generating data\n"); 333 fclose(file); 334 free(buf); 335 return -4; 336 } 337 if (ret != size) { 338 fprintf(stderr, "Unknown error: unexpected data size " 339 "got %" PRId64 ", expected %" PRId64 ")\n", ret, size); 340 fclose(file); 341 free(buf); 342 return -5; 343 } 344 345 written = fwrite(buf, size, 1, file); 346 fclose(file); 347 free(buf); 348 349 if (written != 1) { 350 fprintf(stderr, 351 "Error: data was generated correctly, but could not be " 352 "written to file (generated %" PRId64 " bytes, written " 353 "%zu)\n", size, written); 354 return -6; 355 } 356 357 fprintf(stderr, "Data written to %s\n", path); 358 359 return 0; 360 } 361 362 static int64_t 363 solve_exec(args_t *args) 364 { 365 int i; 366 uint8_t nissflag; 367 FILE *file; 368 char solutions[SOLUTIONS_BUFFER_SIZE], path[MAX_PATH_LENGTH]; 369 unsigned char *buf; 370 char dataid[NISSY_SIZE_DATAID]; 371 long long stats[NISSY_SIZE_SOLVE_STATS]; 372 int64_t ret, gendata_ret, size; 373 size_t read; 374 375 nissflag = parse_nisstype(args->str_nisstype); 376 if (nissflag == UINT8_MAX) { 377 fprintf(stderr, "solve: unknown niss type '%s', use one " 378 "of the following:\nnormal\ninverse\nlinear\nmixed\nall", 379 args->str_nisstype); 380 return -1; 381 } 382 383 size = nissy_solverinfo(args->str_solver, dataid); 384 385 if (size < 0) { 386 fprintf(stderr, "solve: unknown solver '%s'\n", 387 args->str_solver); 388 return size; 389 } 390 391 for (i = 0; tablepaths[i] != NULL; i++) { 392 strcpy(path, tablepaths[i]); 393 strcat(path, dataid); 394 file = fopen(path, "rb"); 395 if (file != NULL) 396 break; 397 } 398 399 if (tablepaths[i] == NULL) { 400 fprintf(stderr, 401 "Cannot read data file, " 402 "generating it (this can take a while)\n"); 403 gendata_ret = gendata_exec(args); 404 if (gendata_ret) 405 return gendata_ret; 406 } 407 408 /* Ugh, this is not elegant TODO */ 409 if (file == NULL) { 410 for (i = 0; tablepaths[i] != NULL; i++) { 411 strcpy(path, tablepaths[i]); 412 strcat(path, dataid); 413 file = fopen(path, "rb"); 414 if (file != NULL) 415 break; 416 } 417 } 418 419 if (tablepaths[i] == NULL) { 420 fprintf(stderr, "Error: data file not found\n"); 421 fclose(file); 422 return -1; 423 } 424 425 if (args->maxsolutions == 0) 426 args->maxsolutions = args->optimal == 20 ? 1 : UINT_MAX; 427 428 buf = malloc(size); 429 read = fread(buf, size, 1, file); 430 fclose(file); 431 if (read != 1) { 432 fprintf(stderr, "Error reading data from file: " 433 "fread() returned %zu instead of 1 when attempting to " 434 "read %" PRId64 " bytes from file %s\n", read, size, path); 435 goto solve_exec_error; 436 } 437 438 ret = nissy_solve( 439 args->cube, args->str_solver, nissflag, args->minmoves, 440 args->maxmoves, args->maxsolutions, args->optimal, args->threads, 441 size, buf, SOLUTIONS_BUFFER_SIZE, solutions, stats, NULL, NULL); 442 443 free(buf); 444 445 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 446 fprintf(stderr, "No solutions found\n"); 447 else 448 printf("%s", solutions); 449 450 return 0; 451 452 solve_exec_error: 453 free(buf); 454 return -2; 455 } 456 457 static int64_t 458 solve_scramble_exec(args_t *args) 459 { 460 nissy_applymoves(NISSY_SOLVED_CUBE, args->str_moves, args->cube); 461 462 return solve_exec(args); 463 } 464 465 static int64_t 466 countmoves_exec(args_t *args) 467 { 468 long long count; 469 470 count = nissy_countmoves(args->str_moves); 471 472 if (count >= 0) 473 printf("%lld\n", count); 474 475 return count >= 0 ? 0 : count; 476 } 477 478 static int64_t 479 help_exec(args_t *args) 480 { 481 int i; 482 483 if (args->str_command == NULL || args->str_command[0] == '\0') { 484 printf("This is a rudimentary shell for the H48 library.\n"); 485 printf("Available commands and usage:\n\n"); 486 for (i = 0; commands[i].name != NULL; i++) 487 printf("%-15s%s\n", commands[i].name, commands[i].syn); 488 printf("\nUse 'help -command COMMAND' for more information.\n"); 489 } else { 490 for (i = 0; commands[i].name != NULL; i++) 491 if (!strcmp(args->str_command, commands[i].name)) 492 break; 493 if (commands[i].name == NULL) { 494 printf("Unknown command %s\n", args->str_command); 495 return 1; 496 } 497 printf("Command %s\n\n", commands[i].name); 498 printf("Synopsis: %s\n\n", commands[i].syn); 499 printf("Description: %s\n", commands[i].desc); 500 } 501 502 return 0; 503 } 504 505 static int 506 parse_args(int argc, char **argv, args_t *args) 507 { 508 int i, j, n; 509 510 *args = (args_t) { 511 .command_index = -1, 512 .cube = "", 513 .str_cube = "", 514 .str_moves = "", 515 .str_trans = "", 516 .str_solver = "", 517 .str_nisstype = "", 518 .minmoves = 0, 519 .maxmoves = 20, 520 .optimal = 20, 521 .maxsolutions = 0, 522 .threads = 0, 523 }; 524 525 if (argc == 0) { 526 printf("No command given\n"); 527 return 1; 528 } 529 530 for (i = 0; commands[i].name != NULL; i++) { 531 if (!strcmp(argv[0], commands[i].name)) { 532 args->command_index = i; 533 break; 534 } 535 } 536 537 if (commands[i].name == NULL) { 538 fprintf(stderr, "Unknown command %s\n", argv[0]); 539 return 1; 540 } 541 542 for (i = 1; i < argc; i++) { 543 for (j = 0; options[j].name != NULL; j++) { 544 n = argc - i - 1; 545 if (strcmp(argv[i], options[j].name)) 546 continue; 547 if (n < options[j].nargs) { 548 fprintf(stderr, 549 "Too few arguments for option %s\n", 550 options[j].name); 551 return 1; 552 } 553 if (!options[j].set(n, argv+i+1, args)) { 554 fprintf(stderr, 555 "Error parsing arguments for option %s\n", 556 options[j].name); 557 return 1; 558 } 559 i += options[j].nargs; 560 break; 561 } 562 if (options[j].name == NULL) { 563 fprintf(stderr, "Unknown option %s\n", argv[i]); 564 return 1; 565 } 566 } 567 568 return 0; 569 } 570 571 static bool 572 parse_uint(const char *argv, unsigned *result) 573 { 574 *result = strtol(argv, NULL, 10); 575 576 /* TODO: figure out how errno works and use it */ 577 return true; 578 } 579 580 static uint8_t 581 parse_nisstype(const char *arg) 582 { 583 if (!strcmp("normal", arg) || !strcmp("", arg)) 584 return NISSY_NISSFLAG_NORMAL; 585 586 if (!strcmp("inverse", arg)) 587 return NISSY_NISSFLAG_INVERSE; 588 589 if (!strcmp("linear", arg)) 590 return NISSY_NISSFLAG_LINEAR; 591 592 if (!strcmp("mixed", arg)) 593 return NISSY_NISSFLAG_MIXED; 594 595 if (!strcmp("all", arg)) 596 return NISSY_NISSFLAG_ALL; 597 598 return UINT8_MAX; 599 } 600 601 static bool 602 set_cube(int argc, char **argv, args_t *args) 603 { 604 memcpy(args->cube, argv[0], NISSY_SIZE_CUBE); 605 args->cube[21] = 0; 606 607 return true; 608 } 609 610 static bool 611 set_str_command(int argc, char **argv, args_t *args) 612 { 613 args->str_command = argv[0]; 614 615 return true; 616 } 617 618 static bool 619 set_str_cube(int argc, char **argv, args_t *args) 620 { 621 args->str_cube = argv[0]; 622 623 return true; 624 } 625 626 static bool 627 set_str_moves(int argc, char **argv, args_t *args) 628 { 629 args->str_moves = argv[0]; 630 631 return true; 632 } 633 634 static bool 635 set_str_trans(int argc, char **argv, args_t *args) 636 { 637 args->str_trans = argv[0]; 638 639 return true; 640 } 641 642 static bool 643 set_str_solver(int argc, char **argv, args_t *args) 644 { 645 args->str_solver = argv[0]; 646 647 return true; 648 } 649 650 static bool 651 set_str_nisstype(int argc, char **argv, args_t *args) 652 { 653 args->str_nisstype = argv[0]; 654 655 return true; 656 } 657 658 static bool 659 set_minmoves(int argc, char **argv, args_t *args) 660 { 661 return parse_uint(argv[0], &args->minmoves); 662 } 663 664 static bool 665 set_maxmoves(int argc, char **argv, args_t *args) 666 { 667 return parse_uint(argv[0], &args->maxmoves); 668 } 669 670 static bool 671 set_optimal(int argc, char **argv, args_t *args) 672 { 673 return parse_uint(argv[0], &args->optimal); 674 } 675 676 static bool 677 set_maxsolutions(int argc, char **argv, args_t *args) 678 { 679 return parse_uint(argv[0], &args->maxsolutions); 680 } 681 682 static bool 683 set_threads(int argc, char **argv, args_t *args) 684 { 685 return parse_uint(argv[0], &args->threads); 686 } 687 688 void 689 log_stderr(const char *str, void *unused) 690 { 691 fprintf(stderr, "%s", str); 692 } 693 694 int 695 main(int argc, char **argv) 696 { 697 int parse_error; 698 args_t args; 699 700 srand(time(NULL)); 701 nissy_setlogger(log_stderr, NULL); 702 703 parse_error = parse_args(argc-1, argv+1, &args); 704 if (parse_error) 705 return parse_error; 706 707 return (int)commands[args.command_index].exec(&args); 708 }