/* Read Sokoban levels from files and export in format usable by Stevedore. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <errno.h> //#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #include <stdint.h> #include <assert.h> #define LINUX 1 #define uint8 unsigned char #ifndef TRUE #define TRUE (0==0) #define FALSE (0!=0) #endif #define MAX_OBJECTS 16 static int lowindex, highindex; // command-line parameters to select puzzles from larger set. #define MAX_LINE 1024 static char Id[MAX_LINE]; static int Width, Height; static int xoffset, yoffset; static int grabbing = FALSE; static int blocks; int map[18][18]; void floodfill_same(int x, int y, int same_ch, int replacement_ch) { if ((x < 0) || (y < 0) || (x > 17) || (y > 17)) return; if (map[x][y] == same_ch) map[x][y] = replacement_ch; else return; floodfill_same(x-1,y, same_ch, replacement_ch); floodfill_same(x+1,y, same_ch, replacement_ch); floodfill_same(x,y-1, same_ch, replacement_ch); floodfill_same(x,y+1, same_ch, replacement_ch); } void floodfill_hack(int x, int y) { if ((x < 0) || (y < 0) || (x > 17) || (y > 17)) return; if (map[x][y] == ' ') { map[x][y] |= 0x80; floodfill_hack(x-1,y); floodfill_hack(x+1,y); floodfill_hack(x,y-1); floodfill_hack(x,y+1); floodfill_hack(x-1,y-1); floodfill_hack(x+1,y+1); floodfill_hack(x+1,y-1); floodfill_hack(x-1,y+1); return; } else if (((x == 0) || (y == 0) || (x == 17) || (y == 17)) && ((map[x][y] == '+') || (map[x][y] == '-') || (map[x][y] == '|'))) { // wall touches boundary map[x][y] |= 0x80; return; } else if (map[x][y] == '+') { map[x][y] |= 0x80; return; } else if (map[x][y] == '-') { map[x][y] |= 0x80; return; } else if (map[x][y] == '|') { map[x][y] |= 0x80; return; } } int hack(void) { int count, x, y; // completely surround playfield with blocks for (x = 0, y = 0; x < 18; x++) floodfill_same(x, y, ' ', '#'); for (x = 0, y = 17; x < 18; x++) floodfill_same(x, y, ' ', '#'); for (y = 0, x = 0; y < 18; y++) floodfill_same(x, y, ' ', '#'); for (y = 0, x = 17; y < 18; y++) floodfill_same(x, y, ' ', '#'); // replace all surrounding blocks with 'ignore' codes for (x = 0, y = 0; x < 18; x++) floodfill_same(x, y, '#', 'X'); for (x = 0, y = 17; x < 18; x++) floodfill_same(x, y, '#', 'X'); for (y = 0, x = 0; y < 18; y++) floodfill_same(x, y, '#', 'X'); for (y = 0, x = 17; y < 18; y++) floodfill_same(x, y, '#', 'X'); // For the innermost layer only of ignore codes surrounding the playfield, // replace with 'wall' code for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { if (map[x][y] == 'X' && (((x < 17) && (map[x+1][y] != 'X') && (map[x+1][y] != 'Y') ) || ((x > 0) && (map[x-1][y] != 'X') && (map[x-1][y] != 'Y')) || ((y < 17) && (map[x][y+1] != 'X') && (map[x][y+1] != 'Y')) || ((y > 0) && (map[x][y-1] != 'X') && (map[x][y-1] != 'Y'))) ) { map[x][y] = 'Y'; } } } // convert outside wall from 8-connected to 4-connected topology (ie fill in corners on diagonal wall paths) for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { // XY -> ZY // Y Y if ((x < 17) && (y < 17) && (map[x][y] == 'X') && (map[x+1][y] == 'Y') && (map[x][y+1] == 'Y') && (map[x+1][y+1] != 'Y')) map[x][y] = 'Z'; // YX YZ // Y -> Y if ((x > 0) && (y < 17) && (map[x][y] == 'X') && (map[x-1][y] == 'Y') && (map[x][y+1] == 'Y') && (map[x-1][y+1] != 'Y')) map[x][y] = 'Z'; // Y Y // YX -> YZ if ((x > 0) && (y > 0) && (map[x][y] == 'X') && (map[x-1][y] == 'Y') && (map[x][y-1] == 'Y') && (map[x-1][y-1] != 'Y')) map[x][y] = 'Z'; // Y Y // XY -> ZY if ((x < 17) && (y > 0) && (map[x][y] == 'X') && (map[x+1][y] == 'Y') && (map[x][y-1] == 'Y') && (map[x+1][y-1] != 'Y')) map[x][y] = 'Z'; } } // remove ignore codes (spaces look nicer) and mark corners // (generation of vector walls much easier if corners are marked) for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { // and finally... if (map[x][y] == 'X') map[x][y] = ' '; if (map[x][y] == 'Z') map[x][y] = '+'; } } // fix up some more corners not previously handled for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { // Y+ -> ++ // + + if ((x < 17) && (y < 17) && (map[x][y] == 'Y') && ((map[x+1][y] == 'Y') || (map[x+1][y] == '+')) && ((map[x][y+1] == 'Y') || (map[x][y+1] == '+'))) map[x][y] = '+'; // +Y ++ // + -> + if ((x > 0) && (y < 17) && (map[x][y] == 'Y') && ((map[x-1][y] == 'Y') || (map[x-1][y] == '+')) && ((map[x][y+1] == 'Y') || (map[x][y+1] == '+'))) map[x][y] = '+'; // + + // +Y -> ++ if ((x > 0) && (y > 0) && (map[x][y] == 'Y') && ((map[x-1][y] == 'Y') || (map[x-1][y] == '+')) && ((map[x][y-1] == 'Y') || (map[x][y-1] == '+'))) map[x][y] = '+'; // + + // Y+ -> ++ if ((x < 17) && (y > 0) && (map[x][y] == 'Y') && ((map[x+1][y] == 'Y') || (map[x+1][y] == '+')) && ((map[x][y-1] == 'Y') || (map[x][y-1] == '+'))) map[x][y] = '+'; } } // replace vertical walls with | for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { if ((map[x][y] == 'Y') && (((y > 0) && ((map[x][y-1] == 'Y') || (map[x][y-1] == '+'))) || ((y < 17) && ((map[x][y+1] == 'Y') || (map[x][y+1] == '+')))) ) map[x][y] = '|'; } } // and horizontal walls with - for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { if ((map[x][y] == 'Y') && (((x > 0) && ((map[x-1][y] == 'Y') || (map[x-1][y] == '+'))) || ((x < 17) && ((map[x+1][y] == 'Y') || (map[x+1][y] == '+')))) ) map[x][y] = '-'; } } // Floodfill again to mark outer walls only for (x = 0, y = 0; x < 18; x++) floodfill_hack(x,y); for (x = 0, y = 17; x < 18; x++) floodfill_hack(x,y); for (y = 0, x = 0; y < 18; y++) floodfill_hack(x,y); for (y = 0, x = 17; y < 18; y++) floodfill_hack(x,y); // replace all internal walls (unmarked) with fixed blocks for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { if ((map[x][y] == '+') || (map[x][y] == '-') || (map[x][y] == '|') ) { map[x][y] = '#'; } } } // And restore outer wall by removing marking for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { map[x][y] &= 0x7F; } } // one last set of patching up to do... // // | | | // + or + => | // | + | // for (y = 0; y < 16; y++) { for (x = 0; x < 18; x++) { if (map[x][y] == '|' && map[x][y+1] == '+' && (map[x][y+2] == '+' || map[x][y+2] == '|') ) map[x][y+1] = '|'; if (map[x][y] == '+' && map[x][y+1] == '+' && (map[x][y+2] == '+' || map[x][y+2] == '|') ) map[x][y+1] = '|'; } } // and similarly for horizontal... for (y = 0; y < 18; y++) { for (x = 0; x < 16; x++) { if (map[x][y] == '-' && map[x+1][y] == '+' && (map[x+2][y] == '+' || map[x+2][y] == '-') ) map[x+1][y] = '-'; if (map[x][y] == '+' && map[x+1][y] == '+' && (map[x+2][y] == '+' || map[x+2][y] == '-') ) map[x+1][y] = '-'; } } // WOW. That was quite a hack. count = 0; for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { // count all blocks but not external wall if (map[x][y] == '.' || map[x][y] == '$' || map[x][y] == '@' || map[x][y] == '#') count += 1; } } // return count of blocks that need to be drawn explicitly return count; } int wall(int x, int y) { if ((x < 0) || (y < 0) || (x > 17) || (y > 17)) return FALSE; return map[x][y] == '+' || map[x][y] == '|' || map[x][y] == '-' || map[x][y] == '#'; } int eliminated(void) { // eliminate maps that we don't want to handle int x, y; //, neighbours; // problematic artefacts for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { if (map[x][y] == 'Y') return TRUE; } } #ifdef NEVER // outer walls which fork off segments inside the maze. Too much hassle to draw for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { if (map[x][y] == '+') { neighbours = (wall(x-1,y)?1:0) + (wall(x+1,y)?1:0) + (wall(x,y-1)?1:0) + (wall(x,y+1)?1:0); if (neighbours > 2) return TRUE; } } } #endif // otherwise all good return FALSE; } static int gameno = 0; void output_title(char *s) { int chars = 0; while (*s == ' ') s++; while (*s != '\0') { if (isalpha(*s)) { if (islower(*s)) { putchar(toupper(*s)); chars++; } else { putchar(*s); chars++; } s += 1; } else if (*s == ' ') { putchar(*s); chars++; while (*s == ' ') s++; } else if (isdigit(*s)) { putchar(*s); chars++; s += 1; } else s += 1; } while (chars < 4) { putchar(' '); chars++; } } int handle(char *s) { static int ypos; int x,y; char Line[MAX_LINE]; //fprintf(stderr, "%s",s); // <Level Id="yConnection" Width="9" Height="10"> // start of a level if (sscanf(s, " <Level Id=\"%[^\"]\" Width=\"%d\" Height=\"%d\"> ", Id, &Width, &Height) == 3) { //fprintf(stderr, "LEVEL! %s\n",s); if ((Width <= 18) && (Height <= 18)) { for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { map[x][y] = ' '; } } xoffset = (18-Width)/2; ypos = yoffset = (18-Height)/2; grabbing = TRUE; } // end of a level } else if (grabbing && strstr(s, "</Level>")) { //fprintf(stderr, "END LEVEL! %s\n",s); // Erode thick wall layers with flood fill algorithm! blocks = hack(); grabbing = FALSE; if ((blocks > 7) && (blocks <= MAX_OBJECTS) && (!(eliminated()))) { // plausible size to be worth looking at if (gameno >= lowindex) { fprintf(stdout, "#define title%0d \"", gameno); output_title(Id); fprintf(stdout, "\"\n"); fprintf(stdout, "#define game%0d \\\n", gameno); for (y = 0; y < 18; y++) { putchar('"'); for (x = 0; x < 18; x++) { putchar(map[x][y]); } putchar('"'); putchar(' '); if (y!=17) putchar('\\'); putchar('\n'); } } return TRUE; } // row within a level } else if (grabbing && (sscanf(s, " <L>%[^<]</L> ", Line) == 1)) { int xpos; //fprintf(stderr, "LINE! %s\n",s); xpos = xoffset; s = Line; while (*s != '\0') { if ((*s == '+') || (*s == '*')) grabbing = FALSE; // not handling these yet. map[xpos++][ypos] = *s; s += 1; } ypos += 1; } return FALSE; } #define MAX_VECS 100 int vecs; int vectors[3*MAX_VECS]; void addvec(int a, int b, int c) { int base = vecs*3; int half, third; if (b >= 14) { third = b/3; vectors[base++] = a; vectors[base++] = third; vectors[base++] = c; vecs++; vectors[base++] = a; vectors[base++] = third; vectors[base++] = c; vecs++; vectors[base++] = a; vectors[base++] = b-third*2; vectors[base++] = c; } else if (b >= 7) { half = b/2; vectors[base++] = a; vectors[base++] = half; vectors[base++] = c; vecs++; vectors[base++] = a; vectors[base++] = b-half; vectors[base++] = c; } else if (b <= -14) { third = -b/3; vectors[base++] = a; vectors[base++] = -third; vectors[base++] = c; vecs++; vectors[base++] = a; vectors[base++] = -third; vectors[base++] = c; vecs++; vectors[base++] = a; vectors[base++] = b+third*2; vectors[base++] = c; } else if (b <= -7) { half = -b/2; vectors[base++] = a; vectors[base++] = -half; vectors[base++] = c; vecs++; vectors[base++] = a; vectors[base++] = b+half; vectors[base++] = c; } else if (c >= 14) { third = c/3; vectors[base++] = a; vectors[base++] = b; vectors[base++] = third; vecs++; vectors[base++] = a; vectors[base++] = b; vectors[base++] = third; vecs++; vectors[base++] = a; vectors[base++] = b; vectors[base++] = c-third*2; } else if (c >= 7) { half = c/2; vectors[base++] = a; vectors[base++] = b; vectors[base++] = half; vecs++; vectors[base++] = a; vectors[base++] = b; vectors[base++] = c-half; } else if (c <= -14) { third = -c/3; vectors[base++] = a; vectors[base++] = b; vectors[base++] = -third; vecs++; vectors[base++] = a; vectors[base++] = b; vectors[base++] = -third; vecs++; vectors[base++] = a; vectors[base++] = b; vectors[base++] = c+third*2; } else if (c <= -7) { half = -c/2; vectors[base++] = a; vectors[base++] = b; vectors[base++] = -half; vecs++; vectors[base++] = a; vectors[base++] = b; vectors[base++] = c+half; } else { vectors[base++] = a; vectors[base++] = b; vectors[base++] = c; } vecs++; } int startx, starty; void dumpvecs(void) { int i, base; // may need to insert extra move_abs if too many vectors in perimeter, and wobble occurs... if (startx-8-2 < -6) { if (starty-8 < -6) { fprintf(stdout, "const int wall%0d[%d] = {\n %d,\n", gameno, 1+vecs*3+3+3, vecs+1+1); fprintf(stdout, " 0,%d*H,%d*W,\n", -4, -4); // check on code 0 - is it abs, or rel with a different scale? fprintf(stdout, " 0,%d*H,%d*W,\n", starty-8+4, startx-8-2+4); } else { fprintf(stdout, "const int wall%0d[%d] = {\n %d,\n", gameno, 1+vecs*3+3+3, vecs+1+1); fprintf(stdout, " 0,%d*H,%d*W,\n", starty-8, -4); fprintf(stdout, " 0,0,%d*W,\n", startx-8-2+4); } } else { if (starty-8 < -6) { fprintf(stdout, "const int wall%0d[%d] = {\n %d,\n", gameno, 1+vecs*3+3+3, vecs+1+1); fprintf(stdout, " 0,%d*H,%d*W,\n", -4, startx-8-2); fprintf(stdout, " 0,%d*H,0,\n", starty-8+4); } else { fprintf(stdout, "const int wall%0d[%d] = {\n %d,\n", gameno, 1+vecs*3+3, vecs+1); fprintf(stdout, " 0,%d*H,%d*W,\n", starty-8, startx-8-2); } } for (i = 0; i < vecs; i++) { base = i*3; fprintf(stdout, " %d,%d*H,%d*W,\n", vectors[base], vectors[base+1], vectors[base+2]); } fprintf(stdout, "};\n\n"); } void Convert(int x, int y, int lastx, int lasty, int tag) { // walk around outline of play area, generating vectors suitable for universal drawlist format if (tag == '!') {startx = x; starty = y; vecs = 0;} map[x][y] = tag; // avoid accidental backtracking if (map[x+1][y] == '-') { assert(lasty == 0); Convert(x+1,y, lastx+1,0, 'x'); } else if ((map[x+1][y] == '+') || (map[x+1][y] == '!')) { //fprintf(stdout, " -1, 0, W*%d,\n", lastx+1); addvec(-1,0,lastx+1); Convert(x+1,y, 0,0, 'x'); } else if (map[x][y+1] == '|') { assert(lastx == 0); Convert(x,y+1, 0,lasty+1, 'x'); } else if ((map[x][y+1] == '+') || (map[x][y+1] == '!')) { //fprintf(stdout, " -1, H*%d, 0,\n", lasty+1); addvec(-1,lasty+1,0); Convert(x,y+1, 0,0, 'x'); } else if (map[x-1][y] == '-') { assert(lasty == 0); Convert(x-1,y, lastx-1,0, 'x'); } else if ((map[x-1][y] == '+') || (map[x-1][y] == '!')) { //fprintf(stdout, " -1, 0, W*%d,\n", lastx-1); addvec(-1,0,lastx-1); Convert(x-1,y, 0,0, 'x'); } else if (map[x][y-1] == '|') { assert(lastx == 0); Convert(x,y-1, 0,lasty-1, 'x'); } else if ((map[x][y-1] == '+') || (map[x][y-1] == '!')) { //fprintf(stdout, " -1, H*%d, 0,\n", lasty-1); addvec(-1,lasty-1,0); Convert(x,y-1, 0,0, 'x'); } return; //implicit 'else' is recursion stopper. } void gen_sok(FILE *SOK) { int x, y; char c; for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { c = map[x][y]; switch (c) { case 'x': case '+': case '-': case '|': fputc('#', SOK); break; default: fputc(c, SOK); break; } } fputc('\r', SOK); // essential for YASS fputc('\n', SOK); } } void gen_outline(FILE *sokfile) { int x, y; #ifdef NEVER for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { if (map[x][y] == '@') { if (gameno >= lowindex) fprintf(stdout, "\n// Player start pos is y=%d x=%d\n", y, x); } } } #endif for (y = 0; y < 18; y++) { for (x = 0; x < 18; x++) { if (map[x][y] == '+') { if (gameno >= lowindex) { Convert(x,y, 0,0, '!'); dumpvecs(); gen_sok(sokfile); } gameno++; return; } } } } int main (int argc, char **argv) { char *fname, *s, line[MAX_LINE+1]; int i, num_games; FILE *file, *sokfile; if (argc == 2) { lowindex = 0; highindex = 10000; } else if (argc == 3) { if (!isdigit(*argv[2])) { fprintf(stderr, "%s: low index (%s) must be an integer 0 or higher\n", argv[0], argv[2]); exit(EXIT_FAILURE); } lowindex = atoi(argv[2]); highindex = lowindex; } else if (argc == 4) { if (!isdigit(*argv[2])) { fprintf(stderr, "%s: low index (%s) must be an integer 0 or higher\n", argv[0], argv[2]); exit(EXIT_FAILURE); } lowindex = atoi(argv[2]); if (!isdigit(*argv[3])) { fprintf(stderr, "%s: high index (%s) must be an integer 0 or higher\n", argv[0], argv[3]); exit(EXIT_FAILURE); } highindex = atoi(argv[3]); } else { fprintf (stderr, "syntax: %s levels/file.slc [lowindex] [highindex] > output.h\n", argv[0]); exit(EXIT_FAILURE); } fname = argv[1]; sokfile = fopen("temp.sok", "wb"); if (sokfile == NULL) { fprintf(stderr, "%s: cannot open temp.sok - %s\n", fname, strerror(errno)); exit(EXIT_FAILURE); } if (lowindex > highindex) { fprintf(stderr, "%s: low index (%d) must not be greater than high index (%d).\n", argv[0], lowindex, highindex); exit(EXIT_FAILURE); } file = fopen(fname, "r"); if (file == NULL) { fprintf(stderr, "%s: cannot open %s - %s\n", argv[0], fname, strerror(errno)); exit(EXIT_FAILURE); } fprintf(stdout, "#define COLLECTION \""); {char *s = fname, *t; t = strrchr(fname, '/'); if (t) s = t+1; while (*s != '\0') { if (*s == '.') break; if (isalpha(*s)) { if (isupper(*s)) putchar(*s); else putchar(toupper(*s)); } else if (isdigit(*s)) { putchar(*s); } else if (*s == ' ') { putchar(' '); } else { // skip } s += 1; } } fprintf(stdout, "\"\n"); for (;;) { s = fgets(line, MAX_LINE, file); line[MAX_LINE] = '\0'; if (s == NULL) break; if (handle(line)) { gen_outline(sokfile); } if (gameno > highindex) break; } // if bounds not known, would be better to output this at end: num_games = gameno-lowindex; fprintf(stdout, "#define NUM_GAMES %d\n", num_games); fprintf(stdout, "const char const *games[NUM_GAMES] __attribute__ ((section(\".text\"))) = {\n"); for (i = 0; i < num_games; i++) { fprintf(stdout, " game%0d,\n", i+lowindex); } fprintf(stdout, "};\n\n"); fprintf(stdout, "const int const *walls[NUM_GAMES] __attribute__ ((section(\".text\"))) = {\n"); for (i = 0; i < num_games; i++) { fprintf(stdout, " wall%0d,\n", i+lowindex); } fprintf(stdout, "};\n\n"); fprintf(stdout, "const char const *titles[NUM_GAMES] __attribute__ ((section(\".text\"))) = {\n"); for (i = 0; i < num_games; i++) { fprintf(stdout, " title%0d,\n", i+lowindex); } fprintf(stdout, "};\n\n"); // now handle the generation of solutions... fclose(sokfile); system("echo | wine yass temp.sok > yass.out 2> yass.err"); remove("temp.sok"); remove("temp, YASS 2.142 Solutions, Statistics.txt"); sokfile = fopen("temp, YASS 2.142 Solutions.sok", "r"); if (sokfile) { int c; fprintf(stdout, "const char const *solutions[NUM_GAMES] __attribute__ ((section(\".text\"))) = {\n"); for (i = 0; i < num_games; i++) { for (;;) { s = fgets(line, MAX_LINE, sokfile); line[MAX_LINE] = '\0'; if (s == NULL) break; if (strstr(line, "Solution/Pushes")) break; } if (s == NULL) break; fprintf(stdout, " \""); for (;;) { c = fgetc(sokfile); if (c == '\r') continue; if (c == ' ') continue; if (c == '\n') { c = fgetc(sokfile); if (c == '\r') c = fgetc(sokfile); if (c == '\n') break; if (c == EOF) break; } putchar(c); } fprintf(stdout, "\",\n"); if (c == EOF) break; } fprintf(stdout, "};\n\n"); fclose(sokfile); if (i == num_games) remove("temp, YASS 2.142 Solutions.sok"); } exit (EXIT_SUCCESS); return EXIT_FAILURE; }