\c char *name;
\c int type;
- \c char *sval;
- \c int ival;
+ \c union { /* type-specific fields */ } u;
+ \e iiiiiiiiiiiiiiiiiiiiiiiiii
\c{name} is an ASCII string giving the textual label for a GUI
control. It is \e{not} expected to be dynamically allocated.
\c{type} contains one of a small number of \c{enum} values defining
- what type of control is being described. The meaning of the \c{sval}
- and \c{ival} fields depends on the value in \c{type}. The valid
- values are:
+ what type of control is being described. The usable member of the
+ union field \c{u} depends on \c{type}. The valid type values are:
\dt \c{C_STRING}
input. The back end does not bother informing the front end that the
box is numeric rather than textual; some front ends do have the
capacity to take this into account, but I decided it wasn't worth
- the extra complexity in the interface.) For this type, \c{ival} is
- unused, and \c{sval} contains a dynamically allocated string
- representing the contents of the input box.
+ the extra complexity in the interface.)
+
+ \lcont{
+
+ For controls of this type, \c{u.string} contains a single field
+
+ \c char *sval;
+
+ which stores a dynamically allocated string representing the contents
+ of the input box.
+
+ }
\dt \c{C_BOOLEAN}
- \dd Describes a simple checkbox. For this type, \c{sval} is unused,
- and \c{ival} is \cw{TRUE} or \cw{FALSE}.
+ \dd Describes a simple checkbox.
+
+ \lcont{
+
+ For controls of this type, \c{u.boolean} contains a single field
+
+ \c int bval;
+
+ which is either \cw{TRUE} or \cw{FALSE}.
+
+ }
\dt \c{C_CHOICES}
\dd Describes a drop-down list presenting one of a small number of
- fixed choices. For this type, \c{sval} contains a list of strings
- describing the choices; the very first character of \c{sval} is used
- as a delimiter when processing the rest (so that the strings
- \cq{:zero:one:two}, \cq{!zero!one!two} and \cq{xzeroxonextwo} all
- define a three-element list containing \cq{zero}, \cq{one} and
- \cq{two}). \c{ival} contains the index of the currently selected
- element, numbering from zero (so that in the above example, 0 would
- mean \cq{zero} and 2 would mean \cq{two}).
+ fixed choices.
\lcont{
- Note that for this control type, \c{sval} is \e{not} dynamically
- allocated, whereas it was for \c{C_STRING}.
+ For controls of this type, \c{u.choices} contains two fields:
+
+ \c const char *choicenames;
+ \c int selected;
+
+ \c{choicenames} contains a list of strings describing the choices. The
+ very first character of \c{sval} is used as a delimiter when
+ processing the rest (so that the strings \cq{:zero:one:two},
+ \cq{!zero!one!two} and \cq{xzeroxonextwo} all define a three-element
+ list containing \cq{zero}, \cq{one} and \cq{two}).
+
+ \c{selected} contains the index of the currently selected element,
+ numbering from zero (so that in the above example, 0 would mean
+ \cq{zero} and 2 would mean \cq{two}).
+
+ Note that \c{u.choices.choicenames} is \e{not} dynamically allocated,
+ unlike \c{u.string.sval}.
}
\dt \c{C_END}
- \dd Marks the end of the array of \c{config_item}s. All other fields
- are unused.
+ \dd Marks the end of the array of \c{config_item}s. There is no
+ associated member of the union field \c{u} for this type.
The array returned from this function is expected to have filled in
the initial values of all the controls according to the input
\S{backend-validate-params} \cw{validate_params()}
- \c char *(*validate_params)(const game_params *params, int full);
+ \c const char *(*validate_params)(const game_params *params,
+ \c int full);
This function takes a \c{game_params} structure as input, and checks
that the parameters described in it fall within sensible limits. (At
\S{backend-validate-desc} \cw{validate_desc()}
- \c char *(*validate_desc)(const game_params *params, const char *desc);
+ \c const char *(*validate_desc)(const game_params *params,
+ \c const char *desc);
This function is given a game description, and its job is to
validate that it describes a puzzle which makes sense.
in response to the input event; the puzzle was not interested in it
at all.
- \b Returning the empty string (\cw{""}) indicates that the input
+ \b Returning the special value \cw{UI_UPDATE} indicates that the input
event has resulted in a change being made to the \c{game_ui} which
- will require a redraw of the game window, but that no actual
- \e{move} was made (i.e. no new \c{game_state} needs to be created).
+ will require a redraw of the game window, but that no actual \e{move}
+ was made (i.e. no new \c{game_state} needs to be created).
\b Returning anything else indicates that a move was made and that a
new \c{game_state} must be created. However, instead of actually
The return value from \cw{interpret_move()} is expected to be
dynamically allocated if and only if it is not either \cw{NULL}
- \e{or} the empty string.
+ \e{or} the special string constant \c{UI_UPDATE}.
After this function is called, the back end is permitted to rely on
some subsequent operations happening in sequence:
\S{backend-solve} \cw{solve()}
\c char *(*solve)(const game_state *orig, const game_state *curr,
- \c const char *aux, char **error);
+ \c const char *aux, const char **error);
This function is called when the user selects the \q{Solve} option
from the menu.
\S{drawing-draw-text} \cw{draw_text()}
\c void draw_text(drawing *dr, int x, int y, int fonttype,
- \c int fontsize, int align, int colour, char *text);
+ \c int fontsize, int align, int colour,
+ \c const char *text);
Draws text in the puzzle window.
\S{drawing-status-bar} \cw{status_bar()}
- \c void status_bar(drawing *dr, char *text);
+ \c void status_bar(drawing *dr, const char *text);
Sets the text in the game's status bar to \c{text}. The text is copied
from the supplied buffer, so the caller is free to deallocate or
\S{drawingapi-draw-text} \cw{draw_text()}
\c void (*draw_text)(void *handle, int x, int y, int fonttype,
- \c int fontsize, int align, int colour, char *text);
+ \c int fontsize, int align, int colour,
+ \c const char *text);
This function behaves exactly like the back end \cw{draw_text()}
function; see \k{drawing-draw-text}.
\S{drawingapi-status-bar} \cw{status_bar()}
- \c void (*status_bar)(void *handle, char *text);
+ \c void (*status_bar)(void *handle, const char *text);
This function behaves exactly like the back end \cw{status_bar()}
function; see \k{drawing-status-bar}.
\H{midend-set-config} \cw{midend_set_config()}
- \c char *midend_set_config(midend *me, int which,
- \c config_item *cfg);
+ \c const char *midend_set_config(midend *me, int which,
+ \c config_item *cfg);
Passes the mid-end the results of a configuration dialog box.
\c{which} should have the same value which it had when
\H{midend-game-id} \cw{midend_game_id()}
- \c char *midend_game_id(midend *me, char *id);
+ \c const char *midend_game_id(midend *me, const char *id);
Passes the mid-end a string game ID (of any of the valid forms
\cq{params}, \cq{params:description} or \cq{params#seed}) which the
\H{midend-solve} \cw{midend_solve()}
- \c char *midend_solve(midend *me);
+ \c const char *midend_solve(midend *me);
Requests the mid-end to perform a Solve operation.
\H{midend-serialise} \cw{midend_serialise()}
\c void midend_serialise(midend *me,
- \c void (*write)(void *ctx, void *buf, int len),
- \c void *wctx);
+ \c void (*write)(void *ctx, const void *buf, int len), void *wctx);
Calling this function causes the mid-end to convert its entire
internal state into a long ASCII text string, and to pass that
\H{midend-deserialise} \cw{midend_deserialise()}
- \c char *midend_deserialise(midend *me,
- \c int (*read)(void *ctx, void *buf, int len),
- \c void *rctx);
+ \c const char *midend_deserialise(midend *me,
+ \c int (*read)(void *ctx, void *buf, int len), void *rctx);
This function is the counterpart to \cw{midend_serialise()}. It
calls the supplied \cw{read} function repeatedly to read a quantity
\H{identify-game} \cw{identify_game()}
- \c char *identify_game(char **name,
- \c int (*read)(void *ctx, void *buf, int len),
- \c void *rctx);
+ \c const char *identify_game(char **name,
+ \c int (*read)(void *ctx, void *buf, int len), void *rctx);
This function examines a serialised midend stream, of the same kind
used by \cw{midend_serialise()} and \cw{midend_deserialise()}, and
\H{frontend-fatal} \cw{fatal()}
- \c void fatal(char *fmt, ...);
+ \c void fatal(const char *fmt, ...);
This is called by some utility functions if they encounter a
genuinely fatal error such as running out of memory. It is a
\c void free_cfg(config_item *cfg);
- This function correctly frees an array of \c{config_item}s,
- including walking the array until it gets to the end and freeing
- precisely those \c{sval} fields which are expected to be dynamically
- allocated.
+ This function correctly frees an array of \c{config_item}s, including
+ walking the array until it gets to the end and freeing any subsidiary
+ data items in each \c{u} sub-union which are expected to be
+ dynamically allocated.
(See \k{backend-configure} for details of the \c{config_item}
structure.)
to animate undo properly seems to be a common error.
Other than that, just use your common sense.
+
+\versionid Simon Tatham's Portable Puzzle Collection, version 20161228.7cae89f
* gtk.c: GTK front end for my puzzle collection.
*/
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 1 /* for PATH_MAX */
+#endif
+
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
+#include <limits.h>
+#include <unistd.h>
+#include <locale.h>
#include <sys/time.h>
#include <sys/resource.h>
#ifdef DEBUGGING
static FILE *debug_fp = NULL;
- void dputs(char *buf)
+ void dputs(const char *buf)
{
if (!debug_fp) {
debug_fp = fopen("debug.log", "w");
}
}
- void debug_printf(char *fmt, ...)
+ void debug_printf(const char *fmt, ...)
{
char buf[4096];
va_list ap;
* Error reporting functions used elsewhere.
*/
- void fatal(char *fmt, ...)
+ void fatal(const char *fmt, ...)
{
va_list ap;
int drawing_area_shrink_pending;
int menubar_is_local;
#endif
+ #if GTK_CHECK_VERSION(3,0,0)
+ /*
+ * This is used to get round an annoying lack of GTK notification
+ * message. If we request a window resize with
+ * gtk_window_resize(), we normally get back a "configure" event
+ * on the window and on its drawing area, and we respond to the
+ * latter by doing an appropriate resize of the puzzle. If the
+ * window is maximised, so that gtk_window_resize() _doesn't_
+ * change its size, then that configure event never shows up. But
+ * if we requested the resize in response to a change of puzzle
+ * parameters (say, the user selected a differently-sized preset
+ * from the menu), then we would still like to be _notified_ that
+ * the window size was staying the same, so that we can respond by
+ * choosing an appropriate tile size for the new puzzle preset in
+ * the existing window size.
+ *
+ * Fortunately, in GTK 3, we may not get a "configure" event on
+ * the drawing area in this situation, but we still get a
+ * "size_allocate" event on the whole window (which, in other
+ * situations when we _do_ get a "configure" on the area, turns up
+ * second). So we treat _that_ event as indicating that if the
+ * "configure" event hasn't already shown up then it's not going
+ * to arrive.
+ *
+ * This flag is where we bookkeep this system. On
+ * gtk_window_resize we set this flag to true; the area's
+ * configure handler sets it back to false; then if that doesn't
+ * happen, the window's size_allocate handler does a fallback
+ * puzzle resize when it sees this flag still set to true.
+ */
+ int awaiting_resize_ack;
+ #endif
};
struct blitter {
#endif
}
- void gtk_status_bar(void *handle, char *text)
+ void gtk_status_bar(void *handle, const char *text)
{
frontend *fe = (frontend *)handle;
}
void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
- int align, int colour, char *text)
+ int align, int colour, const char *text)
{
frontend *fe = (frontend *)handle;
int i;
return TRUE;
}
- static gint configure_area(GtkWidget *widget,
- GdkEventConfigure *event, gpointer data)
+ static void resize_puzzle_to_area(frontend *fe, int x, int y)
{
- frontend *fe = (frontend *)data;
- int x, y;
int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph;
- x = event->width;
- y = event->height;
fe->w = x;
fe->h = y;
midend_size(fe->me, &x, &y, TRUE);
}
midend_force_redraw(fe->me);
+ }
+ static gint configure_area(GtkWidget *widget,
+ GdkEventConfigure *event, gpointer data)
+ {
+ frontend *fe = (frontend *)data;
+ resize_puzzle_to_area(fe, event->width, event->height);
+ fe->awaiting_resize_ack = FALSE;
return TRUE;
}
+ #if GTK_CHECK_VERSION(3,0,0)
+ static void window_size_alloc(GtkWidget *widget, GtkAllocation *allocation,
+ gpointer data)
+ {
+ frontend *fe = (frontend *)data;
+ if (fe->awaiting_resize_ack) {
+ GtkAllocation a;
+ gtk_widget_get_allocation(fe->area, &a);
+ resize_puzzle_to_area(fe, a.width, a.height);
+ fe->awaiting_resize_ack = FALSE;
+ }
+ }
+ #endif
+
static gint timer_func(gpointer data)
{
frontend *fe = (frontend *)data;
}
#if GTK_CHECK_VERSION(3,0,0)
- int message_box(GtkWidget *parent, char *title, char *msg, int centre,
- int type)
+ int message_box(GtkWidget *parent, const char *title, const char *msg,
+ int centre, int type)
{
GtkWidget *window;
gint ret;
}
#endif /* GTK_CHECK_VERSION(3,0,0) */
- void error_box(GtkWidget *parent, char *msg)
+ void error_box(GtkWidget *parent, const char *msg)
{
message_box(parent, "Error", msg, FALSE, MB_OK);
}
static void config_ok_button_clicked(GtkButton *button, gpointer data)
{
frontend *fe = (frontend *)data;
- char *err;
+ const char *err;
err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
{
config_item *i = (config_item *)data;
- sfree(i->sval);
- i->sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed)));
+ assert(i->type == C_STRING);
+ sfree(i->u.string.sval);
+ i->u.string.sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed)));
}
static void button_toggled(GtkToggleButton *tb, gpointer data)
{
config_item *i = (config_item *)data;
- i->ival = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb));
+ assert(i->type == C_BOOLEAN);
+ i->u.boolean.bval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb));
}
static void droplist_sel(GtkComboBox *combo, gpointer data)
{
config_item *i = (config_item *)data;
- i->ival = gtk_combo_box_get_active(combo);
+ assert(i->type == C_CHOICES);
+ i->u.choices.selected = gtk_combo_box_get_active(combo);
}
static int get_config(frontend *fe, int which)
GTK_EXPAND | GTK_SHRINK | GTK_FILL,
3, 3);
#endif
- gtk_entry_set_text(GTK_ENTRY(w), i->sval);
+ gtk_entry_set_text(GTK_ENTRY(w), i->u.string.sval);
g_signal_connect(G_OBJECT(w), "changed",
G_CALLBACK(editbox_changed), i);
g_signal_connect(G_OBJECT(w), "key_press_event",
GTK_EXPAND | GTK_SHRINK | GTK_FILL,
3, 3);
#endif
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i->ival);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
+ i->u.boolean.bval);
gtk_widget_show(w);
break;
{
int c;
- char *p, *q, *name;
+ const char *p, *q;
+ char *name;
GtkListStore *model;
GtkCellRenderer *cr;
GtkTreeIter iter;
model = gtk_list_store_new(1, G_TYPE_STRING);
- c = *i->sval;
- p = i->sval+1;
+ c = *i->u.choices.choicenames;
+ p = i->u.choices.choicenames+1;
while (*p) {
q = p;
w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
- gtk_combo_box_set_active(GTK_COMBO_BOX(w), i->ival);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w),
+ i->u.choices.selected);
cr = gtk_cell_renderer_text_new();
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE);
struct preset_menu_entry *entry =
(struct preset_menu_entry *)g_object_get_data(
G_OBJECT(gs->data), "user-data");
-
- if (entry && entry->id != n)
+ if (!entry || entry->id != n)
gtk_check_menu_item_set_active(
GTK_CHECK_MENU_ITEM(gs->data), FALSE);
else
}
if (found)
gtk_check_menu_item_set_active(
- GTK_CHECK_MENU_ITEM(found->data), FALSE);
+ GTK_CHECK_MENU_ITEM(found->data), TRUE);
}
fe->preset_threaded = FALSE;
#if GTK_CHECK_VERSION(3,0,0)
gtk_window_resize(GTK_WINDOW(fe->window), x, y + window_extra_height(fe));
+ fe->awaiting_resize_ack = TRUE;
#else
fe->drawing_area_shrink_pending = FALSE;
gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
fe->filesel_name = dupstr(name);
}
- static char *file_selector(frontend *fe, char *title, int save)
+ static char *file_selector(frontend *fe, const char *title, int save)
{
GtkWidget *filesel =
gtk_file_selection_new(title);
#else
- static char *file_selector(frontend *fe, char *title, int save)
+ static char *file_selector(frontend *fe, const char *title, int save)
{
char *filesel_name = NULL;
int error;
};
- static void savefile_write(void *wctx, void *buf, int len)
+ static void savefile_write(void *wctx, const void *buf, int len)
{
struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)wctx;
if (fwrite(buf, 1, len, ctx->fp) < len)
static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
{
frontend *fe = (frontend *)data;
- char *name, *err;
+ char *name;
+ const char *err;
name = file_selector(fe, "Enter name of saved game file to load", FALSE);
static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
{
frontend *fe = (frontend *)data;
- char *msg;
+ const char *msg;
msg = midend_solve(fe->me);
midend_redraw(fe->me);
}
+#ifndef HELP_BROWSER_PATH
+#define HELP_BROWSER_PATH "xdg-open:sensible-browser"
+#endif
+
+static void show_help(frontend *fe, const char *topic)
+{
+ const char *list = HELP_BROWSER_PATH;
+ char path[PATH_MAX + 1];
+ struct {
+ const char *s;
+ int len;
+ } lang[3];
+ int i;
+
+ /*
+ * Search for help file, trying:
+ * 1. Version for this locale, ignoring encoding (HTML browsers
+ * must handle multiple encodings)
+ * 2. Version for this locale, ignoring encoding and country
+ * 3. English version
+ */
+ lang[0].s = setlocale(LC_MESSAGES, NULL);
+ lang[0].len = strcspn(lang[0].s, ".@");
+ lang[1].s = lang[0].s;
+ lang[1].len = strcspn(lang[1].s, "_");
+ if (lang[1].len > lang[0].len)
+ lang[1].len = lang[0].len;
+ lang[2].s = "en";
+ lang[2].len = 2;
+ for (i = 0; i < lenof(lang); i++) {
+ sprintf(path, "%s/sgt-puzzles/help/%.*s/%s.html",
+ SHAREDIR, lang[i].len, lang[i].s, topic);
+ if (access(path, R_OK) == 0)
+ break;
+ }
+ if (i == lenof(lang)) {
+ error_box(fe->window, "Help file is not installed");
+ return;
+ }
+
+ for (;;) {
+ size_t len;
+ char buf[PATH_MAX + 1];
+ const char *command;
+ const char *argv[3];
+
+ len = strcspn(list, ":");
+ if (len <= PATH_MAX) {
+ memcpy(buf, list, len);
+ buf[len] = 0;
+ if (buf[0] == '$')
+ command = getenv(buf + 1);
+ else
+ command = buf;
+ if (command) {
+ argv[0] = command;
+ argv[1] = path;
+ argv[2] = NULL;
+ if (g_spawn_async(NULL, (char **)argv, NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL, NULL, NULL, NULL))
+ return;
+ }
+ }
+
+ if (!list[len])
+ break;
+ list += len + 1;
+ }
+
+ error_box(fe->window, "Failed to start a help browser");
+}
+
+static void menu_help_contents_event(GtkMenuItem *menuitem, gpointer data)
+{
+ show_help((frontend *)data, "index");
+}
+
+static void menu_help_specific_event(GtkMenuItem *menuitem, gpointer data)
+{
+ show_help((frontend *)data, thegame.htmlhelp_topic);
+}
+
static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
{
frontend *fe = (frontend *)data;
}
static GtkWidget *add_menu_ui_item(
- frontend *fe, GtkContainer *cont, char *text, int action,
+ frontend *fe, GtkContainer *cont, const char *text, int action,
int accel_key, int accel_keyqual)
{
GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
fe->me = midend_new(fe, &thegame, >k_drawing, fe);
if (arg) {
- char *err;
+ const char *err;
FILE *fp;
errbuf[0] = '\0';
}
#endif
+ #if GTK_CHECK_VERSION(3,0,0)
+ fe->awaiting_resize_ack = FALSE;
+ #endif
+
fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name);
menu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ menuitem = gtk_menu_item_new_with_label("Contents");
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate",
+ G_CALLBACK(menu_help_contents_event), fe);
+ gtk_widget_show(menuitem);
+
+ if (thegame.htmlhelp_topic) {
+ char *item;
+ assert(thegame.name);
+ item = snewn(9+strlen(thegame.name), char); /*ick*/
+ sprintf(item, "Help on %s", thegame.name);
+ menuitem = gtk_menu_item_new_with_label(item);
+ sfree(item);
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate",
+ G_CALLBACK(menu_help_specific_event), fe);
+ gtk_widget_show(menuitem);
+ }
+
menuitem = gtk_menu_item_new_with_label("About");
gtk_container_add(GTK_CONTAINER(menu), menuitem);
g_signal_connect(G_OBJECT(menuitem), "activate",
G_CALLBACK(configure_area), fe);
g_signal_connect(G_OBJECT(fe->window), "configure_event",
G_CALLBACK(configure_window), fe);
+ #if GTK_CHECK_VERSION(3,0,0)
+ g_signal_connect(G_OBJECT(fe->window), "size_allocate",
+ G_CALLBACK(window_size_alloc), fe);
+ #endif
gtk_widget_add_events(GTK_WIDGET(fe->area),
GDK_BUTTON_PRESS_MASK |
int soln = FALSE, colour = FALSE;
float scale = 1.0F;
float redo_proportion = 0.0F;
- char *savefile = NULL, *savesuffix = NULL;
+ const char *savefile = NULL, *savesuffix = NULL;
char *arg = NULL;
int argtype = ARG_EITHER;
char *screenshot_file = NULL;
* generated descriptive game IDs.)
*/
while (ngenerate == 0 || i < n) {
- char *pstr, *err, *seed;
+ char *pstr, *seed;
+ const char *err;
struct rusage before, after;
if (ngenerate == 0) {
* re-entering the same game id, and then try to solve
* it.
*/
- char *game_id, *err;
+ char *game_id;
game_id = midend_get_game_id(me);
err = midend_game_id(me, game_id);
sprintf(realname, "%s%d%s", savefile, i, savesuffix);
if (soln) {
- char *err = midend_solve(me);
+ const char *err = midend_solve(me);
if (err) {
fprintf(stderr, "%s: unable to show solution: %s\n",
realname, err);
ret[0].name = "Width";
ret[0].type = C_STRING;
sprintf(buf, "%d", params->w);
- ret[0].sval = dupstr(buf);
- ret[0].ival = 0;
+ ret[0].u.string.sval = dupstr(buf);
ret[1].name = "Height";
ret[1].type = C_STRING;
sprintf(buf, "%d", params->h);
- ret[1].sval = dupstr(buf);
- ret[1].ival = 0;
+ ret[1].u.string.sval = dupstr(buf);
ret[2].name = "Difficulty";
ret[2].type = C_CHOICES;
- ret[2].sval = DIFFCONFIG;
- ret[2].ival = params->difficulty;
+ ret[2].u.choices.choicenames = DIFFCONFIG;
+ ret[2].u.choices.selected = params->difficulty;
ret[3].name = "Allow unsoluble";
ret[3].type = C_BOOLEAN;
- ret[3].sval = NULL;
- ret[3].ival = params->nosolve;
+ ret[3].u.boolean.bval = params->nosolve;
ret[4].name = NULL;
ret[4].type = C_END;
- ret[4].sval = NULL;
- ret[4].ival = 0;
return ret;
}
{
game_params *ret = snew(game_params);
- ret->w = atoi(cfg[0].sval);
- ret->h = atoi(cfg[1].sval);
- ret->difficulty = cfg[2].ival;
- ret->nosolve = cfg[3].ival;
+ ret->w = atoi(cfg[0].u.string.sval);
+ ret->h = atoi(cfg[1].u.string.sval);
+ ret->difficulty = cfg[2].u.choices.selected;
+ ret->nosolve = cfg[3].u.boolean.bval;
return ret;
}
- static char *validate_params(const game_params *params, int full)
+ static const char *validate_params(const game_params *params, int full)
{
if (params->w < 5) return "Width must be at least five";
if (params->h < 5) return "Height must be at least five";
if (params->difficulty < 0 || params->difficulty >= DIFFCOUNT)
return "Unknown difficulty level";
+ if (params->difficulty >= DIFF_TRICKY && params->w + params->h < 11)
+ return "Width or height must be at least six for Tricky";
return NULL;
}
return desc;
}
- static char *validate_desc(const game_params *params, const char *desc)
+ static const char *validate_desc(const game_params *params, const char *desc)
{
int i, sizesofar;
const int totalsize = params->w * params->h;
}
static char *solve_game(const game_state *state, const game_state *currstate,
- const char *aux, char **error)
+ const char *aux, const char **error)
{
game_state *solved = dup_game(state);
int i, ret, sz = state->shared->sz;
char ch = primary ? 'F' : 'M', *other;
- if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return "";
+ if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return UI_UPDATE;
/* disallow laying a mark over a line, or vice versa. */
other = primary ? state->marks : state->lines;
- if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return "";
+ if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return UI_UPDATE;
sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2);
return dupstr(buf);
ui->dragcoords[0] = gy * w + gx;
ui->ndragcoords = 0; /* will be 1 once drag is confirmed */
- return "";
+ return UI_UPDATE;
}
if (button == LEFT_DRAG && ui->ndragcoords >= 0) {
update_ui_drag(state, ui, gx, gy);
- return "";
+ return UI_UPDATE;
}
if (IS_MOUSE_RELEASE(button)) release = TRUE;
if (ui->ndragcoords >= 0)
update_ui_drag(state, ui, ui->curx, ui->cury);
}
- return "";
+ return UI_UPDATE;
}
if (IS_CURSOR_SELECT(button)) {
if (!ui->cursor_active) {
ui->cursor_active = TRUE;
- return "";
+ return UI_UPDATE;
} else if (button == CURSOR_SELECT) {
if (ui->ndragcoords == -1) {
ui->ndragcoords = 0;
ui->dragcoords[0] = ui->cury * w + ui->curx;
ui->clickx = CENTERED_COORD(ui->curx);
ui->clicky = CENTERED_COORD(ui->cury);
- return "";
+ return UI_UPDATE;
} else release = TRUE;
} else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) {
ui->ndragcoords = -1;
- return "";
+ return UI_UPDATE;
}
}
if (button == 27 || button == '\b') {
ui->ndragcoords = -1;
- return "";
+ return UI_UPDATE;
}
if (release) {
ui->ndragcoords = -1;
- return buf ? buf : "";
+ return buf ? buf : UI_UPDATE;
} else if (ui->ndragcoords == 0) {
/* Click (or tiny drag). Work out which edge we were
* closest to. */
cx = CENTERED_COORD(gx);
cy = CENTERED_COORD(gy);
- if (!INGRID(state, gx, gy)) return "";
+ if (!INGRID(state, gx, gy)) return UI_UPDATE;
if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) {
/* TODO closer to centre of grid: process as a cell click not an edge click. */
- return "";
+ return UI_UPDATE;
} else {
int direction;
if (abs(x-cx) < abs(y-cy)) {
game_params *p = NULL;
random_state *rs = NULL;
time_t seed = time(NULL);
- char *id = NULL, *err;
+ char *id = NULL;
+ const char *err;
setvbuf(stdout, NULL, _IONBF, 0);
ret[0].name = "Width";
ret[0].type = C_STRING;
sprintf(buf, "%d", params->w);
- ret[0].sval = dupstr(buf);
- ret[0].ival = 0;
+ ret[0].u.string.sval = dupstr(buf);
ret[1].name = "Height";
ret[1].type = C_STRING;
sprintf(buf, "%d", params->h);
- ret[1].sval = dupstr(buf);
- ret[1].ival = 0;
+ ret[1].u.string.sval = dupstr(buf);
ret[2].name = "Difficulty";
ret[2].type = C_CHOICES;
- ret[2].sval = DIFFCONFIG;
- ret[2].ival = params->diff;
+ ret[2].u.choices.choicenames = DIFFCONFIG;
+ ret[2].u.choices.selected = params->diff;
ret[3].name = NULL;
ret[3].type = C_END;
- ret[3].sval = NULL;
- ret[3].ival = 0;
return ret;
}
{
game_params *ret = snew(game_params);
- ret->w = atoi(cfg[0].sval);
- ret->h = atoi(cfg[1].sval);
- ret->diff = cfg[2].ival;
+ ret->w = atoi(cfg[0].u.string.sval);
+ ret->h = atoi(cfg[1].u.string.sval);
+ ret->diff = cfg[2].u.choices.selected;
return ret;
}
- static char *validate_params(const game_params *params, int full)
+ static const char *validate_params(const game_params *params, int full)
{
/*
* (At least at the time of writing this comment) The grid
}
static int vbitmap_clear(int w, int h, struct solver_scratch *sc,
- int x, int y, int vbits, char *reason, ...)
+ int x, int y, int vbits, const char *reason, ...)
{
int done_something = FALSE;
int vbit;
int fs, bs, v;
int c1, c2;
#ifdef SOLVER_DIAGNOSTICS
- char *reason = "<internal error>";
+ const char *reason = "<internal error>";
#endif
if (soln[y*w+x])
return desc;
}
- static char *validate_desc(const game_params *params, const char *desc)
+ static const char *validate_desc(const game_params *params, const char *desc)
{
int w = params->w, h = params->h, W = w+1, H = h+1;
int area = W*H;
}
static char *solve_game(const game_state *state, const game_state *currstate,
- const char *aux, char **error)
+ const char *aux, const char **error)
{
int w = state->p.w, h = state->p.h;
signed char *soln;
} else if (IS_CURSOR_SELECT(button)) {
if (!ui->cur_visible) {
ui->cur_visible = 1;
- return "";
+ return UI_UPDATE;
}
x = ui->cur_x;
y = ui->cur_y;
} else if (IS_CURSOR_MOVE(button)) {
move_cursor(button, &ui->cur_x, &ui->cur_y, w, h, 0);
ui->cur_visible = 1;
- return "";
+ return UI_UPDATE;
} else if (button == '\\' || button == '\b' || button == '/') {
int x = ui->cur_x, y = ui->cur_y;
if (button == ("\\" "\b" "/")[state->soln[y*w + x] + 1]) return NULL;
float *ret = snewn(3 * NCOLOURS, float);
/* CURSOR colour is a background highlight. */
- game_mkhighlight(fe, ret, COL_BACKGROUND, COL_CURSOR, -1);
-
- ret[COL_FILLEDSQUARE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0];
- ret[COL_FILLEDSQUARE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1];
- ret[COL_FILLEDSQUARE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
+ game_mkhighlight(fe, ret, COL_BACKGROUND, COL_CURSOR, COL_FILLEDSQUARE);
ret[COL_GRID * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.7F;
ret[COL_GRID * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.7F;
{
game_params *p;
game_state *s;
- char *id = NULL, *desc, *err;
+ char *id = NULL, *desc;
+ const char *err;
int grade = FALSE;
int ret, diff, really_verbose = FALSE;
struct solver_scratch *sc;
ret[0].name = "Grid size";
ret[0].type = C_STRING;
sprintf(buf, "%d", params->w);
- ret[0].sval = dupstr(buf);
- ret[0].ival = 0;
+ ret[0].u.string.sval = dupstr(buf);
ret[1].name = "Difficulty";
ret[1].type = C_CHOICES;
- ret[1].sval = DIFFCONFIG;
- ret[1].ival = params->diff;
+ ret[1].u.choices.choicenames = DIFFCONFIG;
+ ret[1].u.choices.selected = params->diff;
ret[2].name = NULL;
ret[2].type = C_END;
- ret[2].sval = NULL;
- ret[2].ival = 0;
return ret;
}
{
game_params *ret = snew(game_params);
- ret->w = atoi(cfg[0].sval);
- ret->diff = cfg[1].ival;
+ ret->w = atoi(cfg[0].u.string.sval);
+ ret->diff = cfg[1].u.choices.selected;
return ret;
}
- static char *validate_params(const game_params *params, int full)
+ static const char *validate_params(const game_params *params, int full)
{
if (params->w < 3 || params->w > 9)
return "Grid size must be between 3 and 9";
return ret;
#ifdef STANDALONE_SOLVER
- if (solver_show_working)
- sprintf(prefix, "%*slower bounds for clue %s %d:\n",
- solver_recurse_depth*4, "",
- cluepos[c/w], c%w+1);
- else
- prefix[0] = '\0'; /* placate optimiser */
+ if (solver_show_working)
+ sprintf(prefix, "%*slower bounds for clue %s %d:\n",
+ solver_recurse_depth*4, "",
+ cluepos[c/w], c%w+1);
+ else
+ prefix[0] = '\0'; /* placate optimiser */
#endif
i = 0;
* Gameplay.
*/
- static char *validate_desc(const game_params *params, const char *desc)
+ static const char *validate_desc(const game_params *params, const char *desc)
{
int w = params->w, a = w*w;
const char *p = desc;
}
static char *solve_game(const game_state *state, const game_state *currstate,
- const char *aux, char **error)
+ const char *aux, const char **error)
{
int w = state->par.w, a = w*w;
int i, ret;
ui->hpencil = 0;
}
ui->hcursor = 0;
- return ""; /* UI activity occurred */
+ return UI_UPDATE;
}
if (button == RIGHT_BUTTON) {
/*
ui->hshow = 0;
}
ui->hcursor = 0;
- return ""; /* UI activity occurred */
+ return UI_UPDATE;
}
} else if (button == LEFT_BUTTON) {
if (is_clue(state, tx, ty)) {
}
move_cursor(button, &ui->hx, &ui->hy, w, w, 0);
ui->hshow = ui->hcursor = 1;
- return "";
+ return UI_UPDATE;
}
if (ui->hshow &&
(button == CURSOR_SELECT)) {
ui->hpencil = 1 - ui->hpencil;
ui->hcursor = 1;
- return "";
+ return UI_UPDATE;
}
if (ui->hshow &&
{
game_params *p;
game_state *s;
- char *id = NULL, *desc, *err;
+ char *id = NULL, *desc;
+ const char *err;
int grade = FALSE;
int ret, diff, really_show_working = FALSE;