X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=midend.c;h=2eb5ee93e8f3b00667ad3af84f51a8a2e6e3dd22;hb=a0a581c8b5422bf0c5ed3fde6aa25811e4eb89fc;hp=2e8b7f6f7c817e984fa3f4568b0b7654d52ea427;hpb=8f87f2ce894f6ba017581abb89e2e0927067bed7;p=sgt-puzzles.git diff --git a/midend.c b/midend.c index 2e8b7f6..2eb5ee9 100644 --- a/midend.c +++ b/midend.c @@ -30,9 +30,9 @@ struct midend { random_state *random; const game *ourgame; - game_params **presets; - char **preset_names, **preset_encodings; - int npresets, presetsize; + struct preset_menu *preset_menu; + char **encoded_presets; /* for midend_which_preset to check against */ + int n_encoded_presets; /* * `desc' and `privdesc' deserve a comment. @@ -94,6 +94,29 @@ struct midend { } \ } while (0) +void midend_reset_tilesize(midend *me) +{ + me->preferred_tilesize = me->ourgame->preferred_tilesize; + { + /* + * Allow an environment-based override for the default tile + * size by defining a variable along the lines of + * `NET_TILESIZE=15'. + */ + + char buf[80], *e; + int j, k, ts; + + sprintf(buf, "%s_TILESIZE", 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 ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0) + me->preferred_tilesize = ts; + } +} + midend *midend_new(frontend *fe, const game *ourgame, const drawing_api *drapi, void *drhandle) { @@ -109,6 +132,9 @@ midend *midend_new(frontend *fe, const game *ourgame, me->nstates = me->statesize = me->statepos = 0; me->states = NULL; me->params = ourgame->default_params(); + me->game_id_change_notify_function = NULL; + me->game_id_change_notify_ctx = NULL; + /* * Allow environment-based changing of the default settings by * defining a variable along the lines of `NET_DEFAULT=25x25w' @@ -132,10 +158,7 @@ midend *midend_new(frontend *fe, const game *ourgame, me->genmode = GOT_NOTHING; me->drawstate = NULL; me->oldstate = NULL; - me->presets = NULL; - me->preset_names = NULL; - me->preset_encodings = NULL; - me->npresets = me->presetsize = 0; + me->preset_menu = NULL; me->anim_time = me->anim_pos = 0.0F; me->flash_time = me->flash_pos = 0.0F; me->dir = 0; @@ -150,25 +173,7 @@ midend *midend_new(frontend *fe, const game *ourgame, else me->drawing = NULL; - me->preferred_tilesize = ourgame->preferred_tilesize; - { - /* - * Allow an environment-based override for the default tile - * size by defining a variable along the lines of - * `NET_TILESIZE=15'. - */ - - char buf[80], *e; - int j, k, ts; - - sprintf(buf, "%s_TILESIZE", 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 ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0) - me->preferred_tilesize = ts; - } + midend_reset_tilesize(me); sfree(randseed); @@ -201,10 +206,23 @@ static void midend_free_game(midend *me) me->ourgame->free_drawstate(me->drawing, me->drawstate); } -void midend_free(midend *me) +static void midend_free_preset_menu(midend *me, struct preset_menu *menu) { - int i; + if (menu) { + int i; + for (i = 0; i < menu->n_entries; i++) { + sfree(menu->entries[i].title); + if (menu->entries[i].params) + me->ourgame->free_params(menu->entries[i].params); + midend_free_preset_menu(me, menu->entries[i].submenu); + } + sfree(menu->entries); + sfree(menu); + } +} +void midend_free(midend *me) +{ midend_free_game(me); if (me->drawing) @@ -216,16 +234,7 @@ void midend_free(midend *me) sfree(me->seedstr); sfree(me->aux_info); me->ourgame->free_params(me->params); - if (me->npresets) { - for (i = 0; i < me->npresets; i++) { - sfree(me->presets[i]); - sfree(me->preset_names[i]); - sfree(me->preset_encodings[i]); - } - sfree(me->presets); - sfree(me->preset_names); - sfree(me->preset_encodings); - } + midend_free_preset_menu(me, me->preset_menu); if (me->ui) me->ourgame->free_ui(me->ui); if (me->curparams) @@ -347,6 +356,7 @@ void midend_force_redraw(midend *me) void midend_new_game(midend *me) { + midend_stop_anim(me); midend_free_game(me); assert(me->nstates == 0); @@ -437,55 +447,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++; @@ -494,6 +455,8 @@ void midend_new_game(midend *me) me->states[0].state); midend_size_new_drawstate(me); me->elapsed = 0.0F; + me->flash_pos = me->flash_time = 0.0F; + me->anim_pos = me->anim_time = 0.0F; if (me->ui) me->ourgame->free_ui(me->ui); me->ui = me->ourgame->new_ui(me->states[0].state); @@ -587,8 +550,6 @@ void midend_restart_game(midend *me) { game_state *s; - midend_stop_anim(me); - assert(me->statepos >= 1); if (me->statepos == 1) return; /* no point doing anything at all! */ @@ -616,7 +577,7 @@ void midend_restart_game(midend *me) me->ourgame->changed_state(me->ui, me->states[me->statepos-2].state, me->states[me->statepos-1].state); - me->anim_time = 0.0; + me->flash_pos = me->flash_time = 0.0F; midend_finish_move(me); midend_redraw(me); midend_set_timer(me); @@ -637,11 +598,10 @@ static int midend_really_process_key(midend *me, int x, int y, int button) if (!movestr) { if (button == 'n' || button == 'N' || button == '\x0E') { - midend_stop_anim(me); midend_new_game(me); midend_redraw(me); goto done; /* never animate */ - } else if (button == 'u' || button == 'u' || + } else if (button == 'u' || button == 'U' || button == '\x1A' || button == '\x1F') { midend_stop_anim(me); type = me->states[me->statepos-1].movetype; @@ -968,40 +928,177 @@ float *midend_colours(midend *me, int *ncolours) return ret; } -int midend_num_presets(midend *me) +struct preset_menu *preset_menu_new(void) { - if (!me->npresets) { - char *name; + struct preset_menu *menu = snew(struct preset_menu); + menu->n_entries = 0; + menu->entries_size = 0; + menu->entries = NULL; + return menu; +} + +static struct preset_menu_entry *preset_menu_add(struct preset_menu *menu, + char *title) +{ + struct preset_menu_entry *toret; + if (menu->n_entries >= menu->entries_size) { + menu->entries_size = menu->n_entries * 5 / 4 + 10; + menu->entries = sresize(menu->entries, menu->entries_size, + struct preset_menu_entry); + } + toret = &menu->entries[menu->n_entries++]; + toret->title = title; + toret->params = NULL; + toret->submenu = NULL; + return toret; +} + +struct preset_menu *preset_menu_add_submenu(struct preset_menu *parent, + char *title) +{ + struct preset_menu_entry *entry = preset_menu_add(parent, title); + entry->submenu = preset_menu_new(); + return entry->submenu; +} + +void preset_menu_add_preset(struct preset_menu *parent, + char *title, game_params *params) +{ + struct preset_menu_entry *entry = preset_menu_add(parent, title); + entry->params = params; +} + +game_params *preset_menu_lookup_by_id(struct preset_menu *menu, int id) +{ + int i; + game_params *retd; + + for (i = 0; i < menu->n_entries; i++) { + if (id == menu->entries[i].id) + return menu->entries[i].params; + if (menu->entries[i].submenu && + (retd = preset_menu_lookup_by_id( + menu->entries[i].submenu, id)) != NULL) + return retd; + } + + return NULL; +} + +static char *preset_menu_add_from_user_env( + midend *me, struct preset_menu *menu, char *p, int top_level) +{ + while (*p) { + char *name, *val; game_params *preset; - while (me->ourgame->fetch_preset(me->npresets, &name, &preset)) { - if (me->presetsize <= me->npresets) { - me->presetsize = me->npresets + 10; - me->presets = sresize(me->presets, me->presetsize, - game_params *); - me->preset_names = sresize(me->preset_names, me->presetsize, - char *); - me->preset_encodings = sresize(me->preset_encodings, - me->presetsize, char *); + name = p; + while (*p && *p != ':') p++; + if (*p) *p++ = '\0'; + val = p; + while (*p && *p != ':') p++; + if (*p) *p++ = '\0'; + + if (!strcmp(val, "#")) { + /* + * Special case: either open a new submenu with the given + * title, or terminate the current submenu. + */ + if (*name) { + struct preset_menu *submenu = + preset_menu_add_submenu(menu, dupstr(name)); + p = preset_menu_add_from_user_env(me, submenu, p, FALSE); + } else { + /* + * If we get a 'close submenu' indication at the top + * level, there's not much we can do but quietly + * ignore it. + */ + if (!top_level) + return p; } + continue; + } + + preset = me->ourgame->default_params(); + me->ourgame->decode_params(preset, val); - me->presets[me->npresets] = preset; - me->preset_names[me->npresets] = name; - me->preset_encodings[me->npresets] = - me->ourgame->encode_params(preset, TRUE);; - me->npresets++; + if (me->ourgame->validate_params(preset, TRUE)) { + /* Drop this one from the list. */ + me->ourgame->free_params(preset); + continue; } + + preset_menu_add_preset(menu, dupstr(name), preset); + } + + return p; +} + +static void preset_menu_alloc_ids(midend *me, struct preset_menu *menu) +{ + int i; + + for (i = 0; i < menu->n_entries; i++) + menu->entries[i].id = me->n_encoded_presets++; + + for (i = 0; i < menu->n_entries; i++) + if (menu->entries[i].submenu) + preset_menu_alloc_ids(me, menu->entries[i].submenu); +} + +static void preset_menu_encode_params(midend *me, struct preset_menu *menu) +{ + int i; + + for (i = 0; i < menu->n_entries; i++) { + if (menu->entries[i].params) { + me->encoded_presets[menu->entries[i].id] = + me->ourgame->encode_params(menu->entries[i].params, TRUE); + } else { + preset_menu_encode_params(me, menu->entries[i].submenu); + } + } +} + +struct preset_menu *midend_get_presets(midend *me, int *id_limit) +{ + int i; + + if (me->preset_menu) + return me->preset_menu; + +#if 0 + /* Expect the game to implement exactly one of the two preset APIs */ + assert(me->ourgame->fetch_preset || me->ourgame->preset_menu); + assert(!(me->ourgame->fetch_preset && me->ourgame->preset_menu)); +#endif + + if (me->ourgame->fetch_preset) { + char *name; + game_params *preset; + + /* Simple one-level menu */ + assert(!me->ourgame->preset_menu); + me->preset_menu = preset_menu_new(); + for (i = 0; me->ourgame->fetch_preset(i, &name, &preset); i++) + preset_menu_add_preset(me->preset_menu, name, preset); + + } else { + /* Hierarchical menu provided by the game backend */ + me->preset_menu = me->ourgame->preset_menu(); } { /* - * Allow environment-based extensions to the preset list by - * defining a variable along the lines of `SOLO_PRESETS=2x3 - * Advanced:2x3da'. Colon-separated list of items, - * alternating between textual titles in the menu and - * encoded parameter strings. + * Allow user extensions to the preset list by defining an + * environment variable _PRESETS whose value is a + * colon-separated list of items, alternating between textual + * titles in the menu and encoded parameter strings. For + * example, "SOLO_PRESETS=2x3 Advanced:2x3da" would define + * just one additional preset for Solo. */ - char buf[80], *e, *p; + char buf[80], *e; int j, k; sprintf(buf, "%s_PRESETS", me->ourgame->name); @@ -1011,57 +1108,27 @@ int midend_num_presets(midend *me) buf[k] = '\0'; if ((e = getenv(buf)) != NULL) { - p = e = dupstr(e); - - while (*p) { - char *name, *val; - game_params *preset; - - name = p; - while (*p && *p != ':') p++; - if (*p) *p++ = '\0'; - val = p; - while (*p && *p != ':') p++; - if (*p) *p++ = '\0'; - - preset = me->ourgame->default_params(); - me->ourgame->decode_params(preset, val); - - if (me->ourgame->validate_params(preset, TRUE)) { - /* Drop this one from the list. */ - me->ourgame->free_params(preset); - continue; - } - - if (me->presetsize <= me->npresets) { - me->presetsize = me->npresets + 10; - me->presets = sresize(me->presets, me->presetsize, - game_params *); - me->preset_names = sresize(me->preset_names, - me->presetsize, char *); - me->preset_encodings = sresize(me->preset_encodings, - me->presetsize, char *); - } - - me->presets[me->npresets] = preset; - me->preset_names[me->npresets] = dupstr(name); - me->preset_encodings[me->npresets] = - me->ourgame->encode_params(preset, TRUE); - me->npresets++; - } + e = dupstr(e); + preset_menu_add_from_user_env(me, me->preset_menu, e, TRUE); sfree(e); } } - return me->npresets; -} - -void midend_fetch_preset(midend *me, int n, - char **name, game_params **params) -{ - assert(n >= 0 && n < me->npresets); - *name = me->preset_names[n]; - *params = me->presets[n]; + /* + * Finalise the menu: allocate an integer id to each entry, and + * store string encodings of the presets' parameters in + * me->encoded_presets. + */ + me->n_encoded_presets = 0; + preset_menu_alloc_ids(me, me->preset_menu); + me->encoded_presets = snewn(me->n_encoded_presets, char *); + for (i = 0; i < me->n_encoded_presets; i++) + me->encoded_presets[i] = NULL; + preset_menu_encode_params(me, me->preset_menu); + + if (id_limit) + *id_limit = me->n_encoded_presets; + return me->preset_menu; } int midend_which_preset(midend *me) @@ -1070,8 +1137,9 @@ int midend_which_preset(midend *me) int i, ret; ret = -1; - for (i = 0; i < me->npresets; i++) - if (!strcmp(encoding, me->preset_encodings[i])) { + for (i = 0; i < me->n_encoded_presets; i++) + if (me->encoded_presets[i] && + !strcmp(encoding, me->encoded_presets[i])) { ret = i; break; } @@ -1220,7 +1288,45 @@ static char *midend_game_id_int(midend *me, char *id, int defmode) newcurparams = newparams = oldparams1 = oldparams2 = NULL; if (par) { - newcurparams = me->ourgame->dup_params(me->params); + /* + * The params string may underspecify the game parameters, so + * we must first initialise newcurparams with a full set of + * params from somewhere else before we decode_params the + * input string over the top. + * + * But which set? It depends on what other data we have. + * + * If we've been given a _descriptive_ game id, then that may + * well underspecify by design, e.g. Solo game descriptions + * often start just '3x3:' without specifying one of Solo's + * difficulty settings, because it isn't necessary once a game + * has been generated (and you might not even know it, if + * you're manually transcribing a game description). In that + * situation, I've always felt that the best thing to set the + * difficulty to (for use if the user hits 'New Game' after + * pasting in that game id) is whatever it was previously set + * to. That is, we use whatever is already in me->params as + * the basis for our decoding of this input string. + * + * A random-seed based game id, however, should use the real, + * built-in default params, and not even check the + * _DEFAULT environment setting, because when people + * paste each other random seeds - whether it's two users + * arranging to generate the same game at the same time to + * race solving them, or a user sending a bug report upstream + * - the whole point is for the random game id to always be + * interpreted the same way, even if it does underspecify. + * + * A parameter string typed in on its own, with no seed _or_ + * description, gets treated the same way as a random seed, + * because again I think the most likely reason for doing that + * is to have a portable representation of a set of params. + */ + if (desc) { + newcurparams = me->ourgame->dup_params(me->params); + } else { + newcurparams = me->ourgame->default_params(); + } me->ourgame->decode_params(newcurparams, par); error = me->ourgame->validate_params(newcurparams, desc == NULL); if (error) { @@ -1504,7 +1610,9 @@ void midend_serialise(midend *me, #define wr(h,s) do { \ char hbuf[80]; \ char *str = (s); \ - sprintf(hbuf, "%-8.8s:%d:", (h), (int)strlen(str)); \ + char lbuf[9]; \ + copy_left_justified(lbuf, sizeof(lbuf), h); \ + sprintf(hbuf, "%s:%d:", lbuf, (int)strlen(str)); \ write(wctx, hbuf, strlen(hbuf)); \ write(wctx, str, strlen(str)); \ write(wctx, "\n", 1); \