summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranklin Wei <git@fwei.tk>2017-09-30 17:47:13 -0400
committerFranklin Wei <git@fwei.tk>2017-09-30 20:06:50 -0400
commitb9386109e8f0cf346037b72464577fe19bba2d43 (patch)
treef0c15f6c80043376ec94d23cb1b681324eb3df3d
parentea679de8371e4e74fe4e78fb8df8e5df19efffdc (diff)
downloadrockbox-b9386109e8f0cf346037b72464577fe19bba2d43.tar.gz
rockbox-b9386109e8f0cf346037b72464577fe19bba2d43.zip
puzzles: resync with upstream
This brings puzzles to upstream commit 84d3fd2. Change-Id: I808a197f868032d771fc101a15666c5ec4b9f94b
-rw-r--r--apps/plugins/puzzles/src/Buildscr43
-rw-r--r--apps/plugins/puzzles/src/PuzzleApplet.java19
-rw-r--r--apps/plugins/puzzles/src/Recipe1
-rw-r--r--apps/plugins/puzzles/src/chm.but21
-rw-r--r--apps/plugins/puzzles/src/devel.but3
-rw-r--r--apps/plugins/puzzles/src/drawing.c2
-rw-r--r--apps/plugins/puzzles/src/emcc.c89
-rw-r--r--apps/plugins/puzzles/src/emcclib.js65
-rw-r--r--apps/plugins/puzzles/src/emccpre.js118
-rw-r--r--apps/plugins/puzzles/src/emccx.json4
-rw-r--r--apps/plugins/puzzles/src/gtk.c75
-rwxr-xr-xapps/plugins/puzzles/src/html/jspage.pl35
-rw-r--r--apps/plugins/puzzles/src/loopy.c2
-rw-r--r--apps/plugins/puzzles/src/midend.c29
-rw-r--r--apps/plugins/puzzles/src/mines.c43
-rw-r--r--apps/plugins/puzzles/src/misc.c2
-rwxr-xr-xapps/plugins/puzzles/src/mkfiles.pl147
-rw-r--r--apps/plugins/puzzles/src/nestedvm.c28
-rw-r--r--apps/plugins/puzzles/src/net.c759
-rw-r--r--apps/plugins/puzzles/src/osx.m10
-rw-r--r--apps/plugins/puzzles/src/pattern.c15
-rw-r--r--apps/plugins/puzzles/src/puzzles.h10
-rw-r--r--apps/plugins/puzzles/src/tracks.c43
-rwxr-xr-xapps/plugins/puzzles/src/webpage.pl3
-rw-r--r--apps/plugins/puzzles/src/windows.c40
-rw-r--r--apps/plugins/puzzles/src/winwix.mc2
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
54in puzzles do make -f Makefile.doc # build help files for installer 54in puzzles do make -f Makefile.doc # build help files for installer
55in puzzles do mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs 55in puzzles do mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs
56in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss 56in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss
57delegate windows 57ifneq "$(VISUAL_STUDIO)" "yes" then
58 # FIXME: Cygwin alternative? 58 in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl clean
59 in puzzles with visualstudio do/win nmake -f Makefile.vc clean 59 in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl VER=-DVER=$(Version)
60 in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version)
61 # Code-sign the binaries, if the local bob config provides a script 60 # Code-sign the binaries, if the local bob config provides a script
62 # to do so. We assume here that the script accepts an -i option to 61 # to do so. We assume here that the script accepts an -i option to
63 # provide a 'more info' URL, and an optional -n option to provide a 62 # provide a 'more info' URL, and an optional -n option to provide a
64 # program name, and that it can take multiple .exe filename 63 # program name, and that it can take multiple .exe filename
65 # arguments and sign them all in place. 64 # arguments and sign them all in place.
66 ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe 65 ifneq "$(cross_winsigncode)" "" in puzzles do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe
67 # Build installers. 66 # Build installers.
68 in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj 67 in puzzles with wixonlinux do candle -arch x64 puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj
69 in puzzles with innosetup do/win iscc puzzles.iss 68 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
70 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 69else
71 return puzzles/*.exe 70 delegate windows
72 return puzzles/Output/installer.exe 71 in puzzles with visualstudio do/win nmake -f Makefile.vc clean
73 return puzzles/puzzles.msi 72 in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version)
74enddelegate 73 ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe
74 # Build installers.
75 in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj
76 in puzzles with innosetup do/win iscc puzzles.iss
77 return puzzles/*.exe
78 return puzzles/puzzles.msi
79 enddelegate
80endif
75in puzzles do chmod +x *.exe 81in puzzles do chmod +x *.exe
76 82
77# Build the Pocket PC binaries and CAB. 83# Build the Pocket PC binaries and CAB.
@@ -152,13 +158,22 @@ delegate emscripten
152 return puzzles/js/*.js 158 return puzzles/js/*.js
153enddelegate 159enddelegate
154 160
161# Build a set of wrapping HTML pages for easy testing of the
162# Javascript puzzles. These aren't quite the same as the versions that
163# will go on my live website, because those ones will substitute in a
164# different footer, and not have to link to the .js files with the
165# ../js/ prefix. But these ones should be good enough to just open
166# using a file:// URL in a browser after running a build, and make
167# sure the main functionality works.
168in puzzles do mkdir jstest
169in puzzles/jstest do ../html/jspage.pl --jspath=../js/ /dev/null ../html/*.html
170
155# Set up .htaccess containing a redirect for the archive filename. 171# Set up .htaccess containing a redirect for the archive filename.
156in puzzles do echo "AddType application/octet-stream .chm" > .htaccess 172in puzzles do echo "AddType application/octet-stream .chm" > .htaccess
157in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess 173in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess
158in puzzles do echo "AddType application/octet-stream .cnt" >> .htaccess 174in puzzles do echo "AddType application/octet-stream .cnt" >> .htaccess
159in . do set -- puzzles*.tar.gz; echo RedirectMatch temp '(.*/)'puzzles.tar.gz '$$1'"$$1" >> puzzles/.htaccess 175in . do set -- puzzles*.tar.gz; echo RedirectMatch temp '(.*/)'puzzles.tar.gz '$$1'"$$1" >> puzzles/.htaccess
160in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.msi '$$1'puzzles-$(Version)-installer.msi >> .htaccess 176in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.msi '$$1'puzzles-$(Version)-installer.msi >> .htaccess
161in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.exe '$$1'puzzles-$(Version)-installer.exe >> .htaccess
162 177
163# Phew, we're done. Deliver everything! 178# Phew, we're done. Deliver everything!
164deliver puzzles/icons/*-web.png $@ 179deliver puzzles/icons/*-web.png $@
@@ -172,9 +187,9 @@ deliver puzzles/puzzles.hlp $@
172deliver puzzles/puzzles.cnt $@ 187deliver puzzles/puzzles.cnt $@
173deliver puzzles/puzzles.zip $@ 188deliver puzzles/puzzles.zip $@
174deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi 189deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi
175deliver puzzles/Output/installer.exe puzzles-$(Version)-installer.exe
176deliver puzzles/*.jar java/$@ 190deliver puzzles/*.jar java/$@
177deliver puzzles/js/*.js js/$@ 191deliver puzzles/js/*.js js/$@
192deliver puzzles/jstest/*.html jstest/$@
178deliver puzzles/html/*.html html/$@ 193deliver puzzles/html/*.html html/$@
179deliver puzzles/html/*.pl html/$@ 194deliver puzzles/html/*.pl html/$@
180deliver puzzles/wwwspans.html $@ 195deliver 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 {
61 JMenuBar menubar = new JMenuBar(); 61 JMenuBar menubar = new JMenuBar();
62 JMenu jm; 62 JMenu jm;
63 menubar.add(jm = new JMenu("Game")); 63 menubar.add(jm = new JMenu("Game"));
64 addMenuItemWithKey(jm, "New", 'n'); 64 addMenuItemCallback(jm, "New", "jcallback_newgame_event");
65 addMenuItemCallback(jm, "Restart", "jcallback_restart_event"); 65 addMenuItemCallback(jm, "Restart", "jcallback_restart_event");
66 addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC); 66 addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC);
67 addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED); 67 addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED);
68 jm.addSeparator(); 68 jm.addSeparator();
69 addMenuItemWithKey(jm, "Undo", 'u'); 69 addMenuItemCallback(jm, "Undo", "jcallback_undo_event");
70 addMenuItemWithKey(jm, "Redo", 'r'); 70 addMenuItemCallback(jm, "Redo", "jcallback_redo_event");
71 jm.addSeparator(); 71 jm.addSeparator();
72 solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event"); 72 solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
73 solveCommand.setEnabled(false); 73 solveCommand.setEnabled(false);
74 if (mainWindow != null) { 74 if (mainWindow != null) {
75 jm.addSeparator(); 75 jm.addSeparator();
76 addMenuItemWithKey(jm, "Exit", 'q'); 76 addMenuItemCallback(jm, "Exit", "jcallback_quit_event");
77 } 77 }
78 menubar.add(typeMenu = new JMenu("Type")); 78 menubar.add(typeMenu = new JMenu("Type"));
79 typeMenu.setVisible(false); 79 typeMenu.setVisible(false);
@@ -126,7 +126,12 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
126 } 126 }
127 } 127 }
128 public void keyTyped(KeyEvent e) { 128 public void keyTyped(KeyEvent e) {
129 runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()}); 129 int key = e.getKeyChar();
130 if (key == 26 && e.isShiftDown() && e.isControlDown()) {
131 runtimeCall("jcallback_redo_event", new int[0]);
132 return;
133 }
134 runtimeCall("jcallback_key_event", new int[] {0, 0, key});
130 } 135 }
131 }); 136 });
132 pp.addMouseListener(new MouseAdapter() { 137 pp.addMouseListener(new MouseAdapter() {
@@ -217,10 +222,6 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
217 runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()}); 222 runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()});
218 } 223 }
219 224
220 private void addMenuItemWithKey(JMenu jm, String name, int key) {
221 addMenuItemCallback(jm, name, "jcallback_menu_key_event", key);
222 }
223
224 private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) { 225 private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) {
225 return addMenuItemCallback(jm, name, callback, new int[] {arg}, false); 226 return addMenuItemCallback(jm, name, callback, new int[] {arg}, false);
226 } 227 }
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 @@
17!makefile gnustep Makefile.gnustep 17!makefile gnustep Makefile.gnustep
18!makefile nestedvm Makefile.nestedvm 18!makefile nestedvm Makefile.nestedvm
19!makefile emcc Makefile.emcc 19!makefile emcc Makefile.emcc
20!makefile clangcl Makefile.clangcl
20 21
21!srcdir icons/ 22!srcdir icons/
22 23
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 @@
1\# File containing the magic HTML configuration directives to create
2\# an MS HTML Help project. We put this on the end of the Puzzles
3\# docs build command line to build the HHP and friends.
4
5\cfg{html-leaf-level}{infinite}
6\cfg{html-leaf-contains-contents}{false}
7\cfg{html-suppress-navlinks}{true}
8\cfg{html-suppress-address}{true}
9
10\cfg{html-contents-filename}{index.html}
11\cfg{html-template-filename}{%k.html}
12\cfg{html-template-fragment}{%k}
13
14\cfg{html-mshtmlhelp-chm}{puzzles.chm}
15\cfg{html-mshtmlhelp-project}{puzzles.hhp}
16\cfg{html-mshtmlhelp-contents}{puzzles.hhc}
17\cfg{html-mshtmlhelp-index}{puzzles.hhk}
18
19\cfg{html-body-end}{}
20
21\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.
1928 1928
1929This function may be used for both drawing and printing. 1929This function may be used for both drawing and printing.
1930 1930
1931If the specified thickness is less than 1.0, 1.0 is used.
1932This ensures that thin lines are visible even at small scales.
1933
1931\S{drawing-draw-text} \cw{draw_text()} 1934\S{drawing-draw-text} \cw{draw_text()}
1932 1935
1933\c void draw_text(drawing *dr, int x, int y, int fonttype, 1936\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)
90void draw_thick_line(drawing *dr, float thickness, 90void draw_thick_line(drawing *dr, float thickness,
91 float x1, float y1, float x2, float y2, int colour) 91 float x1, float y1, float x2, float y2, int colour)
92{ 92{
93 if (thickness < 1.0)
94 thickness = 1.0;
93 if (dr->api->draw_thick_line) { 95 if (dr->api->draw_thick_line) {
94 dr->api->draw_thick_line(dr->handle, thickness, 96 dr->api->draw_thick_line(dr->handle, thickness,
95 x1, y1, x2, y2, colour); 97 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,
310 keyevent = MOD_NUM_KEYPAD | '7'; 310 keyevent = MOD_NUM_KEYPAD | '7';
311 } else if (!strnullcmp(key, "PageUp") || keycode==33) { 311 } else if (!strnullcmp(key, "PageUp") || keycode==33) {
312 keyevent = MOD_NUM_KEYPAD | '9'; 312 keyevent = MOD_NUM_KEYPAD | '9';
313 } else if (shift && ctrl && (keycode & 0x1F) == 26) {
314 keyevent = UI_REDO;
313 } else if (chr && chr[0] && !chr[1]) { 315 } else if (chr && chr[0] && !chr[1]) {
314 keyevent = chr[0] & 0xFF; 316 keyevent = chr[0] & 0xFF;
315 } else if (keycode >= 96 && keycode < 106) { 317 } else if (keycode >= 96 && keycode < 106) {
@@ -323,10 +325,10 @@ void key(int keycode, int charcode, const char *key, const char *chr,
323 } 325 }
324 326
325 if (keyevent >= 0) { 327 if (keyevent >= 0) {
326 if (shift && keyevent >= 0x100) 328 if (shift && (keyevent >= 0x100 && !IS_UI_FAKE_KEY(keyevent)))
327 keyevent |= MOD_SHFT; 329 keyevent |= MOD_SHFT;
328 330
329 if (ctrl) { 331 if (ctrl && !IS_UI_FAKE_KEY(keyevent)) {
330 if (keyevent >= 0x100) 332 if (keyevent >= 0x100)
331 keyevent |= MOD_CTRL; 333 keyevent |= MOD_CTRL;
332 else 334 else
@@ -725,7 +727,7 @@ void command(int n)
725 update_undo_redo(); 727 update_undo_redo();
726 break; 728 break;
727 case 5: /* New Game */ 729 case 5: /* New Game */
728 midend_process_key(me, 0, 0, 'n'); 730 midend_process_key(me, 0, 0, UI_NEWGAME);
729 update_undo_redo(); 731 update_undo_redo();
730 js_focus_canvas(); 732 js_focus_canvas();
731 break; 733 break;
@@ -735,12 +737,12 @@ void command(int n)
735 js_focus_canvas(); 737 js_focus_canvas();
736 break; 738 break;
737 case 7: /* Undo */ 739 case 7: /* Undo */
738 midend_process_key(me, 0, 0, 'u'); 740 midend_process_key(me, 0, 0, UI_UNDO);
739 update_undo_redo(); 741 update_undo_redo();
740 js_focus_canvas(); 742 js_focus_canvas();
741 break; 743 break;
742 case 8: /* Redo */ 744 case 8: /* Redo */
743 midend_process_key(me, 0, 0, 'r'); 745 midend_process_key(me, 0, 0, UI_REDO);
744 update_undo_redo(); 746 update_undo_redo();
745 js_focus_canvas(); 747 js_focus_canvas();
746 break; 748 break;
@@ -757,6 +759,83 @@ void command(int n)
757} 759}
758 760
759/* ---------------------------------------------------------------------- 761/* ----------------------------------------------------------------------
762 * Called from JS to prepare a save-game file, and free one after it's
763 * been used.
764 */
765
766struct savefile_write_ctx {
767 char *buffer;
768 size_t pos;
769};
770
771static void savefile_write(void *vctx, void *buf, int len)
772{
773 struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)vctx;
774 if (ctx->buffer)
775 memcpy(ctx->buffer + ctx->pos, buf, len);
776 ctx->pos += len;
777}
778
779char *get_save_file(void)
780{
781 struct savefile_write_ctx ctx;
782 size_t size;
783
784 /* First pass, to count up the size */
785 ctx.buffer = NULL;
786 ctx.pos = 0;
787 midend_serialise(me, savefile_write, &ctx);
788 size = ctx.pos;
789
790 /* Second pass, to actually write out the data */
791 ctx.buffer = snewn(size, char);
792 ctx.pos = 0;
793 midend_serialise(me, savefile_write, &ctx);
794 assert(ctx.pos == size);
795
796 return ctx.buffer;
797}
798
799void free_save_file(char *buffer)
800{
801 sfree(buffer);
802}
803
804struct savefile_read_ctx {
805 const char *buffer;
806 int len_remaining;
807};
808
809static int savefile_read(void *vctx, void *buf, int len)
810{
811 struct savefile_read_ctx *ctx = (struct savefile_read_ctx *)vctx;
812 if (ctx->len_remaining < len)
813 return FALSE;
814 memcpy(buf, ctx->buffer, len);
815 ctx->len_remaining -= len;
816 ctx->buffer += len;
817 return TRUE;
818}
819
820void load_game(const char *buffer, int len)
821{
822 struct savefile_read_ctx ctx;
823 const char *err;
824
825 ctx.buffer = buffer;
826 ctx.len_remaining = len;
827 err = midend_deserialise(me, savefile_read, &ctx);
828
829 if (err) {
830 js_error_box(err);
831 } else {
832 select_appropriate_preset();
833 resize();
834 midend_redraw(me);
835 }
836}
837
838/* ----------------------------------------------------------------------
760 * Setup function called at page load time. It's called main() because 839 * Setup function called at page load time. It's called main() because
761 * that's the most convenient thing in Emscripten, but it's not main() 840 * that's the most convenient thing in Emscripten, but it's not main()
762 * in the usual sense of bounding the program's entire execution. 841 * 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, {
108 item.appendChild(tick); 108 item.appendChild(tick);
109 item.appendChild(document.createTextNode(name)); 109 item.appendChild(document.createTextNode(name));
110 var submenu = document.createElement("ul"); 110 var submenu = document.createElement("ul");
111 submenu.className = "left";
112 item.appendChild(submenu); 111 item.appendChild(submenu);
113 gametypesubmenus[menuid].appendChild(item); 112 gametypesubmenus[menuid].appendChild(item);
114 var toret = gametypesubmenus.length; 113 var toret = gametypesubmenus.length;
@@ -575,38 +574,7 @@ mergeInto(LibraryManager.library, {
575 * overlay on top of the rest of the puzzle web page. 574 * overlay on top of the rest of the puzzle web page.
576 */ 575 */
577 js_dialog_init: function(titletext) { 576 js_dialog_init: function(titletext) {
578 // Create an overlay on the page which darkens everything 577 dialog_init(Pointer_stringify(titletext));
579 // beneath it.
580 dlg_dimmer = document.createElement("div");
581 dlg_dimmer.style.width = "100%";
582 dlg_dimmer.style.height = "100%";
583 dlg_dimmer.style.background = '#000000';
584 dlg_dimmer.style.position = 'fixed';
585 dlg_dimmer.style.opacity = 0.3;
586 dlg_dimmer.style.top = dlg_dimmer.style.left = 0;
587 dlg_dimmer.style["z-index"] = 99;
588
589 // Now create a form which sits on top of that in turn.
590 dlg_form = document.createElement("form");
591 dlg_form.style.width = (window.innerWidth * 2 / 3) + "px";
592 dlg_form.style.opacity = 1;
593 dlg_form.style.background = '#ffffff';
594 dlg_form.style.color = '#000000';
595 dlg_form.style.position = 'absolute';
596 dlg_form.style.border = "2px solid black";
597 dlg_form.style.padding = "20px";
598 dlg_form.style.top = (window.innerHeight / 10) + "px";
599 dlg_form.style.left = (window.innerWidth / 6) + "px";
600 dlg_form.style["z-index"] = 100;
601
602 var title = document.createElement("p");
603 title.style.marginTop = "0px";
604 title.appendChild(document.createTextNode
605 (Pointer_stringify(titletext)));
606 dlg_form.appendChild(title);
607
608 dlg_return_funcs = [];
609 dlg_next_id = 0;
610 }, 578 },
611 579
612 /* 580 /*
@@ -701,29 +669,13 @@ mergeInto(LibraryManager.library, {
701 * everything else on the page. 669 * everything else on the page.
702 */ 670 */
703 js_dialog_launch: function() { 671 js_dialog_launch: function() {
704 // Put in the OK and Cancel buttons at the bottom. 672 dialog_launch(function(event) {
705 var button;
706
707 button = document.createElement("input");
708 button.type = "button";
709 button.value = "OK";
710 button.onclick = function(event) {
711 for (var i in dlg_return_funcs) 673 for (var i in dlg_return_funcs)
712 dlg_return_funcs[i](); 674 dlg_return_funcs[i]();
713 command(3); 675 command(3); // OK
714 } 676 }, function(event) {
715 dlg_form.appendChild(button); 677 command(4); // Cancel
716 678 });
717 button = document.createElement("input");
718 button.type = "button";
719 button.value = "Cancel";
720 button.onclick = function(event) {
721 command(4);
722 }
723 dlg_form.appendChild(button);
724
725 document.body.appendChild(dlg_dimmer);
726 document.body.appendChild(dlg_form);
727 }, 679 },
728 680
729 /* 681 /*
@@ -733,10 +685,7 @@ mergeInto(LibraryManager.library, {
733 * associated with it. 685 * associated with it.
734 */ 686 */
735 js_dialog_cleanup: function() { 687 js_dialog_cleanup: function() {
736 document.body.removeChild(dlg_dimmer); 688 dialog_cleanup();
737 document.body.removeChild(dlg_form);
738 dlg_dimmer = dlg_form = null;
739 onscreen_canvas.focus();
740 }, 689 },
741 690
742 /* 691 /*
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) {
129 item.className = ""; 129 item.className = "";
130} 130}
131 131
132// Dialog-box functions called from both C and JS.
133function dialog_init(titletext) {
134 // Create an overlay on the page which darkens everything
135 // beneath it.
136 dlg_dimmer = document.createElement("div");
137 dlg_dimmer.style.width = "100%";
138 dlg_dimmer.style.height = "100%";
139 dlg_dimmer.style.background = '#000000';
140 dlg_dimmer.style.position = 'fixed';
141 dlg_dimmer.style.opacity = 0.3;
142 dlg_dimmer.style.top = dlg_dimmer.style.left = 0;
143 dlg_dimmer.style["z-index"] = 99;
144
145 // Now create a form which sits on top of that in turn.
146 dlg_form = document.createElement("form");
147 dlg_form.style.width = (window.innerWidth * 2 / 3) + "px";
148 dlg_form.style.opacity = 1;
149 dlg_form.style.background = '#ffffff';
150 dlg_form.style.color = '#000000';
151 dlg_form.style.position = 'absolute';
152 dlg_form.style.border = "2px solid black";
153 dlg_form.style.padding = "20px";
154 dlg_form.style.top = (window.innerHeight / 10) + "px";
155 dlg_form.style.left = (window.innerWidth / 6) + "px";
156 dlg_form.style["z-index"] = 100;
157
158 var title = document.createElement("p");
159 title.style.marginTop = "0px";
160 title.appendChild(document.createTextNode(titletext));
161 dlg_form.appendChild(title);
162
163 dlg_return_funcs = [];
164 dlg_next_id = 0;
165}
166
167function dialog_launch(ok_function, cancel_function) {
168 // Put in the OK and Cancel buttons at the bottom.
169 var button;
170
171 if (ok_function) {
172 button = document.createElement("input");
173 button.type = "button";
174 button.value = "OK";
175 button.onclick = ok_function;
176 dlg_form.appendChild(button);
177 }
178
179 if (cancel_function) {
180 button = document.createElement("input");
181 button.type = "button";
182 button.value = "Cancel";
183 button.onclick = cancel_function;
184 dlg_form.appendChild(button);
185 }
186
187 document.body.appendChild(dlg_dimmer);
188 document.body.appendChild(dlg_form);
189}
190
191function dialog_cleanup() {
192 document.body.removeChild(dlg_dimmer);
193 document.body.removeChild(dlg_form);
194 dlg_dimmer = dlg_form = null;
195 onscreen_canvas.focus();
196}
197
132// Init function called from body.onload. 198// Init function called from body.onload.
133function initPuzzle() { 199function initPuzzle() {
134 // Construct the off-screen canvas used for double buffering. 200 // Construct the off-screen canvas used for double buffering.
@@ -230,6 +296,58 @@ function initPuzzle() {
230 command(9); 296 command(9);
231 }; 297 };
232 298
299 // 'number' is used for C pointers
300 get_save_file = Module.cwrap('get_save_file', 'number', []);
301 free_save_file = Module.cwrap('free_save_file', 'void', ['number']);
302 load_game = Module.cwrap('load_game', 'void', ['string', 'number']);
303
304 document.getElementById("save").onclick = function(event) {
305 if (dlg_dimmer === null) {
306 var savefile_ptr = get_save_file();
307 var savefile_text = Pointer_stringify(savefile_ptr);
308 free_save_file(savefile_ptr);
309 dialog_init("Download saved-game file");
310 dlg_form.appendChild(document.createTextNode(
311 "Click to download the "));
312 var a = document.createElement("a");
313 a.download = "puzzle.sav";
314 a.href = "data:application/octet-stream," +
315 encodeURIComponent(savefile_text);
316 a.appendChild(document.createTextNode("saved-game file"));
317 dlg_form.appendChild(a);
318 dlg_form.appendChild(document.createTextNode("."));
319 dlg_form.appendChild(document.createElement("br"));
320 dialog_launch(function(event) {
321 dialog_cleanup();
322 });
323 }
324 };
325
326 document.getElementById("load").onclick = function(event) {
327 if (dlg_dimmer === null) {
328 dialog_init("Upload saved-game file");
329 var input = document.createElement("input");
330 input.type = "file";
331 input.multiple = false;
332 dlg_form.appendChild(input);
333 dlg_form.appendChild(document.createElement("br"));
334 dialog_launch(function(event) {
335 if (input.files.length == 1) {
336 var file = input.files.item(0);
337 var reader = new FileReader();
338 reader.addEventListener("loadend", function() {
339 var string = reader.result;
340 load_game(string, string.length);
341 });
342 reader.readAsBinaryString(file);
343 }
344 dialog_cleanup();
345 }, function(event) {
346 dialog_cleanup();
347 });
348 }
349 };
350
233 gametypelist = document.getElementById("gametype"); 351 gametypelist = document.getElementById("gametype");
234 gametypesubmenus.push(gametypelist); 352 gametypesubmenus.push(gametypelist);
235 353
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 @@
18 '_timer_callback', 18 '_timer_callback',
19 // Callback from button presses in the UI outside the canvas 19 // Callback from button presses in the UI outside the canvas
20 '_command', 20 '_command',
21 // Game-saving and game-loading functions
22 '_get_save_file',
23 '_free_save_file',
24 '_load_game',
21 // Callbacks to return values from dialog boxes 25 // Callbacks to return values from dialog boxes
22 '_dlg_return_sval', 26 '_dlg_return_sval',
23 '_dlg_return_ival', 27 '_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 {
140 */ 140 */
141struct frontend { 141struct frontend {
142 GtkWidget *window; 142 GtkWidget *window;
143 GtkAccelGroup *accelgroup; 143 GtkAccelGroup *dummy_accelgroup;
144 GtkWidget *area; 144 GtkWidget *area;
145 GtkWidget *statusbar; 145 GtkWidget *statusbar;
146 GtkWidget *menubar; 146 GtkWidget *menubar;
@@ -1160,16 +1160,6 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
1160 if (!backing_store_ok(fe)) 1160 if (!backing_store_ok(fe))
1161 return TRUE; 1161 return TRUE;
1162 1162
1163#if !GTK_CHECK_VERSION(2,0,0)
1164 /* Gtk 1.2 passes a key event to this function even if it's also
1165 * defined as an accelerator.
1166 * Gtk 2 doesn't do this, and this function appears not to exist there. */
1167 if (fe->accelgroup &&
1168 gtk_accel_group_get_entry(fe->accelgroup,
1169 event->keyval, event->state))
1170 return TRUE;
1171#endif
1172
1173 /* Handle mnemonics. */ 1163 /* Handle mnemonics. */
1174 if (gtk_window_activate_key(GTK_WINDOW(fe->window), event)) 1164 if (gtk_window_activate_key(GTK_WINDOW(fe->window), event))
1175 return TRUE; 1165 return TRUE;
@@ -1216,6 +1206,8 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
1216 event->keyval == GDK_KEY_Delete || 1206 event->keyval == GDK_KEY_Delete ||
1217 event->keyval == GDK_KEY_KP_Delete) 1207 event->keyval == GDK_KEY_KP_Delete)
1218 keyval = '\177'; 1208 keyval = '\177';
1209 else if ((event->keyval == 'z' || event->keyval == 'Z') && shift && ctrl)
1210 keyval = UI_REDO;
1219 else if (event->string[0] && !event->string[1]) 1211 else if (event->string[0] && !event->string[1])
1220 keyval = (unsigned char)event->string[0]; 1212 keyval = (unsigned char)event->string[0];
1221 else 1213 else
@@ -2348,32 +2340,34 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
2348#endif 2340#endif
2349} 2341}
2350 2342
2351static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, 2343static GtkWidget *add_menu_ui_item(
2352 char *text, int key) 2344 frontend *fe, GtkContainer *cont, char *text, int action,
2345 int accel_key, int accel_keyqual)
2353{ 2346{
2354 GtkWidget *menuitem = gtk_menu_item_new_with_label(text); 2347 GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
2355 int keyqual;
2356 gtk_container_add(cont, menuitem); 2348 gtk_container_add(cont, menuitem);
2357 g_object_set_data(G_OBJECT(menuitem), "user-data", GINT_TO_POINTER(key)); 2349 g_object_set_data(G_OBJECT(menuitem), "user-data",
2350 GINT_TO_POINTER(action));
2358 g_signal_connect(G_OBJECT(menuitem), "activate", 2351 g_signal_connect(G_OBJECT(menuitem), "activate",
2359 G_CALLBACK(menu_key_event), fe); 2352 G_CALLBACK(menu_key_event), fe);
2360 switch (key & ~0x1F) { 2353
2361 case 0x00: 2354 if (accel_key) {
2362 key += 0x60; 2355 /*
2363 keyqual = GDK_CONTROL_MASK; 2356 * Display a keyboard accelerator alongside this menu item.
2364 break; 2357 * Actually this won't be processed via the usual GTK
2365 case 0x40: 2358 * accelerator system, because we add it to a dummy
2366 key += 0x20; 2359 * accelerator group which is never actually activated on the
2367 keyqual = GDK_SHIFT_MASK; 2360 * main window; this permits back ends to override special
2368 break; 2361 * keys like 'n' and 'r' and 'u' in some UI states. So
2369 default: 2362 * whatever keystroke we display here will still go to
2370 keyqual = 0; 2363 * key_event and be handled in the normal way.
2371 break; 2364 */
2365 gtk_widget_add_accelerator(menuitem,
2366 "activate", fe->dummy_accelgroup,
2367 accel_key, accel_keyqual,
2368 GTK_ACCEL_VISIBLE | GTK_ACCEL_LOCKED);
2372 } 2369 }
2373 gtk_widget_add_accelerator(menuitem, 2370
2374 "activate", fe->accelgroup,
2375 key, keyqual,
2376 GTK_ACCEL_VISIBLE);
2377 gtk_widget_show(menuitem); 2371 gtk_widget_show(menuitem);
2378 return menuitem; 2372 return menuitem;
2379} 2373}
@@ -2535,8 +2529,11 @@ static frontend *new_window(char *arg, int argtype, char **error)
2535 gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox)); 2529 gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox));
2536 gtk_widget_show(GTK_WIDGET(vbox)); 2530 gtk_widget_show(GTK_WIDGET(vbox));
2537 2531
2538 fe->accelgroup = gtk_accel_group_new(); 2532 fe->dummy_accelgroup = gtk_accel_group_new();
2539 gtk_window_add_accel_group(GTK_WINDOW(fe->window), fe->accelgroup); 2533 /*
2534 * Intentionally _not_ added to the window via
2535 * gtk_window_add_accel_group; see menu_key_event
2536 */
2540 2537
2541 hbox = GTK_BOX(gtk_hbox_new(FALSE, 0)); 2538 hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
2542 gtk_box_pack_start(vbox, GTK_WIDGET(hbox), FALSE, FALSE, 0); 2539 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)
2553 menu = gtk_menu_new(); 2550 menu = gtk_menu_new();
2554 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); 2551 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
2555 2552
2556 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n'); 2553 add_menu_ui_item(fe, GTK_CONTAINER(menu), "New", UI_NEWGAME, 'n', 0);
2557 2554
2558 menuitem = gtk_menu_item_new_with_label("Restart"); 2555 menuitem = gtk_menu_item_new_with_label("Restart");
2559 gtk_container_add(GTK_CONTAINER(menu), menuitem); 2556 gtk_container_add(GTK_CONTAINER(menu), menuitem);
@@ -2623,8 +2620,8 @@ static frontend *new_window(char *arg, int argtype, char **error)
2623 gtk_widget_show(menuitem); 2620 gtk_widget_show(menuitem);
2624#ifndef STYLUS_BASED 2621#ifndef STYLUS_BASED
2625 add_menu_separator(GTK_CONTAINER(menu)); 2622 add_menu_separator(GTK_CONTAINER(menu));
2626 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u'); 2623 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);
2627 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", 'r'); 2624 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Redo", UI_REDO, 'r', 0);
2628#endif 2625#endif
2629 if (thegame.can_format_as_text_ever) { 2626 if (thegame.can_format_as_text_ever) {
2630 add_menu_separator(GTK_CONTAINER(menu)); 2627 add_menu_separator(GTK_CONTAINER(menu));
@@ -2646,7 +2643,7 @@ static frontend *new_window(char *arg, int argtype, char **error)
2646 gtk_widget_show(menuitem); 2643 gtk_widget_show(menuitem);
2647 } 2644 }
2648 add_menu_separator(GTK_CONTAINER(menu)); 2645 add_menu_separator(GTK_CONTAINER(menu));
2649 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q'); 2646 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Exit", UI_QUIT, 'q', 0);
2650 2647
2651 menuitem = gtk_menu_item_new_with_mnemonic("_Help"); 2648 menuitem = gtk_menu_item_new_with_mnemonic("_Help");
2652 gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem); 2649 gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
@@ -2664,7 +2661,7 @@ static frontend *new_window(char *arg, int argtype, char **error)
2664#ifdef STYLUS_BASED 2661#ifdef STYLUS_BASED
2665 menuitem=gtk_button_new_with_mnemonic("_Redo"); 2662 menuitem=gtk_button_new_with_mnemonic("_Redo");
2666 g_object_set_data(G_OBJECT(menuitem), "user-data", 2663 g_object_set_data(G_OBJECT(menuitem), "user-data",
2667 GINT_TO_POINTER((int)('r'))); 2664 GINT_TO_POINTER(UI_REDO));
2668 g_signal_connect(G_OBJECT(menuitem), "clicked", 2665 g_signal_connect(G_OBJECT(menuitem), "clicked",
2669 G_CALLBACK(menu_key_event), fe); 2666 G_CALLBACK(menu_key_event), fe);
2670 gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0); 2667 gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0);
@@ -2672,7 +2669,7 @@ static frontend *new_window(char *arg, int argtype, char **error)
2672 2669
2673 menuitem=gtk_button_new_with_mnemonic("_Undo"); 2670 menuitem=gtk_button_new_with_mnemonic("_Undo");
2674 g_object_set_data(G_OBJECT(menuitem), "user-data", 2671 g_object_set_data(G_OBJECT(menuitem), "user-data",
2675 GINT_TO_POINTER((int)('u'))); 2672 GINT_TO_POINTER(UI_UNDO));
2676 g_signal_connect(G_OBJECT(menuitem), "clicked", 2673 g_signal_connect(G_OBJECT(menuitem), "clicked",
2677 G_CALLBACK(menu_key_event), fe); 2674 G_CALLBACK(menu_key_event), fe);
2678 gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0); 2675 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 @@
3use strict; 3use strict;
4use warnings; 4use warnings;
5 5
6my $jspath = "";
7while ($ARGV[0] =~ /^-/) {
8 my $opt = shift @ARGV;
9 last if $opt eq "--";
10 if ($opt =~ /^--jspath=(.+)$/) {
11 $jspath = $1;
12 } else {
13 die "jspage.pl: unrecognised option '$opt'\n";
14 }
15}
16
6open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n"; 17open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n";
7my $footer = ""; 18my $footer = "";
8$footer .= $_ while <$footerfile>; 19$footer .= $_ while <$footerfile>;
@@ -62,7 +73,7 @@ EOF
62<head> 73<head>
63<meta http-equiv="Content-Type" content="text/html; charset=ASCII" /> 74<meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
64<title>${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection</title> 75<title>${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection</title>
65<script type="text/javascript" src="${filename}.js"></script> 76<script type="text/javascript" src="${jspath}${filename}.js"></script>
66<style class="text/css"> 77<style class="text/css">
67/* Margins and centring on the top-level div for the game menu */ 78/* Margins and centring on the top-level div for the game menu */
68#gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center } 79#gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center }
@@ -103,6 +114,15 @@ EOF
103 color: rgba(0,0,0,0.5); 114 color: rgba(0,0,0,0.5);
104} 115}
105 116
117#gamemenu ul li.separator {
118 color: transparent;
119 border: 0;
120}
121
122#gamemenu ul li.afterseparator {
123 border-left: 1px solid rgba(0,0,0,0.3);
124}
125
106#gamemenu ul li:first-of-type { 126#gamemenu ul li:first-of-type {
107 /* Reinstate the left border for the leftmost top-level menu item */ 127 /* Reinstate the left border for the leftmost top-level menu item */
108 border-left: 1px solid rgba(0,0,0,0.3); 128 border-left: 1px solid rgba(0,0,0,0.3);
@@ -196,14 +216,19 @@ ${unfinishedpara}
196 216
197<hr> 217<hr>
198<div id="puzzle" style="display: none"> 218<div id="puzzle" style="display: none">
199<div id="gamemenu"><ul><li id="new">New game</li 219<div id="gamemenu"><ul><li>Game...<ul
220><li id="specific">Enter game ID</li
221><li id="random">Enter random seed</li
222><li id="save">Download save file</li
223><li id="load">Upload save file</li
224></ul></li
225><li>Type...<ul id="gametype"></ul></li
226><li class="separator"></li
227><li id="new" class="afterseparator">New game</li
200><li id="restart">Restart game</li 228><li id="restart">Restart game</li
201><li id="undo">Undo move</li 229><li id="undo">Undo move</li
202><li id="redo">Redo move</li 230><li id="redo">Redo move</li
203><li id="solve">Solve game</li 231><li id="solve">Solve game</li
204><li id="specific">Enter game ID</li
205><li id="random">Enter random seed</li
206><li>Select game type<ul id="gametype" class="left"></ul></li
207></ul></div> 232></ul></div>
208<div align=center> 233<div align=center>
209 <div id="resizable" style="position:relative; left:0; top:0"> 234 <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);
288 {amin, omin, \ 288 {amin, omin, \
289 "Width and height for this grid type must both be at least " #amin, \ 289 "Width and height for this grid type must both be at least " #amin, \
290 "At least one of width and height for this grid type must be at least " #omin,}, 290 "At least one of width and height for this grid type must be at least " #omin,},
291enum { GRIDLIST(GRID_LOOPYTYPE) }; 291enum { GRIDLIST(GRID_LOOPYTYPE) LOOPY_GRID_DUMMY_TERMINATOR };
292static char const *const gridnames[] = { GRIDLIST(GRID_NAME) }; 292static char const *const gridnames[] = { GRIDLIST(GRID_NAME) };
293#define GRID_CONFIGS GRIDLIST(GRID_CONFIG) 293#define GRID_CONFIGS GRIDLIST(GRID_CONFIG)
294static grid_type grid_types[] = { GRIDLIST(GRID_GRIDTYPE) }; 294static 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)
590 int type = MOVE, gottype = FALSE, ret = 1; 590 int type = MOVE, gottype = FALSE, ret = 1;
591 float anim_time; 591 float anim_time;
592 game_state *s; 592 game_state *s;
593 char *movestr; 593 char *movestr = NULL;
594 594
595 movestr = 595 if (!IS_UI_FAKE_KEY(button)) {
596 me->ourgame->interpret_move(me->states[me->statepos-1].state, 596 movestr = me->ourgame->interpret_move(
597 me->ui, me->drawstate, x, y, button); 597 me->states[me->statepos-1].state,
598 me->ui, me->drawstate, x, y, button);
599 }
598 600
599 if (!movestr) { 601 if (!movestr) {
600 if (button == 'n' || button == 'N' || button == '\x0E') { 602 if (button == 'n' || button == 'N' || button == '\x0E' ||
603 button == UI_NEWGAME) {
601 midend_new_game(me); 604 midend_new_game(me);
602 midend_redraw(me); 605 midend_redraw(me);
603 goto done; /* never animate */ 606 goto done; /* never animate */
604 } else if (button == 'u' || button == 'U' || 607 } else if (button == 'u' || button == 'U' ||
605 button == '\x1A' || button == '\x1F') { 608 button == '\x1A' || button == '\x1F' ||
609 button == UI_UNDO) {
606 midend_stop_anim(me); 610 midend_stop_anim(me);
607 type = me->states[me->statepos-1].movetype; 611 type = me->states[me->statepos-1].movetype;
608 gottype = TRUE; 612 gottype = TRUE;
609 if (!midend_undo(me)) 613 if (!midend_undo(me))
610 goto done; 614 goto done;
611 } else if (button == 'r' || button == 'R' || 615 } else if (button == 'r' || button == 'R' ||
612 button == '\x12' || button == '\x19') { 616 button == '\x12' || button == '\x19' ||
617 button == UI_REDO) {
613 midend_stop_anim(me); 618 midend_stop_anim(me);
614 if (!midend_redo(me)) 619 if (!midend_redo(me))
615 goto done; 620 goto done;
616 } else if (button == '\x13' && me->ourgame->can_solve) { 621 } else if ((button == '\x13' || button == UI_SOLVE) &&
622 me->ourgame->can_solve) {
617 if (midend_solve(me)) 623 if (midend_solve(me))
618 goto done; 624 goto done;
619 } else if (button == 'q' || button == 'Q' || button == '\x11') { 625 } else if (button == 'q' || button == 'Q' || button == '\x11' ||
626 button == UI_QUIT) {
620 ret = 0; 627 ret = 0;
621 goto done; 628 goto done;
622 } else 629 } else
@@ -2059,6 +2066,8 @@ char *midend_deserialise(midend *me,
2059 me->ourgame->new_drawstate(me->drawing, 2066 me->ourgame->new_drawstate(me->drawing,
2060 me->states[me->statepos-1].state); 2067 me->states[me->statepos-1].state);
2061 midend_size_new_drawstate(me); 2068 midend_size_new_drawstate(me);
2069 if (me->game_id_change_notify_function)
2070 me->game_id_change_notify_function(me->game_id_change_notify_ctx);
2062 2071
2063 ret = NULL; /* success! */ 2072 ret = NULL; /* success! */
2064 2073
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,
2963 float animtime, float flashtime) 2963 float animtime, float flashtime)
2964{ 2964{
2965 int x, y; 2965 int x, y;
2966 int mines, markers, bg; 2966 int mines, markers, closed, bg;
2967 int cx = -1, cy = -1, cmoved; 2967 int cx = -1, cy = -1, cmoved;
2968 2968
2969 if (flashtime) { 2969 if (flashtime) {
@@ -3013,13 +3013,15 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
3013 3013
3014 /* 3014 /*
3015 * Now draw the tiles. Also in this loop, count up the number 3015 * Now draw the tiles. Also in this loop, count up the number
3016 * of mines and mine markers. 3016 * of mines, mine markers, and closed squares.
3017 */ 3017 */
3018 mines = markers = 0; 3018 mines = markers = closed = 0;
3019 for (y = 0; y < ds->h; y++) 3019 for (y = 0; y < ds->h; y++)
3020 for (x = 0; x < ds->w; x++) { 3020 for (x = 0; x < ds->w; x++) {
3021 int v = state->grid[y*ds->w+x], cc = 0; 3021 int v = state->grid[y*ds->w+x], cc = 0;
3022 3022
3023 if (v < 0)
3024 closed++;
3023 if (v == -1) 3025 if (v == -1)
3024 markers++; 3026 markers++;
3025 if (state->layout->mines && state->layout->mines[y*ds->w+x]) 3027 if (state->layout->mines && state->layout->mines[y*ds->w+x])
@@ -3078,7 +3080,42 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
3078 else 3080 else
3079 sprintf(statusbar, "COMPLETED!"); 3081 sprintf(statusbar, "COMPLETED!");
3080 } else { 3082 } else {
3083 int safe_closed = closed - mines;
3081 sprintf(statusbar, "Marked: %d / %d", markers, mines); 3084 sprintf(statusbar, "Marked: %d / %d", markers, mines);
3085 if (safe_closed > 0 && safe_closed <= 9) {
3086 /*
3087 * In the situation where there's a very small number
3088 * of _non_-mine squares left unopened, it's helpful
3089 * to mention that number in the status line, to save
3090 * the player from having to count it up
3091 * painstakingly. This is particularly important if
3092 * the player has turned up the mine density to the
3093 * point where game generation resorts to its weird
3094 * pathological fallback of a very dense mine area
3095 * with a clearing in the middle, because that often
3096 * leads to a deduction you can only make by knowing
3097 * that there is (say) exactly one non-mine square to
3098 * find, and it's a real pain to have to count up two
3099 * large numbers of squares and subtract them to get
3100 * that value of 1.
3101 *
3102 * The threshold value of 8 for displaying this
3103 * information is because that's the largest number of
3104 * non-mine squares that might conceivably fit around
3105 * a single central square, and the most likely way to
3106 * _use_ this information is to observe that if all
3107 * the remaining safe squares are adjacent to _this_
3108 * square then everything else can be immediately
3109 * flagged as a mine.
3110 */
3111 if (safe_closed == 1) {
3112 sprintf(statusbar + strlen(statusbar),
3113 " (1 safe square remains)");
3114 } else {
3115 sprintf(statusbar + strlen(statusbar),
3116 " (%d safe squares remain)", safe_closed);
3117 }
3118 }
3082 } 3119 }
3083 if (ui->deaths) 3120 if (ui->deaths)
3084 sprintf(statusbar + strlen(statusbar), 3121 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)
375/* another kludge for platforms without %g support in *printf() */ 375/* another kludge for platforms without %g support in *printf() */
376int ftoa(char *buf, float f) 376int ftoa(char *buf, float f)
377{ 377{
378 return sprintf(buf, "%d.%06d", (int)f, (int)((f - (int)f)*1e6)); 378 return sprintf(buf, "%d.%06d", (int)f, abs((int)((f - (int)f)*1e6)));
379} 379}
380 380
381/* vim: set shiftwidth=4 tabstop=8: */ 381/* 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($) {
319 # Returns true if the argument is a known makefile type. Otherwise, 319 # Returns true if the argument is a known makefile type. Otherwise,
320 # prints a warning and returns false; 320 # prints a warning and returns false;
321 if (grep { $type eq $_ } 321 if (grep { $type eq $_ }
322 ("vc","vcproj","cygwin","borland","lcc","gtk","am","mpw","nestedvm","osx","wce","gnustep","emcc")) { 322 ("vc","vcproj","cygwin","borland","lcc","gtk","am","mpw","nestedvm","osx","wce","gnustep","emcc","clangcl")) {
323 return 1; 323 return 1;
324 } 324 }
325 warn "$.:unknown makefile type '$type'\n"; 325 warn "$.:unknown makefile type '$type'\n";
@@ -503,6 +503,151 @@ $orig_dir = cwd;
503 503
504# Now we're ready to output the actual Makefiles. 504# Now we're ready to output the actual Makefiles.
505 505
506if (defined $makefiles{'clangcl'}) {
507 $mftyp = 'clangcl';
508 $dirpfx = &dirpfx($makefiles{'clangcl'}, "/");
509
510 ##-- Makefile for cross-compiling using clang-cl, lld-link, and
511 ## MinGW's windres for resource compilation.
512 #
513 # This makefile allows a complete Linux-based cross-compile, but
514 # using the real Visual Studio header files and libraries. In
515 # order to run it, you will need:
516 #
517 # - MinGW windres on your PATH.
518 # * On Ubuntu as of 16.04, you can apt-get install
519 # binutils-mingw-w64-x86-64 and binutils-mingw-w64-i686
520 # which will provide (respectively) 64- and 32-bit versions,
521 # under the names to which RCCMD is defined below.
522 # - clang-cl and lld-link on your PATH.
523 # * I built these from the up-to-date LLVM project trunk git
524 # repositories, as of 2017-02-05.
525 # - case-mashed copies of the Visual Studio include directories.
526 # * On a real VS installation, run vcvars32.bat and look at
527 # the resulting value of %INCLUDE%. Take a full copy of each
528 # of those directories, and inside the copy, for each
529 # include file that has an uppercase letter in its name,
530 # make a lowercased symlink to it. Additionally, one of the
531 # directories will contain files called driverspecs.h and
532 # specstrings.h, and those will need symlinks called
533 # DriverSpecs.h and SpecStrings.h.
534 # * Now, on Linux, define the environment variable INCLUDE to
535 # be a list, separated by *semicolons* (in the Windows
536 # style), of those directories, but before all of them you
537 # must also include lib/clang/5.0.0/include from the clang
538 # installation area (which contains in particular a
539 # clang-compatible stdarg.h overriding the Visual Studio
540 # one).
541 # - similarly case-mashed copies of the library directories.
542 # * Again, on a real VS installation, run vcvars32 or
543 # vcvarsx86_amd64 (as appropriate), look at %LIB%, make a
544 # copy of each directory, and provide symlinks within that
545 # directory so that all the files can be opened as
546 # lowercase.
547 # * Then set LIB to be a semicolon-separated list of those
548 # directories (but you'll need to change which set of
549 # directories depending on whether you want to do a 32-bit
550 # or 64-bit build).
551 # - for a 64-bit build, set 'Platform=x64' in the environment as
552 # well, or else on the make command line.
553 # * This is a variable understood only by this makefile - none
554 # of the tools we invoke will know it - but it's consistent
555 # with the way the VS scripts like vcvarsx86_amd64.bat set
556 # things up, and since the environment has to change
557 # _anyway_ between 32- and 64-bit builds (different set of
558 # paths in $LIB) it's reasonable to have the choice of
559 # compilation target driven by another environment variable
560 # set in parallel with that one.
561 # - for older versions of the VS libraries you may also have to
562 # set EXTRA_console and/or EXTRA_windows to the name of an
563 # object file manually extracted from one of those libraries.
564 # * This is because old VS seems to manage its startup code by
565 # having libcmt.lib contain lots of *crt0.obj objects, one
566 # for each possible user entry point (main, WinMain and the
567 # wide-char versions of both), of which the linker arranges
568 # to include the right one by special-case code. But lld
569 # only seems to mimic half of that code - it does include
570 # the right crt0 object, but it doesn't also deliberately
571 # _avoid_ including the _wrong_ ones, and since all those
572 # objects define a common set of global symbols for other
573 # parts of the library to use, lld may well select an
574 # arbitrary one of them the first time it sees a reference
575 # to one of those global symbols, and then later also select
576 # the _right_ one for the application's entry point, causing
577 # a multiple-definitions crash.
578 # * So the workaround is to explicitly include the right
579 # *crt0.obj file on the linker command line before lld even
580 # begins searching libraries. Hence, for a console
581 # application, you might extract crt0.obj from the library
582 # in question and set EXTRA_console=crt0.obj, and for a GUI
583 # application, do the same with wincrt0.obj. Then this
584 # makefile will include the right one of those objects
585 # alongside the matching /subsystem linker option.
586
587 open OUT, ">$makefiles{'clangcl'}"; select OUT;
588 print
589 "# Makefile for cross-compiling $project_name using clang-cl, lld-link,\n".
590 "# and MinGW's windres, using GNU make on Linux.\n".
591 "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
592 "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
593 print $help;
594 print
595 "\n".
596 "CCCMD = clang-cl\n".
597 "ifeq (\$(Platform),x64)\n".
598 "CCTARGET = x86_64-pc-windows-msvc18.0.0\n".
599 "RCCMD = x86_64-w64-mingw32-windres\n".
600 "else\n".
601 "CCTARGET = i386-pc-windows-msvc18.0.0\n".
602 "RCCMD = i686-w64-mingw32-windres\n".
603 "endif\n".
604 "CC = \$(CCCMD) --target=\$(CCTARGET)\n".
605 &splitline("RC = \$(RCCMD) --preprocessor=\$(CCCMD) ".
606 "--preprocessor-arg=/TC --preprocessor-arg=/E")."\n".
607 "LD = lld-link\n".
608 "\n".
609 "# C compilation flags\n".
610 &splitline("CFLAGS = /nologo /W3 /O1 " .
611 (join " ", map {"-I$dirpfx$_"} @srcdirs) .
612 " /D_WINDOWS /D_WIN32_WINDOWS=0x401 /DWINVER=0x401 ".
613 "/D_CRT_SECURE_NO_WARNINGS")."\n".
614 "LFLAGS = /incremental:no /dynamicbase /nxcompat\n".
615 &splitline("RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs).
616 " -DWIN32 -D_WIN32 -DWINVER=0x0400 --define MINGW32_FIX=1")."\n".
617 "\n".
618 "\n";
619 print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C"));
620 print "\n\n";
621 foreach $p (&prognames("G:C")) {
622 ($prog, $type) = split ",", $p;
623 $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef);
624 print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n";
625
626 $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib");
627 $subsys = ($type eq "G") ? "windows" : "console";
628 print &splitline("\t\$(LD) \$(LFLAGS) \$(XLFLAGS) ".
629 "/out:\$(BUILDDIR)$prog.exe ".
630 "/lldmap:\$(BUILDDIR)$prog.map ".
631 "/subsystem:$subsys\$(SUBSYSVER) ".
632 "\$(EXTRA_$subsys) $objstr")."\n\n";
633 }
634 foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "/", "vc")) {
635 print &splitline(sprintf("%s: %s", $d->{obj},
636 join " ", @{$d->{deps}})), "\n";
637 if ($d->{obj} =~ /\.res$/) {
638 print "\t\$(RC) \$(RCFLAGS) ".$d->{deps}->[0]." -o ".$d->{obj}."\n\n";
639 } else {
640 $deflist = join "", map { " /D$_" } @{$d->{defs}};
641 print "\t\$(CC) /Fo\$(BUILDDIR)".$d->{obj}." \$(COMPAT) \$(CFLAGS) \$(XFLAGS)$deflist /c \$<\n\n";
642 }
643 }
644 print "\nclean:\n".
645 &splitline("\trm -f \$(BUILDDIR)*.obj \$(BUILDDIR)*.exe ".
646 "\$(BUILDDIR)*.res \$(BUILDDIR)*.map ".
647 "\$(BUILDDIR)*.exe.manifest")."\n";
648 select STDOUT; close OUT;
649}
650
506if (defined $makefiles{'cygwin'}) { 651if (defined $makefiles{'cygwin'}) {
507 $mftyp = 'cygwin'; 652 $mftyp = 'cygwin';
508 $dirpfx = &dirpfx($makefiles{'cygwin'}, "/"); 653 $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)
305 return fe->cfgret; 305 return fe->cfgret;
306} 306}
307 307
308int jcallback_menu_key_event(int key) 308int jcallback_newgame_event(void)
309{ 309{
310 frontend *fe = (frontend *)_fe; 310 frontend *fe = (frontend *)_fe;
311 if (!midend_process_key(fe->me, 0, 0, key)) 311 if (!midend_process_key(fe->me, 0, 0, UI_NEWGAME))
312 return 42;
313 return 0;
314}
315
316int jcallback_undo_event(void)
317{
318 frontend *fe = (frontend *)_fe;
319 if (!midend_process_key(fe->me, 0, 0, UI_UNDO))
320 return 42;
321 return 0;
322}
323
324int jcallback_redo_event(void)
325{
326 frontend *fe = (frontend *)_fe;
327 if (!midend_process_key(fe->me, 0, 0, UI_REDO))
328 return 42;
329 return 0;
330}
331
332int jcallback_quit_event(void)
333{
334 frontend *fe = (frontend *)_fe;
335 if (!midend_process_key(fe->me, 0, 0, UI_QUIT))
312 return 42; 336 return 42;
313 return 0; 337 return 0;
314} 338}
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 @@
27#define USE_DRAGGING 27#define USE_DRAGGING
28#endif 28#endif
29 29
30#define MATMUL(xr,yr,m,x,y) do { \
31 float rx, ry, xx = (x), yy = (y), *mat = (m); \
32 rx = mat[0] * xx + mat[2] * yy; \
33 ry = mat[1] * xx + mat[3] * yy; \
34 (xr) = rx; (yr) = ry; \
35} while (0)
36
37/* Direction and other bitfields */ 30/* Direction and other bitfields */
38#define R 0x01 31#define R 0x01
39#define U 0x02 32#define U 0x02
@@ -65,7 +58,7 @@
65 58
66#define PREFERRED_TILE_SIZE 32 59#define PREFERRED_TILE_SIZE 32
67#define TILE_SIZE (ds->tilesize) 60#define TILE_SIZE (ds->tilesize)
68#define TILE_BORDER 1 61#define LINE_THICK ((TILE_SIZE+47)/48)
69#ifdef SMALL_SCREEN 62#ifdef SMALL_SCREEN
70#define WINDOW_OFFSET 4 63#define WINDOW_OFFSET 4
71#else 64#else
@@ -75,13 +68,6 @@
75#define ROTATE_TIME 0.13F 68#define ROTATE_TIME 0.13F
76#define FLASH_FRAME 0.07F 69#define FLASH_FRAME 0.07F
77 70
78/* Transform physical coords to game coords using game_drawstate ds */
79#define GX(x) (((x) + ds->org_x) % ds->width)
80#define GY(y) (((y) + ds->org_y) % ds->height)
81/* ...and game coords to physical coords */
82#define RX(x) (((x) + ds->width - ds->org_x) % ds->width)
83#define RY(y) (((y) + ds->height - ds->org_y) % ds->height)
84
85enum { 71enum {
86 COL_BACKGROUND, 72 COL_BACKGROUND,
87 COL_LOCKED, 73 COL_LOCKED,
@@ -102,12 +88,17 @@ struct game_params {
102 float barrier_probability; 88 float barrier_probability;
103}; 89};
104 90
91typedef struct game_immutable_state {
92 int refcount;
93 unsigned char *barriers;
94} game_immutable_state;
95
105struct game_state { 96struct game_state {
106 int width, height, wrapping, completed; 97 int width, height, wrapping, completed;
107 int last_rotate_x, last_rotate_y, last_rotate_dir; 98 int last_rotate_x, last_rotate_y, last_rotate_dir;
108 int used_solve; 99 int used_solve;
109 unsigned char *tiles; 100 unsigned char *tiles;
110 unsigned char *barriers; 101 struct game_immutable_state *imm;
111}; 102};
112 103
113#define OFFSETWH(x2,y2,x1,y1,dir,width,height) \ 104#define OFFSETWH(x2,y2,x1,y1,dir,width,height) \
@@ -119,7 +110,7 @@ struct game_state {
119 110
120#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] ) 111#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
121#define tile(state, x, y) index(state, (state)->tiles, x, y) 112#define tile(state, x, y) index(state, (state)->tiles, x, y)
122#define barrier(state, x, y) index(state, (state)->barriers, x, y) 113#define barrier(state, x, y) index(state, (state)->imm->barriers, x, y)
123 114
124struct xyd { 115struct xyd {
125 int x, y, direction; 116 int x, y, direction;
@@ -462,6 +453,11 @@ static int todo_get(struct todo *todo) {
462 return ret; 453 return ret;
463} 454}
464 455
456/*
457 * Return values: -1 means puzzle was proved inconsistent, 0 means we
458 * failed to narrow down to a unique solution, +1 means we solved it
459 * fully.
460 */
465static int net_solver(int w, int h, unsigned char *tiles, 461static int net_solver(int w, int h, unsigned char *tiles,
466 unsigned char *barriers, int wrapping) 462 unsigned char *barriers, int wrapping)
467{ 463{
@@ -736,7 +732,11 @@ static int net_solver(int w, int h, unsigned char *tiles,
736#endif 732#endif
737 } 733 }
738 734
739 assert(j > 0); /* we can't lose _all_ possibilities! */ 735 if (j == 0) {
736 /* If we've ruled out all possible orientations for a
737 * tile, then our puzzle has no solution at all. */
738 return -1;
739 }
740 740
741 if (j < i) { 741 if (j < i) {
742 done_something = TRUE; 742 done_something = TRUE;
@@ -816,14 +816,14 @@ static int net_solver(int w, int h, unsigned char *tiles,
816 /* 816 /*
817 * Mark all completely determined tiles as locked. 817 * Mark all completely determined tiles as locked.
818 */ 818 */
819 j = TRUE; 819 j = +1;
820 for (i = 0; i < w*h; i++) { 820 for (i = 0; i < w*h; i++) {
821 if (tilestate[i * 4 + 1] == 255) { 821 if (tilestate[i * 4 + 1] == 255) {
822 assert(tilestate[i * 4 + 0] != 255); 822 assert(tilestate[i * 4 + 0] != 255);
823 tiles[i] = tilestate[i * 4] | LOCKED; 823 tiles[i] = tilestate[i * 4] | LOCKED;
824 } else { 824 } else {
825 tiles[i] &= ~LOCKED; 825 tiles[i] &= ~LOCKED;
826 j = FALSE; 826 j = 0;
827 } 827 }
828 } 828 }
829 829
@@ -1337,7 +1337,7 @@ static char *new_game_desc(const game_params *params, random_state *rs,
1337 /* 1337 /*
1338 * Run the solver to check unique solubility. 1338 * Run the solver to check unique solubility.
1339 */ 1339 */
1340 while (!net_solver(w, h, tiles, NULL, params->wrapping)) { 1340 while (net_solver(w, h, tiles, NULL, params->wrapping) != 1) {
1341 int n = 0; 1341 int n = 0;
1342 1342
1343 /* 1343 /*
@@ -1647,12 +1647,14 @@ static game_state *new_game(midend *me, const game_params *params,
1647 w = state->width = params->width; 1647 w = state->width = params->width;
1648 h = state->height = params->height; 1648 h = state->height = params->height;
1649 state->wrapping = params->wrapping; 1649 state->wrapping = params->wrapping;
1650 state->imm = snew(game_immutable_state);
1651 state->imm->refcount = 1;
1650 state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0; 1652 state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0;
1651 state->completed = state->used_solve = FALSE; 1653 state->completed = state->used_solve = FALSE;
1652 state->tiles = snewn(state->width * state->height, unsigned char); 1654 state->tiles = snewn(state->width * state->height, unsigned char);
1653 memset(state->tiles, 0, state->width * state->height); 1655 memset(state->tiles, 0, state->width * state->height);
1654 state->barriers = snewn(state->width * state->height, unsigned char); 1656 state->imm->barriers = snewn(state->width * state->height, unsigned char);
1655 memset(state->barriers, 0, state->width * state->height); 1657 memset(state->imm->barriers, 0, state->width * state->height);
1656 1658
1657 /* 1659 /*
1658 * Parse the game description into the grid. 1660 * Parse the game description into the grid.
@@ -1723,6 +1725,8 @@ static game_state *dup_game(const game_state *state)
1723 game_state *ret; 1725 game_state *ret;
1724 1726
1725 ret = snew(game_state); 1727 ret = snew(game_state);
1728 ret->imm = state->imm;
1729 ret->imm->refcount++;
1726 ret->width = state->width; 1730 ret->width = state->width;
1727 ret->height = state->height; 1731 ret->height = state->height;
1728 ret->wrapping = state->wrapping; 1732 ret->wrapping = state->wrapping;
@@ -1733,16 +1737,17 @@ static game_state *dup_game(const game_state *state)
1733 ret->last_rotate_y = state->last_rotate_y; 1737 ret->last_rotate_y = state->last_rotate_y;
1734 ret->tiles = snewn(state->width * state->height, unsigned char); 1738 ret->tiles = snewn(state->width * state->height, unsigned char);
1735 memcpy(ret->tiles, state->tiles, state->width * state->height); 1739 memcpy(ret->tiles, state->tiles, state->width * state->height);
1736 ret->barriers = snewn(state->width * state->height, unsigned char);
1737 memcpy(ret->barriers, state->barriers, state->width * state->height);
1738 1740
1739 return ret; 1741 return ret;
1740} 1742}
1741 1743
1742static void free_game(game_state *state) 1744static void free_game(game_state *state)
1743{ 1745{
1746 if (--state->imm->refcount == 0) {
1747 sfree(state->imm->barriers);
1748 sfree(state->imm);
1749 }
1744 sfree(state->tiles); 1750 sfree(state->tiles);
1745 sfree(state->barriers);
1746 sfree(state); 1751 sfree(state);
1747} 1752}
1748 1753
@@ -1761,9 +1766,17 @@ static char *solve_game(const game_state *state, const game_state *currstate,
1761 * Run the internal solver on the provided grid. This might 1766 * Run the internal solver on the provided grid. This might
1762 * not yield a complete solution. 1767 * not yield a complete solution.
1763 */ 1768 */
1769 int solver_result;
1770
1764 memcpy(tiles, state->tiles, state->width * state->height); 1771 memcpy(tiles, state->tiles, state->width * state->height);
1765 net_solver(state->width, state->height, tiles, 1772 solver_result = net_solver(state->width, state->height, tiles,
1766 state->barriers, state->wrapping); 1773 state->imm->barriers, state->wrapping);
1774
1775 if (solver_result < 0) {
1776 *error = "No solution exists for this puzzle";
1777 sfree(tiles);
1778 return NULL;
1779 }
1767 } else { 1780 } else {
1768 for (i = 0; i < state->width * state->height; i++) { 1781 for (i = 0; i < state->width * state->height; i++) {
1769 int c = aux[i]; 1782 int c = aux[i];
@@ -1990,7 +2003,7 @@ static int *compute_loops_inner(int w, int h, int wrapping,
1990static int *compute_loops(const game_state *state) 2003static int *compute_loops(const game_state *state)
1991{ 2004{
1992 return compute_loops_inner(state->width, state->height, state->wrapping, 2005 return compute_loops_inner(state->width, state->height, state->wrapping,
1993 state->tiles, state->barriers); 2006 state->tiles, state->imm->barriers);
1994} 2007}
1995 2008
1996struct game_ui { 2009struct game_ui {
@@ -2051,9 +2064,8 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
2051struct game_drawstate { 2064struct game_drawstate {
2052 int started; 2065 int started;
2053 int width, height; 2066 int width, height;
2054 int org_x, org_y;
2055 int tilesize; 2067 int tilesize;
2056 int *visible; 2068 unsigned long *visible, *to_draw;
2057}; 2069};
2058 2070
2059/* ---------------------------------------------------------------------- 2071/* ----------------------------------------------------------------------
@@ -2093,8 +2105,8 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2093 /* 2105 /*
2094 * The button must have been clicked on a valid tile. 2106 * The button must have been clicked on a valid tile.
2095 */ 2107 */
2096 x -= WINDOW_OFFSET + TILE_BORDER; 2108 x -= WINDOW_OFFSET + LINE_THICK;
2097 y -= WINDOW_OFFSET + TILE_BORDER; 2109 y -= WINDOW_OFFSET + LINE_THICK;
2098 if (x < 0 || y < 0) 2110 if (x < 0 || y < 0)
2099 return nullret; 2111 return nullret;
2100 tx = x / TILE_SIZE; 2112 tx = x / TILE_SIZE;
@@ -2104,8 +2116,8 @@ static char *interpret_move(const game_state *state, game_ui *ui,
2104 /* Transform from physical to game coords */ 2116 /* Transform from physical to game coords */
2105 tx = (tx + ui->org_x) % state->width; 2117 tx = (tx + ui->org_x) % state->width;
2106 ty = (ty + ui->org_y) % state->height; 2118 ty = (ty + ui->org_y) % state->height;
2107 if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER || 2119 if (x % TILE_SIZE >= TILE_SIZE - LINE_THICK ||
2108 y % TILE_SIZE >= TILE_SIZE - TILE_BORDER) 2120 y % TILE_SIZE >= TILE_SIZE - LINE_THICK)
2109 return nullret; 2121 return nullret;
2110 2122
2111#ifdef USE_DRAGGING 2123#ifdef USE_DRAGGING
@@ -2434,20 +2446,25 @@ static game_state *execute_move(const game_state *from, const char *move)
2434static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) 2446static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
2435{ 2447{
2436 game_drawstate *ds = snew(game_drawstate); 2448 game_drawstate *ds = snew(game_drawstate);
2437 int i; 2449 int i, ncells;
2438 2450
2439 ds->started = FALSE; 2451 ds->started = FALSE;
2440 ds->width = state->width; 2452 ds->width = state->width;
2441 ds->height = state->height; 2453 ds->height = state->height;
2442 ds->org_x = ds->org_y = -1; 2454 ncells = (state->width+2) * (state->height+2);
2443 ds->visible = snewn(state->width * state->height, int); 2455 ds->visible = snewn(ncells, unsigned long);
2456 ds->to_draw = snewn(ncells, unsigned long);
2444 ds->tilesize = 0; /* undecided yet */ 2457 ds->tilesize = 0; /* undecided yet */
2445 for (i = 0; i < state->width * state->height; i++) 2458 for (i = 0; i < ncells; i++)
2446 ds->visible[i] = -1; 2459 ds->visible[i] = -1;
2447 2460
2448 return ds; 2461 return ds;
2449} 2462}
2450 2463
2464#define dsindex(ds, field, x, y) ((ds)->field[((y)+1)*((ds)->width+2)+((x)+1)])
2465#define visible(ds, x, y) dsindex(ds, visible, x, y)
2466#define todraw(ds, x, y) dsindex(ds, to_draw, x, y)
2467
2451static void game_free_drawstate(drawing *dr, game_drawstate *ds) 2468static void game_free_drawstate(drawing *dr, game_drawstate *ds)
2452{ 2469{
2453 sfree(ds->visible); 2470 sfree(ds->visible);
@@ -2457,8 +2474,12 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds)
2457static void game_compute_size(const game_params *params, int tilesize, 2474static void game_compute_size(const game_params *params, int tilesize,
2458 int *x, int *y) 2475 int *x, int *y)
2459{ 2476{
2460 *x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER; 2477 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
2461 *y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER; 2478 struct { int tilesize; } ads, *ds = &ads;
2479 ads.tilesize = tilesize;
2480
2481 *x = WINDOW_OFFSET * 2 + TILE_SIZE * params->width + LINE_THICK;
2482 *y = WINDOW_OFFSET * 2 + TILE_SIZE * params->height + LINE_THICK;
2462} 2483}
2463 2484
2464static void game_set_size(drawing *dr, game_drawstate *ds, 2485static void game_set_size(drawing *dr, game_drawstate *ds,
@@ -2532,297 +2553,286 @@ static float *game_colours(frontend *fe, int *ncolours)
2532 return ret; 2553 return ret;
2533} 2554}
2534 2555
2535static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2, 2556static void rotated_coords(float *ox, float *oy, const float matrix[4],
2536 int colour) 2557 float cx, float cy, float ix, float iy)
2537{ 2558{
2538 draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE); 2559 *ox = matrix[0] * ix + matrix[2] * iy + cx;
2539 draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE); 2560 *oy = matrix[1] * ix + matrix[3] * iy + cy;
2540 draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
2541 draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
2542 draw_line(dr, x1, y1, x2, y2, colour);
2543} 2561}
2544 2562
2545static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2, 2563/* Flags describing the visible features of a tile. */
2546 int colour) 2564#define TILE_BARRIER_SHIFT 0 /* 4 bits: R U L D */
2547{ 2565#define TILE_BARRIER_CORNER_SHIFT 4 /* 4 bits: RU UL LD DR */
2548 int mx = (x1 < x2 ? x1 : x2); 2566#define TILE_KEYBOARD_CURSOR (1<<8) /* 1 bit if cursor is here */
2549 int my = (y1 < y2 ? y1 : y2); 2567#define TILE_WIRE_SHIFT 9 /* 8 bits: RR UU LL DD
2550 int dx = (x2 + x1 - 2*mx + 1); 2568 * Each pair: 0=no wire, 1=unpowered,
2551 int dy = (y2 + y1 - 2*my + 1); 2569 * 2=powered, 3=loop err highlight */
2552 2570#define TILE_ENDPOINT_SHIFT 17 /* 2 bits: 0=no endpoint, 1=unpowered,
2553 draw_rect(dr, mx, my, dx, dy, colour); 2571 * 2=powered, 3=power-source square */
2554} 2572#define TILE_WIRE_ON_EDGE_SHIFT 19 /* 8 bits: RR UU LL DD,
2555 2573 * same encoding as TILE_WIRE_SHIFT */
2556/* 2574#define TILE_ROTATING (1UL<<27) /* 1 bit if tile is rotating */
2557 * draw_barrier_corner() and draw_barrier() are passed physical coords 2575#define TILE_LOCKED (1UL<<28) /* 1 bit if tile is locked */
2558 */ 2576
2559static void draw_barrier_corner(drawing *dr, game_drawstate *ds, 2577static void draw_wires(drawing *dr, int cx, int cy, int radius,
2560 int x, int y, int dx, int dy, int phase) 2578 unsigned long tile, int bitmap,
2579 int colour, int halfwidth, const float matrix[4])
2561{ 2580{
2562 int bx = WINDOW_OFFSET + TILE_SIZE * x; 2581 float fpoints[12*2];
2563 int by = WINDOW_OFFSET + TILE_SIZE * y; 2582 int points[12*2];
2564 int x1, y1; 2583 int npoints, d, dsh, i;
2565 2584 int any_wire_this_colour = FALSE;
2566 x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); 2585 float xf, yf;
2567 y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); 2586
2568 2587 npoints = 0;
2569 if (phase == 0) { 2588 for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) {
2570 draw_rect_coords(dr, bx+x1+dx, by+y1, 2589 int wiretype = (tile >> (TILE_WIRE_SHIFT + 2*dsh)) & 3;
2571 bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy, 2590
2572 COL_WIRE); 2591 fpoints[2*npoints+0] = halfwidth * (X(d) + X(C(d)));
2573 draw_rect_coords(dr, bx+x1, by+y1+dy, 2592 fpoints[2*npoints+1] = halfwidth * (Y(d) + Y(C(d)));
2574 bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy, 2593 npoints++;
2575 COL_WIRE); 2594
2576 } else { 2595 if (bitmap & (1 << wiretype)) {
2577 draw_rect_coords(dr, bx+x1, by+y1, 2596 fpoints[2*npoints+0] = radius * X(d) + halfwidth * X(C(d));
2578 bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy, 2597 fpoints[2*npoints+1] = radius * Y(d) + halfwidth * Y(C(d));
2579 COL_BARRIER); 2598 npoints++;
2599 fpoints[2*npoints+0] = radius * X(d) + halfwidth * X(A(d));
2600 fpoints[2*npoints+1] = radius * Y(d) + halfwidth * Y(A(d));
2601 npoints++;
2602
2603 any_wire_this_colour = TRUE;
2604 }
2580 } 2605 }
2581}
2582 2606
2583static void draw_barrier(drawing *dr, game_drawstate *ds, 2607 if (!any_wire_this_colour)
2584 int x, int y, int dir, int phase) 2608 return;
2585{
2586 int bx = WINDOW_OFFSET + TILE_SIZE * x;
2587 int by = WINDOW_OFFSET + TILE_SIZE * y;
2588 int x1, y1, w, h;
2589 2609
2590 x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0); 2610 for (i = 0; i < npoints; i++) {
2591 y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0); 2611 rotated_coords(&xf, &yf, matrix, cx, cy, fpoints[2*i], fpoints[2*i+1]);
2592 w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); 2612 points[2*i] = 0.5 + xf;
2593 h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); 2613 points[2*i+1] = 0.5 + yf;
2594
2595 if (phase == 0) {
2596 draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
2597 } else {
2598 draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER);
2599 } 2614 }
2615
2616 draw_polygon(dr, points, npoints, colour, colour);
2600} 2617}
2601 2618
2602/* 2619static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y,
2603 * draw_tile() is passed physical coordinates 2620 unsigned long tile, float angle)
2604 */
2605static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds,
2606 int x, int y, int tile, int src, float angle, int cursor)
2607{ 2621{
2608 int bx = WINDOW_OFFSET + TILE_SIZE * x; 2622 int tx, ty;
2609 int by = WINDOW_OFFSET + TILE_SIZE * y; 2623 int clipx, clipy, clipX, clipY, clipw, cliph;
2624 int border_br = LINE_THICK/2, border_tl = LINE_THICK - border_br;
2625 int barrier_outline_thick = (LINE_THICK+1)/2;
2626 int bg, d, dsh, pass;
2627 int cx, cy, radius;
2610 float matrix[4]; 2628 float matrix[4];
2611 float cx, cy, ex, ey, tx, ty; 2629
2612 int dir, col, phase; 2630 tx = WINDOW_OFFSET + TILE_SIZE * x + border_br;
2631 ty = WINDOW_OFFSET + TILE_SIZE * y + border_br;
2613 2632
2614 /* 2633 /*
2615 * When we draw a single tile, we must draw everything up to 2634 * Clip to the tile boundary, with adjustments if we're drawing
2616 * and including the borders around the tile. This means that 2635 * just outside the grid.
2617 * if the neighbouring tiles have connections to those borders,
2618 * we must draw those connections on the borders themselves.
2619 */ 2636 */
2620 2637 clipx = tx; clipX = tx + TILE_SIZE;
2621 clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); 2638 clipy = ty; clipY = ty + TILE_SIZE;
2639 if (x == -1) {
2640 clipx = clipX - border_br - barrier_outline_thick;
2641 } else if (x == ds->width) {
2642 clipX = clipx + border_tl + barrier_outline_thick;
2643 }
2644 if (y == -1) {
2645 clipy = clipY - border_br - barrier_outline_thick;
2646 } else if (y == ds->height) {
2647 clipY = clipy + border_tl + barrier_outline_thick;
2648 }
2649 clipw = clipX - clipx;
2650 cliph = clipY - clipy;
2651 clip(dr, clipx, clipy, clipw, cliph);
2622 2652
2623 /* 2653 /*
2624 * So. First blank the tile out completely: draw a big 2654 * Clear the clip region.
2625 * rectangle in border colour, and a smaller rectangle in
2626 * background colour to fill it in.
2627 */ 2655 */
2628 draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER, 2656 bg = (tile & TILE_LOCKED) ? COL_LOCKED : COL_BACKGROUND;
2629 COL_BORDER); 2657 draw_rect(dr, clipx, clipy, clipw, cliph, bg);
2630 draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
2631 TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
2632 tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
2633 2658
2634 /* 2659 /*
2635 * Draw an inset outline rectangle as a cursor, in whichever of 2660 * Draw the grid lines.
2636 * COL_LOCKED and COL_BACKGROUND we aren't currently drawing
2637 * in.
2638 */ 2661 */
2639 if (cursor) { 2662 {
2640 draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8, 2663 int gridl = (x == -1 ? tx+TILE_SIZE-border_br : tx);
2641 bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, 2664 int gridr = (x == ds->width ? tx+border_tl : tx+TILE_SIZE);
2642 tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); 2665 int gridu = (y == -1 ? ty+TILE_SIZE-border_br : ty);
2643 draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8, 2666 int gridd = (y == ds->height ? ty+border_tl : ty+TILE_SIZE);
2644 bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8, 2667 if (x >= 0)
2645 tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); 2668 draw_rect(dr, tx, gridu, border_tl, gridd-gridu, COL_BORDER);
2646 draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8, 2669 if (y >= 0)
2647 bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, 2670 draw_rect(dr, gridl, ty, gridr-gridl, border_tl, COL_BORDER);
2648 tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); 2671 if (x < ds->width)
2649 draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, 2672 draw_rect(dr, tx+TILE_SIZE-border_br, gridu,
2650 bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, 2673 border_br, gridd-gridu, COL_BORDER);
2651 tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); 2674 if (y < ds->height)
2675 draw_rect(dr, gridl, ty+TILE_SIZE-border_br,
2676 gridr-gridl, border_br, COL_BORDER);
2652 } 2677 }
2653 2678
2654 /* 2679 /*
2655 * Set up the rotation matrix. 2680 * Draw the keyboard cursor.
2656 */ 2681 */
2657 matrix[0] = (float)cos(angle * PI / 180.0); 2682 if (tile & TILE_KEYBOARD_CURSOR) {
2658 matrix[1] = (float)-sin(angle * PI / 180.0); 2683 int cursorcol = (tile & TILE_LOCKED) ? COL_BACKGROUND : COL_LOCKED;
2659 matrix[2] = (float)sin(angle * PI / 180.0); 2684 int inset_outer = TILE_SIZE/8, inset_inner = inset_outer + LINE_THICK;
2660 matrix[3] = (float)cos(angle * PI / 180.0); 2685 draw_rect(dr, tx + inset_outer, ty + inset_outer,
2686 TILE_SIZE - 2*inset_outer, TILE_SIZE - 2*inset_outer,
2687 cursorcol);
2688 draw_rect(dr, tx + inset_inner, ty + inset_inner,
2689 TILE_SIZE - 2*inset_inner, TILE_SIZE - 2*inset_inner,
2690 bg);
2691 }
2692
2693 radius = (TILE_SIZE+1)/2;
2694 cx = tx + radius;
2695 cy = ty + radius;
2696 radius++;
2661 2697
2662 /* 2698 /*
2663 * Draw the wires. 2699 * Draw protrusions into this cell's edges of wires in
2700 * neighbouring cells, as given by the TILE_WIRE_ON_EDGE_SHIFT
2701 * flags. We only draw each of these if there _isn't_ a wire of
2702 * our own that's going to overlap it, which means either the
2703 * corresponding TILE_WIRE_SHIFT flag is zero, or else the
2704 * TILE_ROTATING flag is set (so that our main wire won't be drawn
2705 * in quite that place anyway).
2664 */ 2706 */
2665 cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F; 2707 for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) {
2666 col = (tile & ACTIVE ? COL_POWERED : COL_WIRE); 2708 int edgetype = ((tile >> (TILE_WIRE_ON_EDGE_SHIFT + 2*dsh)) & 3);
2667 for (dir = 1; dir < 0x10; dir <<= 1) { 2709 if (edgetype == 0)
2668 if (tile & dir) { 2710 continue; /* there isn't a wire on the edge */
2669 ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); 2711 if (!(tile & TILE_ROTATING) &&
2670 ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); 2712 ((tile >> (TILE_WIRE_SHIFT + 2*dsh)) & 3) != 0)
2671 MATMUL(tx, ty, matrix, ex, ey); 2713 continue; /* wire on edge would be overdrawn anyway */
2672 draw_filled_line(dr, bx+(int)cx, by+(int)cy, 2714
2673 bx+(int)(cx+tx), by+(int)(cy+ty), 2715 for (pass = 0; pass < 2; pass++) {
2674 COL_WIRE); 2716 int x, y, w, h;
2675 } 2717 int col = (pass == 0 || edgetype == 1 ? COL_WIRE :
2676 } 2718 edgetype == 2 ? COL_POWERED : COL_LOOP);
2677 for (dir = 1; dir < 0x10; dir <<= 1) { 2719 int halfwidth = pass == 0 ? 2*LINE_THICK-1 : LINE_THICK-1;
2678 if (tile & dir) { 2720
2679 ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); 2721 if (X(d) < 0) {
2680 ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); 2722 x = tx;
2681 MATMUL(tx, ty, matrix, ex, ey); 2723 w = border_tl;
2682 draw_line(dr, bx+(int)cx, by+(int)cy, 2724 } else if (X(d) > 0) {
2683 bx+(int)(cx+tx), by+(int)(cy+ty), 2725 x = tx + TILE_SIZE - border_br;
2684 (tile & LOOP(dir)) ? COL_LOOP : col); 2726 w = border_br;
2727 } else {
2728 x = cx - halfwidth;
2729 w = 2 * halfwidth + 1;
2730 }
2731
2732 if (Y(d) < 0) {
2733 y = ty;
2734 h = border_tl;
2735 } else if (Y(d) > 0) {
2736 y = ty + TILE_SIZE - border_br;
2737 h = border_br;
2738 } else {
2739 y = cy - halfwidth;
2740 h = 2 * halfwidth + 1;
2741 }
2742
2743 draw_rect(dr, x, y, w, h, col);
2685 } 2744 }
2686 } 2745 }
2687 /* If we've drawn any loop-highlighted arms, make sure the centre
2688 * point is loop-coloured rather than a later arm overwriting it. */
2689 if (tile & (RLOOP | ULOOP | LLOOP | DLOOP))
2690 draw_rect(dr, bx+(int)cx, by+(int)cy, 1, 1, COL_LOOP);
2691 2746
2692 /* 2747 /*
2693 * Draw the box in the middle. We do this in blue if the tile 2748 * Set up the rotation matrix for the main cell contents, i.e.
2694 * is an unpowered endpoint, in cyan if the tile is a powered 2749 * everything that is centred in the grid square and optionally
2695 * endpoint, in black if the tile is the centrepiece, and 2750 * rotated by an arbitrary angle about that centre point.
2696 * otherwise not at all.
2697 */ 2751 */
2698 col = -1; 2752 if (tile & TILE_ROTATING) {
2699 if (src) 2753 matrix[0] = (float)cos(angle * PI / 180.0);
2700 col = COL_WIRE; 2754 matrix[2] = (float)sin(angle * PI / 180.0);
2701 else if (COUNT(tile) == 1) { 2755 } else {
2702 col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT); 2756 matrix[0] = 1.0F;
2703 } 2757 matrix[2] = 0.0F;
2704 if (col >= 0) {
2705 int i, points[8];
2706
2707 points[0] = +1; points[1] = +1;
2708 points[2] = +1; points[3] = -1;
2709 points[4] = -1; points[5] = -1;
2710 points[6] = -1; points[7] = +1;
2711
2712 for (i = 0; i < 8; i += 2) {
2713 ex = (TILE_SIZE * 0.24F) * points[i];
2714 ey = (TILE_SIZE * 0.24F) * points[i+1];
2715 MATMUL(tx, ty, matrix, ex, ey);
2716 points[i] = bx+(int)(cx+tx);
2717 points[i+1] = by+(int)(cy+ty);
2718 }
2719
2720 draw_polygon(dr, points, 4, col, COL_WIRE);
2721 } 2758 }
2759 matrix[3] = matrix[0];
2760 matrix[1] = -matrix[2];
2722 2761
2723 /* 2762 /*
2724 * Draw the points on the border if other tiles are connected 2763 * Draw the wires.
2725 * to us.
2726 */ 2764 */
2727 for (dir = 1; dir < 0x10; dir <<= 1) { 2765 draw_wires(dr, cx, cy, radius, tile,
2728 int dx, dy, px, py, lx, ly, vx, vy, ox, oy; 2766 0xE, COL_WIRE, 2*LINE_THICK-1, matrix);
2729 2767 draw_wires(dr, cx, cy, radius, tile,
2730 dx = X(dir); 2768 0x4, COL_POWERED, LINE_THICK-1, matrix);
2731 dy = Y(dir); 2769 draw_wires(dr, cx, cy, radius, tile,
2732 2770 0x8, COL_LOOP, LINE_THICK-1, matrix);
2733 ox = x + dx;
2734 oy = y + dy;
2735
2736 if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height)
2737 continue;
2738
2739 if (!(tile(state, GX(ox), GY(oy)) & F(dir)))
2740 continue;
2741 2771
2742 px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx); 2772 /*
2743 py = by + (int)(dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy); 2773 * Draw the central box.
2744 lx = dx * (TILE_BORDER-1); 2774 */
2745 ly = dy * (TILE_BORDER-1); 2775 for (pass = 0; pass < 2; pass++) {
2746 vx = (dy ? 1 : 0); 2776 int endtype = (tile >> TILE_ENDPOINT_SHIFT) & 3;
2747 vy = (dx ? 1 : 0); 2777 if (endtype) {
2778 int i, points[8], col;
2779 float boxr = TILE_SIZE * 0.24F + (pass == 0 ? LINE_THICK-1 : 0);
2780
2781 col = (pass == 0 || endtype == 3 ? COL_WIRE :
2782 endtype == 2 ? COL_POWERED : COL_ENDPOINT);
2783
2784 points[0] = +1; points[1] = +1;
2785 points[2] = +1; points[3] = -1;
2786 points[4] = -1; points[5] = -1;
2787 points[6] = -1; points[7] = +1;
2788
2789 for (i = 0; i < 8; i += 2) {
2790 float x, y;
2791 rotated_coords(&x, &y, matrix, cx, cy,
2792 boxr * points[i], boxr * points[i+1]);
2793 points[i] = x + 0.5;
2794 points[i+1] = y + 0.5;
2795 }
2748 2796
2749 if (angle == 0.0 && (tile & dir)) { 2797 draw_polygon(dr, points, 4, col, COL_WIRE);
2750 /*
2751 * If we are fully connected to the other tile, we must
2752 * draw right across the tile border. (We can use our
2753 * own ACTIVE state to determine what colour to do this
2754 * in: if we are fully connected to the other tile then
2755 * the two ACTIVE states will be the same.)
2756 */
2757 draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
2758 draw_rect_coords(dr, px, py, px+lx, py+ly,
2759 ((tile & LOOP(dir)) ? COL_LOOP :
2760 (tile & ACTIVE) ? COL_POWERED :
2761 COL_WIRE));
2762 } else {
2763 /*
2764 * The other tile extends into our border, but isn't
2765 * actually connected to us. Just draw a single black
2766 * dot.
2767 */
2768 draw_rect_coords(dr, px, py, px, py, COL_WIRE);
2769 } 2798 }
2770 } 2799 }
2771 2800
2772 /* 2801 /*
2773 * Draw barrier corners, and then barriers. 2802 * Draw barriers along grid edges.
2774 */ 2803 */
2775 for (phase = 0; phase < 2; phase++) { 2804 for (pass = 0; pass < 2; pass++) {
2776 for (dir = 1; dir < 0x10; dir <<= 1) { 2805 int btl = border_tl, bbr = border_br, col = COL_BARRIER;
2777 int x1, y1, corner = FALSE; 2806 if (pass == 0) {
2778 /* 2807 btl += barrier_outline_thick;
2779 * If at least one barrier terminates at the corner 2808 bbr += barrier_outline_thick;
2780 * between dir and A(dir), draw a barrier corner. 2809 col = COL_WIRE;
2781 */
2782 if (barrier(state, GX(x), GY(y)) & (dir | A(dir))) {
2783 corner = TRUE;
2784 } else {
2785 /*
2786 * Only count barriers terminating at this corner
2787 * if they're physically next to the corner. (That
2788 * is, if they've wrapped round from the far side
2789 * of the screen, they don't count.)
2790 */
2791 x1 = x + X(dir);
2792 y1 = y + Y(dir);
2793 if (x1 >= 0 && x1 < state->width &&
2794 y1 >= 0 && y1 < state->height &&
2795 (barrier(state, GX(x1), GY(y1)) & A(dir))) {
2796 corner = TRUE;
2797 } else {
2798 x1 = x + X(A(dir));
2799 y1 = y + Y(A(dir));
2800 if (x1 >= 0 && x1 < state->width &&
2801 y1 >= 0 && y1 < state->height &&
2802 (barrier(state, GX(x1), GY(y1)) & dir))
2803 corner = TRUE;
2804 }
2805 }
2806
2807 if (corner) {
2808 /*
2809 * At least one barrier terminates here. Draw a
2810 * corner.
2811 */
2812 draw_barrier_corner(dr, ds, x, y,
2813 X(dir)+X(A(dir)), Y(dir)+Y(A(dir)),
2814 phase);
2815 }
2816 } 2810 }
2817 2811
2818 for (dir = 1; dir < 0x10; dir <<= 1) 2812 if (tile & (L << TILE_BARRIER_SHIFT))
2819 if (barrier(state, GX(x), GY(y)) & dir) 2813 draw_rect(dr, tx, ty, btl, TILE_SIZE, col);
2820 draw_barrier(dr, ds, x, y, dir, phase); 2814 if (tile & (R << TILE_BARRIER_SHIFT))
2815 draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, TILE_SIZE, col);
2816 if (tile & (U << TILE_BARRIER_SHIFT))
2817 draw_rect(dr, tx, ty, TILE_SIZE, btl, col);
2818 if (tile & (D << TILE_BARRIER_SHIFT))
2819 draw_rect(dr, tx, ty+TILE_SIZE-bbr, TILE_SIZE, bbr, col);
2820
2821 if (tile & (R << TILE_BARRIER_CORNER_SHIFT))
2822 draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, btl, col);
2823 if (tile & (U << TILE_BARRIER_CORNER_SHIFT))
2824 draw_rect(dr, tx, ty, btl, btl, col);
2825 if (tile & (L << TILE_BARRIER_CORNER_SHIFT))
2826 draw_rect(dr, tx, ty+TILE_SIZE-bbr, btl, bbr, col);
2827 if (tile & (D << TILE_BARRIER_CORNER_SHIFT))
2828 draw_rect(dr, tx+TILE_SIZE-bbr, ty+TILE_SIZE-bbr, bbr, bbr, col);
2821 } 2829 }
2822 2830
2831 /*
2832 * Unclip and draw update, to finish.
2833 */
2823 unclip(dr); 2834 unclip(dr);
2824 2835 draw_update(dr, clipx, clipy, clipw, cliph);
2825 draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
2826} 2836}
2827 2837
2828static void game_redraw(drawing *dr, game_drawstate *ds, 2838static void game_redraw(drawing *dr, game_drawstate *ds,
@@ -2830,73 +2840,26 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
2830 int dir, const game_ui *ui, 2840 int dir, const game_ui *ui,
2831 float t, float ft) 2841 float t, float ft)
2832{ 2842{
2833 int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE; 2843 int tx, ty, dx, dy, d, dsh, last_rotate_dir, frame;
2834 unsigned char *active; 2844 unsigned char *active;
2835 int *loops; 2845 int *loops;
2836 float angle = 0.0; 2846 float angle = 0.0;
2837 2847
2838 /* 2848 /*
2839 * Clear the screen, and draw the exterior barrier lines, if 2849 * Clear the screen on our first call.
2840 * this is our first call or if the origin has changed.
2841 */ 2850 */
2842 if (!ds->started || ui->org_x != ds->org_x || ui->org_y != ds->org_y) { 2851 if (!ds->started) {
2843 int phase; 2852 int w, h;
2853 game_params params;
2844 2854
2845 ds->started = TRUE; 2855 ds->started = TRUE;
2846 2856
2847 draw_rect(dr, 0, 0, 2857 params.width = ds->width;
2848 WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER, 2858 params.height = ds->height;
2849 WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER, 2859 game_compute_size(&params, TILE_SIZE, &w, &h);
2850 COL_BACKGROUND);
2851
2852 ds->org_x = ui->org_x;
2853 ds->org_y = ui->org_y;
2854 moved_origin = TRUE;
2855
2856 draw_update(dr, 0, 0,
2857 WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
2858 WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
2859
2860 for (phase = 0; phase < 2; phase++) {
2861 2860
2862 for (x = 0; x < ds->width; x++) { 2861 draw_rect(dr, 0, 0, w, h, COL_BACKGROUND);
2863 if (x+1 < ds->width) { 2862 draw_update(dr, 0, 0, w, h);
2864 if (barrier(state, GX(x), GY(0)) & R)
2865 draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
2866 if (barrier(state, GX(x), GY(ds->height-1)) & R)
2867 draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
2868 }
2869 if (barrier(state, GX(x), GY(0)) & U) {
2870 draw_barrier_corner(dr, ds, x, -1, -1, +1, phase);
2871 draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
2872 draw_barrier(dr, ds, x, -1, D, phase);
2873 }
2874 if (barrier(state, GX(x), GY(ds->height-1)) & D) {
2875 draw_barrier_corner(dr, ds, x, ds->height, -1, -1, phase);
2876 draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
2877 draw_barrier(dr, ds, x, ds->height, U, phase);
2878 }
2879 }
2880
2881 for (y = 0; y < ds->height; y++) {
2882 if (y+1 < ds->height) {
2883 if (barrier(state, GX(0), GY(y)) & D)
2884 draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
2885 if (barrier(state, GX(ds->width-1), GY(y)) & D)
2886 draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
2887 }
2888 if (barrier(state, GX(0), GY(y)) & L) {
2889 draw_barrier_corner(dr, ds, -1, y, +1, -1, phase);
2890 draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
2891 draw_barrier(dr, ds, -1, y, R, phase);
2892 }
2893 if (barrier(state, GX(ds->width-1), GY(y)) & R) {
2894 draw_barrier_corner(dr, ds, ds->width, y, -1, -1, phase);
2895 draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
2896 draw_barrier(dr, ds, ds->width, y, L, phase);
2897 }
2898 }
2899 }
2900 } 2863 }
2901 2864
2902 tx = ty = -1; 2865 tx = ty = -1;
@@ -2913,30 +2876,83 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
2913 state = oldstate; 2876 state = oldstate;
2914 } 2877 }
2915 2878
2916 frame = -1;
2917 if (ft > 0) { 2879 if (ft > 0) {
2918 /* 2880 /*
2919 * We're animating a completion flash. Find which frame 2881 * We're animating a completion flash. Find which frame
2920 * we're at. 2882 * we're at.
2921 */ 2883 */
2922 frame = (int)(ft / FLASH_FRAME); 2884 frame = (int)(ft / FLASH_FRAME);
2885 } else {
2886 frame = 0;
2923 } 2887 }
2924 2888
2925 /* 2889 /*
2926 * Draw any tile which differs from the way it was last drawn. 2890 * Build up a map of what we want every tile to look like. We
2891 * include tiles one square outside the grid, for the outer edges
2892 * of barriers.
2927 */ 2893 */
2928 active = compute_active(state, ui->cx, ui->cy); 2894 active = compute_active(state, ui->cx, ui->cy);
2929 loops = compute_loops(state); 2895 loops = compute_loops(state);
2930 2896
2931 for (x = 0; x < ds->width; x++) 2897 for (dy = -1; dy < ds->height+1; dy++) {
2932 for (y = 0; y < ds->height; y++) { 2898 for (dx = -1; dx < ds->width+1; dx++) {
2933 int c = tile(state, GX(x), GY(y)) | 2899 todraw(ds, dx, dy) = 0;
2934 index(state, active, GX(x), GY(y)) | 2900 }
2935 index(state, loops, GX(x), GY(y)); 2901 }
2936 int is_src = GX(x) == ui->cx && GY(y) == ui->cy; 2902
2937 int is_anim = GX(x) == tx && GY(y) == ty; 2903 for (dy = 0; dy < ds->height; dy++) {
2938 int is_cursor = ui->cur_visible && 2904 int gy = (dy + ui->org_y) % ds->height;
2939 GX(x) == ui->cur_x && GY(y) == ui->cur_y; 2905 for (dx = 0; dx < ds->width; dx++) {
2906 int gx = (dx + ui->org_x) % ds->width;
2907 int t = (tile(state, gx, gy) |
2908 index(state, loops, gx, gy) |
2909 index(state, active, gx, gy));
2910
2911 for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) {
2912 if (barrier(state, gx, gy) & d) {
2913 todraw(ds, dx, dy) |=
2914 d << TILE_BARRIER_SHIFT;
2915 todraw(ds, dx + X(d), dy + Y(d)) |=
2916 F(d) << TILE_BARRIER_SHIFT;
2917 todraw(ds, dx + X(A(d)), dy + Y(A(d))) |=
2918 C(d) << TILE_BARRIER_CORNER_SHIFT;
2919 todraw(ds, dx + X(A(d)) + X(d), dy + Y(A(d)) + Y(d)) |=
2920 F(d) << TILE_BARRIER_CORNER_SHIFT;
2921 todraw(ds, dx + X(C(d)), dy + Y(C(d))) |=
2922 d << TILE_BARRIER_CORNER_SHIFT;
2923 todraw(ds, dx + X(C(d)) + X(d), dy + Y(C(d)) + Y(d)) |=
2924 A(d) << TILE_BARRIER_CORNER_SHIFT;
2925 }
2926
2927 if (t & d) {
2928 int edgeval = (t & LOOP(d) ? 3 : t & ACTIVE ? 2 : 1);
2929 todraw(ds, dx, dy) |= edgeval << (TILE_WIRE_SHIFT + dsh*2);
2930 if (!(gx == tx && gy == ty)) {
2931 todraw(ds, dx + X(d), dy + Y(d)) |=
2932 edgeval << (TILE_WIRE_ON_EDGE_SHIFT + (dsh ^ 2)*2);
2933 }
2934 }
2935 }
2936
2937 if (ui->cur_visible && gx == ui->cur_x && gy == ui->cur_y)
2938 todraw(ds, dx, dy) |= TILE_KEYBOARD_CURSOR;
2939
2940 if (gx == tx && gy == ty)
2941 todraw(ds, dx, dy) |= TILE_ROTATING;
2942
2943 if (gx == ui->cx && gy == ui->cy) {
2944 todraw(ds, dx, dy) |= 3 << TILE_ENDPOINT_SHIFT;
2945 } else if ((t & 0xF) != R && (t & 0xF) != U &&
2946 (t & 0xF) != L && (t & 0xF) != D) {
2947 /* this is not an endpoint tile */
2948 } else if (t & ACTIVE) {
2949 todraw(ds, dx, dy) |= 2 << TILE_ENDPOINT_SHIFT;
2950 } else {
2951 todraw(ds, dx, dy) |= 1 << TILE_ENDPOINT_SHIFT;
2952 }
2953
2954 if (t & LOCKED)
2955 todraw(ds, dx, dy) |= TILE_LOCKED;
2940 2956
2941 /* 2957 /*
2942 * In a completion flash, we adjust the LOCKED bit 2958 * In a completion flash, we adjust the LOCKED bit
@@ -2944,31 +2960,36 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
2944 * the frame number. 2960 * the frame number.
2945 */ 2961 */
2946 if (frame >= 0) { 2962 if (frame >= 0) {
2947 int rcx = RX(ui->cx), rcy = RY(ui->cy); 2963 int rcx = (ui->cx + ds->width - ui->org_x) % ds->width;
2964 int rcy = (ui->cy + ds->height - ui->org_y) % ds->height;
2948 int xdist, ydist, dist; 2965 int xdist, ydist, dist;
2949 xdist = (x < rcx ? rcx - x : x - rcx); 2966 xdist = (dx < rcx ? rcx - dx : dx - rcx);
2950 ydist = (y < rcy ? rcy - y : y - rcy); 2967 ydist = (dy < rcy ? rcy - dy : dy - rcy);
2951 dist = (xdist > ydist ? xdist : ydist); 2968 dist = (xdist > ydist ? xdist : ydist);
2952 2969
2953 if (frame >= dist && frame < dist+4) { 2970 if (frame >= dist && frame < dist+4 &&
2954 int lock = (frame - dist) & 1; 2971 ((frame - dist) & 1))
2955 lock = lock ? LOCKED : 0; 2972 todraw(ds, dx, dy) ^= TILE_LOCKED;
2956 c = (c &~ LOCKED) | lock;
2957 }
2958 } 2973 }
2974 }
2975 }
2959 2976
2960 if (moved_origin || 2977 /*
2961 index(state, ds->visible, x, y) != c || 2978 * Now draw any tile that differs from the way it was last drawn.
2962 index(state, ds->visible, x, y) == -1 || 2979 * An exception is that if either the previous _or_ current state
2963 is_src || is_anim || is_cursor) { 2980 * has the TILE_ROTATING bit set, we must draw it regardless,
2964 draw_tile(dr, state, ds, x, y, c, 2981 * because it will have rotated to a different angle.q
2965 is_src, (is_anim ? angle : 0.0F), is_cursor); 2982 */
2966 if (is_src || is_anim || is_cursor) 2983 for (dy = -1; dy < ds->height+1; dy++) {
2967 index(state, ds->visible, x, y) = -1; 2984 for (dx = -1; dx < ds->width+1; dx++) {
2968 else 2985 int prev = visible(ds, dx, dy);
2969 index(state, ds->visible, x, y) = c; 2986 int curr = todraw(ds, dx, dy);
2987 if (prev != curr || ((prev | curr) & TILE_ROTATING) != 0) {
2988 draw_tile(dr, ds, dx, dy, curr, angle);
2989 visible(ds, dx, dy) = curr;
2970 } 2990 }
2971 } 2991 }
2992 }
2972 2993
2973 /* 2994 /*
2974 * Update the status bar. 2995 * 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 {
687 if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask)) 687 if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask))
688 c |= MOD_NUM_KEYPAD; 688 c |= MOD_NUM_KEYPAD;
689 689
690 if (c == 26 &&
691 !((NSShiftKeyMask | NSControlKeyMask) & ~[ev modifierFlags]))
692 c = UI_REDO;
693
690 [self processKey:c]; 694 [self processKey:c];
691 } 695 }
692} 696}
@@ -735,7 +739,7 @@ struct frontend {
735 739
736- (void)newGame:(id)sender 740- (void)newGame:(id)sender
737{ 741{
738 [self processKey:'n']; 742 [self processKey:UI_NEWGAME];
739} 743}
740- (void)restartGame:(id)sender 744- (void)restartGame:(id)sender
741{ 745{
@@ -809,11 +813,11 @@ struct frontend {
809} 813}
810- (void)undoMove:(id)sender 814- (void)undoMove:(id)sender
811{ 815{
812 [self processKey:'u']; 816 [self processKey:UI_UNDO];
813} 817}
814- (void)redoMove:(id)sender 818- (void)redoMove:(id)sender
815{ 819{
816 [self processKey:'r'&0x1F]; 820 [self processKey:UI_REDO];
817} 821}
818 822
819- (void)copy:(id)sender 823- (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)
310 fgrid2 = snewn(w*h, float); 310 fgrid2 = snewn(w*h, float);
311 memcpy(fgrid2, fgrid, w*h*sizeof(float)); 311 memcpy(fgrid2, fgrid, w*h*sizeof(float));
312 qsort(fgrid2, w*h, sizeof(float), float_compare); 312 qsort(fgrid2, w*h, sizeof(float), float_compare);
313 threshold = fgrid2[w*h/2]; 313 /* Choose a threshold that makes half the pixels black. In case of
314 * an odd number of pixels, select randomly between just under and
315 * just over half. */
316 {
317 int index = w * h / 2;
318 if (w & h & 1)
319 index += random_upto(rs, 2);
320 if (index < w*h)
321 threshold = fgrid2[index];
322 else
323 threshold = fgrid2[w*h-1] + 1;
324 }
314 sfree(fgrid2); 325 sfree(fgrid2);
315 326
316 for (i = 0; i < h; i++) { 327 for (i = 0; i < h; i++) {
@@ -448,6 +459,8 @@ static int do_row(unsigned char *known, unsigned char *deduced,
448 459
449 if (rowlen == 0) { 460 if (rowlen == 0) {
450 memset(deduced, DOT, len); 461 memset(deduced, DOT, len);
462 } else if (rowlen == 1 && data[0] == len) {
463 memset(deduced, BLOCK, len);
451 } else { 464 } else {
452 do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok, 465 do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok,
453 maxpos_ok, data, len, freespace, 0, 0); 466 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 {
47 CURSOR_RIGHT, 47 CURSOR_RIGHT,
48 CURSOR_SELECT, 48 CURSOR_SELECT,
49 CURSOR_SELECT2, 49 CURSOR_SELECT2,
50 /* UI_* are special keystrokes generated by front ends in response
51 * to menu actions, never passed to back ends */
52 UI_LOWER_BOUND,
53 UI_QUIT,
54 UI_NEWGAME,
55 UI_SOLVE,
56 UI_UNDO,
57 UI_REDO,
58 UI_UPPER_BOUND,
50 59
51 /* made smaller because of 'limited range of datatype' errors. */ 60 /* made smaller because of 'limited range of datatype' errors. */
52 MOD_CTRL = 0x1000, 61 MOD_CTRL = 0x1000,
@@ -64,6 +73,7 @@ enum {
64#define IS_CURSOR_MOVE(m) ( (m) == CURSOR_UP || (m) == CURSOR_DOWN || \ 73#define IS_CURSOR_MOVE(m) ( (m) == CURSOR_UP || (m) == CURSOR_DOWN || \
65 (m) == CURSOR_RIGHT || (m) == CURSOR_LEFT ) 74 (m) == CURSOR_RIGHT || (m) == CURSOR_LEFT )
66#define IS_CURSOR_SELECT(m) ( (m) == CURSOR_SELECT || (m) == CURSOR_SELECT2) 75#define IS_CURSOR_SELECT(m) ( (m) == CURSOR_SELECT || (m) == CURSOR_SELECT2)
76#define IS_UI_FAKE_KEY(m) ( (m) > UI_LOWER_BOUND && (m) < UI_UPPER_BOUND )
67 77
68/* 78/*
69 * Flags in the back end's `flags' word. 79 * 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,
1718#define TILE_SIZE (ds->sz6*6) 1718#define TILE_SIZE (ds->sz6*6)
1719 1719
1720#define BORDER (TILE_SIZE/8) 1720#define BORDER (TILE_SIZE/8)
1721#define BORDER_WIDTH (max(TILE_SIZE / 32, 1)) 1721#define LINE_THICK (TILE_SIZE/16)
1722#define GRID_LINE_TL (ds->grid_line_tl)
1723#define GRID_LINE_BR (ds->grid_line_br)
1724#define GRID_LINE_ALL (ds->grid_line_all)
1722 1725
1723#define COORD(x) ( (x+1) * TILE_SIZE + BORDER ) 1726#define COORD(x) ( (x+1) * TILE_SIZE + BORDER )
1724#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 ) 1727#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,
1738#define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */ 1741#define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */
1739 1742
1740struct game_drawstate { 1743struct game_drawstate {
1741 int sz6; 1744 int sz6, grid_line_all, grid_line_tl, grid_line_br;
1742 int started; 1745 int started;
1743 1746
1744 int w, h, sz; 1747 int w, h, sz;
@@ -2118,7 +2121,6 @@ static void game_compute_size(const game_params *params, int tilesize,
2118 int sz6; 2121 int sz6;
2119 } ads, *ds = &ads; 2122 } ads, *ds = &ads;
2120 ads.sz6 = tilesize/6; 2123 ads.sz6 = tilesize/6;
2121
2122 *x = (params->w+2) * TILE_SIZE + 2 * BORDER; 2124 *x = (params->w+2) * TILE_SIZE + 2 * BORDER;
2123 *y = (params->h+2) * TILE_SIZE + 2 * BORDER; 2125 *y = (params->h+2) * TILE_SIZE + 2 * BORDER;
2124} 2126}
@@ -2127,6 +2129,9 @@ static void game_set_size(drawing *dr, game_drawstate *ds,
2127 const game_params *params, int tilesize) 2129 const game_params *params, int tilesize)
2128{ 2130{
2129 ds->sz6 = tilesize/6; 2131 ds->sz6 = tilesize/6;
2132 ds->grid_line_all = max(LINE_THICK, 1);
2133 ds->grid_line_br = ds->grid_line_all / 2;
2134 ds->grid_line_tl = ds->grid_line_all - ds->grid_line_br;
2130} 2135}
2131 2136
2132enum { 2137enum {
@@ -2346,14 +2351,13 @@ static void draw_square(drawing *dr, game_drawstate *ds,
2346 /* Clip to the grid square. */ 2351 /* Clip to the grid square. */
2347 clip(dr, ox, oy, TILE_SIZE, TILE_SIZE); 2352 clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
2348 2353
2349 /* Clear the square. */ 2354 /* Clear the square so that it's got an appropriately-sized border
2355 * in COL_GRID and a central area in the right background colour. */
2350 best_bits((flags & DS_TRACK) == DS_TRACK, 2356 best_bits((flags & DS_TRACK) == DS_TRACK,
2351 (flags_drag & DS_TRACK) == DS_TRACK, &bg); 2357 (flags_drag & DS_TRACK) == DS_TRACK, &bg);
2352 draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, bg); 2358 draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_GRID);
2353 2359 draw_rect(dr, ox + GRID_LINE_TL, oy + GRID_LINE_TL,
2354 /* Draw outline of grid square */ 2360 TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL, bg);
2355 draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID);
2356 draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID);
2357 2361
2358 /* More outlines for clue squares. */ 2362 /* More outlines for clue squares. */
2359 if (flags & DS_CURSOR) { 2363 if (flags & DS_CURSOR) {
@@ -2389,8 +2393,8 @@ static void draw_square(drawing *dr, game_drawstate *ds,
2389 (flags_drag & DS_NOTRACK) == DS_NOTRACK, &c); 2393 (flags_drag & DS_NOTRACK) == DS_NOTRACK, &c);
2390 if (flags_best) { 2394 if (flags_best) {
2391 off = HALFSZ/2; 2395 off = HALFSZ/2;
2392 draw_line(dr, cx - off, cy - off, cx + off, cy + off, c); 2396 draw_thick_line(dr, LINE_THICK, cx - off, cy - off, cx + off, cy + off, c);
2393 draw_line(dr, cx - off, cy + off, cx + off, cy - off, c); 2397 draw_thick_line(dr, LINE_THICK, cx - off, cy + off, cx + off, cy - off, c);
2394 } 2398 }
2395 2399
2396 c = COL_TRACK; 2400 c = COL_TRACK;
@@ -2404,8 +2408,8 @@ static void draw_square(drawing *dr, game_drawstate *ds,
2404 cx += (d == R) ? t2 : (d == L) ? -t2 : 0; 2408 cx += (d == R) ? t2 : (d == L) ? -t2 : 0;
2405 cy += (d == D) ? t2 : (d == U) ? -t2 : 0; 2409 cy += (d == D) ? t2 : (d == U) ? -t2 : 0;
2406 2410
2407 draw_line(dr, cx - off, cy - off, cx + off, cy + off, c); 2411 draw_thick_line(dr, LINE_THICK, cx - off, cy - off, cx + off, cy + off, c);
2408 draw_line(dr, cx - off, cy + off, cx + off, cy - off, c); 2412 draw_thick_line(dr, LINE_THICK, cx - off, cy + off, cx + off, cy - off, c);
2409 } 2413 }
2410 } 2414 }
2411 2415
@@ -2426,12 +2430,14 @@ static void draw_clue(drawing *dr, game_drawstate *ds, int w, int clue, int i, i
2426 cy = CENTERED_COORD(i-w); 2430 cy = CENTERED_COORD(i-w);
2427 } 2431 }
2428 2432
2429 draw_rect(dr, cx - tsz + BORDER, cy - tsz + BORDER, 2433 draw_rect(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL,
2430 TILE_SIZE - BORDER, TILE_SIZE - BORDER, COL_BACKGROUND); 2434 TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL,
2435 COL_BACKGROUND);
2431 sprintf(buf, "%d", clue); 2436 sprintf(buf, "%d", clue);
2432 draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE, 2437 draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE,
2433 col, buf); 2438 col, buf);
2434 draw_update(dr, cx - tsz, cy - tsz, TILE_SIZE, TILE_SIZE); 2439 draw_update(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL,
2440 TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL);
2435} 2441}
2436 2442
2437static void draw_loop_ends(drawing *dr, game_drawstate *ds, 2443static 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
2498 2504
2499 draw_loop_ends(dr, ds, state, COL_CLUE); 2505 draw_loop_ends(dr, ds, state, COL_CLUE);
2500 2506
2501 draw_line(dr, COORD(ds->w), COORD(0), COORD(ds->w), COORD(ds->h), COL_GRID); 2507 draw_rect(dr, COORD(0) - GRID_LINE_BR, COORD(0) - GRID_LINE_BR,
2502 draw_line(dr, COORD(0), COORD(ds->h), COORD(ds->w), COORD(ds->h), COL_GRID); 2508 ds->w * TILE_SIZE + GRID_LINE_ALL,
2509 ds->h * TILE_SIZE + GRID_LINE_ALL, COL_GRID);
2503 2510
2504 draw_update(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER); 2511 draw_update(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER);
2505 2512
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>) {
27 '<span class="puzzle"><table>'. 27 '<span class="puzzle"><table>'.
28 '<tr><th align="center">%s</th></tr>'. 28 '<tr><th align="center">%s</th></tr>'.
29 '<tr><td align="center">'. 29 '<tr><td align="center">'.
30 '<img style="margin: 0.5em" alt="" title="%s" width=150 height=150 border=0 src="%s-web.png" />'. 30 '<a href="js/%s.html"><img style="margin: 0.5em" alt="" title="%s" width=150 height=150 border=0 src="%s-web.png" /></a>'.
31 '</td></tr>'. 31 '</td></tr>'.
32 '<tr><td align="center" style="font-size: 70%%"><code>[</code>'. 32 '<tr><td align="center" style="font-size: 70%%"><code>[</code>'.
33 ' <a href="java/%s.html">java</a> '. 33 ' <a href="java/%s.html">java</a> '.
@@ -41,6 +41,7 @@ while (<$desc>) {
41 '<tr><td align="center">%s</td></tr></table></span>'. 41 '<tr><td align="center">%s</td></tr></table></span>'.
42 "\n", 42 "\n",
43 encode_entities($displayname), 43 encode_entities($displayname),
44 encode_entities($id),
44 encode_entities($description), 45 encode_entities($description),
45 encode_entities($id), 46 encode_entities($id),
46 encode_entities($id), 47 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)
1545 fe->statusbar = NULL; 1545 fe->statusbar = NULL;
1546 fe->bitmap = NULL; 1546 fe->bitmap = NULL;
1547 1547
1548 SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe); 1548 SetWindowLongPtr(fe->hwnd, GWLP_USERDATA, (LONG_PTR)fe);
1549 1549
1550 return fe; 1550 return fe;
1551} 1551}
@@ -1992,7 +1992,7 @@ static void make_dialog_full_screen(HWND hwnd)
1992static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg, 1992static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg,
1993 WPARAM wParam, LPARAM lParam) 1993 WPARAM wParam, LPARAM lParam)
1994{ 1994{
1995 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); 1995 frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1996 1996
1997 switch (msg) { 1997 switch (msg) {
1998 case WM_INITDIALOG: 1998 case WM_INITDIALOG:
@@ -2249,7 +2249,7 @@ static void create_config_controls(frontend * fe)
2249static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg, 2249static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
2250 WPARAM wParam, LPARAM lParam) 2250 WPARAM wParam, LPARAM lParam)
2251{ 2251{
2252 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); 2252 frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
2253 config_item *i; 2253 config_item *i;
2254 struct cfg_aux *j; 2254 struct cfg_aux *j;
2255 2255
@@ -2260,7 +2260,7 @@ static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
2260 char *title; 2260 char *title;
2261 2261
2262 fe = (frontend *) lParam; 2262 fe = (frontend *) lParam;
2263 SetWindowLong(hwnd, GWL_USERDATA, lParam); 2263 SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam);
2264 fe->cfgbox = hwnd; 2264 fe->cfgbox = hwnd;
2265 2265
2266 fe->cfg = frontend_get_config(fe, fe->cfg_which, &title); 2266 fe->cfg = frontend_get_config(fe, fe->cfg_which, &title);
@@ -2479,8 +2479,8 @@ static void about(frontend *fe)
2479 2479
2480 SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE); 2480 SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
2481 2481
2482 SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe); 2482 SetWindowLongPtr(fe->cfgbox, GWLP_USERDATA, (LONG_PTR)fe);
2483 SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)AboutDlgProc); 2483 SetWindowLongPtr(fe->cfgbox, DWLP_DLGPROC, (LONG_PTR)AboutDlgProc);
2484 2484
2485 id = 1000; 2485 id = 1000;
2486 y = height/2; 2486 y = height/2;
@@ -2660,8 +2660,8 @@ static int get_config(frontend *fe, int which)
2660 2660
2661 SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE); 2661 SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
2662 2662
2663 SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe); 2663 SetWindowLongPtr(fe->cfgbox, GWLP_USERDATA, (LONG_PTR)fe);
2664 SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)ConfigDlgProc); 2664 SetWindowLongPtr(fe->cfgbox, DWLP_DLGPROC, (LONG_PTR)ConfigDlgProc);
2665 2665
2666 /* 2666 /*
2667 * Count the controls so we can allocate cfgaux. 2667 * Count the controls so we can allocate cfgaux.
@@ -2975,7 +2975,7 @@ static int is_alt_pressed(void)
2975static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, 2975static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
2976 WPARAM wParam, LPARAM lParam) 2976 WPARAM wParam, LPARAM lParam)
2977{ 2977{
2978 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); 2978 frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
2979 int cmd; 2979 int cmd;
2980 2980
2981 switch (message) { 2981 switch (message) {
@@ -2993,18 +2993,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
2993 cmd = wParam & ~0xF; /* low 4 bits reserved to Windows */ 2993 cmd = wParam & ~0xF; /* low 4 bits reserved to Windows */
2994 switch (cmd) { 2994 switch (cmd) {
2995 case IDM_NEW: 2995 case IDM_NEW:
2996 if (!midend_process_key(fe->me, 0, 0, 'n')) 2996 if (!midend_process_key(fe->me, 0, 0, UI_NEWGAME))
2997 PostQuitMessage(0); 2997 PostQuitMessage(0);
2998 break; 2998 break;
2999 case IDM_RESTART: 2999 case IDM_RESTART:
3000 midend_restart_game(fe->me); 3000 midend_restart_game(fe->me);
3001 break; 3001 break;
3002 case IDM_UNDO: 3002 case IDM_UNDO:
3003 if (!midend_process_key(fe->me, 0, 0, 'u')) 3003 if (!midend_process_key(fe->me, 0, 0, UI_UNDO))
3004 PostQuitMessage(0); 3004 PostQuitMessage(0);
3005 break; 3005 break;
3006 case IDM_REDO: 3006 case IDM_REDO:
3007 if (!midend_process_key(fe->me, 0, 0, '\x12')) 3007 if (!midend_process_key(fe->me, 0, 0, UI_REDO))
3008 PostQuitMessage(0); 3008 PostQuitMessage(0);
3009 break; 3009 break;
3010 case IDM_COPY: 3010 case IDM_COPY:
@@ -3026,7 +3026,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
3026 } 3026 }
3027 break; 3027 break;
3028 case IDM_QUIT: 3028 case IDM_QUIT:
3029 if (!midend_process_key(fe->me, 0, 0, 'q')) 3029 if (!midend_process_key(fe->me, 0, 0, UI_QUIT))
3030 PostQuitMessage(0); 3030 PostQuitMessage(0);
3031 break; 3031 break;
3032 case IDM_CONFIG: 3032 case IDM_CONFIG:
@@ -3405,8 +3405,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
3405 } 3405 }
3406 break; 3406 break;
3407 case WM_CHAR: 3407 case WM_CHAR:
3408 if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam)) 3408 {
3409 PostQuitMessage(0); 3409 int key = (unsigned char)wParam;
3410 if (key == '\x1A') {
3411 BYTE keystate[256];
3412 if (GetKeyboardState(keystate) &&
3413 (keystate[VK_SHIFT] & 0x80) &&
3414 (keystate[VK_CONTROL] & 0x80))
3415 key = UI_REDO;
3416 }
3417 if (!midend_process_key(fe->me, 0, 0, key))
3418 PostQuitMessage(0);
3419 }
3410 return 0; 3420 return 0;
3411 case WM_TIMER: 3421 case WM_TIMER:
3412 if (fe->timer) { 3422 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);
61% # (individual files or shortcuts or additions to PATH) that are 61% # (individual files or shortcuts or additions to PATH) that are
62% # installed. 62% # installed.
63 <Directory Id="TARGETDIR" Name="SourceDir"> 63 <Directory Id="TARGETDIR" Name="SourceDir">
64 <Directory Id="ProgramFilesFolder" Name="PFiles"> 64 <Directory Id="ProgramFiles64Folder" Name="PFiles">
65 <Directory Id="INSTALLDIR" Name="Simon Tatham's Portable Puzzle Collection"> 65 <Directory Id="INSTALLDIR" Name="Simon Tatham's Portable Puzzle Collection">
66 66
67% # The following components all install things in the main 67% # The following components all install things in the main