diff options
author | Franklin Wei <git@fwei.tk> | 2017-09-30 17:47:13 -0400 |
---|---|---|
committer | Franklin Wei <git@fwei.tk> | 2017-09-30 20:06:50 -0400 |
commit | b9386109e8f0cf346037b72464577fe19bba2d43 (patch) | |
tree | f0c15f6c80043376ec94d23cb1b681324eb3df3d | |
parent | ea679de8371e4e74fe4e78fb8df8e5df19efffdc (diff) | |
download | rockbox-b938610.tar.gz rockbox-b938610.zip |
puzzles: resync with upstream
This brings puzzles to upstream commit 84d3fd2.
Change-Id: I808a197f868032d771fc101a15666c5ec4b9f94b
26 files changed, 1031 insertions, 577 deletions
diff --git a/apps/plugins/puzzles/src/Buildscr b/apps/plugins/puzzles/src/Buildscr index c72084477b..b8a585b43e 100644 --- a/apps/plugins/puzzles/src/Buildscr +++ b/apps/plugins/puzzles/src/Buildscr @@ -54,24 +54,30 @@ in puzzles do make -f Makefile.doc clean in puzzles do make -f Makefile.doc # build help files for installer in puzzles do mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss -delegate windows - # FIXME: Cygwin alternative? - in puzzles with visualstudio do/win nmake -f Makefile.vc clean - in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version) +ifneq "$(VISUAL_STUDIO)" "yes" then + in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl clean + in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl VER=-DVER=$(Version) # Code-sign the binaries, if the local bob config provides a script # to do so. We assume here that the script accepts an -i option to # provide a 'more info' URL, and an optional -n option to provide a # program name, and that it can take multiple .exe filename # arguments and sign them all in place. - ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe + ifneq "$(cross_winsigncode)" "" in puzzles do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe # Build installers. - in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj - in puzzles with innosetup do/win iscc puzzles.iss - ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ -n "Simon Tatham's Portable Puzzle Collection Installer" puzzles.msi Output/installer.exe - return puzzles/*.exe - return puzzles/Output/installer.exe - return puzzles/puzzles.msi -enddelegate + in puzzles with wixonlinux do candle -arch x64 puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj + ifneq "$(cross_winsigncode)" "" in puzzles do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ -n "Simon Tatham's Portable Puzzle Collection Installer" puzzles.msi +else + delegate windows + in puzzles with visualstudio do/win nmake -f Makefile.vc clean + in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version) + ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe + # Build installers. + in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj + in puzzles with innosetup do/win iscc puzzles.iss + return puzzles/*.exe + return puzzles/puzzles.msi + enddelegate +endif in puzzles do chmod +x *.exe # Build the Pocket PC binaries and CAB. @@ -152,13 +158,22 @@ delegate emscripten return puzzles/js/*.js enddelegate +# Build a set of wrapping HTML pages for easy testing of the +# Javascript puzzles. These aren't quite the same as the versions that +# will go on my live website, because those ones will substitute in a +# different footer, and not have to link to the .js files with the +# ../js/ prefix. But these ones should be good enough to just open +# using a file:// URL in a browser after running a build, and make +# sure the main functionality works. +in puzzles do mkdir jstest +in puzzles/jstest do ../html/jspage.pl --jspath=../js/ /dev/null ../html/*.html + # Set up .htaccess containing a redirect for the archive filename. in puzzles do echo "AddType application/octet-stream .chm" > .htaccess in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess in puzzles do echo "AddType application/octet-stream .cnt" >> .htaccess in . do set -- puzzles*.tar.gz; echo RedirectMatch temp '(.*/)'puzzles.tar.gz '$$1'"$$1" >> puzzles/.htaccess in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.msi '$$1'puzzles-$(Version)-installer.msi >> .htaccess -in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.exe '$$1'puzzles-$(Version)-installer.exe >> .htaccess # Phew, we're done. Deliver everything! deliver puzzles/icons/*-web.png $@ @@ -172,9 +187,9 @@ deliver puzzles/puzzles.hlp $@ deliver puzzles/puzzles.cnt $@ deliver puzzles/puzzles.zip $@ deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi -deliver puzzles/Output/installer.exe puzzles-$(Version)-installer.exe deliver puzzles/*.jar java/$@ deliver puzzles/js/*.js js/$@ +deliver puzzles/jstest/*.html jstest/$@ deliver puzzles/html/*.html html/$@ deliver puzzles/html/*.pl html/$@ deliver puzzles/wwwspans.html $@ diff --git a/apps/plugins/puzzles/src/PuzzleApplet.java b/apps/plugins/puzzles/src/PuzzleApplet.java index 512aede580..8455734dd1 100644 --- a/apps/plugins/puzzles/src/PuzzleApplet.java +++ b/apps/plugins/puzzles/src/PuzzleApplet.java @@ -61,19 +61,19 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB { JMenuBar menubar = new JMenuBar(); JMenu jm; menubar.add(jm = new JMenu("Game")); - addMenuItemWithKey(jm, "New", 'n'); + addMenuItemCallback(jm, "New", "jcallback_newgame_event"); addMenuItemCallback(jm, "Restart", "jcallback_restart_event"); addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC); addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED); jm.addSeparator(); - addMenuItemWithKey(jm, "Undo", 'u'); - addMenuItemWithKey(jm, "Redo", 'r'); + addMenuItemCallback(jm, "Undo", "jcallback_undo_event"); + addMenuItemCallback(jm, "Redo", "jcallback_redo_event"); jm.addSeparator(); solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event"); solveCommand.setEnabled(false); if (mainWindow != null) { jm.addSeparator(); - addMenuItemWithKey(jm, "Exit", 'q'); + addMenuItemCallback(jm, "Exit", "jcallback_quit_event"); } menubar.add(typeMenu = new JMenu("Type")); typeMenu.setVisible(false); @@ -126,7 +126,12 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB { } } public void keyTyped(KeyEvent e) { - runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()}); + int key = e.getKeyChar(); + if (key == 26 && e.isShiftDown() && e.isControlDown()) { + runtimeCall("jcallback_redo_event", new int[0]); + return; + } + runtimeCall("jcallback_key_event", new int[] {0, 0, key}); } }); pp.addMouseListener(new MouseAdapter() { @@ -217,10 +222,6 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB { runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()}); } - private void addMenuItemWithKey(JMenu jm, String name, int key) { - addMenuItemCallback(jm, name, "jcallback_menu_key_event", key); - } - private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) { return addMenuItemCallback(jm, name, callback, new int[] {arg}, false); } diff --git a/apps/plugins/puzzles/src/Recipe b/apps/plugins/puzzles/src/Recipe index ba8317f51a..3b57ef5e54 100644 --- a/apps/plugins/puzzles/src/Recipe +++ b/apps/plugins/puzzles/src/Recipe @@ -17,6 +17,7 @@ !makefile gnustep Makefile.gnustep !makefile nestedvm Makefile.nestedvm !makefile emcc Makefile.emcc +!makefile clangcl Makefile.clangcl !srcdir icons/ diff --git a/apps/plugins/puzzles/src/chm.but b/apps/plugins/puzzles/src/chm.but deleted file mode 100644 index e0237044e4..0000000000 --- a/apps/plugins/puzzles/src/chm.but +++ /dev/null @@ -1,21 +0,0 @@ -\# File containing the magic HTML configuration directives to create -\# an MS HTML Help project. We put this on the end of the Puzzles -\# docs build command line to build the HHP and friends. - -\cfg{html-leaf-level}{infinite} -\cfg{html-leaf-contains-contents}{false} -\cfg{html-suppress-navlinks}{true} -\cfg{html-suppress-address}{true} - -\cfg{html-contents-filename}{index.html} -\cfg{html-template-filename}{%k.html} -\cfg{html-template-fragment}{%k} - -\cfg{html-mshtmlhelp-chm}{puzzles.chm} -\cfg{html-mshtmlhelp-project}{puzzles.hhp} -\cfg{html-mshtmlhelp-contents}{puzzles.hhc} -\cfg{html-mshtmlhelp-index}{puzzles.hhk} - -\cfg{html-body-end}{} - -\cfg{html-head-end}{<link rel="stylesheet" type="text/css" href="chm.css">} diff --git a/apps/plugins/puzzles/src/devel.but b/apps/plugins/puzzles/src/devel.but index a38fdda5d0..25a6c62dfa 100644 --- a/apps/plugins/puzzles/src/devel.but +++ b/apps/plugins/puzzles/src/devel.but @@ -1928,6 +1928,9 @@ Indeed, even horizontal or vertical lines may be anti-aliased. This function may be used for both drawing and printing. +If the specified thickness is less than 1.0, 1.0 is used. +This ensures that thin lines are visible even at small scales. + \S{drawing-draw-text} \cw{draw_text()} \c void draw_text(drawing *dr, int x, int y, int fonttype, diff --git a/apps/plugins/puzzles/src/drawing.c b/apps/plugins/puzzles/src/drawing.c index 7f4a6cf674..a10a7f06d6 100644 --- a/apps/plugins/puzzles/src/drawing.c +++ b/apps/plugins/puzzles/src/drawing.c @@ -90,6 +90,8 @@ void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) void draw_thick_line(drawing *dr, float thickness, float x1, float y1, float x2, float y2, int colour) { + if (thickness < 1.0) + thickness = 1.0; if (dr->api->draw_thick_line) { dr->api->draw_thick_line(dr->handle, thickness, x1, y1, x2, y2, colour); diff --git a/apps/plugins/puzzles/src/emcc.c b/apps/plugins/puzzles/src/emcc.c index ca033cbd47..23ab333f5d 100644 --- a/apps/plugins/puzzles/src/emcc.c +++ b/apps/plugins/puzzles/src/emcc.c @@ -310,6 +310,8 @@ void key(int keycode, int charcode, const char *key, const char *chr, keyevent = MOD_NUM_KEYPAD | '7'; } else if (!strnullcmp(key, "PageUp") || keycode==33) { keyevent = MOD_NUM_KEYPAD | '9'; + } else if (shift && ctrl && (keycode & 0x1F) == 26) { + keyevent = UI_REDO; } else if (chr && chr[0] && !chr[1]) { keyevent = chr[0] & 0xFF; } else if (keycode >= 96 && keycode < 106) { @@ -323,10 +325,10 @@ void key(int keycode, int charcode, const char *key, const char *chr, } if (keyevent >= 0) { - if (shift && keyevent >= 0x100) + if (shift && (keyevent >= 0x100 && !IS_UI_FAKE_KEY(keyevent))) keyevent |= MOD_SHFT; - if (ctrl) { + if (ctrl && !IS_UI_FAKE_KEY(keyevent)) { if (keyevent >= 0x100) keyevent |= MOD_CTRL; else @@ -725,7 +727,7 @@ void command(int n) update_undo_redo(); break; case 5: /* New Game */ - midend_process_key(me, 0, 0, 'n'); + midend_process_key(me, 0, 0, UI_NEWGAME); update_undo_redo(); js_focus_canvas(); break; @@ -735,12 +737,12 @@ void command(int n) js_focus_canvas(); break; case 7: /* Undo */ - midend_process_key(me, 0, 0, 'u'); + midend_process_key(me, 0, 0, UI_UNDO); update_undo_redo(); js_focus_canvas(); break; case 8: /* Redo */ - midend_process_key(me, 0, 0, 'r'); + midend_process_key(me, 0, 0, UI_REDO); update_undo_redo(); js_focus_canvas(); break; @@ -757,6 +759,83 @@ void command(int n) } /* ---------------------------------------------------------------------- + * Called from JS to prepare a save-game file, and free one after it's + * been used. + */ + +struct savefile_write_ctx { + char *buffer; + size_t pos; +}; + +static void savefile_write(void *vctx, void *buf, int len) +{ + struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)vctx; + if (ctx->buffer) + memcpy(ctx->buffer + ctx->pos, buf, len); + ctx->pos += len; +} + +char *get_save_file(void) +{ + struct savefile_write_ctx ctx; + size_t size; + + /* First pass, to count up the size */ + ctx.buffer = NULL; + ctx.pos = 0; + midend_serialise(me, savefile_write, &ctx); + size = ctx.pos; + + /* Second pass, to actually write out the data */ + ctx.buffer = snewn(size, char); + ctx.pos = 0; + midend_serialise(me, savefile_write, &ctx); + assert(ctx.pos == size); + + return ctx.buffer; +} + +void free_save_file(char *buffer) +{ + sfree(buffer); +} + +struct savefile_read_ctx { + const char *buffer; + int len_remaining; +}; + +static int savefile_read(void *vctx, void *buf, int len) +{ + struct savefile_read_ctx *ctx = (struct savefile_read_ctx *)vctx; + if (ctx->len_remaining < len) + return FALSE; + memcpy(buf, ctx->buffer, len); + ctx->len_remaining -= len; + ctx->buffer += len; + return TRUE; +} + +void load_game(const char *buffer, int len) +{ + struct savefile_read_ctx ctx; + const char *err; + + ctx.buffer = buffer; + ctx.len_remaining = len; + err = midend_deserialise(me, savefile_read, &ctx); + + if (err) { + js_error_box(err); + } else { + select_appropriate_preset(); + resize(); + midend_redraw(me); + } +} + +/* ---------------------------------------------------------------------- * Setup function called at page load time. It's called main() because * that's the most convenient thing in Emscripten, but it's not main() * in the usual sense of bounding the program's entire execution. diff --git a/apps/plugins/puzzles/src/emcclib.js b/apps/plugins/puzzles/src/emcclib.js index cd8876e76d..907dc19995 100644 --- a/apps/plugins/puzzles/src/emcclib.js +++ b/apps/plugins/puzzles/src/emcclib.js @@ -108,7 +108,6 @@ mergeInto(LibraryManager.library, { item.appendChild(tick); item.appendChild(document.createTextNode(name)); var submenu = document.createElement("ul"); - submenu.className = "left"; item.appendChild(submenu); gametypesubmenus[menuid].appendChild(item); var toret = gametypesubmenus.length; @@ -575,38 +574,7 @@ mergeInto(LibraryManager.library, { * overlay on top of the rest of the puzzle web page. */ js_dialog_init: function(titletext) { - // Create an overlay on the page which darkens everything - // beneath it. - dlg_dimmer = document.createElement("div"); - dlg_dimmer.style.width = "100%"; - dlg_dimmer.style.height = "100%"; - dlg_dimmer.style.background = '#000000'; - dlg_dimmer.style.position = 'fixed'; - dlg_dimmer.style.opacity = 0.3; - dlg_dimmer.style.top = dlg_dimmer.style.left = 0; - dlg_dimmer.style["z-index"] = 99; - - // Now create a form which sits on top of that in turn. - dlg_form = document.createElement("form"); - dlg_form.style.width = (window.innerWidth * 2 / 3) + "px"; - dlg_form.style.opacity = 1; - dlg_form.style.background = '#ffffff'; - dlg_form.style.color = '#000000'; - dlg_form.style.position = 'absolute'; - dlg_form.style.border = "2px solid black"; - dlg_form.style.padding = "20px"; - dlg_form.style.top = (window.innerHeight / 10) + "px"; - dlg_form.style.left = (window.innerWidth / 6) + "px"; - dlg_form.style["z-index"] = 100; - - var title = document.createElement("p"); - title.style.marginTop = "0px"; - title.appendChild(document.createTextNode - (Pointer_stringify(titletext))); - dlg_form.appendChild(title); - - dlg_return_funcs = []; - dlg_next_id = 0; + dialog_init(Pointer_stringify(titletext)); }, /* @@ -701,29 +669,13 @@ mergeInto(LibraryManager.library, { * everything else on the page. */ js_dialog_launch: function() { - // Put in the OK and Cancel buttons at the bottom. - var button; - - button = document.createElement("input"); - button.type = "button"; - button.value = "OK"; - button.onclick = function(event) { + dialog_launch(function(event) { for (var i in dlg_return_funcs) dlg_return_funcs[i](); - command(3); - } - dlg_form.appendChild(button); - - button = document.createElement("input"); - button.type = "button"; - button.value = "Cancel"; - button.onclick = function(event) { - command(4); - } - dlg_form.appendChild(button); - - document.body.appendChild(dlg_dimmer); - document.body.appendChild(dlg_form); + command(3); // OK + }, function(event) { + command(4); // Cancel + }); }, /* @@ -733,10 +685,7 @@ mergeInto(LibraryManager.library, { * associated with it. */ js_dialog_cleanup: function() { - document.body.removeChild(dlg_dimmer); - document.body.removeChild(dlg_form); - dlg_dimmer = dlg_form = null; - onscreen_canvas.focus(); + dialog_cleanup(); }, /* diff --git a/apps/plugins/puzzles/src/emccpre.js b/apps/plugins/puzzles/src/emccpre.js index d715858883..5082555617 100644 --- a/apps/plugins/puzzles/src/emccpre.js +++ b/apps/plugins/puzzles/src/emccpre.js @@ -129,6 +129,72 @@ function disable_menu_item(item, disabledFlag) { item.className = ""; } +// Dialog-box functions called from both C and JS. +function dialog_init(titletext) { + // Create an overlay on the page which darkens everything + // beneath it. + dlg_dimmer = document.createElement("div"); + dlg_dimmer.style.width = "100%"; + dlg_dimmer.style.height = "100%"; + dlg_dimmer.style.background = '#000000'; + dlg_dimmer.style.position = 'fixed'; + dlg_dimmer.style.opacity = 0.3; + dlg_dimmer.style.top = dlg_dimmer.style.left = 0; + dlg_dimmer.style["z-index"] = 99; + + // Now create a form which sits on top of that in turn. + dlg_form = document.createElement("form"); + dlg_form.style.width = (window.innerWidth * 2 / 3) + "px"; + dlg_form.style.opacity = 1; + dlg_form.style.background = '#ffffff'; + dlg_form.style.color = '#000000'; + dlg_form.style.position = 'absolute'; + dlg_form.style.border = "2px solid black"; + dlg_form.style.padding = "20px"; + dlg_form.style.top = (window.innerHeight / 10) + "px"; + dlg_form.style.left = (window.innerWidth / 6) + "px"; + dlg_form.style["z-index"] = 100; + + var title = document.createElement("p"); + title.style.marginTop = "0px"; + title.appendChild(document.createTextNode(titletext)); + dlg_form.appendChild(title); + + dlg_return_funcs = []; + dlg_next_id = 0; +} + +function dialog_launch(ok_function, cancel_function) { + // Put in the OK and Cancel buttons at the bottom. + var button; + + if (ok_function) { + button = document.createElement("input"); + button.type = "button"; + button.value = "OK"; + button.onclick = ok_function; + dlg_form.appendChild(button); + } + + if (cancel_function) { + button = document.createElement("input"); + button.type = "button"; + button.value = "Cancel"; + button.onclick = cancel_function; + dlg_form.appendChild(button); + } + + document.body.appendChild(dlg_dimmer); + document.body.appendChild(dlg_form); +} + +function dialog_cleanup() { + document.body.removeChild(dlg_dimmer); + document.body.removeChild(dlg_form); + dlg_dimmer = dlg_form = null; + onscreen_canvas.focus(); +} + // Init function called from body.onload. function initPuzzle() { // Construct the off-screen canvas used for double buffering. @@ -230,6 +296,58 @@ function initPuzzle() { command(9); }; + // 'number' is used for C pointers + get_save_file = Module.cwrap('get_save_file', 'number', []); + free_save_file = Module.cwrap('free_save_file', 'void', ['number']); + load_game = Module.cwrap('load_game', 'void', ['string', 'number']); + + document.getElementById("save").onclick = function(event) { + if (dlg_dimmer === null) { + var savefile_ptr = get_save_file(); + var savefile_text = Pointer_stringify(savefile_ptr); + free_save_file(savefile_ptr); + dialog_init("Download saved-game file"); + dlg_form.appendChild(document.createTextNode( + "Click to download the ")); + var a = document.createElement("a"); + a.download = "puzzle.sav"; + a.href = "data:application/octet-stream," + + encodeURIComponent(savefile_text); + a.appendChild(document.createTextNode("saved-game file")); + dlg_form.appendChild(a); + dlg_form.appendChild(document.createTextNode(".")); + dlg_form.appendChild(document.createElement("br")); + dialog_launch(function(event) { + dialog_cleanup(); + }); + } + }; + + document.getElementById("load").onclick = function(event) { + if (dlg_dimmer === null) { + dialog_init("Upload saved-game file"); + var input = document.createElement("input"); + input.type = "file"; + input.multiple = false; + dlg_form.appendChild(input); + dlg_form.appendChild(document.createElement("br")); + dialog_launch(function(event) { + if (input.files.length == 1) { + var file = input.files.item(0); + var reader = new FileReader(); + reader.addEventListener("loadend", function() { + var string = reader.result; + load_game(string, string.length); + }); + reader.readAsBinaryString(file); + } + dialog_cleanup(); + }, function(event) { + dialog_cleanup(); + }); + } + }; + gametypelist = document.getElementById("gametype"); gametypesubmenus.push(gametypelist); diff --git a/apps/plugins/puzzles/src/emccx.json b/apps/plugins/puzzles/src/emccx.json index e03f7e25c7..bdab346d79 100644 --- a/apps/plugins/puzzles/src/emccx.json +++ b/apps/plugins/puzzles/src/emccx.json @@ -18,6 +18,10 @@ '_timer_callback', // Callback from button presses in the UI outside the canvas '_command', + // Game-saving and game-loading functions + '_get_save_file', + '_free_save_file', + '_load_game', // Callbacks to return values from dialog boxes '_dlg_return_sval', '_dlg_return_ival', diff --git a/apps/plugins/puzzles/src/gtk.c b/apps/plugins/puzzles/src/gtk.c index c5e3d1c997..c212522957 100644 --- a/apps/plugins/puzzles/src/gtk.c +++ b/apps/plugins/puzzles/src/gtk.c @@ -140,7 +140,7 @@ struct font { */ struct frontend { GtkWidget *window; - GtkAccelGroup *accelgroup; + GtkAccelGroup *dummy_accelgroup; GtkWidget *area; GtkWidget *statusbar; GtkWidget *menubar; @@ -1160,16 +1160,6 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) if (!backing_store_ok(fe)) return TRUE; -#if !GTK_CHECK_VERSION(2,0,0) - /* Gtk 1.2 passes a key event to this function even if it's also - * defined as an accelerator. - * Gtk 2 doesn't do this, and this function appears not to exist there. */ - if (fe->accelgroup && - gtk_accel_group_get_entry(fe->accelgroup, - event->keyval, event->state)) - return TRUE; -#endif - /* Handle mnemonics. */ if (gtk_window_activate_key(GTK_WINDOW(fe->window), event)) return TRUE; @@ -1216,6 +1206,8 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) event->keyval == GDK_KEY_Delete || event->keyval == GDK_KEY_KP_Delete) keyval = '\177'; + else if ((event->keyval == 'z' || event->keyval == 'Z') && shift && ctrl) + keyval = UI_REDO; else if (event->string[0] && !event->string[1]) keyval = (unsigned char)event->string[0]; else @@ -2348,32 +2340,34 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data) #endif } -static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, - char *text, int key) +static GtkWidget *add_menu_ui_item( + frontend *fe, GtkContainer *cont, char *text, int action, + int accel_key, int accel_keyqual) { GtkWidget *menuitem = gtk_menu_item_new_with_label(text); - int keyqual; gtk_container_add(cont, menuitem); - g_object_set_data(G_OBJECT(menuitem), "user-data", GINT_TO_POINTER(key)); + g_object_set_data(G_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(action)); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_key_event), fe); - switch (key & ~0x1F) { - case 0x00: - key += 0x60; - keyqual = GDK_CONTROL_MASK; - break; - case 0x40: - key += 0x20; - keyqual = GDK_SHIFT_MASK; - break; - default: - keyqual = 0; - break; + + if (accel_key) { + /* + * Display a keyboard accelerator alongside this menu item. + * Actually this won't be processed via the usual GTK + * accelerator system, because we add it to a dummy + * accelerator group which is never actually activated on the + * main window; this permits back ends to override special + * keys like 'n' and 'r' and 'u' in some UI states. So + * whatever keystroke we display here will still go to + * key_event and be handled in the normal way. + */ + gtk_widget_add_accelerator(menuitem, + "activate", fe->dummy_accelgroup, + accel_key, accel_keyqual, + GTK_ACCEL_VISIBLE | GTK_ACCEL_LOCKED); } - gtk_widget_add_accelerator(menuitem, - "activate", fe->accelgroup, - key, keyqual, - GTK_ACCEL_VISIBLE); + gtk_widget_show(menuitem); return menuitem; } @@ -2535,8 +2529,11 @@ static frontend *new_window(char *arg, int argtype, char **error) gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox)); gtk_widget_show(GTK_WIDGET(vbox)); - fe->accelgroup = gtk_accel_group_new(); - gtk_window_add_accel_group(GTK_WINDOW(fe->window), fe->accelgroup); + fe->dummy_accelgroup = gtk_accel_group_new(); + /* + * Intentionally _not_ added to the window via + * gtk_window_add_accel_group; see menu_key_event + */ hbox = GTK_BOX(gtk_hbox_new(FALSE, 0)); gtk_box_pack_start(vbox, GTK_WIDGET(hbox), FALSE, FALSE, 0); @@ -2553,7 +2550,7 @@ static frontend *new_window(char *arg, int argtype, char **error) menu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); - add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n'); + add_menu_ui_item(fe, GTK_CONTAINER(menu), "New", UI_NEWGAME, 'n', 0); menuitem = gtk_menu_item_new_with_label("Restart"); gtk_container_add(GTK_CONTAINER(menu), menuitem); @@ -2623,8 +2620,8 @@ static frontend *new_window(char *arg, int argtype, char **error) gtk_widget_show(menuitem); #ifndef STYLUS_BASED add_menu_separator(GTK_CONTAINER(menu)); - add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u'); - add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", 'r'); + add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0); + add_menu_ui_item(fe, GTK_CONTAINER(menu), "Redo", UI_REDO, 'r', 0); #endif if (thegame.can_format_as_text_ever) { add_menu_separator(GTK_CONTAINER(menu)); @@ -2646,7 +2643,7 @@ static frontend *new_window(char *arg, int argtype, char **error) gtk_widget_show(menuitem); } add_menu_separator(GTK_CONTAINER(menu)); - add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q'); + add_menu_ui_item(fe, GTK_CONTAINER(menu), "Exit", UI_QUIT, 'q', 0); menuitem = gtk_menu_item_new_with_mnemonic("_Help"); gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem); @@ -2664,7 +2661,7 @@ static frontend *new_window(char *arg, int argtype, char **error) #ifdef STYLUS_BASED menuitem=gtk_button_new_with_mnemonic("_Redo"); g_object_set_data(G_OBJECT(menuitem), "user-data", - GINT_TO_POINTER((int)('r'))); + GINT_TO_POINTER(UI_REDO)); g_signal_connect(G_OBJECT(menuitem), "clicked", G_CALLBACK(menu_key_event), fe); gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0); @@ -2672,7 +2669,7 @@ static frontend *new_window(char *arg, int argtype, char **error) menuitem=gtk_button_new_with_mnemonic("_Undo"); g_object_set_data(G_OBJECT(menuitem), "user-data", - GINT_TO_POINTER((int)('u'))); + GINT_TO_POINTER(UI_UNDO)); g_signal_connect(G_OBJECT(menuitem), "clicked", G_CALLBACK(menu_key_event), fe); gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0); diff --git a/apps/plugins/puzzles/src/html/jspage.pl b/apps/plugins/puzzles/src/html/jspage.pl index a21f977166..b409783f15 100755 --- a/apps/plugins/puzzles/src/html/jspage.pl +++ b/apps/plugins/puzzles/src/html/jspage.pl @@ -3,6 +3,17 @@ use strict; use warnings; +my $jspath = ""; +while ($ARGV[0] =~ /^-/) { + my $opt = shift @ARGV; + last if $opt eq "--"; + if ($opt =~ /^--jspath=(.+)$/) { + $jspath = $1; + } else { + die "jspage.pl: unrecognised option '$opt'\n"; + } +} + open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n"; my $footer = ""; $footer .= $_ while <$footerfile>; @@ -62,7 +73,7 @@ EOF <head> <meta http-equiv="Content-Type" content="text/html; charset=ASCII" /> <title>${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection</title> -<script type="text/javascript" src="${filename}.js"></script> +<script type="text/javascript" src="${jspath}${filename}.js"></script> <style class="text/css"> /* Margins and centring on the top-level div for the game menu */ #gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center } @@ -103,6 +114,15 @@ EOF color: rgba(0,0,0,0.5); } +#gamemenu ul li.separator { + color: transparent; + border: 0; +} + +#gamemenu ul li.afterseparator { + border-left: 1px solid rgba(0,0,0,0.3); +} + #gamemenu ul li:first-of-type { /* Reinstate the left border for the leftmost top-level menu item */ border-left: 1px solid rgba(0,0,0,0.3); @@ -196,14 +216,19 @@ ${unfinishedpara} <hr> <div id="puzzle" style="display: none"> -<div id="gamemenu"><ul><li id="new">New game</li +<div id="gamemenu"><ul><li>Game...<ul +><li id="specific">Enter game ID</li +><li id="random">Enter random seed</li +><li id="save">Download save file</li +><li id="load">Upload save file</li +></ul></li +><li>Type...<ul id="gametype"></ul></li +><li class="separator"></li +><li id="new" class="afterseparator">New game</li ><li id="restart">Restart game</li ><li id="undo">Undo move</li ><li id="redo">Redo move</li ><li id="solve">Solve game</li -><li id="specific">Enter game ID</li -><li id="random">Enter random seed</li -><li>Select game type<ul id="gametype" class="left"></ul></li ></ul></div> <div align=center> <div id="resizable" style="position:relative; left:0; top:0"> diff --git a/apps/plugins/puzzles/src/loopy.c b/apps/plugins/puzzles/src/loopy.c index 7d3436aacb..92b27ab516 100644 --- a/apps/plugins/puzzles/src/loopy.c +++ b/apps/plugins/puzzles/src/loopy.c @@ -288,7 +288,7 @@ static void check_caches(const solver_state* sstate); {amin, omin, \ "Width and height for this grid type must both be at least " #amin, \ "At least one of width and height for this grid type must be at least " #omin,}, -enum { GRIDLIST(GRID_LOOPYTYPE) }; +enum { GRIDLIST(GRID_LOOPYTYPE) LOOPY_GRID_DUMMY_TERMINATOR }; static char const *const gridnames[] = { GRIDLIST(GRID_NAME) }; #define GRID_CONFIGS GRIDLIST(GRID_CONFIG) static grid_type grid_types[] = { GRIDLIST(GRID_GRIDTYPE) }; diff --git a/apps/plugins/puzzles/src/midend.c b/apps/plugins/puzzles/src/midend.c index f80a7fa19f..09b59b25e2 100644 --- a/apps/plugins/puzzles/src/midend.c +++ b/apps/plugins/puzzles/src/midend.c @@ -590,33 +590,40 @@ static int midend_really_process_key(midend *me, int x, int y, int button) int type = MOVE, gottype = FALSE, ret = 1; float anim_time; game_state *s; - char *movestr; - - movestr = - me->ourgame->interpret_move(me->states[me->statepos-1].state, - me->ui, me->drawstate, x, y, button); + char *movestr = NULL; + + if (!IS_UI_FAKE_KEY(button)) { + movestr = me->ourgame->interpret_move( + me->states[me->statepos-1].state, + me->ui, me->drawstate, x, y, button); + } if (!movestr) { - if (button == 'n' || button == 'N' || button == '\x0E') { + if (button == 'n' || button == 'N' || button == '\x0E' || + button == UI_NEWGAME) { midend_new_game(me); midend_redraw(me); goto done; /* never animate */ } else if (button == 'u' || button == 'U' || - button == '\x1A' || button == '\x1F') { + button == '\x1A' || button == '\x1F' || + button == UI_UNDO) { midend_stop_anim(me); type = me->states[me->statepos-1].movetype; gottype = TRUE; if (!midend_undo(me)) goto done; } else if (button == 'r' || button == 'R' || - button == '\x12' || button == '\x19') { + button == '\x12' || button == '\x19' || + button == UI_REDO) { midend_stop_anim(me); if (!midend_redo(me)) goto done; - } else if (button == '\x13' && me->ourgame->can_solve) { + } else if ((button == '\x13' || button == UI_SOLVE) && + me->ourgame->can_solve) { if (midend_solve(me)) goto done; - } else if (button == 'q' || button == 'Q' || button == '\x11') { + } else if (button == 'q' || button == 'Q' || button == '\x11' || + button == UI_QUIT) { ret = 0; goto done; } else @@ -2059,6 +2066,8 @@ char *midend_deserialise(midend *me, me->ourgame->new_drawstate(me->drawing, me->states[me->statepos-1].state); midend_size_new_drawstate(me); + if (me->game_id_change_notify_function) + me->game_id_change_notify_function(me->game_id_change_notify_ctx); ret = NULL; /* success! */ diff --git a/apps/plugins/puzzles/src/mines.c b/apps/plugins/puzzles/src/mines.c index 4bee0f3157..107b3ba159 100644 --- a/apps/plugins/puzzles/src/mines.c +++ b/apps/plugins/puzzles/src/mines.c @@ -2963,7 +2963,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, float animtime, float flashtime) { int x, y; - int mines, markers, bg; + int mines, markers, closed, bg; int cx = -1, cy = -1, cmoved; if (flashtime) { @@ -3013,13 +3013,15 @@ static void game_redraw(drawing *dr, game_drawstate *ds, /* * Now draw the tiles. Also in this loop, count up the number - * of mines and mine markers. + * of mines, mine markers, and closed squares. */ - mines = markers = 0; + mines = markers = closed = 0; for (y = 0; y < ds->h; y++) for (x = 0; x < ds->w; x++) { int v = state->grid[y*ds->w+x], cc = 0; + if (v < 0) + closed++; if (v == -1) markers++; if (state->layout->mines && state->layout->mines[y*ds->w+x]) @@ -3078,7 +3080,42 @@ static void game_redraw(drawing *dr, game_drawstate *ds, else sprintf(statusbar, "COMPLETED!"); } else { + int safe_closed = closed - mines; sprintf(statusbar, "Marked: %d / %d", markers, mines); + if (safe_closed > 0 && safe_closed <= 9) { + /* + * In the situation where there's a very small number + * of _non_-mine squares left unopened, it's helpful + * to mention that number in the status line, to save + * the player from having to count it up + * painstakingly. This is particularly important if + * the player has turned up the mine density to the + * point where game generation resorts to its weird + * pathological fallback of a very dense mine area + * with a clearing in the middle, because that often + * leads to a deduction you can only make by knowing + * that there is (say) exactly one non-mine square to + * find, and it's a real pain to have to count up two + * large numbers of squares and subtract them to get + * that value of 1. + * + * The threshold value of 8 for displaying this + * information is because that's the largest number of + * non-mine squares that might conceivably fit around + * a single central square, and the most likely way to + * _use_ this information is to observe that if all + * the remaining safe squares are adjacent to _this_ + * square then everything else can be immediately + * flagged as a mine. + */ + if (safe_closed == 1) { + sprintf(statusbar + strlen(statusbar), + " (1 safe square remains)"); + } else { + sprintf(statusbar + strlen(statusbar), + " (%d safe squares remain)", safe_closed); + } + } } if (ui->deaths) sprintf(statusbar + strlen(statusbar), diff --git a/apps/plugins/puzzles/src/misc.c b/apps/plugins/puzzles/src/misc.c index 2bf35d391b..816d47e43a 100644 --- a/apps/plugins/puzzles/src/misc.c +++ b/apps/plugins/puzzles/src/misc.c @@ -375,7 +375,7 @@ void copy_left_justified(char *buf, size_t sz, const char *str) /* another kludge for platforms without %g support in *printf() */ int ftoa(char *buf, float f) { - return sprintf(buf, "%d.%06d", (int)f, (int)((f - (int)f)*1e6)); + return sprintf(buf, "%d.%06d", (int)f, abs((int)((f - (int)f)*1e6))); } /* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/src/mkfiles.pl b/apps/plugins/puzzles/src/mkfiles.pl index c1623dfd12..c0874ae07e 100755 --- a/apps/plugins/puzzles/src/mkfiles.pl +++ b/apps/plugins/puzzles/src/mkfiles.pl @@ -319,7 +319,7 @@ sub mfval($) { # Returns true if the argument is a known makefile type. Otherwise, # prints a warning and returns false; if (grep { $type eq $_ } - ("vc","vcproj","cygwin","borland","lcc","gtk","am","mpw","nestedvm","osx","wce","gnustep","emcc")) { + ("vc","vcproj","cygwin","borland","lcc","gtk","am","mpw","nestedvm","osx","wce","gnustep","emcc","clangcl")) { return 1; } warn "$.:unknown makefile type '$type'\n"; @@ -503,6 +503,151 @@ $orig_dir = cwd; # Now we're ready to output the actual Makefiles. +if (defined $makefiles{'clangcl'}) { + $mftyp = 'clangcl'; + $dirpfx = &dirpfx($makefiles{'clangcl'}, "/"); + + ##-- Makefile for cross-compiling using clang-cl, lld-link, and + ## MinGW's windres for resource compilation. + # + # This makefile allows a complete Linux-based cross-compile, but + # using the real Visual Studio header files and libraries. In + # order to run it, you will need: + # + # - MinGW windres on your PATH. + # * On Ubuntu as of 16.04, you can apt-get install + # binutils-mingw-w64-x86-64 and binutils-mingw-w64-i686 + # which will provide (respectively) 64- and 32-bit versions, + # under the names to which RCCMD is defined below. + # - clang-cl and lld-link on your PATH. + # * I built these from the up-to-date LLVM project trunk git + # repositories, as of 2017-02-05. + # - case-mashed copies of the Visual Studio include directories. + # * On a real VS installation, run vcvars32.bat and look at + # the resulting value of %INCLUDE%. Take a full copy of each + # of those directories, and inside the copy, for each + # include file that has an uppercase letter in its name, + # make a lowercased symlink to it. Additionally, one of the + # directories will contain files called driverspecs.h and + # specstrings.h, and those will need symlinks called + # DriverSpecs.h and SpecStrings.h. + # * Now, on Linux, define the environment variable INCLUDE to + # be a list, separated by *semicolons* (in the Windows + # style), of those directories, but before all of them you + # must also include lib/clang/5.0.0/include from the clang + # installation area (which contains in particular a + # clang-compatible stdarg.h overriding the Visual Studio + # one). + # - similarly case-mashed copies of the library directories. + # * Again, on a real VS installation, run vcvars32 or + # vcvarsx86_amd64 (as appropriate), look at %LIB%, make a + # copy of each directory, and provide symlinks within that + # directory so that all the files can be opened as + # lowercase. + # * Then set LIB to be a semicolon-separated list of those + # directories (but you'll need to change which set of + # directories depending on whether you want to do a 32-bit + # or 64-bit build). + # - for a 64-bit build, set 'Platform=x64' in the environment as + # well, or else on the make command line. + # * This is a variable understood only by this makefile - none + # of the tools we invoke will know it - but it's consistent + # with the way the VS scripts like vcvarsx86_amd64.bat set + # things up, and since the environment has to change + # _anyway_ between 32- and 64-bit builds (different set of + # paths in $LIB) it's reasonable to have the choice of + # compilation target driven by another environment variable + # set in parallel with that one. + # - for older versions of the VS libraries you may also have to + # set EXTRA_console and/or EXTRA_windows to the name of an + # object file manually extracted from one of those libraries. + # * This is because old VS seems to manage its startup code by + # having libcmt.lib contain lots of *crt0.obj objects, one + # for each possible user entry point (main, WinMain and the + # wide-char versions of both), of which the linker arranges + # to include the right one by special-case code. But lld + # only seems to mimic half of that code - it does include + # the right crt0 object, but it doesn't also deliberately + # _avoid_ including the _wrong_ ones, and since all those + # objects define a common set of global symbols for other + # parts of the library to use, lld may well select an + # arbitrary one of them the first time it sees a reference + # to one of those global symbols, and then later also select + # the _right_ one for the application's entry point, causing + # a multiple-definitions crash. + # * So the workaround is to explicitly include the right + # *crt0.obj file on the linker command line before lld even + # begins searching libraries. Hence, for a console + # application, you might extract crt0.obj from the library + # in question and set EXTRA_console=crt0.obj, and for a GUI + # application, do the same with wincrt0.obj. Then this + # makefile will include the right one of those objects + # alongside the matching /subsystem linker option. + + open OUT, ">$makefiles{'clangcl'}"; select OUT; + print + "# Makefile for cross-compiling $project_name using clang-cl, lld-link,\n". + "# and MinGW's windres, using GNU make on Linux.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + print $help; + print + "\n". + "CCCMD = clang-cl\n". + "ifeq (\$(Platform),x64)\n". + "CCTARGET = x86_64-pc-windows-msvc18.0.0\n". + "RCCMD = x86_64-w64-mingw32-windres\n". + "else\n". + "CCTARGET = i386-pc-windows-msvc18.0.0\n". + "RCCMD = i686-w64-mingw32-windres\n". + "endif\n". + "CC = \$(CCCMD) --target=\$(CCTARGET)\n". + &splitline("RC = \$(RCCMD) --preprocessor=\$(CCCMD) ". + "--preprocessor-arg=/TC --preprocessor-arg=/E")."\n". + "LD = lld-link\n". + "\n". + "# C compilation flags\n". + &splitline("CFLAGS = /nologo /W3 /O1 " . + (join " ", map {"-I$dirpfx$_"} @srcdirs) . + " /D_WINDOWS /D_WIN32_WINDOWS=0x401 /DWINVER=0x401 ". + "/D_CRT_SECURE_NO_WARNINGS")."\n". + "LFLAGS = /incremental:no /dynamicbase /nxcompat\n". + &splitline("RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs). + " -DWIN32 -D_WIN32 -DWINVER=0x0400 --define MINGW32_FIX=1")."\n". + "\n". + "\n"; + print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C")); + print "\n\n"; + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef); + print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n"; + + $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib"); + $subsys = ($type eq "G") ? "windows" : "console"; + print &splitline("\t\$(LD) \$(LFLAGS) \$(XLFLAGS) ". + "/out:\$(BUILDDIR)$prog.exe ". + "/lldmap:\$(BUILDDIR)$prog.map ". + "/subsystem:$subsys\$(SUBSYSVER) ". + "\$(EXTRA_$subsys) $objstr")."\n\n"; + } + foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "/", "vc")) { + print &splitline(sprintf("%s: %s", $d->{obj}, + join " ", @{$d->{deps}})), "\n"; + if ($d->{obj} =~ /\.res$/) { + print "\t\$(RC) \$(RCFLAGS) ".$d->{deps}->[0]." -o ".$d->{obj}."\n\n"; + } else { + $deflist = join "", map { " /D$_" } @{$d->{defs}}; + print "\t\$(CC) /Fo\$(BUILDDIR)".$d->{obj}." \$(COMPAT) \$(CFLAGS) \$(XFLAGS)$deflist /c \$<\n\n"; + } + } + print "\nclean:\n". + &splitline("\trm -f \$(BUILDDIR)*.obj \$(BUILDDIR)*.exe ". + "\$(BUILDDIR)*.res \$(BUILDDIR)*.map ". + "\$(BUILDDIR)*.exe.manifest")."\n"; + select STDOUT; close OUT; +} + if (defined $makefiles{'cygwin'}) { $mftyp = 'cygwin'; $dirpfx = &dirpfx($makefiles{'cygwin'}, "/"); diff --git a/apps/plugins/puzzles/src/nestedvm.c b/apps/plugins/puzzles/src/nestedvm.c index 79b797116f..f7a2ae8ec5 100644 --- a/apps/plugins/puzzles/src/nestedvm.c +++ b/apps/plugins/puzzles/src/nestedvm.c @@ -305,10 +305,34 @@ static int get_config(frontend *fe, int which) return fe->cfgret; } -int jcallback_menu_key_event(int key) +int jcallback_newgame_event(void) { frontend *fe = (frontend *)_fe; - if (!midend_process_key(fe->me, 0, 0, key)) + if (!midend_process_key(fe->me, 0, 0, UI_NEWGAME)) + return 42; + return 0; +} + +int jcallback_undo_event(void) +{ + frontend *fe = (frontend *)_fe; + if (!midend_process_key(fe->me, 0, 0, UI_UNDO)) + return 42; + return 0; +} + +int jcallback_redo_event(void) +{ + frontend *fe = (frontend *)_fe; + if (!midend_process_key(fe->me, 0, 0, UI_REDO)) + return 42; + return 0; +} + +int jcallback_quit_event(void) +{ + frontend *fe = (frontend *)_fe; + if (!midend_process_key(fe->me, 0, 0, UI_QUIT)) return 42; return 0; } diff --git a/apps/plugins/puzzles/src/net.c b/apps/plugins/puzzles/src/net.c index f479f03bb7..0b3b82446d 100644 --- a/apps/plugins/puzzles/src/net.c +++ b/apps/plugins/puzzles/src/net.c @@ -27,13 +27,6 @@ #define USE_DRAGGING #endif -#define MATMUL(xr,yr,m,x,y) do { \ - float rx, ry, xx = (x), yy = (y), *mat = (m); \ - rx = mat[0] * xx + mat[2] * yy; \ - ry = mat[1] * xx + mat[3] * yy; \ - (xr) = rx; (yr) = ry; \ -} while (0) - /* Direction and other bitfields */ #define R 0x01 #define U 0x02 @@ -65,7 +58,7 @@ #define PREFERRED_TILE_SIZE 32 #define TILE_SIZE (ds->tilesize) -#define TILE_BORDER 1 +#define LINE_THICK ((TILE_SIZE+47)/48) #ifdef SMALL_SCREEN #define WINDOW_OFFSET 4 #else @@ -75,13 +68,6 @@ #define ROTATE_TIME 0.13F #define FLASH_FRAME 0.07F -/* Transform physical coords to game coords using game_drawstate ds */ -#define GX(x) (((x) + ds->org_x) % ds->width) -#define GY(y) (((y) + ds->org_y) % ds->height) -/* ...and game coords to physical coords */ -#define RX(x) (((x) + ds->width - ds->org_x) % ds->width) -#define RY(y) (((y) + ds->height - ds->org_y) % ds->height) - enum { COL_BACKGROUND, COL_LOCKED, @@ -102,12 +88,17 @@ struct game_params { float barrier_probability; }; +typedef struct game_immutable_state { + int refcount; + unsigned char *barriers; +} game_immutable_state; + struct game_state { int width, height, wrapping, completed; int last_rotate_x, last_rotate_y, last_rotate_dir; int used_solve; unsigned char *tiles; - unsigned char *barriers; + struct game_immutable_state *imm; }; #define OFFSETWH(x2,y2,x1,y1,dir,width,height) \ @@ -119,7 +110,7 @@ struct game_state { #define index(state, a, x, y) ( a[(y) * (state)->width + (x)] ) #define tile(state, x, y) index(state, (state)->tiles, x, y) -#define barrier(state, x, y) index(state, (state)->barriers, x, y) +#define barrier(state, x, y) index(state, (state)->imm->barriers, x, y) struct xyd { int x, y, direction; @@ -462,6 +453,11 @@ static int todo_get(struct todo *todo) { return ret; } +/* + * Return values: -1 means puzzle was proved inconsistent, 0 means we + * failed to narrow down to a unique solution, +1 means we solved it + * fully. + */ static int net_solver(int w, int h, unsigned char *tiles, unsigned char *barriers, int wrapping) { @@ -736,7 +732,11 @@ static int net_solver(int w, int h, unsigned char *tiles, #endif } - assert(j > 0); /* we can't lose _all_ possibilities! */ + if (j == 0) { + /* If we've ruled out all possible orientations for a + * tile, then our puzzle has no solution at all. */ + return -1; + } if (j < i) { done_something = TRUE; @@ -816,14 +816,14 @@ static int net_solver(int w, int h, unsigned char *tiles, /* * Mark all completely determined tiles as locked. */ - j = TRUE; + j = +1; for (i = 0; i < w*h; i++) { if (tilestate[i * 4 + 1] == 255) { assert(tilestate[i * 4 + 0] != 255); tiles[i] = tilestate[i * 4] | LOCKED; } else { tiles[i] &= ~LOCKED; - j = FALSE; + j = 0; } } @@ -1337,7 +1337,7 @@ static char *new_game_desc(const game_params *params, random_state *rs, /* * Run the solver to check unique solubility. */ - while (!net_solver(w, h, tiles, NULL, params->wrapping)) { + while (net_solver(w, h, tiles, NULL, params->wrapping) != 1) { int n = 0; /* @@ -1647,12 +1647,14 @@ static game_state *new_game(midend *me, const game_params *params, w = state->width = params->width; h = state->height = params->height; state->wrapping = params->wrapping; + state->imm = snew(game_immutable_state); + state->imm->refcount = 1; state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0; state->completed = state->used_solve = FALSE; state->tiles = snewn(state->width * state->height, unsigned char); memset(state->tiles, 0, state->width * state->height); - state->barriers = snewn(state->width * state->height, unsigned char); - memset(state->barriers, 0, state->width * state->height); + state->imm->barriers = snewn(state->width * state->height, unsigned char); + memset(state->imm->barriers, 0, state->width * state->height); /* * Parse the game description into the grid. @@ -1723,6 +1725,8 @@ static game_state *dup_game(const game_state *state) game_state *ret; ret = snew(game_state); + ret->imm = state->imm; + ret->imm->refcount++; ret->width = state->width; ret->height = state->height; ret->wrapping = state->wrapping; @@ -1733,16 +1737,17 @@ static game_state *dup_game(const game_state *state) ret->last_rotate_y = state->last_rotate_y; ret->tiles = snewn(state->width * state->height, unsigned char); memcpy(ret->tiles, state->tiles, state->width * state->height); - ret->barriers = snewn(state->width * state->height, unsigned char); - memcpy(ret->barriers, state->barriers, state->width * state->height); return ret; } static void free_game(game_state *state) { + if (--state->imm->refcount == 0) { + sfree(state->imm->barriers); + sfree(state->imm); + } sfree(state->tiles); - sfree(state->barriers); sfree(state); } @@ -1761,9 +1766,17 @@ static char *solve_game(const game_state *state, const game_state *currstate, * Run the internal solver on the provided grid. This might * not yield a complete solution. */ + int solver_result; + memcpy(tiles, state->tiles, state->width * state->height); - net_solver(state->width, state->height, tiles, - state->barriers, state->wrapping); + solver_result = net_solver(state->width, state->height, tiles, + state->imm->barriers, state->wrapping); + + if (solver_result < 0) { + *error = "No solution exists for this puzzle"; + sfree(tiles); + return NULL; + } } else { for (i = 0; i < state->width * state->height; i++) { int c = aux[i]; @@ -1990,7 +2003,7 @@ static int *compute_loops_inner(int w, int h, int wrapping, static int *compute_loops(const game_state *state) { return compute_loops_inner(state->width, state->height, state->wrapping, - state->tiles, state->barriers); + state->tiles, state->imm->barriers); } struct game_ui { @@ -2051,9 +2064,8 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate, struct game_drawstate { int started; int width, height; - int org_x, org_y; int tilesize; - int *visible; + unsigned long *visible, *to_draw; }; /* ---------------------------------------------------------------------- @@ -2093,8 +2105,8 @@ static char *interpret_move(const game_state *state, game_ui *ui, /* * The button must have been clicked on a valid tile. */ - x -= WINDOW_OFFSET + TILE_BORDER; - y -= WINDOW_OFFSET + TILE_BORDER; + x -= WINDOW_OFFSET + LINE_THICK; + y -= WINDOW_OFFSET + LINE_THICK; if (x < 0 || y < 0) return nullret; tx = x / TILE_SIZE; @@ -2104,8 +2116,8 @@ static char *interpret_move(const game_state *state, game_ui *ui, /* Transform from physical to game coords */ tx = (tx + ui->org_x) % state->width; ty = (ty + ui->org_y) % state->height; - if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER || - y % TILE_SIZE >= TILE_SIZE - TILE_BORDER) + if (x % TILE_SIZE >= TILE_SIZE - LINE_THICK || + y % TILE_SIZE >= TILE_SIZE - LINE_THICK) return nullret; #ifdef USE_DRAGGING @@ -2434,20 +2446,25 @@ static game_state *execute_move(const game_state *from, const char *move) static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) { game_drawstate *ds = snew(game_drawstate); - int i; + int i, ncells; ds->started = FALSE; ds->width = state->width; ds->height = state->height; - ds->org_x = ds->org_y = -1; - ds->visible = snewn(state->width * state->height, int); + ncells = (state->width+2) * (state->height+2); + ds->visible = snewn(ncells, unsigned long); + ds->to_draw = snewn(ncells, unsigned long); ds->tilesize = 0; /* undecided yet */ - for (i = 0; i < state->width * state->height; i++) + for (i = 0; i < ncells; i++) ds->visible[i] = -1; return ds; } +#define dsindex(ds, field, x, y) ((ds)->field[((y)+1)*((ds)->width+2)+((x)+1)]) +#define visible(ds, x, y) dsindex(ds, visible, x, y) +#define todraw(ds, x, y) dsindex(ds, to_draw, x, y) + static void game_free_drawstate(drawing *dr, game_drawstate *ds) { sfree(ds->visible); @@ -2457,8 +2474,12 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds) static void game_compute_size(const game_params *params, int tilesize, int *x, int *y) { - *x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER; - *y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER; + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = WINDOW_OFFSET * 2 + TILE_SIZE * params->width + LINE_THICK; + *y = WINDOW_OFFSET * 2 + TILE_SIZE * params->height + LINE_THICK; } static void game_set_size(drawing *dr, game_drawstate *ds, @@ -2532,297 +2553,286 @@ static float *game_colours(frontend *fe, int *ncolours) return ret; } -static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2, - int colour) +static void rotated_coords(float *ox, float *oy, const float matrix[4], + float cx, float cy, float ix, float iy) { - draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE); - draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE); - draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE); - draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE); - draw_line(dr, x1, y1, x2, y2, colour); + *ox = matrix[0] * ix + matrix[2] * iy + cx; + *oy = matrix[1] * ix + matrix[3] * iy + cy; } -static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2, - int colour) -{ - int mx = (x1 < x2 ? x1 : x2); - int my = (y1 < y2 ? y1 : y2); - int dx = (x2 + x1 - 2*mx + 1); - int dy = (y2 + y1 - 2*my + 1); - - draw_rect(dr, mx, my, dx, dy, colour); -} - -/* - * draw_barrier_corner() and draw_barrier() are passed physical coords - */ -static void draw_barrier_corner(drawing *dr, game_drawstate *ds, - int x, int y, int dx, int dy, int phase) +/* Flags describing the visible features of a tile. */ +#define TILE_BARRIER_SHIFT 0 /* 4 bits: R U L D */ +#define TILE_BARRIER_CORNER_SHIFT 4 /* 4 bits: RU UL LD DR */ +#define TILE_KEYBOARD_CURSOR (1<<8) /* 1 bit if cursor is here */ +#define TILE_WIRE_SHIFT 9 /* 8 bits: RR UU LL DD + * Each pair: 0=no wire, 1=unpowered, + * 2=powered, 3=loop err highlight */ +#define TILE_ENDPOINT_SHIFT 17 /* 2 bits: 0=no endpoint, 1=unpowered, + * 2=powered, 3=power-source square */ +#define TILE_WIRE_ON_EDGE_SHIFT 19 /* 8 bits: RR UU LL DD, + * same encoding as TILE_WIRE_SHIFT */ +#define TILE_ROTATING (1UL<<27) /* 1 bit if tile is rotating */ +#define TILE_LOCKED (1UL<<28) /* 1 bit if tile is locked */ + +static void draw_wires(drawing *dr, int cx, int cy, int radius, + unsigned long tile, int bitmap, + int colour, int halfwidth, const float matrix[4]) { - int bx = WINDOW_OFFSET + TILE_SIZE * x; - int by = WINDOW_OFFSET + TILE_SIZE * y; - int x1, y1; - - x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); - y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); - - if (phase == 0) { - draw_rect_coords(dr, bx+x1+dx, by+y1, - bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy, - COL_WIRE); - draw_rect_coords(dr, bx+x1, by+y1+dy, - bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy, - COL_WIRE); - } else { - draw_rect_coords(dr, bx+x1, by+y1, - bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy, - COL_BARRIER); + float fpoints[12*2]; + int points[12*2]; + int npoints, d, dsh, i; + int any_wire_this_colour = FALSE; + float xf, yf; + + npoints = 0; + for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) { + int wiretype = (tile >> (TILE_WIRE_SHIFT + 2*dsh)) & 3; + + fpoints[2*npoints+0] = halfwidth * (X(d) + X(C(d))); + fpoints[2*npoints+1] = halfwidth * (Y(d) + Y(C(d))); + npoints++; + + if (bitmap & (1 << wiretype)) { + fpoints[2*npoints+0] = radius * X(d) + halfwidth * X(C(d)); + fpoints[2*npoints+1] = radius * Y(d) + halfwidth * Y(C(d)); + npoints++; + fpoints[2*npoints+0] = radius * X(d) + halfwidth * X(A(d)); + fpoints[2*npoints+1] = radius * Y(d) + halfwidth * Y(A(d)); + npoints++; + + any_wire_this_colour = TRUE; + } } -} -static void draw_barrier(drawing *dr, game_drawstate *ds, - int x, int y, int dir, int phase) -{ - int bx = WINDOW_OFFSET + TILE_SIZE * x; - int by = WINDOW_OFFSET + TILE_SIZE * y; - int x1, y1, w, h; + if (!any_wire_this_colour) + return; - x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0); - y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0); - w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); - h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); - - if (phase == 0) { - draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE); - } else { - draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER); + for (i = 0; i < npoints; i++) { + rotated_coords(&xf, &yf, matrix, cx, cy, fpoints[2*i], fpoints[2*i+1]); + points[2*i] = 0.5 + xf; + points[2*i+1] = 0.5 + yf; } + + draw_polygon(dr, points, npoints, colour, colour); } -/* - * draw_tile() is passed physical coordinates - */ -static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds, - int x, int y, int tile, int src, float angle, int cursor) +static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, + unsigned long tile, float angle) { - int bx = WINDOW_OFFSET + TILE_SIZE * x; - int by = WINDOW_OFFSET + TILE_SIZE * y; + int tx, ty; + int clipx, clipy, clipX, clipY, clipw, cliph; + int border_br = LINE_THICK/2, border_tl = LINE_THICK - border_br; + int barrier_outline_thick = (LINE_THICK+1)/2; + int bg, d, dsh, pass; + int cx, cy, radius; float matrix[4]; - float cx, cy, ex, ey, tx, ty; - int dir, col, phase; + + tx = WINDOW_OFFSET + TILE_SIZE * x + border_br; + ty = WINDOW_OFFSET + TILE_SIZE * y + border_br; /* - * When we draw a single tile, we must draw everything up to - * and including the borders around the tile. This means that - * if the neighbouring tiles have connections to those borders, - * we must draw those connections on the borders themselves. + * Clip to the tile boundary, with adjustments if we're drawing + * just outside the grid. */ - - clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); + clipx = tx; clipX = tx + TILE_SIZE; + clipy = ty; clipY = ty + TILE_SIZE; + if (x == -1) { + clipx = clipX - border_br - barrier_outline_thick; + } else if (x == ds->width) { + clipX = clipx + border_tl + barrier_outline_thick; + } + if (y == -1) { + clipy = clipY - border_br - barrier_outline_thick; + } else if (y == ds->height) { + clipY = clipy + border_tl + barrier_outline_thick; + } + clipw = clipX - clipx; + cliph = clipY - clipy; + clip(dr, clipx, clipy, clipw, cliph); /* - * So. First blank the tile out completely: draw a big - * rectangle in border colour, and a smaller rectangle in - * background colour to fill it in. + * Clear the clip region. */ - draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER, - COL_BORDER); - draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER, - TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER, - tile & LOCKED ? COL_LOCKED : COL_BACKGROUND); + bg = (tile & TILE_LOCKED) ? COL_LOCKED : COL_BACKGROUND; + draw_rect(dr, clipx, clipy, clipw, cliph, bg); /* - * Draw an inset outline rectangle as a cursor, in whichever of - * COL_LOCKED and COL_BACKGROUND we aren't currently drawing - * in. + * Draw the grid lines. */ - if (cursor) { - draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8, - bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, - tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); - draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8, - bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8, - tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); - draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8, - bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, - tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); - draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, - bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, - tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); + { + int gridl = (x == -1 ? tx+TILE_SIZE-border_br : tx); + int gridr = (x == ds->width ? tx+border_tl : tx+TILE_SIZE); + int gridu = (y == -1 ? ty+TILE_SIZE-border_br : ty); + int gridd = (y == ds->height ? ty+border_tl : ty+TILE_SIZE); + if (x >= 0) + draw_rect(dr, tx, gridu, border_tl, gridd-gridu, COL_BORDER); + if (y >= 0) + draw_rect(dr, gridl, ty, gridr-gridl, border_tl, COL_BORDER); + if (x < ds->width) + draw_rect(dr, tx+TILE_SIZE-border_br, gridu, + border_br, gridd-gridu, COL_BORDER); + if (y < ds->height) + draw_rect(dr, gridl, ty+TILE_SIZE-border_br, + gridr-gridl, border_br, COL_BORDER); } /* - * Set up the rotation matrix. + * Draw the keyboard cursor. */ - matrix[0] = (float)cos(angle * PI / 180.0); - matrix[1] = (float)-sin(angle * PI / 180.0); - matrix[2] = (float)sin(angle * PI / 180.0); - matrix[3] = (float)cos(angle * PI / 180.0); + if (tile & TILE_KEYBOARD_CURSOR) { + int cursorcol = (tile & TILE_LOCKED) ? COL_BACKGROUND : COL_LOCKED; + int inset_outer = TILE_SIZE/8, inset_inner = inset_outer + LINE_THICK; + draw_rect(dr, tx + inset_outer, ty + inset_outer, + TILE_SIZE - 2*inset_outer, TILE_SIZE - 2*inset_outer, + cursorcol); + draw_rect(dr, tx + inset_inner, ty + inset_inner, + TILE_SIZE - 2*inset_inner, TILE_SIZE - 2*inset_inner, + bg); + } + + radius = (TILE_SIZE+1)/2; + cx = tx + radius; + cy = ty + radius; + radius++; /* - * Draw the wires. + * Draw protrusions into this cell's edges of wires in + * neighbouring cells, as given by the TILE_WIRE_ON_EDGE_SHIFT + * flags. We only draw each of these if there _isn't_ a wire of + * our own that's going to overlap it, which means either the + * corresponding TILE_WIRE_SHIFT flag is zero, or else the + * TILE_ROTATING flag is set (so that our main wire won't be drawn + * in quite that place anyway). */ - cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F; - col = (tile & ACTIVE ? COL_POWERED : COL_WIRE); - for (dir = 1; dir < 0x10; dir <<= 1) { - if (tile & dir) { - ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); - ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); - MATMUL(tx, ty, matrix, ex, ey); - draw_filled_line(dr, bx+(int)cx, by+(int)cy, - bx+(int)(cx+tx), by+(int)(cy+ty), - COL_WIRE); - } - } - for (dir = 1; dir < 0x10; dir <<= 1) { - if (tile & dir) { - ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); - ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); - MATMUL(tx, ty, matrix, ex, ey); - draw_line(dr, bx+(int)cx, by+(int)cy, - bx+(int)(cx+tx), by+(int)(cy+ty), - (tile & LOOP(dir)) ? COL_LOOP : col); + for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) { + int edgetype = ((tile >> (TILE_WIRE_ON_EDGE_SHIFT + 2*dsh)) & 3); + if (edgetype == 0) + continue; /* there isn't a wire on the edge */ + if (!(tile & TILE_ROTATING) && + ((tile >> (TILE_WIRE_SHIFT + 2*dsh)) & 3) != 0) + continue; /* wire on edge would be overdrawn anyway */ + + for (pass = 0; pass < 2; pass++) { + int x, y, w, h; + int col = (pass == 0 || edgetype == 1 ? COL_WIRE : + edgetype == 2 ? COL_POWERED : COL_LOOP); + int halfwidth = pass == 0 ? 2*LINE_THICK-1 : LINE_THICK-1; + + if (X(d) < 0) { + x = tx; + w = border_tl; + } else if (X(d) > 0) { + x = tx + TILE_SIZE - border_br; + w = border_br; + } else { + x = cx - halfwidth; + w = 2 * halfwidth + 1; + } + + if (Y(d) < 0) { + y = ty; + h = border_tl; + } else if (Y(d) > 0) { + y = ty + TILE_SIZE - border_br; + h = border_br; + } else { + y = cy - halfwidth; + h = 2 * halfwidth + 1; + } + + draw_rect(dr, x, y, w, h, col); } } - /* If we've drawn any loop-highlighted arms, make sure the centre - * point is loop-coloured rather than a later arm overwriting it. */ - if (tile & (RLOOP | ULOOP | LLOOP | DLOOP)) - draw_rect(dr, bx+(int)cx, by+(int)cy, 1, 1, COL_LOOP); /* - * Draw the box in the middle. We do this in blue if the tile - * is an unpowered endpoint, in cyan if the tile is a powered - * endpoint, in black if the tile is the centrepiece, and - * otherwise not at all. + * Set up the rotation matrix for the main cell contents, i.e. + * everything that is centred in the grid square and optionally + * rotated by an arbitrary angle about that centre point. */ - col = -1; - if (src) - col = COL_WIRE; - else if (COUNT(tile) == 1) { - col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT); - } - if (col >= 0) { - int i, points[8]; - - points[0] = +1; points[1] = +1; - points[2] = +1; points[3] = -1; - points[4] = -1; points[5] = -1; - points[6] = -1; points[7] = +1; - - for (i = 0; i < 8; i += 2) { - ex = (TILE_SIZE * 0.24F) * points[i]; - ey = (TILE_SIZE * 0.24F) * points[i+1]; - MATMUL(tx, ty, matrix, ex, ey); - points[i] = bx+(int)(cx+tx); - points[i+1] = by+(int)(cy+ty); - } - - draw_polygon(dr, points, 4, col, COL_WIRE); + if (tile & TILE_ROTATING) { + matrix[0] = (float)cos(angle * PI / 180.0); + matrix[2] = (float)sin(angle * PI / 180.0); + } else { + matrix[0] = 1.0F; + matrix[2] = 0.0F; } + matrix[3] = matrix[0]; + matrix[1] = -matrix[2]; /* - * Draw the points on the border if other tiles are connected - * to us. + * Draw the wires. */ - for (dir = 1; dir < 0x10; dir <<= 1) { - int dx, dy, px, py, lx, ly, vx, vy, ox, oy; - - dx = X(dir); - dy = Y(dir); - - ox = x + dx; - oy = y + dy; - - if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height) - continue; - - if (!(tile(state, GX(ox), GY(oy)) & F(dir))) - continue; + draw_wires(dr, cx, cy, radius, tile, + 0xE, COL_WIRE, 2*LINE_THICK-1, matrix); + draw_wires(dr, cx, cy, radius, tile, + 0x4, COL_POWERED, LINE_THICK-1, matrix); + draw_wires(dr, cx, cy, radius, tile, + 0x8, COL_LOOP, LINE_THICK-1, matrix); - px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx); - py = by + (int)(dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy); - lx = dx * (TILE_BORDER-1); - ly = dy * (TILE_BORDER-1); - vx = (dy ? 1 : 0); - vy = (dx ? 1 : 0); + /* + * Draw the central box. + */ + for (pass = 0; pass < 2; pass++) { + int endtype = (tile >> TILE_ENDPOINT_SHIFT) & 3; + if (endtype) { + int i, points[8], col; + float boxr = TILE_SIZE * 0.24F + (pass == 0 ? LINE_THICK-1 : 0); + + col = (pass == 0 || endtype == 3 ? COL_WIRE : + endtype == 2 ? COL_POWERED : COL_ENDPOINT); + + points[0] = +1; points[1] = +1; + points[2] = +1; points[3] = -1; + points[4] = -1; points[5] = -1; + points[6] = -1; points[7] = +1; + + for (i = 0; i < 8; i += 2) { + float x, y; + rotated_coords(&x, &y, matrix, cx, cy, + boxr * points[i], boxr * points[i+1]); + points[i] = x + 0.5; + points[i+1] = y + 0.5; + } - if (angle == 0.0 && (tile & dir)) { - /* - * If we are fully connected to the other tile, we must - * draw right across the tile border. (We can use our - * own ACTIVE state to determine what colour to do this - * in: if we are fully connected to the other tile then - * the two ACTIVE states will be the same.) - */ - draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE); - draw_rect_coords(dr, px, py, px+lx, py+ly, - ((tile & LOOP(dir)) ? COL_LOOP : - (tile & ACTIVE) ? COL_POWERED : - COL_WIRE)); - } else { - /* - * The other tile extends into our border, but isn't - * actually connected to us. Just draw a single black - * dot. - */ - draw_rect_coords(dr, px, py, px, py, COL_WIRE); + draw_polygon(dr, points, 4, col, COL_WIRE); } } /* - * Draw barrier corners, and then barriers. + * Draw barriers along grid edges. */ - for (phase = 0; phase < 2; phase++) { - for (dir = 1; dir < 0x10; dir <<= 1) { - int x1, y1, corner = FALSE; - /* - * If at least one barrier terminates at the corner - * between dir and A(dir), draw a barrier corner. - */ - if (barrier(state, GX(x), GY(y)) & (dir | A(dir))) { - corner = TRUE; - } else { - /* - * Only count barriers terminating at this corner - * if they're physically next to the corner. (That - * is, if they've wrapped round from the far side - * of the screen, they don't count.) - */ - x1 = x + X(dir); - y1 = y + Y(dir); - if (x1 >= 0 && x1 < state->width && - y1 >= 0 && y1 < state->height && - (barrier(state, GX(x1), GY(y1)) & A(dir))) { - corner = TRUE; - } else { - x1 = x + X(A(dir)); - y1 = y + Y(A(dir)); - if (x1 >= 0 && x1 < state->width && - y1 >= 0 && y1 < state->height && - (barrier(state, GX(x1), GY(y1)) & dir)) - corner = TRUE; - } - } - - if (corner) { - /* - * At least one barrier terminates here. Draw a - * corner. - */ - draw_barrier_corner(dr, ds, x, y, - X(dir)+X(A(dir)), Y(dir)+Y(A(dir)), - phase); - } + for (pass = 0; pass < 2; pass++) { + int btl = border_tl, bbr = border_br, col = COL_BARRIER; + if (pass == 0) { + btl += barrier_outline_thick; + bbr += barrier_outline_thick; + col = COL_WIRE; } - for (dir = 1; dir < 0x10; dir <<= 1) - if (barrier(state, GX(x), GY(y)) & dir) - draw_barrier(dr, ds, x, y, dir, phase); + if (tile & (L << TILE_BARRIER_SHIFT)) + draw_rect(dr, tx, ty, btl, TILE_SIZE, col); + if (tile & (R << TILE_BARRIER_SHIFT)) + draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, TILE_SIZE, col); + if (tile & (U << TILE_BARRIER_SHIFT)) + draw_rect(dr, tx, ty, TILE_SIZE, btl, col); + if (tile & (D << TILE_BARRIER_SHIFT)) + draw_rect(dr, tx, ty+TILE_SIZE-bbr, TILE_SIZE, bbr, col); + + if (tile & (R << TILE_BARRIER_CORNER_SHIFT)) + draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, btl, col); + if (tile & (U << TILE_BARRIER_CORNER_SHIFT)) + draw_rect(dr, tx, ty, btl, btl, col); + if (tile & (L << TILE_BARRIER_CORNER_SHIFT)) + draw_rect(dr, tx, ty+TILE_SIZE-bbr, btl, bbr, col); + if (tile & (D << TILE_BARRIER_CORNER_SHIFT)) + draw_rect(dr, tx+TILE_SIZE-bbr, ty+TILE_SIZE-bbr, bbr, bbr, col); } + /* + * Unclip and draw update, to finish. + */ unclip(dr); - - draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); + draw_update(dr, clipx, clipy, clipw, cliph); } static void game_redraw(drawing *dr, game_drawstate *ds, @@ -2830,73 +2840,26 @@ static void game_redraw(drawing *dr, game_drawstate *ds, int dir, const game_ui *ui, float t, float ft) { - int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE; + int tx, ty, dx, dy, d, dsh, last_rotate_dir, frame; unsigned char *active; int *loops; float angle = 0.0; /* - * Clear the screen, and draw the exterior barrier lines, if - * this is our first call or if the origin has changed. + * Clear the screen on our first call. */ - if (!ds->started || ui->org_x != ds->org_x || ui->org_y != ds->org_y) { - int phase; + if (!ds->started) { + int w, h; + game_params params; ds->started = TRUE; - draw_rect(dr, 0, 0, - WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER, - WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER, - COL_BACKGROUND); - - ds->org_x = ui->org_x; - ds->org_y = ui->org_y; - moved_origin = TRUE; - - draw_update(dr, 0, 0, - WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER, - WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER); - - for (phase = 0; phase < 2; phase++) { + params.width = ds->width; + params.height = ds->height; + game_compute_size(¶ms, TILE_SIZE, &w, &h); - for (x = 0; x < ds->width; x++) { - if (x+1 < ds->width) { - if (barrier(state, GX(x), GY(0)) & R) - draw_barrier_corner(dr, ds, x, -1, +1, +1, phase); - if (barrier(state, GX(x), GY(ds->height-1)) & R) - draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase); - } - if (barrier(state, GX(x), GY(0)) & U) { - draw_barrier_corner(dr, ds, x, -1, -1, +1, phase); - draw_barrier_corner(dr, ds, x, -1, +1, +1, phase); - draw_barrier(dr, ds, x, -1, D, phase); - } - if (barrier(state, GX(x), GY(ds->height-1)) & D) { - draw_barrier_corner(dr, ds, x, ds->height, -1, -1, phase); - draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase); - draw_barrier(dr, ds, x, ds->height, U, phase); - } - } - - for (y = 0; y < ds->height; y++) { - if (y+1 < ds->height) { - if (barrier(state, GX(0), GY(y)) & D) - draw_barrier_corner(dr, ds, -1, y, +1, +1, phase); - if (barrier(state, GX(ds->width-1), GY(y)) & D) - draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase); - } - if (barrier(state, GX(0), GY(y)) & L) { - draw_barrier_corner(dr, ds, -1, y, +1, -1, phase); - draw_barrier_corner(dr, ds, -1, y, +1, +1, phase); - draw_barrier(dr, ds, -1, y, R, phase); - } - if (barrier(state, GX(ds->width-1), GY(y)) & R) { - draw_barrier_corner(dr, ds, ds->width, y, -1, -1, phase); - draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase); - draw_barrier(dr, ds, ds->width, y, L, phase); - } - } - } + draw_rect(dr, 0, 0, w, h, COL_BACKGROUND); + draw_update(dr, 0, 0, w, h); } tx = ty = -1; @@ -2913,30 +2876,83 @@ static void game_redraw(drawing *dr, game_drawstate *ds, state = oldstate; } - frame = -1; if (ft > 0) { /* * We're animating a completion flash. Find which frame * we're at. */ frame = (int)(ft / FLASH_FRAME); + } else { + frame = 0; } /* - * Draw any tile which differs from the way it was last drawn. + * Build up a map of what we want every tile to look like. We + * include tiles one square outside the grid, for the outer edges + * of barriers. */ active = compute_active(state, ui->cx, ui->cy); loops = compute_loops(state); - for (x = 0; x < ds->width; x++) - for (y = 0; y < ds->height; y++) { - int c = tile(state, GX(x), GY(y)) | - index(state, active, GX(x), GY(y)) | - index(state, loops, GX(x), GY(y)); - int is_src = GX(x) == ui->cx && GY(y) == ui->cy; - int is_anim = GX(x) == tx && GY(y) == ty; - int is_cursor = ui->cur_visible && - GX(x) == ui->cur_x && GY(y) == ui->cur_y; + for (dy = -1; dy < ds->height+1; dy++) { + for (dx = -1; dx < ds->width+1; dx++) { + todraw(ds, dx, dy) = 0; + } + } + + for (dy = 0; dy < ds->height; dy++) { + int gy = (dy + ui->org_y) % ds->height; + for (dx = 0; dx < ds->width; dx++) { + int gx = (dx + ui->org_x) % ds->width; + int t = (tile(state, gx, gy) | + index(state, loops, gx, gy) | + index(state, active, gx, gy)); + + for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) { + if (barrier(state, gx, gy) & d) { + todraw(ds, dx, dy) |= + d << TILE_BARRIER_SHIFT; + todraw(ds, dx + X(d), dy + Y(d)) |= + F(d) << TILE_BARRIER_SHIFT; + todraw(ds, dx + X(A(d)), dy + Y(A(d))) |= + C(d) << TILE_BARRIER_CORNER_SHIFT; + todraw(ds, dx + X(A(d)) + X(d), dy + Y(A(d)) + Y(d)) |= + F(d) << TILE_BARRIER_CORNER_SHIFT; + todraw(ds, dx + X(C(d)), dy + Y(C(d))) |= + d << TILE_BARRIER_CORNER_SHIFT; + todraw(ds, dx + X(C(d)) + X(d), dy + Y(C(d)) + Y(d)) |= + A(d) << TILE_BARRIER_CORNER_SHIFT; + } + + if (t & d) { + int edgeval = (t & LOOP(d) ? 3 : t & ACTIVE ? 2 : 1); + todraw(ds, dx, dy) |= edgeval << (TILE_WIRE_SHIFT + dsh*2); + if (!(gx == tx && gy == ty)) { + todraw(ds, dx + X(d), dy + Y(d)) |= + edgeval << (TILE_WIRE_ON_EDGE_SHIFT + (dsh ^ 2)*2); + } + } + } + + if (ui->cur_visible && gx == ui->cur_x && gy == ui->cur_y) + todraw(ds, dx, dy) |= TILE_KEYBOARD_CURSOR; + + if (gx == tx && gy == ty) + todraw(ds, dx, dy) |= TILE_ROTATING; + + if (gx == ui->cx && gy == ui->cy) { + todraw(ds, dx, dy) |= 3 << TILE_ENDPOINT_SHIFT; + } else if ((t & 0xF) != R && (t & 0xF) != U && + (t & 0xF) != L && (t & 0xF) != D) { + /* this is not an endpoint tile */ + } else if (t & ACTIVE) { + todraw(ds, dx, dy) |= 2 << TILE_ENDPOINT_SHIFT; + } else { + todraw(ds, dx, dy) |= 1 << TILE_ENDPOINT_SHIFT; + } + + if (t & LOCKED) + todraw(ds, dx, dy) |= TILE_LOCKED; /* * In a completion flash, we adjust the LOCKED bit @@ -2944,31 +2960,36 @@ static void game_redraw(drawing *dr, game_drawstate *ds, * the frame number. */ if (frame >= 0) { - int rcx = RX(ui->cx), rcy = RY(ui->cy); + int rcx = (ui->cx + ds->width - ui->org_x) % ds->width; + int rcy = (ui->cy + ds->height - ui->org_y) % ds->height; int xdist, ydist, dist; - xdist = (x < rcx ? rcx - x : x - rcx); - ydist = (y < rcy ? rcy - y : y - rcy); + xdist = (dx < rcx ? rcx - dx : dx - rcx); + ydist = (dy < rcy ? rcy - dy : dy - rcy); dist = (xdist > ydist ? xdist : ydist); - if (frame >= dist && frame < dist+4) { - int lock = (frame - dist) & 1; - lock = lock ? LOCKED : 0; - c = (c &~ LOCKED) | lock; - } + if (frame >= dist && frame < dist+4 && + ((frame - dist) & 1)) + todraw(ds, dx, dy) ^= TILE_LOCKED; } + } + } - if (moved_origin || - index(state, ds->visible, x, y) != c || - index(state, ds->visible, x, y) == -1 || - is_src || is_anim || is_cursor) { - draw_tile(dr, state, ds, x, y, c, - is_src, (is_anim ? angle : 0.0F), is_cursor); - if (is_src || is_anim || is_cursor) - index(state, ds->visible, x, y) = -1; - else - index(state, ds->visible, x, y) = c; + /* + * Now draw any tile that differs from the way it was last drawn. + * An exception is that if either the previous _or_ current state + * has the TILE_ROTATING bit set, we must draw it regardless, + * because it will have rotated to a different angle.q + */ + for (dy = -1; dy < ds->height+1; dy++) { + for (dx = -1; dx < ds->width+1; dx++) { + int prev = visible(ds, dx, dy); + int curr = todraw(ds, dx, dy); + if (prev != curr || ((prev | curr) & TILE_ROTATING) != 0) { + draw_tile(dr, ds, dx, dy, curr, angle); + visible(ds, dx, dy) = curr; } } + } /* * Update the status bar. diff --git a/apps/plugins/puzzles/src/osx.m b/apps/plugins/puzzles/src/osx.m index 9d74da1574..be29819b62 100644 --- a/apps/plugins/puzzles/src/osx.m +++ b/apps/plugins/puzzles/src/osx.m @@ -687,6 +687,10 @@ struct frontend { if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask)) c |= MOD_NUM_KEYPAD; + if (c == 26 && + !((NSShiftKeyMask | NSControlKeyMask) & ~[ev modifierFlags])) + c = UI_REDO; + [self processKey:c]; } } @@ -735,7 +739,7 @@ struct frontend { - (void)newGame:(id)sender { - [self processKey:'n']; + [self processKey:UI_NEWGAME]; } - (void)restartGame:(id)sender { @@ -809,11 +813,11 @@ struct frontend { } - (void)undoMove:(id)sender { - [self processKey:'u']; + [self processKey:UI_UNDO]; } - (void)redoMove:(id)sender { - [self processKey:'r'&0x1F]; + [self processKey:UI_REDO]; } - (void)copy:(id)sender diff --git a/apps/plugins/puzzles/src/pattern.c b/apps/plugins/puzzles/src/pattern.c index 15cdd281c9..270b558bda 100644 --- a/apps/plugins/puzzles/src/pattern.c +++ b/apps/plugins/puzzles/src/pattern.c @@ -310,7 +310,18 @@ static void generate(random_state *rs, int w, int h, unsigned char *retgrid) fgrid2 = snewn(w*h, float); memcpy(fgrid2, fgrid, w*h*sizeof(float)); qsort(fgrid2, w*h, sizeof(float), float_compare); - threshold = fgrid2[w*h/2]; + /* Choose a threshold that makes half the pixels black. In case of + * an odd number of pixels, select randomly between just under and + * just over half. */ + { + int index = w * h / 2; + if (w & h & 1) + index += random_upto(rs, 2); + if (index < w*h) + threshold = fgrid2[index]; + else + threshold = fgrid2[w*h-1] + 1; + } sfree(fgrid2); for (i = 0; i < h; i++) { @@ -448,6 +459,8 @@ static int do_row(unsigned char *known, unsigned char *deduced, if (rowlen == 0) { memset(deduced, DOT, len); + } else if (rowlen == 1 && data[0] == len) { + memset(deduced, BLOCK, len); } else { do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok, maxpos_ok, data, len, freespace, 0, 0); diff --git a/apps/plugins/puzzles/src/puzzles.h b/apps/plugins/puzzles/src/puzzles.h index fbfcfce4f8..73b31ea7f9 100644 --- a/apps/plugins/puzzles/src/puzzles.h +++ b/apps/plugins/puzzles/src/puzzles.h @@ -47,6 +47,15 @@ enum { CURSOR_RIGHT, CURSOR_SELECT, CURSOR_SELECT2, + /* UI_* are special keystrokes generated by front ends in response + * to menu actions, never passed to back ends */ + UI_LOWER_BOUND, + UI_QUIT, + UI_NEWGAME, + UI_SOLVE, + UI_UNDO, + UI_REDO, + UI_UPPER_BOUND, /* made smaller because of 'limited range of datatype' errors. */ MOD_CTRL = 0x1000, @@ -64,6 +73,7 @@ enum { #define IS_CURSOR_MOVE(m) ( (m) == CURSOR_UP || (m) == CURSOR_DOWN || \ (m) == CURSOR_RIGHT || (m) == CURSOR_LEFT ) #define IS_CURSOR_SELECT(m) ( (m) == CURSOR_SELECT || (m) == CURSOR_SELECT2) +#define IS_UI_FAKE_KEY(m) ( (m) > UI_LOWER_BOUND && (m) < UI_UPPER_BOUND ) /* * Flags in the back end's `flags' word. diff --git a/apps/plugins/puzzles/src/tracks.c b/apps/plugins/puzzles/src/tracks.c index 0c06c59ae9..578813b1a3 100644 --- a/apps/plugins/puzzles/src/tracks.c +++ b/apps/plugins/puzzles/src/tracks.c @@ -1718,7 +1718,10 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate, #define TILE_SIZE (ds->sz6*6) #define BORDER (TILE_SIZE/8) -#define BORDER_WIDTH (max(TILE_SIZE / 32, 1)) +#define LINE_THICK (TILE_SIZE/16) +#define GRID_LINE_TL (ds->grid_line_tl) +#define GRID_LINE_BR (ds->grid_line_br) +#define GRID_LINE_ALL (ds->grid_line_all) #define COORD(x) ( (x+1) * TILE_SIZE + BORDER ) #define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 ) @@ -1738,7 +1741,7 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate, #define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */ struct game_drawstate { - int sz6; + int sz6, grid_line_all, grid_line_tl, grid_line_br; int started; int w, h, sz; @@ -2118,7 +2121,6 @@ static void game_compute_size(const game_params *params, int tilesize, int sz6; } ads, *ds = &ads; ads.sz6 = tilesize/6; - *x = (params->w+2) * TILE_SIZE + 2 * BORDER; *y = (params->h+2) * TILE_SIZE + 2 * BORDER; } @@ -2127,6 +2129,9 @@ static void game_set_size(drawing *dr, game_drawstate *ds, const game_params *params, int tilesize) { ds->sz6 = tilesize/6; + ds->grid_line_all = max(LINE_THICK, 1); + ds->grid_line_br = ds->grid_line_all / 2; + ds->grid_line_tl = ds->grid_line_all - ds->grid_line_br; } enum { @@ -2346,14 +2351,13 @@ static void draw_square(drawing *dr, game_drawstate *ds, /* Clip to the grid square. */ clip(dr, ox, oy, TILE_SIZE, TILE_SIZE); - /* Clear the square. */ + /* Clear the square so that it's got an appropriately-sized border + * in COL_GRID and a central area in the right background colour. */ best_bits((flags & DS_TRACK) == DS_TRACK, (flags_drag & DS_TRACK) == DS_TRACK, &bg); - draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, bg); - - /* Draw outline of grid square */ - draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID); - draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID); + draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_GRID); + draw_rect(dr, ox + GRID_LINE_TL, oy + GRID_LINE_TL, + TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL, bg); /* More outlines for clue squares. */ if (flags & DS_CURSOR) { @@ -2389,8 +2393,8 @@ static void draw_square(drawing *dr, game_drawstate *ds, (flags_drag & DS_NOTRACK) == DS_NOTRACK, &c); if (flags_best) { off = HALFSZ/2; - draw_line(dr, cx - off, cy - off, cx + off, cy + off, c); - draw_line(dr, cx - off, cy + off, cx + off, cy - off, c); + draw_thick_line(dr, LINE_THICK, cx - off, cy - off, cx + off, cy + off, c); + draw_thick_line(dr, LINE_THICK, cx - off, cy + off, cx + off, cy - off, c); } c = COL_TRACK; @@ -2404,8 +2408,8 @@ static void draw_square(drawing *dr, game_drawstate *ds, cx += (d == R) ? t2 : (d == L) ? -t2 : 0; cy += (d == D) ? t2 : (d == U) ? -t2 : 0; - draw_line(dr, cx - off, cy - off, cx + off, cy + off, c); - draw_line(dr, cx - off, cy + off, cx + off, cy - off, c); + draw_thick_line(dr, LINE_THICK, cx - off, cy - off, cx + off, cy + off, c); + draw_thick_line(dr, LINE_THICK, cx - off, cy + off, cx + off, cy - off, c); } } @@ -2426,12 +2430,14 @@ static void draw_clue(drawing *dr, game_drawstate *ds, int w, int clue, int i, i cy = CENTERED_COORD(i-w); } - draw_rect(dr, cx - tsz + BORDER, cy - tsz + BORDER, - TILE_SIZE - BORDER, TILE_SIZE - BORDER, COL_BACKGROUND); + draw_rect(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL, + TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL, + COL_BACKGROUND); sprintf(buf, "%d", clue); draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE, col, buf); - draw_update(dr, cx - tsz, cy - tsz, TILE_SIZE, TILE_SIZE); + draw_update(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL, + TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL); } static void draw_loop_ends(drawing *dr, game_drawstate *ds, @@ -2498,8 +2504,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldst draw_loop_ends(dr, ds, state, COL_CLUE); - draw_line(dr, COORD(ds->w), COORD(0), COORD(ds->w), COORD(ds->h), COL_GRID); - draw_line(dr, COORD(0), COORD(ds->h), COORD(ds->w), COORD(ds->h), COL_GRID); + draw_rect(dr, COORD(0) - GRID_LINE_BR, COORD(0) - GRID_LINE_BR, + ds->w * TILE_SIZE + GRID_LINE_ALL, + ds->h * TILE_SIZE + GRID_LINE_ALL, COL_GRID); draw_update(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER); diff --git a/apps/plugins/puzzles/src/webpage.pl b/apps/plugins/puzzles/src/webpage.pl index 3a0779ef0a..c6144bb467 100755 --- a/apps/plugins/puzzles/src/webpage.pl +++ b/apps/plugins/puzzles/src/webpage.pl @@ -27,7 +27,7 @@ while (<$desc>) { '<span class="puzzle"><table>'. '<tr><th align="center">%s</th></tr>'. '<tr><td align="center">'. - '<img style="margin: 0.5em" alt="" title="%s" width=150 height=150 border=0 src="%s-web.png" />'. + '<a href="js/%s.html"><img style="margin: 0.5em" alt="" title="%s" width=150 height=150 border=0 src="%s-web.png" /></a>'. '</td></tr>'. '<tr><td align="center" style="font-size: 70%%"><code>[</code>'. ' <a href="java/%s.html">java</a> '. @@ -41,6 +41,7 @@ while (<$desc>) { '<tr><td align="center">%s</td></tr></table></span>'. "\n", encode_entities($displayname), + encode_entities($id), encode_entities($description), encode_entities($id), encode_entities($id), diff --git a/apps/plugins/puzzles/src/windows.c b/apps/plugins/puzzles/src/windows.c index d4b30386a6..ffd0f75894 100644 --- a/apps/plugins/puzzles/src/windows.c +++ b/apps/plugins/puzzles/src/windows.c @@ -1545,7 +1545,7 @@ static frontend *frontend_new(HINSTANCE inst) fe->statusbar = NULL; fe->bitmap = NULL; - SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe); + SetWindowLongPtr(fe->hwnd, GWLP_USERDATA, (LONG_PTR)fe); return fe; } @@ -1992,7 +1992,7 @@ static void make_dialog_full_screen(HWND hwnd) static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); + frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (msg) { case WM_INITDIALOG: @@ -2249,7 +2249,7 @@ static void create_config_controls(frontend * fe) static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); + frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA); config_item *i; struct cfg_aux *j; @@ -2260,7 +2260,7 @@ static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg, char *title; fe = (frontend *) lParam; - SetWindowLong(hwnd, GWL_USERDATA, lParam); + SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam); fe->cfgbox = hwnd; fe->cfg = frontend_get_config(fe, fe->cfg_which, &title); @@ -2479,8 +2479,8 @@ static void about(frontend *fe) SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE); - SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe); - SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)AboutDlgProc); + SetWindowLongPtr(fe->cfgbox, GWLP_USERDATA, (LONG_PTR)fe); + SetWindowLongPtr(fe->cfgbox, DWLP_DLGPROC, (LONG_PTR)AboutDlgProc); id = 1000; y = height/2; @@ -2660,8 +2660,8 @@ static int get_config(frontend *fe, int which) SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE); - SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe); - SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)ConfigDlgProc); + SetWindowLongPtr(fe->cfgbox, GWLP_USERDATA, (LONG_PTR)fe); + SetWindowLongPtr(fe->cfgbox, DWLP_DLGPROC, (LONG_PTR)ConfigDlgProc); /* * Count the controls so we can allocate cfgaux. @@ -2975,7 +2975,7 @@ static int is_alt_pressed(void) static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { - frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); + frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA); int cmd; switch (message) { @@ -2993,18 +2993,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, cmd = wParam & ~0xF; /* low 4 bits reserved to Windows */ switch (cmd) { case IDM_NEW: - if (!midend_process_key(fe->me, 0, 0, 'n')) + if (!midend_process_key(fe->me, 0, 0, UI_NEWGAME)) PostQuitMessage(0); break; case IDM_RESTART: midend_restart_game(fe->me); break; case IDM_UNDO: - if (!midend_process_key(fe->me, 0, 0, 'u')) + if (!midend_process_key(fe->me, 0, 0, UI_UNDO)) PostQuitMessage(0); break; case IDM_REDO: - if (!midend_process_key(fe->me, 0, 0, '\x12')) + if (!midend_process_key(fe->me, 0, 0, UI_REDO)) PostQuitMessage(0); break; case IDM_COPY: @@ -3026,7 +3026,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } break; case IDM_QUIT: - if (!midend_process_key(fe->me, 0, 0, 'q')) + if (!midend_process_key(fe->me, 0, 0, UI_QUIT)) PostQuitMessage(0); break; case IDM_CONFIG: @@ -3405,8 +3405,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } break; case WM_CHAR: - if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam)) - PostQuitMessage(0); + { + int key = (unsigned char)wParam; + if (key == '\x1A') { + BYTE keystate[256]; + if (GetKeyboardState(keystate) && + (keystate[VK_SHIFT] & 0x80) && + (keystate[VK_CONTROL] & 0x80)) + key = UI_REDO; + } + if (!midend_process_key(fe->me, 0, 0, key)) + PostQuitMessage(0); + } return 0; case WM_TIMER: if (fe->timer) { diff --git a/apps/plugins/puzzles/src/winwix.mc b/apps/plugins/puzzles/src/winwix.mc index 1a1e620b82..4a72c09123 100644 --- a/apps/plugins/puzzles/src/winwix.mc +++ b/apps/plugins/puzzles/src/winwix.mc @@ -61,7 +61,7 @@ has 'descfile' => (required => 1); % # (individual files or shortcuts or additions to PATH) that are % # installed. <Directory Id="TARGETDIR" Name="SourceDir"> - <Directory Id="ProgramFilesFolder" Name="PFiles"> + <Directory Id="ProgramFiles64Folder" Name="PFiles"> <Directory Id="INSTALLDIR" Name="Simon Tatham's Portable Puzzle Collection"> % # The following components all install things in the main |