puzzles: resync with upstream

This brings puzzles to upstream commit 84d3fd2.

Change-Id: I808a197f868032d771fc101a15666c5ec4b9f94b
This commit is contained in:
Franklin Wei 2017-09-30 17:47:13 -04:00
parent ea679de837
commit b9386109e8
26 changed files with 1037 additions and 583 deletions

View file

@ -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 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 mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs
in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss
delegate windows ifneq "$(VISUAL_STUDIO)" "yes" then
# FIXME: Cygwin alternative? in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl clean
in puzzles with visualstudio do/win nmake -f Makefile.vc clean in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl VER=-DVER=$(Version)
in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version)
# Code-sign the binaries, if the local bob config provides a script # 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 # 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 # provide a 'more info' URL, and an optional -n option to provide a
# program name, and that it can take multiple .exe filename # program name, and that it can take multiple .exe filename
# arguments and sign them all in place. # arguments and sign them all in place.
ifneq "$(cross_winsigncode)" "" in puzzles do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe
# Build installers.
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 ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe
# Build installers. # Build installers.
in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj
in puzzles with innosetup do/win iscc puzzles.iss 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/*.exe
return puzzles/Output/installer.exe
return puzzles/puzzles.msi return puzzles/puzzles.msi
enddelegate enddelegate
endif
in puzzles do chmod +x *.exe in puzzles do chmod +x *.exe
# Build the Pocket PC binaries and CAB. # Build the Pocket PC binaries and CAB.
@ -152,13 +158,22 @@ delegate emscripten
return puzzles/js/*.js return puzzles/js/*.js
enddelegate 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. # 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 .chm" > .htaccess
in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess
in puzzles do echo "AddType application/octet-stream .cnt" >> .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 . 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.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! # Phew, we're done. Deliver everything!
deliver puzzles/icons/*-web.png $@ deliver puzzles/icons/*-web.png $@
@ -172,9 +187,9 @@ deliver puzzles/puzzles.hlp $@
deliver puzzles/puzzles.cnt $@ deliver puzzles/puzzles.cnt $@
deliver puzzles/puzzles.zip $@ deliver puzzles/puzzles.zip $@
deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi
deliver puzzles/Output/installer.exe puzzles-$(Version)-installer.exe
deliver puzzles/*.jar java/$@ deliver puzzles/*.jar java/$@
deliver puzzles/js/*.js js/$@ deliver puzzles/js/*.js js/$@
deliver puzzles/jstest/*.html jstest/$@
deliver puzzles/html/*.html html/$@ deliver puzzles/html/*.html html/$@
deliver puzzles/html/*.pl html/$@ deliver puzzles/html/*.pl html/$@
deliver puzzles/wwwspans.html $@ deliver puzzles/wwwspans.html $@

View file

@ -61,19 +61,19 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
JMenuBar menubar = new JMenuBar(); JMenuBar menubar = new JMenuBar();
JMenu jm; JMenu jm;
menubar.add(jm = new JMenu("Game")); menubar.add(jm = new JMenu("Game"));
addMenuItemWithKey(jm, "New", 'n'); addMenuItemCallback(jm, "New", "jcallback_newgame_event");
addMenuItemCallback(jm, "Restart", "jcallback_restart_event"); addMenuItemCallback(jm, "Restart", "jcallback_restart_event");
addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC); addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC);
addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED); addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED);
jm.addSeparator(); jm.addSeparator();
addMenuItemWithKey(jm, "Undo", 'u'); addMenuItemCallback(jm, "Undo", "jcallback_undo_event");
addMenuItemWithKey(jm, "Redo", 'r'); addMenuItemCallback(jm, "Redo", "jcallback_redo_event");
jm.addSeparator(); jm.addSeparator();
solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event"); solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
solveCommand.setEnabled(false); solveCommand.setEnabled(false);
if (mainWindow != null) { if (mainWindow != null) {
jm.addSeparator(); jm.addSeparator();
addMenuItemWithKey(jm, "Exit", 'q'); addMenuItemCallback(jm, "Exit", "jcallback_quit_event");
} }
menubar.add(typeMenu = new JMenu("Type")); menubar.add(typeMenu = new JMenu("Type"));
typeMenu.setVisible(false); typeMenu.setVisible(false);
@ -126,7 +126,12 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
} }
} }
public void keyTyped(KeyEvent e) { 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() { 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()}); 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) { private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) {
return addMenuItemCallback(jm, name, callback, new int[] {arg}, false); return addMenuItemCallback(jm, name, callback, new int[] {arg}, false);
} }

View file

@ -17,6 +17,7 @@
!makefile gnustep Makefile.gnustep !makefile gnustep Makefile.gnustep
!makefile nestedvm Makefile.nestedvm !makefile nestedvm Makefile.nestedvm
!makefile emcc Makefile.emcc !makefile emcc Makefile.emcc
!makefile clangcl Makefile.clangcl
!srcdir icons/ !srcdir icons/

View file

@ -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">}

View file

@ -1928,6 +1928,9 @@ Indeed, even horizontal or vertical lines may be anti-aliased.
This function may be used for both drawing and printing. 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()} \S{drawing-draw-text} \cw{draw_text()}
\c void draw_text(drawing *dr, int x, int y, int fonttype, \c void draw_text(drawing *dr, int x, int y, int fonttype,

View file

@ -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, void draw_thick_line(drawing *dr, float thickness,
float x1, float y1, float x2, float y2, int colour) float x1, float y1, float x2, float y2, int colour)
{ {
if (thickness < 1.0)
thickness = 1.0;
if (dr->api->draw_thick_line) { if (dr->api->draw_thick_line) {
dr->api->draw_thick_line(dr->handle, thickness, dr->api->draw_thick_line(dr->handle, thickness,
x1, y1, x2, y2, colour); x1, y1, x2, y2, colour);

View file

@ -310,6 +310,8 @@ void key(int keycode, int charcode, const char *key, const char *chr,
keyevent = MOD_NUM_KEYPAD | '7'; keyevent = MOD_NUM_KEYPAD | '7';
} else if (!strnullcmp(key, "PageUp") || keycode==33) { } else if (!strnullcmp(key, "PageUp") || keycode==33) {
keyevent = MOD_NUM_KEYPAD | '9'; keyevent = MOD_NUM_KEYPAD | '9';
} else if (shift && ctrl && (keycode & 0x1F) == 26) {
keyevent = UI_REDO;
} else if (chr && chr[0] && !chr[1]) { } else if (chr && chr[0] && !chr[1]) {
keyevent = chr[0] & 0xFF; keyevent = chr[0] & 0xFF;
} else if (keycode >= 96 && keycode < 106) { } 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 (keyevent >= 0) {
if (shift && keyevent >= 0x100) if (shift && (keyevent >= 0x100 && !IS_UI_FAKE_KEY(keyevent)))
keyevent |= MOD_SHFT; keyevent |= MOD_SHFT;
if (ctrl) { if (ctrl && !IS_UI_FAKE_KEY(keyevent)) {
if (keyevent >= 0x100) if (keyevent >= 0x100)
keyevent |= MOD_CTRL; keyevent |= MOD_CTRL;
else else
@ -725,7 +727,7 @@ void command(int n)
update_undo_redo(); update_undo_redo();
break; break;
case 5: /* New Game */ case 5: /* New Game */
midend_process_key(me, 0, 0, 'n'); midend_process_key(me, 0, 0, UI_NEWGAME);
update_undo_redo(); update_undo_redo();
js_focus_canvas(); js_focus_canvas();
break; break;
@ -735,12 +737,12 @@ void command(int n)
js_focus_canvas(); js_focus_canvas();
break; break;
case 7: /* Undo */ case 7: /* Undo */
midend_process_key(me, 0, 0, 'u'); midend_process_key(me, 0, 0, UI_UNDO);
update_undo_redo(); update_undo_redo();
js_focus_canvas(); js_focus_canvas();
break; break;
case 8: /* Redo */ case 8: /* Redo */
midend_process_key(me, 0, 0, 'r'); midend_process_key(me, 0, 0, UI_REDO);
update_undo_redo(); update_undo_redo();
js_focus_canvas(); js_focus_canvas();
break; break;
@ -756,6 +758,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 * 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() * that's the most convenient thing in Emscripten, but it's not main()

