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