diff --git a/Recipe b/Recipe index 5d63ae5..1c2568b 100644 --- a/Recipe +++ b/Recipe @@ -205,3 +205,19 @@ PuzzleApplet.class: PuzzleApplet.java org echo '' >$*.html mv PuzzleEngine.class $< !end + +# A benchmarking and testing target for the GTK puzzles. +!begin gtk +test: benchmark.html benchmark.txt + +benchmark.html: benchmark.txt benchmark.pl + ./benchmark.pl benchmark.txt > $@ + +benchmark.txt: $(GAMES) + for i in $(GAMES); do \ + for params in $$(env -i ./$(BINPREFIX)$$i --list-presets | cut -f1 -d' '); do \ + env -i ./$(BINPREFIX)$$i --test-solve --time-generation --generate 100 $$params \ + || exit 1; \ + done; \ + done > $@ +!end diff --git a/benchmark.pl b/benchmark.pl new file mode 100755 index 0000000..131faaf --- /dev/null +++ b/benchmark.pl @@ -0,0 +1,153 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +my @presets = (); +my %presets = (); +my $maxval = 0; + +while (<>) { + chomp; + if (/^(.*)(#.*): ([\d\.]+)$/) { + push @presets, $1 unless defined $presets{$1}; + push @{$presets{$1}}, $3; + $maxval = $3 if $maxval < $3; + } +} + +print < + + + +Puzzle generation-time benchmarks + + + +Puzzle generation-time benchmarks + +Preset +EOF + +for my $preset (@presets) { + print "", &escape($preset), " $b } @{$presets{$preset}}; + print "]\" data-scale=\"$maxval\">\n"; +} + +print < + +EOF + +sub escape { + my ($text) = @_; + $text =~ s/&/&/g; + $text =~ s/</g; + $text =~ s/>/>/g; + return $text; +} diff --git a/gtk.c b/gtk.c index 4222bd4..a2eba2c 100644 --- a/gtk.c +++ b/gtk.c @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -2482,6 +2483,7 @@ int main(int argc, char **argv) char *pname = argv[0]; char *error; int ngenerate = 0, print = FALSE, px = 1, py = 1; + int time_generation = FALSE, test_solve = FALSE, list_presets = FALSE; int soln = FALSE, colour = FALSE; float scale = 1.0F; float redo_proportion = 0.0F; @@ -2534,6 +2536,12 @@ int main(int argc, char **argv) } } else ngenerate = 1; + } else if (doing_opts && !strcmp(p, "--time-generation")) { + time_generation = TRUE; + } else if (doing_opts && !strcmp(p, "--test-solve")) { + test_solve = TRUE; + } else if (doing_opts && !strcmp(p, "--list-presets")) { + list_presets = TRUE; } else if (doing_opts && !strcmp(p, "--save")) { if (--ac > 0) { savefile = *++av; @@ -2722,7 +2730,8 @@ int main(int argc, char **argv) * generated descriptive game IDs.) */ while (ngenerate == 0 || i < n) { - char *pstr, *err; + char *pstr, *err, *seed; + struct rusage before, after; if (ngenerate == 0) { pstr = fgetline(stdin); @@ -2748,9 +2757,63 @@ int main(int argc, char **argv) return 1; } } - sfree(pstr); - midend_new_game(me); + if (time_generation) + getrusage(RUSAGE_SELF, &before); + + midend_new_game(me); + + seed = midend_get_random_seed(me); + + if (time_generation) { + double elapsed; + + getrusage(RUSAGE_SELF, &after); + + elapsed = (after.ru_utime.tv_sec - + before.ru_utime.tv_sec); + elapsed += (after.ru_utime.tv_usec - + before.ru_utime.tv_usec) / 1000000.0; + + printf("%s %s: %.6f\n", thegame.name, seed, elapsed); + } + + if (test_solve && thegame.can_solve) { + /* + * Now destroy the aux_info in the midend, by means of + * re-entering the same game id, and then try to solve + * it. + */ + char *game_id, *err; + + game_id = midend_get_game_id(me); + err = midend_game_id(me, game_id); + if (err) { + fprintf(stderr, "%s %s: game id re-entry error: %s\n", + thegame.name, seed, err); + return 1; + } + midend_new_game(me); + sfree(game_id); + + err = midend_solve(me); + /* + * If the solve operation returned the error "Solution + * not known for this puzzle", that's OK, because that + * just means it's a puzzle for which we don't have an + * algorithmic solver and hence can't solve it without + * the aux_info, e.g. Netslide. Any other error is a + * problem, though. + */ + if (err && strcmp(err, "Solution not known for this puzzle")) { + fprintf(stderr, "%s %s: solve error: %s\n", + thegame.name, seed, err); + return 1; + } + } + + sfree(pstr); + sfree(seed); if (doc) { err = midend_print_puzzle(me, doc, soln); @@ -2794,7 +2857,7 @@ int main(int argc, char **argv) } sfree(realname); } - if (!doc && !savefile) { + if (!doc && !savefile && !time_generation) { id = midend_get_game_id(me); puts(id); sfree(id); @@ -2813,6 +2876,30 @@ int main(int argc, char **argv) midend_free(me); return 0; + } else if (list_presets) { + /* + * Another specialist mode which causes the puzzle to list the + * game_params strings for all its preset configurations. + */ + int i, npresets; + midend *me; + + me = midend_new(NULL, &thegame, NULL, NULL); + npresets = midend_num_presets(me); + + for (i = 0; i < npresets; i++) { + game_params *params; + char *name, *paramstr; + + midend_fetch_preset(me, i, &name, ¶ms); + paramstr = thegame.encode_params(params, TRUE); + + printf("%s %s\n", paramstr, name); + sfree(paramstr); + } + + midend_free(me); + return 0; } else { frontend *fe; diff --git a/midend.c b/midend.c index c361274..178650b 100644 --- a/midend.c +++ b/midend.c @@ -445,55 +445,6 @@ void midend_new_game(midend *me) sfree(movestr); } - /* - * Soak test, enabled by setting _TESTSOLVE in the - * environment. This causes an immediate attempt to re-solve the - * game without benefit of aux_info. The effect is that (at least - * on Unix) you can run 'FOO_TESTSOLVE=1 foo --generate 10000 - * #12345' and it will generate a lot of game ids and - * instantly pass each one back to the solver. - * - * (It's worth putting in an explicit seed in any such test, so - * you can repeat it to diagnose a problem if one comes up!) - */ - { - char buf[80]; - int j, k; - static int doing_test_solve = -1; - if (doing_test_solve < 0) { - sprintf(buf, "%s_TESTSOLVE", me->ourgame->name); - for (j = k = 0; buf[j]; j++) - if (!isspace((unsigned char)buf[j])) - buf[k++] = toupper((unsigned char)buf[j]); - buf[k] = '\0'; - if (getenv(buf)) { - /* - * Since this is used for correctness testing, it's - * helpful to have a visual acknowledgment that the - * user hasn't mistyped the environment variable name. - */ - fprintf(stderr, "Running solver soak tests\n"); - doing_test_solve = TRUE; - } else { - doing_test_solve = FALSE; - } - } - if (doing_test_solve) { - game_state *s; - char *msg, *movestr; - - msg = NULL; - movestr = me->ourgame->solve(me->states[0].state, - me->states[0].state, - NULL, &msg); - assert(movestr && !msg); - s = me->ourgame->execute_move(me->states[0].state, movestr); - assert(s); - me->ourgame->free_game(s); - sfree(movestr); - } - } - me->states[me->nstates].movestr = NULL; me->states[me->nstates].movetype = NEWGAME; me->nstates++;