View file

@ -108,7 +108,6 @@ mergeInto(LibraryManager.library, {
item.appendChild(tick); item.appendChild(tick);
item.appendChild(document.createTextNode(name)); item.appendChild(document.createTextNode(name));
var submenu = document.createElement("ul"); var submenu = document.createElement("ul");
submenu.className = "left";
item.appendChild(submenu); item.appendChild(submenu);
gametypesubmenus[menuid].appendChild(item); gametypesubmenus[menuid].appendChild(item);
var toret = gametypesubmenus.length; var toret = gametypesubmenus.length;
@ -575,38 +574,7 @@ mergeInto(LibraryManager.library, {
* overlay on top of the rest of the puzzle web page. * overlay on top of the rest of the puzzle web page.
*/ */
js_dialog_init: function(titletext) { js_dialog_init: function(titletext) {
// Create an overlay on the page which darkens everything dialog_init(Pointer_stringify(titletext));
// 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;
}, },
/* /*
@ -701,29 +669,13 @@ mergeInto(LibraryManager.library, {
* everything else on the page. * everything else on the page.
*/ */
js_dialog_launch: function() { js_dialog_launch: function() {
// Put in the OK and Cancel buttons at the bottom. dialog_launch(function(event) {
var button;
button = document.createElement("input");
button.type = "button";
button.value = "OK";
button.onclick = function(event) {
for (var i in dlg_return_funcs) for (var i in dlg_return_funcs)
dlg_return_funcs[i](); dlg_return_funcs[i]();
command(3); command(3); // OK
} }, function(event) {
dlg_form.appendChild(button); command(4); // Cancel
});
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);
}, },
/* /*
@ -733,10 +685,7 @@ mergeInto(LibraryManager.library, {
* associated with it. * associated with it.
*/ */
js_dialog_cleanup: function() { js_dialog_cleanup: function() {
document.body.removeChild(dlg_dimmer); dialog_cleanup();
document.body.removeChild(dlg_form);
dlg_dimmer = dlg_form = null;
onscreen_canvas.focus();
}, },
/* /*

View file

@ -129,6 +129,72 @@ function disable_menu_item(item, disabledFlag) {
item.className = ""; 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. // Init function called from body.onload.
function initPuzzle() { function initPuzzle() {
// Construct the off-screen canvas used for double buffering. // Construct the off-screen canvas used for double buffering.
@ -230,6 +296,58 @@ function initPuzzle() {
command(9); 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"); gametypelist = document.getElementById("gametype");
gametypesubmenus.push(gametypelist); gametypesubmenus.push(gametypelist);

View file

@ -18,6 +18,10 @@
'_timer_callback', '_timer_callback',
// Callback from button presses in the UI outside the canvas // Callback from button presses in the UI outside the canvas
'_command', '_command',
// Game-saving and game-loading functions
'_get_save_file',
'_free_save_file',
'_load_game',
// Callbacks to return values from dialog boxes // Callbacks to return values from dialog boxes
'_dlg_return_sval', '_dlg_return_sval',
'_dlg_return_ival', '_dlg_return_ival',

View file

@ -140,7 +140,7 @@ struct font {
*/ */
struct frontend { struct frontend {
GtkWidget *window; GtkWidget *window;
GtkAccelGroup *accelgroup; GtkAccelGroup *dummy_accelgroup;
GtkWidget *area; GtkWidget *area;
GtkWidget *statusbar; GtkWidget *statusbar;
GtkWidget *menubar; GtkWidget *menubar;
@ -1160,16 +1160,6 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
if (!backing_store_ok(fe)) if (!backing_store_ok(fe))
return TRUE; 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. */ /* Handle mnemonics. */
if (gtk_window_activate_key(GTK_WINDOW(fe->window), event)) if (gtk_window_activate_key(GTK_WINDOW(fe->window), event))
return TRUE; 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_Delete ||
event->keyval == GDK_KEY_KP_Delete) event->keyval == GDK_KEY_KP_Delete)
keyval = '\177'; keyval = '\177';
else if ((event->keyval == 'z' || event->keyval == 'Z') && shift && ctrl)
keyval = UI_REDO;
else if (event->string[0] && !event->string[1]) else if (event->string[0] && !event->string[1])
keyval = (unsigned char)event->string[0]; keyval = (unsigned char)event->string[0];
else else
@ -2348,32 +2340,34 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
#endif #endif
} }
static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, static GtkWidget *add_menu_ui_item(
char *text, int key) frontend *fe, GtkContainer *cont, char *text, int action,
int accel_key, int accel_keyqual)
{ {
GtkWidget *menuitem = gtk_menu_item_new_with_label(text); GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
int keyqual;
gtk_container_add(cont, menuitem); 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_signal_connect(G_OBJECT(menuitem), "activate",
G_CALLBACK(menu_key_event), fe); G_CALLBACK(menu_key_event), fe);
switch (key & ~0x1F) {
case 0x00: if (accel_key) {
key += 0x60; /*
keyqual = GDK_CONTROL_MASK; * Display a keyboard accelerator alongside this menu item.
break; * Actually this won't be processed via the usual GTK
case 0x40: * accelerator system, because we add it to a dummy
key += 0x20; * accelerator group which is never actually activated on the
keyqual = GDK_SHIFT_MASK; * main window; this permits back ends to override special
break; * keys like 'n' and 'r' and 'u' in some UI states. So
default: * whatever keystroke we display here will still go to
keyqual = 0; * key_event and be handled in the normal way.
break; */
}
gtk_widget_add_accelerator(menuitem, gtk_widget_add_accelerator(menuitem,
"activate", fe->accelgroup, "activate", fe->dummy_accelgroup,
key, keyqual, accel_key, accel_keyqual,
GTK_ACCEL_VISIBLE); GTK_ACCEL_VISIBLE | GTK_ACCEL_LOCKED);
}
gtk_widget_show(menuitem); gtk_widget_show(menuitem);
return 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_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox));
gtk_widget_show(GTK_WIDGET(vbox)); gtk_widget_show(GTK_WIDGET(vbox));
fe->accelgroup = gtk_accel_group_new(); fe->dummy_accelgroup = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(fe->window), fe->accelgroup); /*
* Intentionally _not_ added to the window via
* gtk_window_add_accel_group; see menu_key_event
*/
hbox = GTK_BOX(gtk_hbox_new(FALSE, 0)); hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
gtk_box_pack_start(vbox, GTK_WIDGET(hbox), FALSE, 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(); menu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); 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"); menuitem = gtk_menu_item_new_with_label("Restart");
gtk_container_add(GTK_CONTAINER(menu), menuitem); 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); gtk_widget_show(menuitem);
#ifndef STYLUS_BASED #ifndef STYLUS_BASED
add_menu_separator(GTK_CONTAINER(menu)); add_menu_separator(GTK_CONTAINER(menu));
add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u'); add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);
add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", 'r'); add_menu_ui_item(fe, GTK_CONTAINER(menu), "Redo", UI_REDO, 'r', 0);
#endif #endif
if (thegame.can_format_as_text_ever) { if (thegame.can_format_as_text_ever) {
add_menu_separator(GTK_CONTAINER(menu)); add_menu_separator(GTK_CONTAINER(menu));
@ -2646,7 +2643,7 @@ static frontend *new_window(char *arg, int argtype, char **error)
gtk_widget_show(menuitem); gtk_widget_show(menuitem);
} }
add_menu_separator(GTK_CONTAINER(menu)); 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"); menuitem = gtk_menu_item_new_with_mnemonic("_Help");
gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem); 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 #ifdef STYLUS_BASED
menuitem=gtk_button_new_with_mnemonic("_Redo"); menuitem=gtk_button_new_with_mnemonic("_Redo");
g_object_set_data(G_OBJECT(menuitem), "user-data", 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_signal_connect(G_OBJECT(menuitem), "clicked",
G_CALLBACK(menu_key_event), fe); G_CALLBACK(menu_key_event), fe);
gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0); 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"); menuitem=gtk_button_new_with_mnemonic("_Undo");
g_object_set_data(G_OBJECT(menuitem), "user-data", 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_signal_connect(G_OBJECT(menuitem), "clicked",
G_CALLBACK(menu_key_event), fe); G_CALLBACK(menu_key_event), fe);
gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0); gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0);

