//--------------------------------------------------------------------------- // SokoSplit.cpp // // Split Sokoban multiple-puzzle sets into single-puzzle files // suitable for use with SokoSave. // // Copyright (C), 2004, Paul S. McCarthy. // // mailto: // // This program is 'freeware'. You may use this program for free. // You may make and distribute UNMODIFIED copies of this program for // free provided that no fee of any kind is charged. You may make and // keep MODIFIED copies of this program for your own personal, non- // commercial use. You MAY NOT distribute modified copies of this // program. // // This program is provided free of charge "AS-IS" with NO WARRANTY. // //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- // $Id$ // $Log$ //--------------------------------------------------------------------------- extern "C" { #include #include #include #include #include #include } #ifndef __BORLANDC__ extern "C" { #endif #include #ifndef __BORLANDC__ } #endif static char const PROGRAM[] = "SokoSplit"; static char const VERSION[] = "v0.5"; struct Line_Info { char const* text; int puzzle_assignment; bool blanks_only; bool puzzle_chars_only; }; static char const* EXTENSION = "xsb"; static bool QUIET = false; // '#' 0x23 Wall. // ' ' 0x20 Plain empty square. // '.' 0x2e Empty goal square. // '$' 0x24 Crate on an empty square. // '*' 0x2a Crate on a goal square. // '@' 0x40 Player on an empty square. // '+' 0x2b Player on a goal square. static char const PUZZLE_CHARS[] = "# .$*@+"; // YASGen extended characters: // '#' 0x23 Wall // '_' 0x5f Plain empty square // 'o' 0x6f Empty goal square. // 'b' 0x62 Box (crate) on an empty square. // 'B' 0x42 Box (crate) on a goal square. // 'p' 0x70 Player on an empty square. // 'P' 0x50 Player on a goal square. static char const YASGEN_CHARS[] = "#_obBpP"; //--------------------------------------------------------------------------- // banner //--------------------------------------------------------------------------- static void banner() { fprintf( stdout, "%s: %s Copyright (C), 2004, Paul S. McCarthy.\n", PROGRAM, VERSION ); } //--------------------------------------------------------------------------- // help //--------------------------------------------------------------------------- static void help() { banner(); fprintf( stdout, "\n" "This program splits multi-puzzle sokoban sets into individual\n" "single-puzzle files.\n" "\n" "usage: %s --help Produces this message.\n" "\n" "usage: %s --copying Displays copyright information.\n" "\n" "usage: %s [-quiet] filename [extension]\n" "\n" "where:\n" " 'filename' is the name of the file containing the set of\n" " puzzles to be broken out.\n" " 'extension' is the extension to use on the output filenames.\n" " The default extension is \"%s\". The standard\n" " extensions are: Sokoban=.xsb, Hexoban=.hsb and\n" " Trioban=.tsb\n" "\n", PROGRAM, PROGRAM, PROGRAM, EXTENSION ); } //--------------------------------------------------------------------------- // copying //--------------------------------------------------------------------------- static void copying() { fprintf( stdout, "%s %s\n" "\n" "Copyright (C), 2004, Paul S. McCarthy.\n" "\n" "mailto:\n" "\n" "This program is 'freeware'. You may use this program for free.\n" "You may make and distribute UNMODIFIED copies of this program for\n" "free provided that no fee of any kind is charged. You may make and\n" "keep MODIFIED copies of this program for your own personal, non-\n" "commercial use. You MAY NOT distribute modified copies of this\n" "program.\n" "\n" "This program is provided free of charge \"AS-IS\" with NO WARRANTY.\n" "\n", PROGRAM, VERSION ); } //--------------------------------------------------------------------------- // upper //--------------------------------------------------------------------------- static inline char upper( char ch ) { return (islower(ch) ? toupper(ch) : ch); } //--------------------------------------------------------------------------- // str_equal //--------------------------------------------------------------------------- static bool str_equal( char const* s, char const* t ) { while (upper(*s) == upper(*t) && *s != '\0') { ++s; ++t; } return (*s == '\0' && *t == '\0'); } //--------------------------------------------------------------------------- // is_help //--------------------------------------------------------------------------- static bool is_help( char const* s ) { while (*s == '-' || *s == '/') ++s; return (str_equal( "?", s ) || str_equal( "H", s ) || str_equal( "HELP", s )); } //--------------------------------------------------------------------------- // is_copying //--------------------------------------------------------------------------- static bool is_copying( char const* s ) { if (*s == '-' || *s == '/') { while (*s == '-' || *s == '/') ++s; return (str_equal( "COPY", s ) || str_equal( "COPYING", s ) || str_equal( "COPYRIGHT", s )); } return false; } //--------------------------------------------------------------------------- // calc_num_digits //--------------------------------------------------------------------------- static int calc_num_digits( int x ) { int n = 0; while (x != 0) { x /= 10; ++n; } if (n == 0) ++n; return n; } //--------------------------------------------------------------------------- // count_lines //--------------------------------------------------------------------------- static int count_lines( char const* s, size_t nbytes ) { int num_lines = 0; char const* const sN = s + nbytes; while (s < sN) { if (*s == '\r') { ++s; ++num_lines; if (s < sN && *s == '\n') // ++s; // else } else if (*s == '\n') // { ++s; ++num_lines; } else ++s; } if (nbytes > 0) { char const ch = *--s; if (ch != '\r' && ch != '\n') // ++num_lines; } return num_lines; } //--------------------------------------------------------------------------- // parse_lines // // NOTE *1* Many puzzle-sets from www.sokomind.de have file-header // information separated from the first puzzle by a line which has // only a single semi-colon. //--------------------------------------------------------------------------- static void parse_lines( Line_Info* lines, int num_lines, char const* text, size_t nbytes ) { bool at_start_of_line = true; bool blanks_only = true; bool puzzle_chars_only = true; char const* s = text; char const* sN = text + nbytes; Line_Info* p = lines; Line_Info* const pN = p + num_lines; p->text = s; while (s < sN) { if (*s == '\r' || *s == '\n') { if (*s == '\r') { ++s; if (s < sN && *s == '\n') ++s; } else // (*s == '\n') ++s; p->blanks_only = blanks_only; p->puzzle_chars_only = puzzle_chars_only; blanks_only = true; puzzle_chars_only = true; at_start_of_line = true; ++p; if (p < pN) p->text = s; } else { if (*s > ' ') { if (!at_start_of_line || *s != ';') // NOTE *1* blanks_only = false; } if (puzzle_chars_only && strchr( PUZZLE_CHARS, *s ) == 0) puzzle_chars_only = false; ++s; at_start_of_line = false; } } if (nbytes > 0) { char const last_char = *--s; if (last_char != '\r' && last_char != '\n') { if (p < pN) { p->blanks_only = blanks_only; p->puzzle_chars_only = puzzle_chars_only; } } } } //--------------------------------------------------------------------------- // init_puzzles //--------------------------------------------------------------------------- static int init_puzzles( Line_Info* lines, int num_lines ) { int num_puzzles = 0; bool in_puzzle = false; Line_Info* p = lines; Line_Info* const pN = p + num_lines; for ( ; p < pN; ++p) { if (!p->blanks_only && p->puzzle_chars_only) { if (!in_puzzle) { in_puzzle = true; ++num_puzzles; } p->puzzle_assignment = num_puzzles; } else { in_puzzle = false; p->puzzle_assignment = 0; } } return num_puzzles; } //--------------------------------------------------------------------------- // annex_preceeding_single_blank_line //--------------------------------------------------------------------------- static void annex_preceeding_single_blank_line( Line_Info* lines, int num_lines ) { int last_puzzle = 0; for (Line_Info* p = lines + num_lines; p-- > lines; ) { if (last_puzzle != 0 && p->puzzle_assignment == 0 && p->blanks_only) { p->puzzle_assignment = last_puzzle; last_puzzle = 0; } else last_puzzle = p->puzzle_assignment; } } //--------------------------------------------------------------------------- // annex_preceeding_non_blank_lines //--------------------------------------------------------------------------- static void annex_preceeding_non_blank_lines( Line_Info* lines, int num_lines ) { int last_puzzle = 0; for (Line_Info* p = lines + num_lines; p-- > lines; ) { if (last_puzzle != 0 && p->puzzle_assignment == 0 && !p->blanks_only) { p->puzzle_assignment = last_puzzle; } last_puzzle = p->puzzle_assignment; } } //--------------------------------------------------------------------------- // annex_following_non_blank_lines //--------------------------------------------------------------------------- static void annex_following_non_blank_lines( Line_Info* lines, int num_lines ) { int last_puzzle = 0; Line_Info* const pN = lines + num_lines; for (Line_Info* p = lines; p < pN; ++p) { if (last_puzzle != 0 && p->puzzle_assignment == 0 && !p->blanks_only) { p->puzzle_assignment = last_puzzle; } last_puzzle = p->puzzle_assignment; } } //--------------------------------------------------------------------------- // annex_following_lines //--------------------------------------------------------------------------- static void annex_following_lines( Line_Info* lines, int num_lines ) { int last_puzzle = 0; Line_Info* const pN = lines + num_lines; for (Line_Info* p = lines; p < pN; ++p) { if (last_puzzle != 0 && p->puzzle_assignment == 0) { p->puzzle_assignment = last_puzzle; } last_puzzle = p->puzzle_assignment; } } //--------------------------------------------------------------------------- // trim_preceeding_blank_lines //--------------------------------------------------------------------------- static void trim_preceeding_blank_lines( Line_Info* lines, int num_lines ) { int last_puzzle = 0; Line_Info* const pN = lines + num_lines; for (Line_Info* p = lines; p < pN; ++p) { if (p->puzzle_assignment != last_puzzle && p->puzzle_assignment != 0 && p->blanks_only) { p->puzzle_assignment = 0; } last_puzzle = p->puzzle_assignment; } } //--------------------------------------------------------------------------- // trim_following_blank_lines //--------------------------------------------------------------------------- static void trim_following_blank_lines( Line_Info* lines, int num_lines ) { int last_puzzle = 0; for (Line_Info* p = lines + num_lines; p-- > lines; ) { if (p->puzzle_assignment != last_puzzle && p->puzzle_assignment != 0 && p->blanks_only) { p->puzzle_assignment = 0; } last_puzzle = p->puzzle_assignment; } } //--------------------------------------------------------------------------- // count_header_lines // // Count the number of header lines for each puzzle. // Return the greatest number of header lines for any single // puzzle. // //--------------------------------------------------------------------------- static int count_header_lines( int* header_lens, int num_puzzles, Line_Info const* lines, int num_lines ) { int max_lines = 0; Line_Info const* p = lines; Line_Info const* const pN = p + num_lines; for (int i = 0; i++ < num_puzzles; ) { while (p < pN && p->puzzle_assignment == 0) // in between ++p; Line_Info const* const p0 = p; // header lines while (p < pN && p->puzzle_assignment == i && (!p->puzzle_chars_only || p->blanks_only)) ++p; int const n = (p - p0); header_lens[ i - 1 ] = n; if (max_lines < n) max_lines = n; while (p < pN && p->puzzle_assignment == i) // puzzle and footer ++p; } return max_lines; } //--------------------------------------------------------------------------- // extract_file_prefix //--------------------------------------------------------------------------- static void extract_file_prefix( size_t* prefix_len, int num_puzzles, Line_Info* lines, int num_lines ) { Line_Info const* p = lines; Line_Info const* const pN = p + num_lines; for ( ; p < pN && p->puzzle_assignment == 0; ++p) {} size_t const nbytes = (p->text - lines[0].text); *prefix_len = nbytes; if (nbytes == 0) { int* const header_lens = new int [ num_puzzles ]; if (header_lens == 0) { fprintf( stdout, "%s: Out of memory.\n", PROGRAM ); } else { int const max_header_lines = count_header_lines( header_lens, num_puzzles, lines, num_lines ); int* const buckets = new int [ max_header_lines ]; if (buckets == 0) { fprintf( stdout, "%s: Out of memory.\n", PROGRAM ); } else { int i; for (i = 0; i < max_header_lines; ++i) buckets[i] = 0; // Exclude first puzzle. for (i = 1; i < num_puzzles; ++i) ++(buckets[ header_lens[i] ]); int max_bucket = 0; int max_count = buckets[0]; for (i = 1; i < max_header_lines; ++i) if (max_count < buckets[i]) { max_count = buckets[i]; max_bucket = i; } int const first_header_len = header_lens[0]; int const mean_header_len = max_bucket; if (max_count == num_puzzles - 1) { // All other puzzles (beside the first one) // have the same number of header lines. if (first_header_len > mean_header_len) { // Assign only the last mean_header_len // lines to the first puzzle's header. int const num_prefix_lines = first_header_len - mean_header_len; for (i = 0; i < num_prefix_lines; ++i) lines[i].puzzle_assignment = 0; *prefix_len = lines[i].text - lines[0].text; } else // (first_header_len <= mean_header_len) { // Do nothing. Assign all prefix lines to // the first puzzle's header. } } else { // FIXME: Write this. // Work harder to figure out what the // correct division between the first // puzzle's header and the file prefix // should be. } delete [] buckets; } delete [] header_lens; } } } //--------------------------------------------------------------------------- // output_puzzles //--------------------------------------------------------------------------- static void output_puzzles( int num_puzzles, char const* input_filename, size_t prefix_len, Line_Info const* lines, int num_lines ) { int num_digits = calc_num_digits( num_puzzles ); // skip over leading directory names. char const* basename = 0; for (char const* s = input_filename; *s != '\0'; ++s) if (*s == '/' || *s == '\\' || *s == ':') basename = s; if (basename != 0) ++basename; else basename = input_filename; // find the extension. char const* period = strrchr( basename, '.' ); if (period == 0) period = basename + strlen( basename ); size_t const core_len = (period - basename); size_t const filename_len = core_len + num_digits + strlen( EXTENSION ) + 2; // '_' + '.' char* const filename_buff = (char*) malloc( filename_len + 1 ); if (filename_buff == 0) { fprintf( stdout, "%s: malloc(%u): %d: %s\n", PROGRAM, filename_len + 1, errno, strerror(errno) ); } else { strcpy( filename_buff, basename ); char* const tail = filename_buff + core_len; char const* const prefix = lines[0].text; Line_Info const* p = lines; Line_Info const* const pN = p + num_lines; bool ok = true; int puzzle_number = 0; while (p < pN && ok) { ++puzzle_number; while (p < pN && p->puzzle_assignment == 0) ++p; if (p < pN) { sprintf( tail, "_%0*d.%s", num_digits, puzzle_number, EXTENSION ); FILE* const file = fopen( filename_buff, "wb" ); if (file == 0) { fprintf( stdout, "%s: fopen(\"%s\",\"wb\"): %d: %s\n", PROGRAM, filename_buff, errno, strerror(errno) ); ok = false; } else { if (prefix_len > 0) { size_t const n = fwrite( prefix, 1, prefix_len, file ); if (n != prefix_len) { fprintf( stdout, "%s: fwrite(%s): %d: %s\n", PROGRAM, filename_buff, errno, strerror(errno) ); ok = false; } } if (ok) { char const* const s0 = p->text; while (p < pN && p->puzzle_assignment == puzzle_number) ++p; char const* const sN = p->text; size_t const len = (sN - s0); size_t const n = fwrite( s0, 1, len, file ); if (n != len) { fprintf( stdout, "%s: fwrite(%s): %d: %s\n", PROGRAM, filename_buff, errno, strerror(errno) ); ok = false; } } fclose( file ); } } } free( filename_buff ); if (!QUIET) fprintf( stdout, "%s: %s %d puzzles.\n", PROGRAM, input_filename, num_puzzles ); } } //--------------------------------------------------------------------------- // process //--------------------------------------------------------------------------- static void process( char const* filename, char const* text, size_t nbytes ) { int num_lines = count_lines( text, nbytes ); Line_Info* const lines = new Line_Info[ num_lines + 1 ]; if (lines == 0) { fprintf( stdout, "%s: malloc(%u): %d: %s\n", PROGRAM, (num_lines + 1) * sizeof(*lines), errno, strerror(errno) ); } else { parse_lines( lines, num_lines, text, nbytes ); lines[ num_lines ].text = text + nbytes; // Sentinel value. lines[ num_lines ].puzzle_assignment = 0; int num_puzzles = init_puzzles( lines, num_lines ); if (num_puzzles > 1) { annex_preceeding_single_blank_line( lines, num_lines ); annex_following_non_blank_lines( lines, num_lines ); annex_preceeding_non_blank_lines( lines, num_lines ); annex_following_lines( lines, num_lines ); trim_preceeding_blank_lines( lines, num_lines ); trim_following_blank_lines( lines, num_lines ); size_t prefix_len = 0; extract_file_prefix( &prefix_len, num_puzzles, lines, num_lines ); output_puzzles( num_puzzles, filename, prefix_len, lines, num_lines ); } else if (num_puzzles == 0) { fprintf( stdout, "%s: No puzzles found in '%s'.\n", PROGRAM, filename ); } else { fprintf( stdout, "%s: Only one puzzle found in '%s'.\n", PROGRAM, filename ); } delete [] lines; } } //--------------------------------------------------------------------------- // main //--------------------------------------------------------------------------- int main( int argc, char const* const* argv ) { int rc = 0; if (argc < 2 || 4 < argc || (argc > 1 && is_help( argv[1] ))) help(); else if (argc > 1 && is_copying( argv[1] )) copying(); else { char const* s = argv[1]; if ((*s == '-' || *s == '/')) { do { ++s; } while (*s == '-' || *s == '/'); if (str_equal( s, "Q" ) || str_equal( s, "QUIET" )) { QUIET = true; ++argv; --argc; if (argc < 2) { help(); return 1; } } } if (argc == 3) { char const* s = argv[2]; if (*s == '.') ++s; EXTENSION = s; } if (!QUIET) banner(); char const* filename = argv[1]; struct stat st; if (stat( filename, &st ) != 0) { rc = errno; fprintf( stdout, "%s: stat(%s): %d: %s\n", PROGRAM, filename, rc, strerror(rc) ); } else { size_t nbytes = st.st_size; if (nbytes == 0) { fprintf( stdout, "%s: zero-length file (%s)\n", PROGRAM, filename ); } else { char* const buff = (char*) malloc( nbytes + 1 ); if (buff == 0) { rc = errno; fprintf( stdout, "%s: malloc(%u): %d: %s\n", PROGRAM, nbytes, rc, strerror(rc) ); } else { FILE* const file = fopen( filename, "rb" ); if (file == 0) { rc = errno; fprintf( stdout, "%s: fopen(%s): %d: %s\n", PROGRAM, filename, rc, strerror(rc) ); } else { size_t const nread = fread( buff, 1, nbytes, file ); fclose( file ); if (nread != nbytes) { rc = errno; fprintf( stdout, "%s: fread(%s,%u): %d: %s\n", PROGRAM, filename, nbytes, rc, strerror(rc) ); } else { buff[ nbytes ] = '\n'; process( filename, buff, nbytes ); } } free( buff ); } } } } return rc; }