diff options
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 | |||
54 | in puzzles do make -f Makefile.doc # build help files for installer | 54 | in puzzles do make -f Makefile.doc # build help files for installer |
55 | in puzzles do mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs | 55 | in puzzles do mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs |
56 | in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss | 56 | in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss |
57 | delegate windows | 57 | ifneq "$(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 | 69 | else |
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) |
74 | enddelegate | 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 | ||
80 | endif | ||
75 | in puzzles do chmod +x *.exe | 81 | in 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 |
153 | enddelegate | 159 | enddelegate |
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. | ||
168 | in puzzles do mkdir jstest | ||
169 | in 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. |
156 | in puzzles do echo "AddType application/octet-stream .chm" > .htaccess | 172 | in puzzles do echo "AddType application/octet-stream .chm" > .htaccess |
157 | in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess | 173 | in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess |
158 | in puzzles do echo "AddType application/octet-stream .cnt" >> .htaccess | 174 | in puzzles do echo "AddType application/octet-stream .cnt" >> .htaccess |
159 | in . do set -- puzzles*.tar.gz; echo RedirectMatch temp '(.*/)'puzzles.tar.gz '$$1'"$$1" >> puzzles/.htaccess | 175 | in . do set -- puzzles*.tar.gz; echo RedirectMatch temp '(.*/)'puzzles.tar.gz '$$1'"$$1" >> puzzles/.htaccess |
160 | in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.msi '$$1'puzzles-$(Version)-installer.msi >> .htaccess | 176 | in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.msi '$$1'puzzles-$(Version)-installer.msi >> .htaccess |
161 | in 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! |
164 | deliver puzzles/icons/*-web.png $@ | 179 | deliver puzzles/icons/*-web.png $@ |
@@ -172,9 +187,9 @@ deliver puzzles/puzzles.hlp $@ | |||
172 | deliver puzzles/puzzles.cnt $@ | 187 | deliver puzzles/puzzles.cnt $@ |
173 | deliver puzzles/puzzles.zip $@ | 188 | deliver puzzles/puzzles.zip $@ |
174 | deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi | 189 | deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi |
175 | deliver puzzles/Output/installer.exe puzzles-$(Version)-installer.exe | ||
176 | deliver puzzles/*.jar java/$@ | 190 | deliver puzzles/*.jar java/$@ |
177 | deliver puzzles/js/*.js js/$@ | 191 | deliver puzzles/js/*.js js/$@ |
192 | deliver puzzles/jstest/*.html jstest/$@ | ||
178 | deliver puzzles/html/*.html html/$@ | 193 | deliver puzzles/html/*.html html/$@ |
179 | deliver puzzles/html/*.pl html/$@ | 194 | deliver puzzles/html/*.pl html/$@ |
180 | deliver puzzles/wwwspans.html $@ | 195 | deliver puzzles/wwwspans.html $@ |
diff --git a/apps/plugins/puzzles/src/PuzzleApplet.java b/apps/plugins/puzzles/src/PuzzleApplet.java index 512aede580..8455734dd1 100644 --- a/apps/plugins/puzzles/src/PuzzleApplet.java +++ b/apps/plugins/puzzles/src/PuzzleApplet.java | |||
@@ -61,19 +61,19 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB { | |||
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 | ||
1929 | This function may be used for both drawing and printing. | 1929 | This function may be used for both drawing and printing. |
1930 | 1930 | ||
1931 | If the specified thickness is less than 1.0, 1.0 is used. | ||
1932 | This 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) | |||
90 | void draw_thick_line(drawing *dr, float thickness, | 90 | void 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 | |||
766 | struct savefile_write_ctx { | ||
767 | char *buffer; | ||
768 | size_t pos; | ||
769 | }; | ||
770 | |||
771 | static 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 | |||
779 | char *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 | |||
799 | void free_save_file(char *buffer) | ||
800 | { | ||
801 | sfree(buffer); | ||
802 | } | ||
803 | |||
804 | struct savefile_read_ctx { | ||
805 | const char *buffer; | ||
806 | int len_remaining; | ||
807 | }; | ||
808 | |||
809 | static 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 | |||
820 | void 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. | ||
133 | function 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 | |||
167 | function 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 | |||
191 | function 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. |
133 | function initPuzzle() { | 199 | function 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 | */ |
141 | struct frontend { | 141 | struct 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 | ||
2351 | static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, | 2343 | static 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 @@ | |||
3 | use strict; | 3 | use strict; |
4 | use warnings; | 4 | use warnings; |
5 | 5 | ||
6 | my $jspath = ""; | ||
7 | while ($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 | |||
6 | open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n"; | 17 | open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n"; |
7 | my $footer = ""; | 18 | my $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,}, |
291 | enum { GRIDLIST(GRID_LOOPYTYPE) }; | 291 | enum { GRIDLIST(GRID_LOOPYTYPE) LOOPY_GRID_DUMMY_TERMINATOR }; |
292 | static char const *const gridnames[] = { GRIDLIST(GRID_NAME) }; | 292 | static char const *const gridnames[] = { GRIDLIST(GRID_NAME) }; |
293 | #define GRID_CONFIGS GRIDLIST(GRID_CONFIG) | 293 | #define GRID_CONFIGS GRIDLIST(GRID_CONFIG) |
294 | static grid_type grid_types[] = { GRIDLIST(GRID_GRIDTYPE) }; | 294 | static grid_type grid_types[] = { GRIDLIST(GRID_GRIDTYPE) }; |
diff --git a/apps/plugins/puzzles/src/midend.c b/apps/plugins/puzzles/src/midend.c index f80a7fa19f..09b59b25e2 100644 --- a/apps/plugins/puzzles/src/midend.c +++ b/apps/plugins/puzzles/src/midend.c | |||
@@ -590,33 +590,40 @@ static int midend_really_process_key(midend *me, int x, int y, int button) | |||
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() */ |
376 | int ftoa(char *buf, float f) | 376 | int 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 | ||
506 | if (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 | |||
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 | |||
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 | |||
506 | if (defined $makefiles{'cygwin'}) { | 651 | if (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 | ||
308 | int jcallback_menu_key_event(int key) | 308 | int 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 | |||
316 | int 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 | |||
324 | int 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 | |||
332 | int 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 | |||
85 | enum { | 71 | enum { |
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 | ||
91 | typedef struct game_immutable_state { | ||
92 | int refcount; | ||
93 | unsigned char *barriers; | ||
94 | } game_immutable_state; | ||
95 | |||
105 | struct game_state { | 96 | struct 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 | ||
124 | struct xyd { | 115 | struct 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 | */ | ||
465 | static int net_solver(int w, int h, unsigned char *tiles, | 461 | static 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 | ||
1742 | static void free_game(game_state *state) | 1744 | static 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, | |||
1990 | static int *compute_loops(const game_state *state) | 2003 | static 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 | ||
1996 | struct game_ui { | 2009 | struct game_ui { |
@@ -2051,9 +2064,8 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate, | |||
2051 | struct game_drawstate { | 2064 | struct 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) | |||
2434 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | 2446 | static 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 | |||
2451 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | 2468 | static 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) | |||
2457 | static void game_compute_size(const game_params *params, int tilesize, | 2474 | static 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 | ||
2464 | static void game_set_size(drawing *dr, game_drawstate *ds, | 2485 | static 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 | ||
2535 | static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2, | 2556 | static 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 | ||
2545 | static 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 | |
2559 | static void draw_barrier_corner(drawing *dr, game_drawstate *ds, | 2577 | static 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 | ||
2583 | static 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 | /* | 2619 | static 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 | */ | ||
2605 | static 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 | ||
2828 | static void game_redraw(drawing *dr, game_drawstate *ds, | 2838 | static 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(¶ms, 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 | ||
1740 | struct game_drawstate { | 1743 | struct 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 | ||
2132 | enum { | 2137 | enum { |
@@ -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 | ||
2437 | static void draw_loop_ends(drawing *dr, game_drawstate *ds, | 2443 | static void draw_loop_ends(drawing *dr, game_drawstate *ds, |
@@ -2498,8 +2504,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldst | |||
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) | |||
1992 | static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg, | 1992 | static 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) | |||
2249 | static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg, | 2249 | static 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) | |||
2975 | static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, | 2975 | static 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 |