View file

@ -3,6 +3,17 @@
use strict; use strict;
use warnings; 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"; open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n";
my $footer = ""; my $footer = "";
$footer .= $_ while <$footerfile>; $footer .= $_ while <$footerfile>;
@ -62,7 +73,7 @@ EOF
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=ASCII" /> <meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
<title>${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection</title> <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"> <style class="text/css">
/* Margins and centring on the top-level div for the game menu */ /* Margins and centring on the top-level div for the game menu */
#gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center } #gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center }
@ -103,6 +114,15 @@ EOF
color: rgba(0,0,0,0.5); 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 { #gamemenu ul li:first-of-type {
/* Reinstate the left border for the leftmost top-level menu item */ /* Reinstate the left border for the leftmost top-level menu item */
border-left: 1px solid rgba(0,0,0,0.3); border-left: 1px solid rgba(0,0,0,0.3);
@ -196,14 +216,19 @@ ${unfinishedpara}
<hr> <hr>
<div id="puzzle" style="display: none"> <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="restart">Restart game</li
><li id="undo">Undo move</li ><li id="undo">Undo move</li
><li id="redo">Redo move</li ><li id="redo">Redo move</li
><li id="solve">Solve game</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> ></ul></div>
<div align=center> <div align=center>
<div id="resizable" style="position:relative; left:0; top:0"> <div id="resizable" style="position:relative; left:0; top:0">

View file

@ -288,7 +288,7 @@ static void check_caches(const solver_state* sstate);
{amin, omin, \ {amin, omin, \
"Width and height for this grid type must both be at least " #amin, \ "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,}, "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) }; static char const *const gridnames[] = { GRIDLIST(GRID_NAME) };
#define GRID_CONFIGS GRIDLIST(GRID_CONFIG) #define GRID_CONFIGS GRIDLIST(GRID_CONFIG)
static grid_type grid_types[] = { GRIDLIST(GRID_GRIDTYPE) }; static grid_type grid_types[] = { GRIDLIST(GRID_GRIDTYPE) };

View file

@ -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; int type = MOVE, gottype = FALSE, ret = 1;
float anim_time; float anim_time;
game_state *s; game_state *s;
char *movestr; char *movestr = NULL;
movestr = if (!IS_UI_FAKE_KEY(button)) {
me->ourgame->interpret_move(me->states[me->statepos-1].state, movestr = me->ourgame->interpret_move(
me->states[me->statepos-1].state,
me->ui, me->drawstate, x, y, button); me->ui, me->drawstate, x, y, button);
}
if (!movestr) { if (!movestr) {
if (button == 'n' || button == 'N' || button == '\x0E') { if (button == 'n' || button == 'N' || button == '\x0E' ||
button == UI_NEWGAME) {
midend_new_game(me); midend_new_game(me);
midend_redraw(me); midend_redraw(me);
goto done; /* never animate */ goto done; /* never animate */
} else if (button == 'u' || button == 'U' || } else if (button == 'u' || button == 'U' ||
button == '\x1A' || button == '\x1F') { button == '\x1A' || button == '\x1F' ||
button == UI_UNDO) {
midend_stop_anim(me); midend_stop_anim(me);
type = me->states[me->statepos-1].movetype; type = me->states[me->statepos-1].movetype;
gottype = TRUE; gottype = TRUE;
if (!midend_undo(me)) if (!midend_undo(me))
goto done; goto done;
} else if (button == 'r' || button == 'R' || } else if (button == 'r' || button == 'R' ||
button == '\x12' || button == '\x19') { button == '\x12' || button == '\x19' ||
button == UI_REDO) {
midend_stop_anim(me); midend_stop_anim(me);
if (!midend_redo(me)) if (!midend_redo(me))
goto done; 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)) if (midend_solve(me))
goto done; goto done;
} else if (button == 'q' || button == 'Q' || button == '\x11') { } else if (button == 'q' || button == 'Q' || button == '\x11' ||
button == UI_QUIT) {
ret = 0; ret = 0;
goto done; goto done;
} else } else
@ -2059,6 +2066,8 @@ char *midend_deserialise(midend *me,
me->ourgame->new_drawstate(me->drawing, me->ourgame->new_drawstate(me->drawing,
me->states[me->statepos-1].state); me->states[me->statepos-1].state);
midend_size_new_drawstate(me); 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! */ ret = NULL; /* success! */

View file

@ -2963,7 +2963,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
float animtime, float flashtime) float animtime, float flashtime)
{ {
int x, y; int x, y;
int mines, markers, bg; int mines, markers, closed, bg;
int cx = -1, cy = -1, cmoved; int cx = -1, cy = -1, cmoved;
if (flashtime) { 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 * 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 (y = 0; y < ds->h; y++)
for (x = 0; x < ds->w; x++) { for (x = 0; x < ds->w; x++) {
int v = state->grid[y*ds->w+x], cc = 0; int v = state->grid[y*ds->w+x], cc = 0;
if (v < 0)
closed++;
if (v == -1) if (v == -1)
markers++; markers++;
if (state->layout->mines && state->layout->mines[y*ds->w+x]) 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 else
sprintf(statusbar, "COMPLETED!"); sprintf(statusbar, "COMPLETED!");
} else { } else {
int safe_closed = closed - mines;
sprintf(statusbar, "Marked: %d / %d", markers, 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) if (ui->deaths)
sprintf(statusbar + strlen(statusbar), sprintf(statusbar + strlen(statusbar),

View file

@ -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() */ /* another kludge for platforms without %g support in *printf() */
int ftoa(char *buf, float f) 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: */ /* vim: set shiftwidth=4 tabstop=8: */

View file

@ -319,7 +319,7 @@ sub mfval($) {
# Returns true if the argument is a known makefile type. Otherwise, # Returns true if the argument is a known makefile type. Otherwise,
# prints a warning and returns false; # prints a warning and returns false;
if (grep { $type eq $_ } 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; return 1;
} }
warn "$.:unknown makefile type '$type'\n"; warn "$.:unknown makefile type '$type'\n";
@ -503,6 +503,151 @@ $orig_dir = cwd;
# Now we're ready to output the actual Makefiles. # 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'}) { if (defined $makefiles{'cygwin'}) {
$mftyp = 'cygwin'; $mftyp = 'cygwin';
$dirpfx = &dirpfx($makefiles{'cygwin'}, "/"); $dirpfx = &dirpfx($makefiles{'cygwin'}, "/");

View file

@ -305,10 +305,34 @@ static int get_config(frontend *fe, int which)
return fe->cfgret; return fe->cfgret;
} }
int jcallback_menu_key_event(int key) int jcallback_newgame_event(void)
{ {
frontend *fe = (frontend *)_fe; 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 42;
return 0; return 0;
} }

View file

@ -27,13 +27,6 @@
#define USE_DRAGGING #define USE_DRAGGING
#endif #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 */ /* Direction and other bitfields */
#define R 0x01 #define R 0x01
#define U 0x02 #define U 0x02
@ -65,7 +58,7 @@
#define PREFERRED_TILE_SIZE 32 #define PREFERRED_TILE_SIZE 32
#define TILE_SIZE (ds->tilesize) #define TILE_SIZE (ds->tilesize)
#define TILE_BORDER 1 #define LINE_THICK ((TILE_SIZE+47)/48)
#ifdef SMALL_SCREEN #ifdef SMALL_SCREEN
#define WINDOW_OFFSET 4 #define WINDOW_OFFSET 4
#else #else
@ -75,13 +68,6 @@
#define ROTATE_TIME 0.13F #define ROTATE_TIME 0.13F
#define FLASH_FRAME 0.07F #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 { enum {
COL_BACKGROUND, COL_BACKGROUND,
COL_LOCKED, COL_LOCKED,
@ -102,12 +88,17 @@ struct game_params {
float barrier_probability; float barrier_probability;
}; };
typedef struct game_immutable_state {
int refcount;
unsigned char *barriers;
} game_immutable_state;
struct game_state { struct game_state {
int width, height, wrapping, completed; int width, height, wrapping, completed;
int last_rotate_x, last_rotate_y, last_rotate_dir; int last_rotate_x, last_rotate_y, last_rotate_dir;
int used_solve; int used_solve;
unsigned char *tiles; unsigned char *tiles;
unsigned char *barriers; struct game_immutable_state *imm;
}; };
#define OFFSETWH(x2,y2,x1,y1,dir,width,height) \ #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 index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
#define tile(state, x, y) index(state, (state)->tiles, x, y) #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 { struct xyd {
int x, y, direction; int x, y, direction;
@ -462,6 +453,11 @@ static int todo_get(struct todo *todo) {
return ret; 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, static int net_solver(int w, int h, unsigned char *tiles,
unsigned char *barriers, int wrapping) unsigned char *barriers, int wrapping)
{ {
@ -736,7 +732,11 @@ static int net_solver(int w, int h, unsigned char *tiles,
#endif #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) { if (j < i) {
done_something = TRUE; 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. * Mark all completely determined tiles as locked.
*/ */
j = TRUE; j = +1;
for (i = 0; i < w*h; i++) { for (i = 0; i < w*h; i++) {
if (tilestate[i * 4 + 1] == 255) { if (tilestate[i * 4 + 1] == 255) {
assert(tilestate[i * 4 + 0] != 255); assert(tilestate[i * 4 + 0] != 255);
tiles[i] = tilestate[i * 4] | LOCKED; tiles[i] = tilestate[i * 4] | LOCKED;
} else { } else {
tiles[i] &= ~LOCKED; 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. * 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; int n = 0;
/* /*
@ -1647,12 +1647,14 @@ static game_state *new_game(midend *me, const game_params *params,
w = state->width = params->width; w = state->width = params->width;
h = state->height = params->height; h = state->height = params->height;
state->wrapping = params->wrapping; 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->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0;
state->completed = state->used_solve = FALSE; state->completed = state->used_solve = FALSE;
state->tiles = snewn(state->width * state->height, unsigned char); state->tiles = snewn(state->width * state->height, unsigned char);
memset(state->tiles, 0, state->width * state->height); memset(state->tiles, 0, state->width * state->height);
state->barriers = snewn(state->width * state->height, unsigned char); state->imm->barriers = snewn(state->width * state->height, unsigned char);
memset(state->barriers, 0, state->width * state->height); memset(state->imm->barriers, 0, state->width * state->height);
/* /*
* Parse the game description into the grid. * Parse the game description into the grid.
@ -1723,6 +1725,8 @@ static game_state *dup_game(const game_state *state)
game_state *ret; game_state *ret;
ret = snew(game_state); ret = snew(game_state);
ret->imm = state->imm;
ret->imm->refcount++;
ret->width = state->width; ret->width = state->width;
ret->height = state->height; ret->height = state->height;
ret->wrapping = state->wrapping; 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->last_rotate_y = state->last_rotate_y;
ret->tiles = snewn(state->width * state->height, unsigned char); ret->tiles = snewn(state->width * state->height, unsigned char);
memcpy(ret->tiles, state->tiles, state->width * state->height); 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; return ret;
} }
static void free_game(game_state *state) static void free_game(game_state *state)
{ {
if (--state->imm->refcount == 0) {
sfree(state->imm->barriers);
sfree(state->imm);
}
sfree(state->tiles); sfree(state->tiles);
sfree(state->barriers);
sfree(state); 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 * Run the internal solver on the provided grid. This might
* not yield a complete solution. * not yield a complete solution.
*/ */
int solver_result;
memcpy(tiles, state->tiles, state->width * state->height); memcpy(tiles, state->tiles, state->width * state->height);
net_solver(state->width, state->height, tiles, solver_result = net_solver(state->width, state->height, tiles,
state->barriers, state->wrapping); state->imm->barriers, state->wrapping);
if (solver_result < 0) {
*error = "No solution exists for this puzzle";
sfree(tiles);
return NULL;
}
} else { } else {
for (i = 0; i < state->width * state->height; i++) { for (i = 0; i < state->width * state->height; i++) {
int c = aux[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) static int *compute_loops(const game_state *state)
{ {
return compute_loops_inner(state->width, state->height, state->wrapping, return compute_loops_inner(state->width, state->height, state->wrapping,
state->tiles, state->barriers); state->tiles, state->imm->barriers);
} }
struct game_ui { struct game_ui {
@ -2051,9 +2064,8 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
struct game_drawstate { struct game_drawstate {
int started; int started;
int width, height; int width, height;
int org_x, org_y;
int tilesize; 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. * The button must have been clicked on a valid tile.
*/ */
x -= WINDOW_OFFSET + TILE_BORDER; x -= WINDOW_OFFSET + LINE_THICK;
y -= WINDOW_OFFSET + TILE_BORDER; y -= WINDOW_OFFSET + LINE_THICK;
if (x < 0 || y < 0) if (x < 0 || y < 0)
return nullret; return nullret;
tx = x / TILE_SIZE; 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 */ /* Transform from physical to game coords */
tx = (tx + ui->org_x) % state->width; tx = (tx + ui->org_x) % state->width;
ty = (ty + ui->org_y) % state->height; ty = (ty + ui->org_y) % state->height;
if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER || if (x % TILE_SIZE >= TILE_SIZE - LINE_THICK ||
y % TILE_SIZE >= TILE_SIZE - TILE_BORDER) y % TILE_SIZE >= TILE_SIZE - LINE_THICK)
return nullret; return nullret;
#ifdef USE_DRAGGING #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) static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
{ {
game_drawstate *ds = snew(game_drawstate); game_drawstate *ds = snew(game_drawstate);
int i; int i, ncells;
ds->started = FALSE; ds->started = FALSE;
ds->width = state->width; ds->width = state->width;
ds->height = state->height; ds->height = state->height;
ds->org_x = ds->org_y = -1; ncells = (state->width+2) * (state->height+2);
ds->visible = snewn(state->width * state->height, int); ds->visible = snewn(ncells, unsigned long);
ds->to_draw = snewn(ncells, unsigned long);
ds->tilesize = 0; /* undecided yet */ ds->tilesize = 0; /* undecided yet */
for (i = 0; i < state->width * state->height; i++) for (i = 0; i < ncells; i++)
ds->visible[i] = -1; ds->visible[i] = -1;
return ds; 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) static void game_free_drawstate(drawing *dr, game_drawstate *ds)
{ {
sfree(ds->visible); 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, static void game_compute_size(const game_params *params, int tilesize,
int *x, int *y) int *x, int *y)
{ {
*x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER; /* Ick: fake up `ds->tilesize' for macro expansion purposes */
*y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER; 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, static void game_set_size(drawing *dr, game_drawstate *ds,
@ -2532,177 +2553,233 @@ static float *game_colours(frontend *fe, int *ncolours)
return ret; return ret;
} }
static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2, static void rotated_coords(float *ox, float *oy, const float matrix[4],
int colour) float cx, float cy, float ix, float iy)
{ {
draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE); *ox = matrix[0] * ix + matrix[2] * iy + cx;
draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE); *oy = matrix[1] * ix + matrix[3] * iy + cy;
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);
} }
static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2, /* Flags describing the visible features of a tile. */
int colour) #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 mx = (x1 < x2 ? x1 : x2); float fpoints[12*2];
int my = (y1 < y2 ? y1 : y2); int points[12*2];
int dx = (x2 + x1 - 2*mx + 1); int npoints, d, dsh, i;
int dy = (y2 + y1 - 2*my + 1); int any_wire_this_colour = FALSE;
float xf, yf;
draw_rect(dr, mx, my, dx, dy, colour); 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)));
* draw_barrier_corner() and draw_barrier() are passed physical coords fpoints[2*npoints+1] = halfwidth * (Y(d) + Y(C(d)));
*/ npoints++;
static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
int x, int y, int dx, int dy, int phase)
{
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); if (bitmap & (1 << wiretype)) {
y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); 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++;
if (phase == 0) { any_wire_this_colour = TRUE;
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);
} }
}
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;
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);
} }
if (!any_wire_this_colour)
return;
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);
} }
/* static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y,
* draw_tile() is passed physical coordinates unsigned long tile, float angle)
*/
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)
{ {
int bx = WINDOW_OFFSET + TILE_SIZE * x; int tx, ty;
int by = WINDOW_OFFSET + TILE_SIZE * y; 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 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 * Clip to the tile boundary, with adjustments if we're drawing
* and including the borders around the tile. This means that * just outside the grid.
* if the neighbouring tiles have connections to those borders,
* we must draw those connections on the borders themselves.
*/ */
clipx = tx; clipX = tx + TILE_SIZE;
clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); 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 * Clear the clip region.
* rectangle in border colour, and a smaller rectangle in
* background colour to fill it in.
*/ */
draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER, bg = (tile & TILE_LOCKED) ? COL_LOCKED : COL_BACKGROUND;
COL_BORDER); draw_rect(dr, clipx, clipy, clipw, cliph, bg);
draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
/* /*
* Draw an inset outline rectangle as a cursor, in whichever of * Draw the grid lines.
* COL_LOCKED and COL_BACKGROUND we aren't currently drawing
* in.
*/ */
if (cursor) { {
draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8, int gridl = (x == -1 ? tx+TILE_SIZE-border_br : tx);
bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, int gridr = (x == ds->width ? tx+border_tl : tx+TILE_SIZE);
tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); int gridu = (y == -1 ? ty+TILE_SIZE-border_br : ty);
draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8, int gridd = (y == ds->height ? ty+border_tl : ty+TILE_SIZE);
bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8, if (x >= 0)
tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); draw_rect(dr, tx, gridu, border_tl, gridd-gridu, COL_BORDER);
draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8, if (y >= 0)
bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, draw_rect(dr, gridl, ty, gridr-gridl, border_tl, COL_BORDER);
tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); if (x < ds->width)
draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, draw_rect(dr, tx+TILE_SIZE-border_br, gridu,
bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, border_br, gridd-gridu, COL_BORDER);
tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); 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.
*/ */
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 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).
*/
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);
}
}
/*
* 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.
*/
if (tile & TILE_ROTATING) {
matrix[0] = (float)cos(angle * PI / 180.0); 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[2] = (float)sin(angle * PI / 180.0);
matrix[3] = (float)cos(angle * PI / 180.0); } else {
matrix[0] = 1.0F;
matrix[2] = 0.0F;
}
matrix[3] = matrix[0];
matrix[1] = -matrix[2];
/* /*
* Draw the wires. * Draw the wires.
*/ */
cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F; draw_wires(dr, cx, cy, radius, tile,
col = (tile & ACTIVE ? COL_POWERED : COL_WIRE); 0xE, COL_WIRE, 2*LINE_THICK-1, matrix);
for (dir = 1; dir < 0x10; dir <<= 1) { draw_wires(dr, cx, cy, radius, tile,
if (tile & dir) { 0x4, COL_POWERED, LINE_THICK-1, matrix);
ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); draw_wires(dr, cx, cy, radius, tile,
ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); 0x8, COL_LOOP, LINE_THICK-1, matrix);
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);
}
}
/* 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 * Draw the central box.
* 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.
*/ */
col = -1; for (pass = 0; pass < 2; pass++) {
if (src) int endtype = (tile >> TILE_ENDPOINT_SHIFT) & 3;
col = COL_WIRE; if (endtype) {
else if (COUNT(tile) == 1) { int i, points[8], col;
col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT); float boxr = TILE_SIZE * 0.24F + (pass == 0 ? LINE_THICK-1 : 0);
}
if (col >= 0) { col = (pass == 0 || endtype == 3 ? COL_WIRE :
int i, points[8]; endtype == 2 ? COL_POWERED : COL_ENDPOINT);
points[0] = +1; points[1] = +1; points[0] = +1; points[1] = +1;
points[2] = +1; points[3] = -1; points[2] = +1; points[3] = -1;
@ -2710,119 +2787,52 @@ static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds,
points[6] = -1; points[7] = +1; points[6] = -1; points[7] = +1;
for (i = 0; i < 8; i += 2) { for (i = 0; i < 8; i += 2) {
ex = (TILE_SIZE * 0.24F) * points[i]; float x, y;
ey = (TILE_SIZE * 0.24F) * points[i+1]; rotated_coords(&x, &y, matrix, cx, cy,
MATMUL(tx, ty, matrix, ex, ey); boxr * points[i], boxr * points[i+1]);
points[i] = bx+(int)(cx+tx); points[i] = x + 0.5;
points[i+1] = by+(int)(cy+ty); points[i+1] = y + 0.5;
} }
draw_polygon(dr, points, 4, col, COL_WIRE); draw_polygon(dr, points, 4, col, COL_WIRE);
} }
/*
* Draw the points on the border if other tiles are connected
* to us.
*/
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;
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);
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 barrier corners, and then barriers. * Draw barriers along grid edges.
*/ */
for (phase = 0; phase < 2; phase++) { for (pass = 0; pass < 2; pass++) {
for (dir = 1; dir < 0x10; dir <<= 1) { int btl = border_tl, bbr = border_br, col = COL_BARRIER;
int x1, y1, corner = FALSE; if (pass == 0) {
/* btl += barrier_outline_thick;
* If at least one barrier terminates at the corner bbr += barrier_outline_thick;
* between dir and A(dir), draw a barrier corner. col = COL_WIRE;
*/ }
if (barrier(state, GX(x), GY(y)) & (dir | A(dir))) {
corner = TRUE; if (tile & (L << TILE_BARRIER_SHIFT))
} else { draw_rect(dr, tx, ty, btl, TILE_SIZE, col);
/* if (tile & (R << TILE_BARRIER_SHIFT))
* Only count barriers terminating at this corner draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, TILE_SIZE, col);
* if they're physically next to the corner. (That if (tile & (U << TILE_BARRIER_SHIFT))
* is, if they've wrapped round from the far side draw_rect(dr, tx, ty, TILE_SIZE, btl, col);
* of the screen, they don't count.) if (tile & (D << TILE_BARRIER_SHIFT))
*/ draw_rect(dr, tx, ty+TILE_SIZE-bbr, TILE_SIZE, bbr, col);
x1 = x + X(dir);
y1 = y + Y(dir); if (tile & (R << TILE_BARRIER_CORNER_SHIFT))
if (x1 >= 0 && x1 < state->width && draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, btl, col);
y1 >= 0 && y1 < state->height && if (tile & (U << TILE_BARRIER_CORNER_SHIFT))
(barrier(state, GX(x1), GY(y1)) & A(dir))) { draw_rect(dr, tx, ty, btl, btl, col);
corner = TRUE; if (tile & (L << TILE_BARRIER_CORNER_SHIFT))
} else { draw_rect(dr, tx, ty+TILE_SIZE-bbr, btl, bbr, col);
x1 = x + X(A(dir)); if (tile & (D << TILE_BARRIER_CORNER_SHIFT))
y1 = y + Y(A(dir)); draw_rect(dr, tx+TILE_SIZE-bbr, ty+TILE_SIZE-bbr, bbr, bbr, col);
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 * Unclip and draw update, to finish.
* corner.
*/ */
draw_barrier_corner(dr, ds, x, y,
X(dir)+X(A(dir)), Y(dir)+Y(A(dir)),
phase);
}
}
for (dir = 1; dir < 0x10; dir <<= 1)
if (barrier(state, GX(x), GY(y)) & dir)
draw_barrier(dr, ds, x, y, dir, phase);
}
unclip(dr); unclip(dr);
draw_update(dr, clipx, clipy, clipw, cliph);
draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
} }
static void game_redraw(drawing *dr, game_drawstate *ds, 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, int dir, const game_ui *ui,
float t, float ft) 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; unsigned char *active;
int *loops; int *loops;
float angle = 0.0; float angle = 0.0;
/* /*
* Clear the screen, and draw the exterior barrier lines, if * Clear the screen on our first call.
* this is our first call or if the origin has changed.
*/ */
if (!ds->started || ui->org_x != ds->org_x || ui->org_y != ds->org_y) { if (!ds->started) {
int phase; int w, h;
game_params params;
ds->started = TRUE; ds->started = TRUE;
draw_rect(dr, 0, 0, params.width = ds->width;
WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER, params.height = ds->height;
WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER, game_compute_size(&params, TILE_SIZE, &w, &h);
COL_BACKGROUND);
ds->org_x = ui->org_x; draw_rect(dr, 0, 0, w, h, COL_BACKGROUND);
ds->org_y = ui->org_y; draw_update(dr, 0, 0, w, h);
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++) {
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);
}
}
}
} }
tx = ty = -1; tx = ty = -1;
@ -2913,30 +2876,83 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
state = oldstate; state = oldstate;
} }
frame = -1;
if (ft > 0) { if (ft > 0) {
/* /*
* We're animating a completion flash. Find which frame * We're animating a completion flash. Find which frame
* we're at. * we're at.
*/ */
frame = (int)(ft / FLASH_FRAME); 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); active = compute_active(state, ui->cx, ui->cy);
loops = compute_loops(state); loops = compute_loops(state);
for (x = 0; x < ds->width; x++) for (dy = -1; dy < ds->height+1; dy++) {
for (y = 0; y < ds->height; y++) { for (dx = -1; dx < ds->width+1; dx++) {
int c = tile(state, GX(x), GY(y)) | todraw(ds, dx, dy) = 0;
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; for (dy = 0; dy < ds->height; dy++) {
int is_cursor = ui->cur_visible && int gy = (dy + ui->org_y) % ds->height;
GX(x) == ui->cur_x && GY(y) == ui->cur_y; 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 * In a completion flash, we adjust the LOCKED bit
@ -2944,29 +2960,34 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
* the frame number. * the frame number.
*/ */
if (frame >= 0) { 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; int xdist, ydist, dist;
xdist = (x < rcx ? rcx - x : x - rcx); xdist = (dx < rcx ? rcx - dx : dx - rcx);
ydist = (y < rcy ? rcy - y : y - rcy); ydist = (dy < rcy ? rcy - dy : dy - rcy);
dist = (xdist > ydist ? xdist : ydist); dist = (xdist > ydist ? xdist : ydist);
if (frame >= dist && frame < dist+4) { if (frame >= dist && frame < dist+4 &&
int lock = (frame - dist) & 1; ((frame - dist) & 1))
lock = lock ? LOCKED : 0; todraw(ds, dx, dy) ^= TILE_LOCKED;
c = (c &~ LOCKED) | lock; }
} }
} }
if (moved_origin || /*
index(state, ds->visible, x, y) != c || * Now draw any tile that differs from the way it was last drawn.
index(state, ds->visible, x, y) == -1 || * An exception is that if either the previous _or_ current state
is_src || is_anim || is_cursor) { * has the TILE_ROTATING bit set, we must draw it regardless,
draw_tile(dr, state, ds, x, y, c, * because it will have rotated to a different angle.q
is_src, (is_anim ? angle : 0.0F), is_cursor); */
if (is_src || is_anim || is_cursor) for (dy = -1; dy < ds->height+1; dy++) {
index(state, ds->visible, x, y) = -1; for (dx = -1; dx < ds->width+1; dx++) {
else int prev = visible(ds, dx, dy);
index(state, ds->visible, x, y) = c; 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;
}
} }
} }

View file

@ -687,6 +687,10 @@ struct frontend {
if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask)) if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask))
c |= MOD_NUM_KEYPAD; c |= MOD_NUM_KEYPAD;
if (c == 26 &&
!((NSShiftKeyMask | NSControlKeyMask) & ~[ev modifierFlags]))
c = UI_REDO;
[self processKey:c]; [self processKey:c];
} }
} }
@ -735,7 +739,7 @@ struct frontend {
- (void)newGame:(id)sender - (void)newGame:(id)sender
{ {
[self processKey:'n']; [self processKey:UI_NEWGAME];
} }
- (void)restartGame:(id)sender - (void)restartGame:(id)sender
{ {
@ -809,11 +813,11 @@ struct frontend {
} }
- (void)undoMove:(id)sender - (void)undoMove:(id)sender
{ {
[self processKey:'u']; [self processKey:UI_UNDO];
} }
- (void)redoMove:(id)sender - (void)redoMove:(id)sender
{ {
[self processKey:'r'&0x1F]; [self processKey:UI_REDO];
} }
- (void)copy:(id)sender - (void)copy:(id)sender

View file

@ -310,7 +310,18 @@ static void generate(random_state *rs, int w, int h, unsigned char *retgrid)
fgrid2 = snewn(w*h, float); fgrid2 = snewn(w*h, float);
memcpy(fgrid2, fgrid, w*h*sizeof(float)); memcpy(fgrid2, fgrid, w*h*sizeof(float));
qsort(fgrid2, w*h, sizeof(float), float_compare); 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); sfree(fgrid2);
for (i = 0; i < h; i++) { for (i = 0; i < h; i++) {
@ -448,6 +459,8 @@ static int do_row(unsigned char *known, unsigned char *deduced,
if (rowlen == 0) { if (rowlen == 0) {
memset(deduced, DOT, len); memset(deduced, DOT, len);
} else if (rowlen == 1 && data[0] == len) {
memset(deduced, BLOCK, len);
} else { } else {
do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok, do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok,
maxpos_ok, data, len, freespace, 0, 0); maxpos_ok, data, len, freespace, 0, 0);

View file

@ -47,6 +47,15 @@ enum {
CURSOR_RIGHT, CURSOR_RIGHT,
CURSOR_SELECT, CURSOR_SELECT,
CURSOR_SELECT2, 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. */ /* made smaller because of 'limited range of datatype' errors. */
MOD_CTRL = 0x1000, MOD_CTRL = 0x1000,
@ -64,6 +73,7 @@ enum {
#define IS_CURSOR_MOVE(m) ( (m) == CURSOR_UP || (m) == CURSOR_DOWN || \ #define IS_CURSOR_MOVE(m) ( (m) == CURSOR_UP || (m) == CURSOR_DOWN || \
(m) == CURSOR_RIGHT || (m) == CURSOR_LEFT ) (m) == CURSOR_RIGHT || (m) == CURSOR_LEFT )
#define IS_CURSOR_SELECT(m) ( (m) == CURSOR_SELECT || (m) == CURSOR_SELECT2) #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. * Flags in the back end's `flags' word.

View file

@ -1718,7 +1718,10 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
#define TILE_SIZE (ds->sz6*6) #define TILE_SIZE (ds->sz6*6)
#define BORDER (TILE_SIZE/8) #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 COORD(x) ( (x+1) * TILE_SIZE + BORDER )
#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 ) #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 */ #define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */
struct game_drawstate { struct game_drawstate {
int sz6; int sz6, grid_line_all, grid_line_tl, grid_line_br;
int started; int started;
int w, h, sz; int w, h, sz;
@ -2118,7 +2121,6 @@ static void game_compute_size(const game_params *params, int tilesize,
int sz6; int sz6;
} ads, *ds = &ads; } ads, *ds = &ads;
ads.sz6 = tilesize/6; ads.sz6 = tilesize/6;
*x = (params->w+2) * TILE_SIZE + 2 * BORDER; *x = (params->w+2) * TILE_SIZE + 2 * BORDER;
*y = (params->h+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) const game_params *params, int tilesize)
{ {
ds->sz6 = tilesize/6; 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 { enum {
@ -2346,14 +2351,13 @@ static void draw_square(drawing *dr, game_drawstate *ds,
/* Clip to the grid square. */ /* Clip to the grid square. */
clip(dr, ox, oy, TILE_SIZE, TILE_SIZE); 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, best_bits((flags & DS_TRACK) == DS_TRACK,
(flags_drag & DS_TRACK) == DS_TRACK, &bg); (flags_drag & DS_TRACK) == DS_TRACK, &bg);
draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, bg); draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_GRID);
draw_rect(dr, ox + GRID_LINE_TL, oy + GRID_LINE_TL,
/* Draw outline of grid square */ TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL, bg);
draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID);
draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID);
/* More outlines for clue squares. */ /* More outlines for clue squares. */
if (flags & DS_CURSOR) { if (flags & DS_CURSOR) {
@ -2389,8 +2393,8 @@ static void draw_square(drawing *dr, game_drawstate *ds,
(flags_drag & DS_NOTRACK) == DS_NOTRACK, &c); (flags_drag & DS_NOTRACK) == DS_NOTRACK, &c);
if (flags_best) { if (flags_best) {
off = HALFSZ/2; off = HALFSZ/2;
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_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);
} }
c = COL_TRACK; c = COL_TRACK;
@ -2404,8 +2408,8 @@ static void draw_square(drawing *dr, game_drawstate *ds,
cx += (d == R) ? t2 : (d == L) ? -t2 : 0; cx += (d == R) ? t2 : (d == L) ? -t2 : 0;
cy += (d == D) ? t2 : (d == U) ? -t2 : 0; cy += (d == D) ? t2 : (d == U) ? -t2 : 0;
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_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);
} }
} }
@ -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); cy = CENTERED_COORD(i-w);
} }
draw_rect(dr, cx - tsz + BORDER, cy - tsz + BORDER, draw_rect(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL,
TILE_SIZE - BORDER, TILE_SIZE - BORDER, COL_BACKGROUND); TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL,
COL_BACKGROUND);
sprintf(buf, "%d", clue); sprintf(buf, "%d", clue);
draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE, draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE,
col, buf); 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, 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_loop_ends(dr, ds, state, COL_CLUE);
draw_line(dr, COORD(ds->w), COORD(0), COORD(ds->w), COORD(ds->h), COL_GRID); draw_rect(dr, COORD(0) - GRID_LINE_BR, COORD(0) - GRID_LINE_BR,
draw_line(dr, COORD(0), COORD(ds->h), COORD(ds->w), COORD(ds->h), COL_GRID); 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); draw_update(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER);

View file

@ -27,7 +27,7 @@ while (<$desc>) {
'<span class="puzzle"><table>'. '<span class="puzzle"><table>'.
'<tr><th align="center">%s</th></tr>'. '<tr><th align="center">%s</th></tr>'.
'<tr><td align="center">'. '<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>'. '</td></tr>'.
'<tr><td align="center" style="font-size: 70%%"><code>[</code>'. '<tr><td align="center" style="font-size: 70%%"><code>[</code>'.
' <a href="java/%s.html">java</a> '. ' <a href="java/%s.html">java</a> '.
@ -41,6 +41,7 @@ while (<$desc>) {
'<tr><td align="center">%s</td></tr></table></span>'. '<tr><td align="center">%s</td></tr></table></span>'.
"\n", "\n",
encode_entities($displayname), encode_entities($displayname),
encode_entities($id),
encode_entities($description), encode_entities($description),
encode_entities($id), encode_entities($id),
encode_entities($id), encode_entities($id),

View file

@ -1545,7 +1545,7 @@ static frontend *frontend_new(HINSTANCE inst)
fe->statusbar = NULL; fe->statusbar = NULL;
fe->bitmap = NULL; fe->bitmap = NULL;
SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe); SetWindowLongPtr(fe->hwnd, GWLP_USERDATA, (LONG_PTR)fe);
return fe; return fe;
} }
@ -1992,7 +1992,7 @@ static void make_dialog_full_screen(HWND hwnd)
static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg, static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) WPARAM wParam, LPARAM lParam)
{ {
frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (msg) { switch (msg) {
case WM_INITDIALOG: case WM_INITDIALOG:
@ -2249,7 +2249,7 @@ static void create_config_controls(frontend * fe)
static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg, static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) WPARAM wParam, LPARAM lParam)
{ {
frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
config_item *i; config_item *i;
struct cfg_aux *j; struct cfg_aux *j;
@ -2260,7 +2260,7 @@ static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
char *title; char *title;
fe = (frontend *) lParam; fe = (frontend *) lParam;
SetWindowLong(hwnd, GWL_USERDATA, lParam); SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam);
fe->cfgbox = hwnd; fe->cfgbox = hwnd;
fe->cfg = frontend_get_config(fe, fe->cfg_which, &title); 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); SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe); SetWindowLongPtr(fe->cfgbox, GWLP_USERDATA, (LONG_PTR)fe);
SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)AboutDlgProc); SetWindowLongPtr(fe->cfgbox, DWLP_DLGPROC, (LONG_PTR)AboutDlgProc);
id = 1000; id = 1000;
y = height/2; y = height/2;
@ -2660,8 +2660,8 @@ static int get_config(frontend *fe, int which)
SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE); SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe); SetWindowLongPtr(fe->cfgbox, GWLP_USERDATA, (LONG_PTR)fe);
SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)ConfigDlgProc); SetWindowLongPtr(fe->cfgbox, DWLP_DLGPROC, (LONG_PTR)ConfigDlgProc);
/* /*
* Count the controls so we can allocate cfgaux. * 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, static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam) WPARAM wParam, LPARAM lParam)
{ {
frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
int cmd; int cmd;
switch (message) { switch (message) {
@ -2993,18 +2993,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
cmd = wParam & ~0xF; /* low 4 bits reserved to Windows */ cmd = wParam & ~0xF; /* low 4 bits reserved to Windows */
switch (cmd) { switch (cmd) {
case IDM_NEW: case IDM_NEW:
if (!midend_process_key(fe->me, 0, 0, 'n')) if (!midend_process_key(fe->me, 0, 0, UI_NEWGAME))
PostQuitMessage(0); PostQuitMessage(0);
break; break;
case IDM_RESTART: case IDM_RESTART:
midend_restart_game(fe->me); midend_restart_game(fe->me);
break; break;
case IDM_UNDO: case IDM_UNDO:
if (!midend_process_key(fe->me, 0, 0, 'u')) if (!midend_process_key(fe->me, 0, 0, UI_UNDO))
PostQuitMessage(0); PostQuitMessage(0);
break; break;
case IDM_REDO: case IDM_REDO:
if (!midend_process_key(fe->me, 0, 0, '\x12')) if (!midend_process_key(fe->me, 0, 0, UI_REDO))
PostQuitMessage(0); PostQuitMessage(0);
break; break;
case IDM_COPY: case IDM_COPY:
@ -3026,7 +3026,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
} }
break; break;
case IDM_QUIT: case IDM_QUIT:
if (!midend_process_key(fe->me, 0, 0, 'q')) if (!midend_process_key(fe->me, 0, 0, UI_QUIT))
PostQuitMessage(0); PostQuitMessage(0);
break; break;
case IDM_CONFIG: case IDM_CONFIG:
@ -3405,8 +3405,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
} }
break; break;
case WM_CHAR: case WM_CHAR:
if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam)) {
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); PostQuitMessage(0);
}
return 0; return 0;
case WM_TIMER: case WM_TIMER:
if (fe->timer) { if (fe->timer) {

View file

@ -61,7 +61,7 @@ has 'descfile' => (required => 1);
% # (individual files or shortcuts or additions to PATH) that are % # (individual files or shortcuts or additions to PATH) that are
% # installed. % # installed.
<Directory Id="TARGETDIR" Name="SourceDir"> <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"> <Directory Id="INSTALLDIR" Name="Simon Tatham's Portable Puzzle Collection">
% # The following components all install things in the main % # The following components all install things in the main