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 mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs
in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss
delegate windows
# FIXME: Cygwin alternative?
in puzzles with visualstudio do/win nmake -f Makefile.vc clean
in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version)
ifneq "$(VISUAL_STUDIO)" "yes" then
in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl clean
in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl VER=-DVER=$(Version)
# Code-sign the binaries, if the local bob config provides a script
# to do so. We assume here that the script accepts an -i option to
# provide a 'more info' URL, and an optional -n option to provide a
# program name, and that it can take multiple .exe filename
# arguments and sign them all in place.
ifneq "$(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
# Build installers.
in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj
in puzzles with innosetup do/win iscc puzzles.iss
ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ -n "Simon Tatham's Portable Puzzle Collection Installer" puzzles.msi Output/installer.exe
return puzzles/*.exe
return puzzles/Output/installer.exe
return puzzles/puzzles.msi
enddelegate
enddelegate
endif
in puzzles do chmod +x *.exe
# Build the Pocket PC binaries and CAB.
@ -152,13 +158,22 @@ delegate emscripten
return puzzles/js/*.js
enddelegate
# Build a set of wrapping HTML pages for easy testing of the
# Javascript puzzles. These aren't quite the same as the versions that
# will go on my live website, because those ones will substitute in a
# different footer, and not have to link to the .js files with the
# ../js/ prefix. But these ones should be good enough to just open
# using a file:// URL in a browser after running a build, and make
# sure the main functionality works.
in puzzles do mkdir jstest
in puzzles/jstest do ../html/jspage.pl --jspath=../js/ /dev/null ../html/*.html
# Set up .htaccess containing a redirect for the archive filename.
in puzzles do echo "AddType application/octet-stream .chm" > .htaccess
in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess
in puzzles do echo "AddType application/octet-stream .cnt" >> .htaccess
in . do set -- puzzles*.tar.gz; echo RedirectMatch temp '(.*/)'puzzles.tar.gz '$$1'"$$1" >> puzzles/.htaccess
in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.msi '$$1'puzzles-$(Version)-installer.msi >> .htaccess
in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.exe '$$1'puzzles-$(Version)-installer.exe >> .htaccess
# Phew, we're done. Deliver everything!
deliver puzzles/icons/*-web.png $@
@ -172,9 +187,9 @@ deliver puzzles/puzzles.hlp $@
deliver puzzles/puzzles.cnt $@
deliver puzzles/puzzles.zip $@
deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi
deliver puzzles/Output/installer.exe puzzles-$(Version)-installer.exe
deliver puzzles/*.jar java/$@
deliver puzzles/js/*.js js/$@
deliver puzzles/jstest/*.html jstest/$@
deliver puzzles/html/*.html html/$@
deliver puzzles/html/*.pl html/$@
deliver puzzles/wwwspans.html $@

View file

@ -61,19 +61,19 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
JMenuBar menubar = new JMenuBar();
JMenu jm;
menubar.add(jm = new JMenu("Game"));
addMenuItemWithKey(jm, "New", 'n');
addMenuItemCallback(jm, "New", "jcallback_newgame_event");
addMenuItemCallback(jm, "Restart", "jcallback_restart_event");
addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC);
addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED);
jm.addSeparator();
addMenuItemWithKey(jm, "Undo", 'u');
addMenuItemWithKey(jm, "Redo", 'r');
addMenuItemCallback(jm, "Undo", "jcallback_undo_event");
addMenuItemCallback(jm, "Redo", "jcallback_redo_event");
jm.addSeparator();
solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
solveCommand.setEnabled(false);
if (mainWindow != null) {
jm.addSeparator();
addMenuItemWithKey(jm, "Exit", 'q');
addMenuItemCallback(jm, "Exit", "jcallback_quit_event");
}
menubar.add(typeMenu = new JMenu("Type"));
typeMenu.setVisible(false);
@ -126,7 +126,12 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
}
}
public void keyTyped(KeyEvent e) {
runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()});
int key = e.getKeyChar();
if (key == 26 && e.isShiftDown() && e.isControlDown()) {
runtimeCall("jcallback_redo_event", new int[0]);
return;
}
runtimeCall("jcallback_key_event", new int[] {0, 0, key});
}
});
pp.addMouseListener(new MouseAdapter() {
@ -217,10 +222,6 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()});
}
private void addMenuItemWithKey(JMenu jm, String name, int key) {
addMenuItemCallback(jm, name, "jcallback_menu_key_event", key);
}
private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) {
return addMenuItemCallback(jm, name, callback, new int[] {arg}, false);
}

View file

@ -17,6 +17,7 @@
!makefile gnustep Makefile.gnustep
!makefile nestedvm Makefile.nestedvm
!makefile emcc Makefile.emcc
!makefile clangcl Makefile.clangcl
!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.
If the specified thickness is less than 1.0, 1.0 is used.
This ensures that thin lines are visible even at small scales.
\S{drawing-draw-text} \cw{draw_text()}
\c void draw_text(drawing *dr, int x, int y, int fonttype,

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

View file

@ -310,6 +310,8 @@ void key(int keycode, int charcode, const char *key, const char *chr,
keyevent = MOD_NUM_KEYPAD | '7';
} else if (!strnullcmp(key, "PageUp") || keycode==33) {
keyevent = MOD_NUM_KEYPAD | '9';
} else if (shift && ctrl && (keycode & 0x1F) == 26) {
keyevent = UI_REDO;
} else if (chr && chr[0] && !chr[1]) {
keyevent = chr[0] & 0xFF;
} else if (keycode >= 96 && keycode < 106) {
@ -323,10 +325,10 @@ void key(int keycode, int charcode, const char *key, const char *chr,
}
if (keyevent >= 0) {
if (shift && keyevent >= 0x100)
if (shift && (keyevent >= 0x100 && !IS_UI_FAKE_KEY(keyevent)))
keyevent |= MOD_SHFT;
if (ctrl) {
if (ctrl && !IS_UI_FAKE_KEY(keyevent)) {
if (keyevent >= 0x100)
keyevent |= MOD_CTRL;
else
@ -725,7 +727,7 @@ void command(int n)
update_undo_redo();
break;
case 5: /* New Game */
midend_process_key(me, 0, 0, 'n');
midend_process_key(me, 0, 0, UI_NEWGAME);
update_undo_redo();
js_focus_canvas();
break;
@ -735,12 +737,12 @@ void command(int n)
js_focus_canvas();
break;
case 7: /* Undo */
midend_process_key(me, 0, 0, 'u');
midend_process_key(me, 0, 0, UI_UNDO);
update_undo_redo();
js_focus_canvas();
break;
case 8: /* Redo */
midend_process_key(me, 0, 0, 'r');
midend_process_key(me, 0, 0, UI_REDO);
update_undo_redo();
js_focus_canvas();
break;
@ -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
* 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(document.createTextNode(name));
var submenu = document.createElement("ul");
submenu.className = "left";
item.appendChild(submenu);
gametypesubmenus[menuid].appendChild(item);
var toret = gametypesubmenus.length;
@ -575,38 +574,7 @@ mergeInto(LibraryManager.library, {
* overlay on top of the rest of the puzzle web page.
*/
js_dialog_init: function(titletext) {
// Create an overlay on the page which darkens everything
// beneath it.
dlg_dimmer = document.createElement("div");
dlg_dimmer.style.width = "100%";
dlg_dimmer.style.height = "100%";
dlg_dimmer.style.background = '#000000';
dlg_dimmer.style.position = 'fixed';
dlg_dimmer.style.opacity = 0.3;
dlg_dimmer.style.top = dlg_dimmer.style.left = 0;
dlg_dimmer.style["z-index"] = 99;
// Now create a form which sits on top of that in turn.
dlg_form = document.createElement("form");
dlg_form.style.width = (window.innerWidth * 2 / 3) + "px";
dlg_form.style.opacity = 1;
dlg_form.style.background = '#ffffff';
dlg_form.style.color = '#000000';
dlg_form.style.position = 'absolute';
dlg_form.style.border = "2px solid black";
dlg_form.style.padding = "20px";
dlg_form.style.top = (window.innerHeight / 10) + "px";
dlg_form.style.left = (window.innerWidth / 6) + "px";
dlg_form.style["z-index"] = 100;
var title = document.createElement("p");
title.style.marginTop = "0px";
title.appendChild(document.createTextNode
(Pointer_stringify(titletext)));
dlg_form.appendChild(title);
dlg_return_funcs = [];
dlg_next_id = 0;
dialog_init(Pointer_stringify(titletext));
},
/*
@ -701,29 +669,13 @@ mergeInto(LibraryManager.library, {
* everything else on the page.
*/
js_dialog_launch: function() {
// Put in the OK and Cancel buttons at the bottom.
var button;
button = document.createElement("input");
button.type = "button";
button.value = "OK";
button.onclick = function(event) {
dialog_launch(function(event) {
for (var i in dlg_return_funcs)
dlg_return_funcs[i]();
command(3);
}
dlg_form.appendChild(button);
button = document.createElement("input");
button.type = "button";
button.value = "Cancel";
button.onclick = function(event) {
command(4);
}
dlg_form.appendChild(button);
document.body.appendChild(dlg_dimmer);
document.body.appendChild(dlg_form);
command(3); // OK
}, function(event) {
command(4); // Cancel
});
},
/*
@ -733,10 +685,7 @@ mergeInto(LibraryManager.library, {
* associated with it.
*/
js_dialog_cleanup: function() {
document.body.removeChild(dlg_dimmer);
document.body.removeChild(dlg_form);
dlg_dimmer = dlg_form = null;
onscreen_canvas.focus();
dialog_cleanup();
},
/*

View file

@ -129,6 +129,72 @@ function disable_menu_item(item, disabledFlag) {
item.className = "";
}
// Dialog-box functions called from both C and JS.
function dialog_init(titletext) {
// Create an overlay on the page which darkens everything
// beneath it.
dlg_dimmer = document.createElement("div");
dlg_dimmer.style.width = "100%";
dlg_dimmer.style.height = "100%";
dlg_dimmer.style.background = '#000000';
dlg_dimmer.style.position = 'fixed';
dlg_dimmer.style.opacity = 0.3;
dlg_dimmer.style.top = dlg_dimmer.style.left = 0;
dlg_dimmer.style["z-index"] = 99;
// Now create a form which sits on top of that in turn.
dlg_form = document.createElement("form");
dlg_form.style.width = (window.innerWidth * 2 / 3) + "px";
dlg_form.style.opacity = 1;
dlg_form.style.background = '#ffffff';
dlg_form.style.color = '#000000';
dlg_form.style.position = 'absolute';
dlg_form.style.border = "2px solid black";
dlg_form.style.padding = "20px";
dlg_form.style.top = (window.innerHeight / 10) + "px";
dlg_form.style.left = (window.innerWidth / 6) + "px";
dlg_form.style["z-index"] = 100;
var title = document.createElement("p");
title.style.marginTop = "0px";
title.appendChild(document.createTextNode(titletext));
dlg_form.appendChild(title);
dlg_return_funcs = [];
dlg_next_id = 0;
}
function dialog_launch(ok_function, cancel_function) {
// Put in the OK and Cancel buttons at the bottom.
var button;
if (ok_function) {
button = document.createElement("input");
button.type = "button";
button.value = "OK";
button.onclick = ok_function;
dlg_form.appendChild(button);
}
if (cancel_function) {
button = document.createElement("input");
button.type = "button";
button.value = "Cancel";
button.onclick = cancel_function;
dlg_form.appendChild(button);
}
document.body.appendChild(dlg_dimmer);
document.body.appendChild(dlg_form);
}
function dialog_cleanup() {
document.body.removeChild(dlg_dimmer);
document.body.removeChild(dlg_form);
dlg_dimmer = dlg_form = null;
onscreen_canvas.focus();
}
// Init function called from body.onload.
function initPuzzle() {
// Construct the off-screen canvas used for double buffering.
@ -230,6 +296,58 @@ function initPuzzle() {
command(9);
};
// 'number' is used for C pointers
get_save_file = Module.cwrap('get_save_file', 'number', []);
free_save_file = Module.cwrap('free_save_file', 'void', ['number']);
load_game = Module.cwrap('load_game', 'void', ['string', 'number']);
document.getElementById("save").onclick = function(event) {
if (dlg_dimmer === null) {
var savefile_ptr = get_save_file();
var savefile_text = Pointer_stringify(savefile_ptr);
free_save_file(savefile_ptr);
dialog_init("Download saved-game file");
dlg_form.appendChild(document.createTextNode(
"Click to download the "));
var a = document.createElement("a");
a.download = "puzzle.sav";
a.href = "data:application/octet-stream," +
encodeURIComponent(savefile_text);
a.appendChild(document.createTextNode("saved-game file"));
dlg_form.appendChild(a);
dlg_form.appendChild(document.createTextNode("."));
dlg_form.appendChild(document.createElement("br"));
dialog_launch(function(event) {
dialog_cleanup();
});
}
};
document.getElementById("load").onclick = function(event) {
if (dlg_dimmer === null) {
dialog_init("Upload saved-game file");
var input = document.createElement("input");
input.type = "file";
input.multiple = false;
dlg_form.appendChild(input);
dlg_form.appendChild(document.createElement("br"));
dialog_launch(function(event) {
if (input.files.length == 1) {
var file = input.files.item(0);
var reader = new FileReader();
reader.addEventListener("loadend", function() {
var string = reader.result;
load_game(string, string.length);
});
reader.readAsBinaryString(file);
}
dialog_cleanup();
}, function(event) {
dialog_cleanup();
});
}
};
gametypelist = document.getElementById("gametype");
gametypesubmenus.push(gametypelist);

View file

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

View file

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

View file

@ -3,6 +3,17 @@
use strict;
use warnings;
my $jspath = "";
while ($ARGV[0] =~ /^-/) {
my $opt = shift @ARGV;
last if $opt eq "--";
if ($opt =~ /^--jspath=(.+)$/) {
$jspath = $1;
} else {
die "jspage.pl: unrecognised option '$opt'\n";
}
}
open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n";
my $footer = "";
$footer .= $_ while <$footerfile>;
@ -62,7 +73,7 @@ EOF
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
<title>${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection</title>
<script type="text/javascript" src="${filename}.js"></script>
<script type="text/javascript" src="${jspath}${filename}.js"></script>
<style class="text/css">
/* Margins and centring on the top-level div for the game menu */
#gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center }
@ -103,6 +114,15 @@ EOF
color: rgba(0,0,0,0.5);
}
#gamemenu ul li.separator {
color: transparent;
border: 0;
}
#gamemenu ul li.afterseparator {
border-left: 1px solid rgba(0,0,0,0.3);
}
#gamemenu ul li:first-of-type {
/* Reinstate the left border for the leftmost top-level menu item */
border-left: 1px solid rgba(0,0,0,0.3);
@ -196,14 +216,19 @@ ${unfinishedpara}
<hr>
<div id="puzzle" style="display: none">
<div id="gamemenu"><ul><li id="new">New game</li
<div id="gamemenu"><ul><li>Game...<ul
><li id="specific">Enter game ID</li
><li id="random">Enter random seed</li
><li id="save">Download save file</li
><li id="load">Upload save file</li
></ul></li
><li>Type...<ul id="gametype"></ul></li
><li class="separator"></li
><li id="new" class="afterseparator">New game</li
><li id="restart">Restart game</li
><li id="undo">Undo move</li
><li id="redo">Redo move</li
><li id="solve">Solve game</li
><li id="specific">Enter game ID</li
><li id="random">Enter random seed</li
><li>Select game type<ul id="gametype" class="left"></ul></li
></ul></div>
<div align=center>
<div id="resizable" style="position:relative; left:0; top:0">

View file

@ -288,7 +288,7 @@ static void check_caches(const solver_state* sstate);
{amin, omin, \
"Width and height for this grid type must both be at least " #amin, \
"At least one of width and height for this grid type must be at least " #omin,},
enum { GRIDLIST(GRID_LOOPYTYPE) };
enum { GRIDLIST(GRID_LOOPYTYPE) LOOPY_GRID_DUMMY_TERMINATOR };
static char const *const gridnames[] = { GRIDLIST(GRID_NAME) };
#define GRID_CONFIGS GRIDLIST(GRID_CONFIG)
static grid_type grid_types[] = { GRIDLIST(GRID_GRIDTYPE) };

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

View file

@ -2963,7 +2963,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
float animtime, float flashtime)
{
int x, y;
int mines, markers, bg;
int mines, markers, closed, bg;
int cx = -1, cy = -1, cmoved;
if (flashtime) {
@ -3013,13 +3013,15 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
/*
* Now draw the tiles. Also in this loop, count up the number
* of mines and mine markers.
* of mines, mine markers, and closed squares.
*/
mines = markers = 0;
mines = markers = closed = 0;
for (y = 0; y < ds->h; y++)
for (x = 0; x < ds->w; x++) {
int v = state->grid[y*ds->w+x], cc = 0;
if (v < 0)
closed++;
if (v == -1)
markers++;
if (state->layout->mines && state->layout->mines[y*ds->w+x])
@ -3078,7 +3080,42 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
else
sprintf(statusbar, "COMPLETED!");
} else {
int safe_closed = closed - mines;
sprintf(statusbar, "Marked: %d / %d", markers, mines);
if (safe_closed > 0 && safe_closed <= 9) {
/*
* In the situation where there's a very small number
* of _non_-mine squares left unopened, it's helpful
* to mention that number in the status line, to save
* the player from having to count it up
* painstakingly. This is particularly important if
* the player has turned up the mine density to the
* point where game generation resorts to its weird
* pathological fallback of a very dense mine area
* with a clearing in the middle, because that often
* leads to a deduction you can only make by knowing
* that there is (say) exactly one non-mine square to
* find, and it's a real pain to have to count up two
* large numbers of squares and subtract them to get
* that value of 1.
*
* The threshold value of 8 for displaying this
* information is because that's the largest number of
* non-mine squares that might conceivably fit around
* a single central square, and the most likely way to
* _use_ this information is to observe that if all
* the remaining safe squares are adjacent to _this_
* square then everything else can be immediately
* flagged as a mine.
*/
if (safe_closed == 1) {
sprintf(statusbar + strlen(statusbar),
" (1 safe square remains)");
} else {
sprintf(statusbar + strlen(statusbar),
" (%d safe squares remain)", safe_closed);
}
}
}
if (ui->deaths)
sprintf(statusbar + strlen(statusbar),

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() */
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: */

View file

@ -319,7 +319,7 @@ sub mfval($) {
# Returns true if the argument is a known makefile type. Otherwise,
# prints a warning and returns false;
if (grep { $type eq $_ }
("vc","vcproj","cygwin","borland","lcc","gtk","am","mpw","nestedvm","osx","wce","gnustep","emcc")) {
("vc","vcproj","cygwin","borland","lcc","gtk","am","mpw","nestedvm","osx","wce","gnustep","emcc","clangcl")) {
return 1;
}
warn "$.:unknown makefile type '$type'\n";
@ -503,6 +503,151 @@ $orig_dir = cwd;
# Now we're ready to output the actual Makefiles.
if (defined $makefiles{'clangcl'}) {
$mftyp = 'clangcl';
$dirpfx = &dirpfx($makefiles{'clangcl'}, "/");
##-- Makefile for cross-compiling using clang-cl, lld-link, and
## MinGW's windres for resource compilation.
#
# This makefile allows a complete Linux-based cross-compile, but
# using the real Visual Studio header files and libraries. In
# order to run it, you will need:
#
# - MinGW windres on your PATH.
# * On Ubuntu as of 16.04, you can apt-get install
# binutils-mingw-w64-x86-64 and binutils-mingw-w64-i686
# which will provide (respectively) 64- and 32-bit versions,
# under the names to which RCCMD is defined below.
# - clang-cl and lld-link on your PATH.
# * I built these from the up-to-date LLVM project trunk git
# repositories, as of 2017-02-05.
# - case-mashed copies of the Visual Studio include directories.
# * On a real VS installation, run vcvars32.bat and look at
# the resulting value of %INCLUDE%. Take a full copy of each
# of those directories, and inside the copy, for each
# include file that has an uppercase letter in its name,
# make a lowercased symlink to it. Additionally, one of the
# directories will contain files called driverspecs.h and
# specstrings.h, and those will need symlinks called
# DriverSpecs.h and SpecStrings.h.
# * Now, on Linux, define the environment variable INCLUDE to
# be a list, separated by *semicolons* (in the Windows
# style), of those directories, but before all of them you
# must also include lib/clang/5.0.0/include from the clang
# installation area (which contains in particular a
# clang-compatible stdarg.h overriding the Visual Studio
# one).
# - similarly case-mashed copies of the library directories.
# * Again, on a real VS installation, run vcvars32 or
# vcvarsx86_amd64 (as appropriate), look at %LIB%, make a
# copy of each directory, and provide symlinks within that
# directory so that all the files can be opened as
# lowercase.
# * Then set LIB to be a semicolon-separated list of those
# directories (but you'll need to change which set of
# directories depending on whether you want to do a 32-bit
# or 64-bit build).
# - for a 64-bit build, set 'Platform=x64' in the environment as
# well, or else on the make command line.
# * This is a variable understood only by this makefile - none
# of the tools we invoke will know it - but it's consistent
# with the way the VS scripts like vcvarsx86_amd64.bat set
# things up, and since the environment has to change
# _anyway_ between 32- and 64-bit builds (different set of
# paths in $LIB) it's reasonable to have the choice of
# compilation target driven by another environment variable
# set in parallel with that one.
# - for older versions of the VS libraries you may also have to
# set EXTRA_console and/or EXTRA_windows to the name of an
# object file manually extracted from one of those libraries.
# * This is because old VS seems to manage its startup code by
# having libcmt.lib contain lots of *crt0.obj objects, one
# for each possible user entry point (main, WinMain and the
# wide-char versions of both), of which the linker arranges
# to include the right one by special-case code. But lld
# only seems to mimic half of that code - it does include
# the right crt0 object, but it doesn't also deliberately
# _avoid_ including the _wrong_ ones, and since all those
# objects define a common set of global symbols for other
# parts of the library to use, lld may well select an
# arbitrary one of them the first time it sees a reference
# to one of those global symbols, and then later also select
# the _right_ one for the application's entry point, causing
# a multiple-definitions crash.
# * So the workaround is to explicitly include the right
# *crt0.obj file on the linker command line before lld even
# begins searching libraries. Hence, for a console
# application, you might extract crt0.obj from the library
# in question and set EXTRA_console=crt0.obj, and for a GUI
# application, do the same with wincrt0.obj. Then this
# makefile will include the right one of those objects
# alongside the matching /subsystem linker option.
open OUT, ">$makefiles{'clangcl'}"; select OUT;
print
"# Makefile for cross-compiling $project_name using clang-cl, lld-link,\n".
"# and MinGW's windres, using GNU make on Linux.\n".
"#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
"# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
print $help;
print
"\n".
"CCCMD = clang-cl\n".
"ifeq (\$(Platform),x64)\n".
"CCTARGET = x86_64-pc-windows-msvc18.0.0\n".
"RCCMD = x86_64-w64-mingw32-windres\n".
"else\n".
"CCTARGET = i386-pc-windows-msvc18.0.0\n".
"RCCMD = i686-w64-mingw32-windres\n".
"endif\n".
"CC = \$(CCCMD) --target=\$(CCTARGET)\n".
&splitline("RC = \$(RCCMD) --preprocessor=\$(CCCMD) ".
"--preprocessor-arg=/TC --preprocessor-arg=/E")."\n".
"LD = lld-link\n".
"\n".
"# C compilation flags\n".
&splitline("CFLAGS = /nologo /W3 /O1 " .
(join " ", map {"-I$dirpfx$_"} @srcdirs) .
" /D_WINDOWS /D_WIN32_WINDOWS=0x401 /DWINVER=0x401 ".
"/D_CRT_SECURE_NO_WARNINGS")."\n".
"LFLAGS = /incremental:no /dynamicbase /nxcompat\n".
&splitline("RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs).
" -DWIN32 -D_WIN32 -DWINVER=0x0400 --define MINGW32_FIX=1")."\n".
"\n".
"\n";
print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C"));
print "\n\n";
foreach $p (&prognames("G:C")) {
($prog, $type) = split ",", $p;
$objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef);
print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n";
$objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib");
$subsys = ($type eq "G") ? "windows" : "console";
print &splitline("\t\$(LD) \$(LFLAGS) \$(XLFLAGS) ".
"/out:\$(BUILDDIR)$prog.exe ".
"/lldmap:\$(BUILDDIR)$prog.map ".
"/subsystem:$subsys\$(SUBSYSVER) ".
"\$(EXTRA_$subsys) $objstr")."\n\n";
}
foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "/", "vc")) {
print &splitline(sprintf("%s: %s", $d->{obj},
join " ", @{$d->{deps}})), "\n";
if ($d->{obj} =~ /\.res$/) {
print "\t\$(RC) \$(RCFLAGS) ".$d->{deps}->[0]." -o ".$d->{obj}."\n\n";
} else {
$deflist = join "", map { " /D$_" } @{$d->{defs}};
print "\t\$(CC) /Fo\$(BUILDDIR)".$d->{obj}." \$(COMPAT) \$(CFLAGS) \$(XFLAGS)$deflist /c \$<\n\n";
}
}
print "\nclean:\n".
&splitline("\trm -f \$(BUILDDIR)*.obj \$(BUILDDIR)*.exe ".
"\$(BUILDDIR)*.res \$(BUILDDIR)*.map ".
"\$(BUILDDIR)*.exe.manifest")."\n";
select STDOUT; close OUT;
}
if (defined $makefiles{'cygwin'}) {
$mftyp = 'cygwin';
$dirpfx = &dirpfx($makefiles{'cygwin'}, "/");

View file

@ -305,10 +305,34 @@ static int get_config(frontend *fe, int which)
return fe->cfgret;
}
int jcallback_menu_key_event(int key)
int jcallback_newgame_event(void)
{
frontend *fe = (frontend *)_fe;
if (!midend_process_key(fe->me, 0, 0, key))
if (!midend_process_key(fe->me, 0, 0, UI_NEWGAME))
return 42;
return 0;
}
int jcallback_undo_event(void)
{
frontend *fe = (frontend *)_fe;
if (!midend_process_key(fe->me, 0, 0, UI_UNDO))
return 42;
return 0;
}
int jcallback_redo_event(void)
{
frontend *fe = (frontend *)_fe;
if (!midend_process_key(fe->me, 0, 0, UI_REDO))
return 42;
return 0;
}
int jcallback_quit_event(void)
{
frontend *fe = (frontend *)_fe;
if (!midend_process_key(fe->me, 0, 0, UI_QUIT))
return 42;
return 0;
}

View file

@ -27,13 +27,6 @@
#define USE_DRAGGING
#endif
#define MATMUL(xr,yr,m,x,y) do { \
float rx, ry, xx = (x), yy = (y), *mat = (m); \
rx = mat[0] * xx + mat[2] * yy; \
ry = mat[1] * xx + mat[3] * yy; \
(xr) = rx; (yr) = ry; \
} while (0)
/* Direction and other bitfields */
#define R 0x01
#define U 0x02
@ -65,7 +58,7 @@
#define PREFERRED_TILE_SIZE 32
#define TILE_SIZE (ds->tilesize)
#define TILE_BORDER 1
#define LINE_THICK ((TILE_SIZE+47)/48)
#ifdef SMALL_SCREEN
#define WINDOW_OFFSET 4
#else
@ -75,13 +68,6 @@
#define ROTATE_TIME 0.13F
#define FLASH_FRAME 0.07F
/* Transform physical coords to game coords using game_drawstate ds */
#define GX(x) (((x) + ds->org_x) % ds->width)
#define GY(y) (((y) + ds->org_y) % ds->height)
/* ...and game coords to physical coords */
#define RX(x) (((x) + ds->width - ds->org_x) % ds->width)
#define RY(y) (((y) + ds->height - ds->org_y) % ds->height)
enum {
COL_BACKGROUND,
COL_LOCKED,
@ -102,12 +88,17 @@ struct game_params {
float barrier_probability;
};
typedef struct game_immutable_state {
int refcount;
unsigned char *barriers;
} game_immutable_state;
struct game_state {
int width, height, wrapping, completed;
int last_rotate_x, last_rotate_y, last_rotate_dir;
int used_solve;
unsigned char *tiles;
unsigned char *barriers;
struct game_immutable_state *imm;
};
#define OFFSETWH(x2,y2,x1,y1,dir,width,height) \
@ -119,7 +110,7 @@ struct game_state {
#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
#define tile(state, x, y) index(state, (state)->tiles, x, y)
#define barrier(state, x, y) index(state, (state)->barriers, x, y)
#define barrier(state, x, y) index(state, (state)->imm->barriers, x, y)
struct xyd {
int x, y, direction;
@ -462,6 +453,11 @@ static int todo_get(struct todo *todo) {
return ret;
}
/*
* Return values: -1 means puzzle was proved inconsistent, 0 means we
* failed to narrow down to a unique solution, +1 means we solved it
* fully.
*/
static int net_solver(int w, int h, unsigned char *tiles,
unsigned char *barriers, int wrapping)
{
@ -736,7 +732,11 @@ static int net_solver(int w, int h, unsigned char *tiles,
#endif
}
assert(j > 0); /* we can't lose _all_ possibilities! */
if (j == 0) {
/* If we've ruled out all possible orientations for a
* tile, then our puzzle has no solution at all. */
return -1;
}
if (j < i) {
done_something = TRUE;
@ -816,14 +816,14 @@ static int net_solver(int w, int h, unsigned char *tiles,
/*
* Mark all completely determined tiles as locked.
*/
j = TRUE;
j = +1;
for (i = 0; i < w*h; i++) {
if (tilestate[i * 4 + 1] == 255) {
assert(tilestate[i * 4 + 0] != 255);
tiles[i] = tilestate[i * 4] | LOCKED;
} else {
tiles[i] &= ~LOCKED;
j = FALSE;
j = 0;
}
}
@ -1337,7 +1337,7 @@ static char *new_game_desc(const game_params *params, random_state *rs,
/*
* Run the solver to check unique solubility.
*/
while (!net_solver(w, h, tiles, NULL, params->wrapping)) {
while (net_solver(w, h, tiles, NULL, params->wrapping) != 1) {
int n = 0;
/*
@ -1647,12 +1647,14 @@ static game_state *new_game(midend *me, const game_params *params,
w = state->width = params->width;
h = state->height = params->height;
state->wrapping = params->wrapping;
state->imm = snew(game_immutable_state);
state->imm->refcount = 1;
state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0;
state->completed = state->used_solve = FALSE;
state->tiles = snewn(state->width * state->height, unsigned char);
memset(state->tiles, 0, state->width * state->height);
state->barriers = snewn(state->width * state->height, unsigned char);
memset(state->barriers, 0, state->width * state->height);
state->imm->barriers = snewn(state->width * state->height, unsigned char);
memset(state->imm->barriers, 0, state->width * state->height);
/*
* Parse the game description into the grid.
@ -1723,6 +1725,8 @@ static game_state *dup_game(const game_state *state)
game_state *ret;
ret = snew(game_state);
ret->imm = state->imm;
ret->imm->refcount++;
ret->width = state->width;
ret->height = state->height;
ret->wrapping = state->wrapping;
@ -1733,16 +1737,17 @@ static game_state *dup_game(const game_state *state)
ret->last_rotate_y = state->last_rotate_y;
ret->tiles = snewn(state->width * state->height, unsigned char);
memcpy(ret->tiles, state->tiles, state->width * state->height);
ret->barriers = snewn(state->width * state->height, unsigned char);
memcpy(ret->barriers, state->barriers, state->width * state->height);
return ret;
}
static void free_game(game_state *state)
{
if (--state->imm->refcount == 0) {
sfree(state->imm->barriers);
sfree(state->imm);
}
sfree(state->tiles);
sfree(state->barriers);
sfree(state);
}
@ -1761,9 +1766,17 @@ static char *solve_game(const game_state *state, const game_state *currstate,
* Run the internal solver on the provided grid. This might
* not yield a complete solution.
*/
int solver_result;
memcpy(tiles, state->tiles, state->width * state->height);
net_solver(state->width, state->height, tiles,
state->barriers, state->wrapping);
solver_result = net_solver(state->width, state->height, tiles,
state->imm->barriers, state->wrapping);
if (solver_result < 0) {
*error = "No solution exists for this puzzle";
sfree(tiles);
return NULL;
}
} else {
for (i = 0; i < state->width * state->height; i++) {
int c = aux[i];
@ -1990,7 +2003,7 @@ static int *compute_loops_inner(int w, int h, int wrapping,
static int *compute_loops(const game_state *state)
{
return compute_loops_inner(state->width, state->height, state->wrapping,
state->tiles, state->barriers);
state->tiles, state->imm->barriers);
}
struct game_ui {
@ -2051,9 +2064,8 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
struct game_drawstate {
int started;
int width, height;
int org_x, org_y;
int tilesize;
int *visible;
unsigned long *visible, *to_draw;
};
/* ----------------------------------------------------------------------
@ -2093,8 +2105,8 @@ static char *interpret_move(const game_state *state, game_ui *ui,
/*
* The button must have been clicked on a valid tile.
*/
x -= WINDOW_OFFSET + TILE_BORDER;
y -= WINDOW_OFFSET + TILE_BORDER;
x -= WINDOW_OFFSET + LINE_THICK;
y -= WINDOW_OFFSET + LINE_THICK;
if (x < 0 || y < 0)
return nullret;
tx = x / TILE_SIZE;
@ -2104,8 +2116,8 @@ static char *interpret_move(const game_state *state, game_ui *ui,
/* Transform from physical to game coords */
tx = (tx + ui->org_x) % state->width;
ty = (ty + ui->org_y) % state->height;
if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER ||
y % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
if (x % TILE_SIZE >= TILE_SIZE - LINE_THICK ||
y % TILE_SIZE >= TILE_SIZE - LINE_THICK)
return nullret;
#ifdef USE_DRAGGING
@ -2434,20 +2446,25 @@ static game_state *execute_move(const game_state *from, const char *move)
static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
{
game_drawstate *ds = snew(game_drawstate);
int i;
int i, ncells;
ds->started = FALSE;
ds->width = state->width;
ds->height = state->height;
ds->org_x = ds->org_y = -1;
ds->visible = snewn(state->width * state->height, int);
ncells = (state->width+2) * (state->height+2);
ds->visible = snewn(ncells, unsigned long);
ds->to_draw = snewn(ncells, unsigned long);
ds->tilesize = 0; /* undecided yet */
for (i = 0; i < state->width * state->height; i++)
for (i = 0; i < ncells; i++)
ds->visible[i] = -1;
return ds;
}
#define dsindex(ds, field, x, y) ((ds)->field[((y)+1)*((ds)->width+2)+((x)+1)])
#define visible(ds, x, y) dsindex(ds, visible, x, y)
#define todraw(ds, x, y) dsindex(ds, to_draw, x, y)
static void game_free_drawstate(drawing *dr, game_drawstate *ds)
{
sfree(ds->visible);
@ -2457,8 +2474,12 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds)
static void game_compute_size(const game_params *params, int tilesize,
int *x, int *y)
{
*x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER;
*y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER;
/* Ick: fake up `ds->tilesize' for macro expansion purposes */
struct { int tilesize; } ads, *ds = &ads;
ads.tilesize = tilesize;
*x = WINDOW_OFFSET * 2 + TILE_SIZE * params->width + LINE_THICK;
*y = WINDOW_OFFSET * 2 + TILE_SIZE * params->height + LINE_THICK;
}
static void game_set_size(drawing *dr, game_drawstate *ds,
@ -2532,177 +2553,233 @@ static float *game_colours(frontend *fe, int *ncolours)
return ret;
}
static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2,
int colour)
static void rotated_coords(float *ox, float *oy, const float matrix[4],
float cx, float cy, float ix, float iy)
{
draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
draw_line(dr, x1, y1, x2, y2, colour);
*ox = matrix[0] * ix + matrix[2] * iy + cx;
*oy = matrix[1] * ix + matrix[3] * iy + cy;
}
static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2,
int colour)
/* Flags describing the visible features of a tile. */
#define TILE_BARRIER_SHIFT 0 /* 4 bits: R U L D */
#define TILE_BARRIER_CORNER_SHIFT 4 /* 4 bits: RU UL LD DR */
#define TILE_KEYBOARD_CURSOR (1<<8) /* 1 bit if cursor is here */
#define TILE_WIRE_SHIFT 9 /* 8 bits: RR UU LL DD
* Each pair: 0=no wire, 1=unpowered,
* 2=powered, 3=loop err highlight */
#define TILE_ENDPOINT_SHIFT 17 /* 2 bits: 0=no endpoint, 1=unpowered,
* 2=powered, 3=power-source square */
#define TILE_WIRE_ON_EDGE_SHIFT 19 /* 8 bits: RR UU LL DD,
* same encoding as TILE_WIRE_SHIFT */
#define TILE_ROTATING (1UL<<27) /* 1 bit if tile is rotating */
#define TILE_LOCKED (1UL<<28) /* 1 bit if tile is locked */
static void draw_wires(drawing *dr, int cx, int cy, int radius,
unsigned long tile, int bitmap,
int colour, int halfwidth, const float matrix[4])
{
int mx = (x1 < x2 ? x1 : x2);
int my = (y1 < y2 ? y1 : y2);
int dx = (x2 + x1 - 2*mx + 1);
int dy = (y2 + y1 - 2*my + 1);
float fpoints[12*2];
int points[12*2];
int npoints, d, dsh, i;
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;
/*
* draw_barrier_corner() and draw_barrier() are passed physical coords
*/
static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
int x, int y, int dx, int dy, int phase)
{
int bx = WINDOW_OFFSET + TILE_SIZE * x;
int by = WINDOW_OFFSET + TILE_SIZE * y;
int x1, y1;
fpoints[2*npoints+0] = halfwidth * (X(d) + X(C(d)));
fpoints[2*npoints+1] = halfwidth * (Y(d) + Y(C(d)));
npoints++;
x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
if (bitmap & (1 << wiretype)) {
fpoints[2*npoints+0] = radius * X(d) + halfwidth * X(C(d));
fpoints[2*npoints+1] = radius * Y(d) + halfwidth * Y(C(d));
npoints++;
fpoints[2*npoints+0] = radius * X(d) + halfwidth * X(A(d));
fpoints[2*npoints+1] = radius * Y(d) + halfwidth * Y(A(d));
npoints++;
if (phase == 0) {
draw_rect_coords(dr, bx+x1+dx, by+y1,
bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
COL_WIRE);
draw_rect_coords(dr, bx+x1, by+y1+dy,
bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
COL_WIRE);
} else {
draw_rect_coords(dr, bx+x1, by+y1,
bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
COL_BARRIER);
any_wire_this_colour = TRUE;
}
}
static void draw_barrier(drawing *dr, game_drawstate *ds,
int x, int y, int dir, int phase)
{
int bx = WINDOW_OFFSET + TILE_SIZE * x;
int by = WINDOW_OFFSET + TILE_SIZE * y;
int x1, y1, w, h;
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);
}
/*
* draw_tile() is passed physical coordinates
*/
static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds,
int x, int y, int tile, int src, float angle, int cursor)
static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y,
unsigned long tile, float angle)
{
int bx = WINDOW_OFFSET + TILE_SIZE * x;
int by = WINDOW_OFFSET + TILE_SIZE * y;
int tx, ty;
int clipx, clipy, clipX, clipY, clipw, cliph;
int border_br = LINE_THICK/2, border_tl = LINE_THICK - border_br;
int barrier_outline_thick = (LINE_THICK+1)/2;
int bg, d, dsh, pass;
int cx, cy, radius;
float matrix[4];
float cx, cy, ex, ey, tx, ty;
int dir, col, phase;
tx = WINDOW_OFFSET + TILE_SIZE * x + border_br;
ty = WINDOW_OFFSET + TILE_SIZE * y + border_br;
/*
* When we draw a single tile, we must draw everything up to
* and including the borders around the tile. This means that
* if the neighbouring tiles have connections to those borders,
* we must draw those connections on the borders themselves.
* Clip to the tile boundary, with adjustments if we're drawing
* just outside the grid.
*/
clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
clipx = tx; clipX = tx + TILE_SIZE;
clipy = ty; clipY = ty + TILE_SIZE;
if (x == -1) {
clipx = clipX - border_br - barrier_outline_thick;
} else if (x == ds->width) {
clipX = clipx + border_tl + barrier_outline_thick;
}
if (y == -1) {
clipy = clipY - border_br - barrier_outline_thick;
} else if (y == ds->height) {
clipY = clipy + border_tl + barrier_outline_thick;
}
clipw = clipX - clipx;
cliph = clipY - clipy;
clip(dr, clipx, clipy, clipw, cliph);
/*
* So. First blank the tile out completely: draw a big
* rectangle in border colour, and a smaller rectangle in
* background colour to fill it in.
* Clear the clip region.
*/
draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
COL_BORDER);
draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
bg = (tile & TILE_LOCKED) ? COL_LOCKED : COL_BACKGROUND;
draw_rect(dr, clipx, clipy, clipw, cliph, bg);
/*
* Draw an inset outline rectangle as a cursor, in whichever of
* COL_LOCKED and COL_BACKGROUND we aren't currently drawing
* in.
* Draw the grid lines.
*/
if (cursor) {
draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
{
int gridl = (x == -1 ? tx+TILE_SIZE-border_br : tx);
int gridr = (x == ds->width ? tx+border_tl : tx+TILE_SIZE);
int gridu = (y == -1 ? ty+TILE_SIZE-border_br : ty);
int gridd = (y == ds->height ? ty+border_tl : ty+TILE_SIZE);
if (x >= 0)
draw_rect(dr, tx, gridu, border_tl, gridd-gridu, COL_BORDER);
if (y >= 0)
draw_rect(dr, gridl, ty, gridr-gridl, border_tl, COL_BORDER);
if (x < ds->width)
draw_rect(dr, tx+TILE_SIZE-border_br, gridu,
border_br, gridd-gridu, COL_BORDER);
if (y < ds->height)
draw_rect(dr, gridl, ty+TILE_SIZE-border_br,
gridr-gridl, border_br, COL_BORDER);
}
/*
* Set up the rotation matrix.
* Draw the keyboard cursor.
*/
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[1] = (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.
*/
cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F;
col = (tile & ACTIVE ? COL_POWERED : COL_WIRE);
for (dir = 1; dir < 0x10; dir <<= 1) {
if (tile & dir) {
ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
MATMUL(tx, ty, matrix, ex, ey);
draw_filled_line(dr, bx+(int)cx, by+(int)cy,
bx+(int)(cx+tx), by+(int)(cy+ty),
COL_WIRE);
}
}
for (dir = 1; dir < 0x10; dir <<= 1) {
if (tile & dir) {
ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
MATMUL(tx, ty, matrix, ex, ey);
draw_line(dr, bx+(int)cx, by+(int)cy,
bx+(int)(cx+tx), by+(int)(cy+ty),
(tile & LOOP(dir)) ? COL_LOOP : col);
}
}
/* 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_wires(dr, cx, cy, radius, tile,
0xE, COL_WIRE, 2*LINE_THICK-1, matrix);
draw_wires(dr, cx, cy, radius, tile,
0x4, COL_POWERED, LINE_THICK-1, matrix);
draw_wires(dr, cx, cy, radius, tile,
0x8, COL_LOOP, LINE_THICK-1, matrix);
/*
* Draw the box in the middle. We do this in blue if the tile
* is an unpowered endpoint, in cyan if the tile is a powered
* endpoint, in black if the tile is the centrepiece, and
* otherwise not at all.
* Draw the central box.
*/
col = -1;
if (src)
col = COL_WIRE;
else if (COUNT(tile) == 1) {
col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT);
}
if (col >= 0) {
int i, points[8];
for (pass = 0; pass < 2; pass++) {
int endtype = (tile >> TILE_ENDPOINT_SHIFT) & 3;
if (endtype) {
int i, points[8], col;
float boxr = TILE_SIZE * 0.24F + (pass == 0 ? LINE_THICK-1 : 0);
col = (pass == 0 || endtype == 3 ? COL_WIRE :
endtype == 2 ? COL_POWERED : COL_ENDPOINT);
points[0] = +1; points[1] = +1;
points[2] = +1; points[3] = -1;
@ -2710,119 +2787,52 @@ static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds,
points[6] = -1; points[7] = +1;
for (i = 0; i < 8; i += 2) {
ex = (TILE_SIZE * 0.24F) * points[i];
ey = (TILE_SIZE * 0.24F) * points[i+1];
MATMUL(tx, ty, matrix, ex, ey);
points[i] = bx+(int)(cx+tx);
points[i+1] = by+(int)(cy+ty);
float x, y;
rotated_coords(&x, &y, matrix, cx, cy,
boxr * points[i], boxr * points[i+1]);
points[i] = x + 0.5;
points[i+1] = y + 0.5;
}
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 (dir = 1; dir < 0x10; dir <<= 1) {
int x1, y1, corner = FALSE;
/*
* If at least one barrier terminates at the corner
* between dir and A(dir), draw a barrier corner.
*/
if (barrier(state, GX(x), GY(y)) & (dir | A(dir))) {
corner = TRUE;
} else {
/*
* Only count barriers terminating at this corner
* if they're physically next to the corner. (That
* is, if they've wrapped round from the far side
* of the screen, they don't count.)
*/
x1 = x + X(dir);
y1 = y + Y(dir);
if (x1 >= 0 && x1 < state->width &&
y1 >= 0 && y1 < state->height &&
(barrier(state, GX(x1), GY(y1)) & A(dir))) {
corner = TRUE;
} else {
x1 = x + X(A(dir));
y1 = y + Y(A(dir));
if (x1 >= 0 && x1 < state->width &&
y1 >= 0 && y1 < state->height &&
(barrier(state, GX(x1), GY(y1)) & dir))
corner = TRUE;
}
for (pass = 0; pass < 2; pass++) {
int btl = border_tl, bbr = border_br, col = COL_BARRIER;
if (pass == 0) {
btl += barrier_outline_thick;
bbr += barrier_outline_thick;
col = COL_WIRE;
}
if (tile & (L << TILE_BARRIER_SHIFT))
draw_rect(dr, tx, ty, btl, TILE_SIZE, col);
if (tile & (R << TILE_BARRIER_SHIFT))
draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, TILE_SIZE, col);
if (tile & (U << TILE_BARRIER_SHIFT))
draw_rect(dr, tx, ty, TILE_SIZE, btl, col);
if (tile & (D << TILE_BARRIER_SHIFT))
draw_rect(dr, tx, ty+TILE_SIZE-bbr, TILE_SIZE, bbr, col);
if (tile & (R << TILE_BARRIER_CORNER_SHIFT))
draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, btl, col);
if (tile & (U << TILE_BARRIER_CORNER_SHIFT))
draw_rect(dr, tx, ty, btl, btl, col);
if (tile & (L << TILE_BARRIER_CORNER_SHIFT))
draw_rect(dr, tx, ty+TILE_SIZE-bbr, btl, bbr, col);
if (tile & (D << TILE_BARRIER_CORNER_SHIFT))
draw_rect(dr, tx+TILE_SIZE-bbr, ty+TILE_SIZE-bbr, bbr, bbr, col);
}
if (corner) {
/*
* At least one barrier terminates here. Draw a
* corner.
* Unclip and draw update, to finish.
*/
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);
draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
draw_update(dr, clipx, clipy, clipw, cliph);
}
static void game_redraw(drawing *dr, game_drawstate *ds,
@ -2830,73 +2840,26 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
int dir, const game_ui *ui,
float t, float ft)
{
int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE;
int tx, ty, dx, dy, d, dsh, last_rotate_dir, frame;
unsigned char *active;
int *loops;
float angle = 0.0;
/*
* Clear the screen, and draw the exterior barrier lines, if
* this is our first call or if the origin has changed.
* Clear the screen on our first call.
*/
if (!ds->started || ui->org_x != ds->org_x || ui->org_y != ds->org_y) {
int phase;
if (!ds->started) {
int w, h;
game_params params;
ds->started = TRUE;
draw_rect(dr, 0, 0,
WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
COL_BACKGROUND);
params.width = ds->width;
params.height = ds->height;
game_compute_size(&params, TILE_SIZE, &w, &h);
ds->org_x = ui->org_x;
ds->org_y = ui->org_y;
moved_origin = TRUE;
draw_update(dr, 0, 0,
WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
for (phase = 0; phase < 2; phase++) {
for (x = 0; x < ds->width; x++) {
if (x+1 < ds->width) {
if (barrier(state, GX(x), GY(0)) & R)
draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
if (barrier(state, GX(x), GY(ds->height-1)) & R)
draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
}
if (barrier(state, GX(x), GY(0)) & U) {
draw_barrier_corner(dr, ds, x, -1, -1, +1, phase);
draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
draw_barrier(dr, ds, x, -1, D, phase);
}
if (barrier(state, GX(x), GY(ds->height-1)) & D) {
draw_barrier_corner(dr, ds, x, ds->height, -1, -1, phase);
draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
draw_barrier(dr, ds, x, ds->height, U, phase);
}
}
for (y = 0; y < ds->height; y++) {
if (y+1 < ds->height) {
if (barrier(state, GX(0), GY(y)) & D)
draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
if (barrier(state, GX(ds->width-1), GY(y)) & D)
draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
}
if (barrier(state, GX(0), GY(y)) & L) {
draw_barrier_corner(dr, ds, -1, y, +1, -1, phase);
draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
draw_barrier(dr, ds, -1, y, R, phase);
}
if (barrier(state, GX(ds->width-1), GY(y)) & R) {
draw_barrier_corner(dr, ds, ds->width, y, -1, -1, phase);
draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
draw_barrier(dr, ds, ds->width, y, L, phase);
}
}
}
draw_rect(dr, 0, 0, w, h, COL_BACKGROUND);
draw_update(dr, 0, 0, w, h);
}
tx = ty = -1;
@ -2913,30 +2876,83 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
state = oldstate;
}
frame = -1;
if (ft > 0) {
/*
* We're animating a completion flash. Find which frame
* we're at.
*/
frame = (int)(ft / FLASH_FRAME);
} else {
frame = 0;
}
/*
* Draw any tile which differs from the way it was last drawn.
* Build up a map of what we want every tile to look like. We
* include tiles one square outside the grid, for the outer edges
* of barriers.
*/
active = compute_active(state, ui->cx, ui->cy);
loops = compute_loops(state);
for (x = 0; x < ds->width; x++)
for (y = 0; y < ds->height; y++) {
int c = tile(state, GX(x), GY(y)) |
index(state, active, GX(x), GY(y)) |
index(state, loops, GX(x), GY(y));
int is_src = GX(x) == ui->cx && GY(y) == ui->cy;
int is_anim = GX(x) == tx && GY(y) == ty;
int is_cursor = ui->cur_visible &&
GX(x) == ui->cur_x && GY(y) == ui->cur_y;
for (dy = -1; dy < ds->height+1; dy++) {
for (dx = -1; dx < ds->width+1; dx++) {
todraw(ds, dx, dy) = 0;
}
}
for (dy = 0; dy < ds->height; dy++) {
int gy = (dy + ui->org_y) % ds->height;
for (dx = 0; dx < ds->width; dx++) {
int gx = (dx + ui->org_x) % ds->width;
int t = (tile(state, gx, gy) |
index(state, loops, gx, gy) |
index(state, active, gx, gy));
for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) {
if (barrier(state, gx, gy) & d) {
todraw(ds, dx, dy) |=
d << TILE_BARRIER_SHIFT;
todraw(ds, dx + X(d), dy + Y(d)) |=
F(d) << TILE_BARRIER_SHIFT;
todraw(ds, dx + X(A(d)), dy + Y(A(d))) |=
C(d) << TILE_BARRIER_CORNER_SHIFT;
todraw(ds, dx + X(A(d)) + X(d), dy + Y(A(d)) + Y(d)) |=
F(d) << TILE_BARRIER_CORNER_SHIFT;
todraw(ds, dx + X(C(d)), dy + Y(C(d))) |=
d << TILE_BARRIER_CORNER_SHIFT;
todraw(ds, dx + X(C(d)) + X(d), dy + Y(C(d)) + Y(d)) |=
A(d) << TILE_BARRIER_CORNER_SHIFT;
}
if (t & d) {
int edgeval = (t & LOOP(d) ? 3 : t & ACTIVE ? 2 : 1);
todraw(ds, dx, dy) |= edgeval << (TILE_WIRE_SHIFT + dsh*2);
if (!(gx == tx && gy == ty)) {
todraw(ds, dx + X(d), dy + Y(d)) |=
edgeval << (TILE_WIRE_ON_EDGE_SHIFT + (dsh ^ 2)*2);
}
}
}
if (ui->cur_visible && gx == ui->cur_x && gy == ui->cur_y)
todraw(ds, dx, dy) |= TILE_KEYBOARD_CURSOR;
if (gx == tx && gy == ty)
todraw(ds, dx, dy) |= TILE_ROTATING;
if (gx == ui->cx && gy == ui->cy) {
todraw(ds, dx, dy) |= 3 << TILE_ENDPOINT_SHIFT;
} else if ((t & 0xF) != R && (t & 0xF) != U &&
(t & 0xF) != L && (t & 0xF) != D) {
/* this is not an endpoint tile */
} else if (t & ACTIVE) {
todraw(ds, dx, dy) |= 2 << TILE_ENDPOINT_SHIFT;
} else {
todraw(ds, dx, dy) |= 1 << TILE_ENDPOINT_SHIFT;
}
if (t & LOCKED)
todraw(ds, dx, dy) |= TILE_LOCKED;
/*
* In a completion flash, we adjust the LOCKED bit
@ -2944,29 +2960,34 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
* the frame number.
*/
if (frame >= 0) {
int rcx = RX(ui->cx), rcy = RY(ui->cy);
int rcx = (ui->cx + ds->width - ui->org_x) % ds->width;
int rcy = (ui->cy + ds->height - ui->org_y) % ds->height;
int xdist, ydist, dist;
xdist = (x < rcx ? rcx - x : x - rcx);
ydist = (y < rcy ? rcy - y : y - rcy);
xdist = (dx < rcx ? rcx - dx : dx - rcx);
ydist = (dy < rcy ? rcy - dy : dy - rcy);
dist = (xdist > ydist ? xdist : ydist);
if (frame >= dist && frame < dist+4) {
int lock = (frame - dist) & 1;
lock = lock ? LOCKED : 0;
c = (c &~ LOCKED) | lock;
if (frame >= dist && frame < dist+4 &&
((frame - dist) & 1))
todraw(ds, dx, dy) ^= TILE_LOCKED;
}
}
}
if (moved_origin ||
index(state, ds->visible, x, y) != c ||
index(state, ds->visible, x, y) == -1 ||
is_src || is_anim || is_cursor) {
draw_tile(dr, state, ds, x, y, c,
is_src, (is_anim ? angle : 0.0F), is_cursor);
if (is_src || is_anim || is_cursor)
index(state, ds->visible, x, y) = -1;
else
index(state, ds->visible, x, y) = c;
/*
* Now draw any tile that differs from the way it was last drawn.
* An exception is that if either the previous _or_ current state
* has the TILE_ROTATING bit set, we must draw it regardless,
* because it will have rotated to a different angle.q
*/
for (dy = -1; dy < ds->height+1; dy++) {
for (dx = -1; dx < ds->width+1; dx++) {
int prev = visible(ds, dx, dy);
int curr = todraw(ds, dx, dy);
if (prev != curr || ((prev | curr) & TILE_ROTATING) != 0) {
draw_tile(dr, ds, dx, dy, curr, angle);
visible(ds, dx, dy) = curr;
}
}
}

View file

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

View file

@ -310,7 +310,18 @@ static void generate(random_state *rs, int w, int h, unsigned char *retgrid)
fgrid2 = snewn(w*h, float);
memcpy(fgrid2, fgrid, w*h*sizeof(float));
qsort(fgrid2, w*h, sizeof(float), float_compare);
threshold = fgrid2[w*h/2];
/* Choose a threshold that makes half the pixels black. In case of
* an odd number of pixels, select randomly between just under and
* just over half. */
{
int index = w * h / 2;
if (w & h & 1)
index += random_upto(rs, 2);
if (index < w*h)
threshold = fgrid2[index];
else
threshold = fgrid2[w*h-1] + 1;
}
sfree(fgrid2);
for (i = 0; i < h; i++) {
@ -448,6 +459,8 @@ static int do_row(unsigned char *known, unsigned char *deduced,
if (rowlen == 0) {
memset(deduced, DOT, len);
} else if (rowlen == 1 && data[0] == len) {
memset(deduced, BLOCK, len);
} else {
do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok,
maxpos_ok, data, len, freespace, 0, 0);

View file

@ -47,6 +47,15 @@ enum {
CURSOR_RIGHT,
CURSOR_SELECT,
CURSOR_SELECT2,
/* UI_* are special keystrokes generated by front ends in response
* to menu actions, never passed to back ends */
UI_LOWER_BOUND,
UI_QUIT,
UI_NEWGAME,
UI_SOLVE,
UI_UNDO,
UI_REDO,
UI_UPPER_BOUND,
/* made smaller because of 'limited range of datatype' errors. */
MOD_CTRL = 0x1000,
@ -64,6 +73,7 @@ enum {
#define IS_CURSOR_MOVE(m) ( (m) == CURSOR_UP || (m) == CURSOR_DOWN || \
(m) == CURSOR_RIGHT || (m) == CURSOR_LEFT )
#define IS_CURSOR_SELECT(m) ( (m) == CURSOR_SELECT || (m) == CURSOR_SELECT2)
#define IS_UI_FAKE_KEY(m) ( (m) > UI_LOWER_BOUND && (m) < UI_UPPER_BOUND )
/*
* Flags in the back end's `flags' word.

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 BORDER (TILE_SIZE/8)
#define BORDER_WIDTH (max(TILE_SIZE / 32, 1))
#define LINE_THICK (TILE_SIZE/16)
#define GRID_LINE_TL (ds->grid_line_tl)
#define GRID_LINE_BR (ds->grid_line_br)
#define GRID_LINE_ALL (ds->grid_line_all)
#define COORD(x) ( (x+1) * TILE_SIZE + BORDER )
#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 )
@ -1738,7 +1741,7 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
#define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */
struct game_drawstate {
int sz6;
int sz6, grid_line_all, grid_line_tl, grid_line_br;
int started;
int w, h, sz;
@ -2118,7 +2121,6 @@ static void game_compute_size(const game_params *params, int tilesize,
int sz6;
} ads, *ds = &ads;
ads.sz6 = tilesize/6;
*x = (params->w+2) * TILE_SIZE + 2 * BORDER;
*y = (params->h+2) * TILE_SIZE + 2 * BORDER;
}
@ -2127,6 +2129,9 @@ static void game_set_size(drawing *dr, game_drawstate *ds,
const game_params *params, int tilesize)
{
ds->sz6 = tilesize/6;
ds->grid_line_all = max(LINE_THICK, 1);
ds->grid_line_br = ds->grid_line_all / 2;
ds->grid_line_tl = ds->grid_line_all - ds->grid_line_br;
}
enum {
@ -2346,14 +2351,13 @@ static void draw_square(drawing *dr, game_drawstate *ds,
/* Clip to the grid square. */
clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
/* Clear the square. */
/* Clear the square so that it's got an appropriately-sized border
* in COL_GRID and a central area in the right background colour. */
best_bits((flags & DS_TRACK) == DS_TRACK,
(flags_drag & DS_TRACK) == DS_TRACK, &bg);
draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, bg);
/* Draw outline of grid square */
draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID);
draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID);
draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_GRID);
draw_rect(dr, ox + GRID_LINE_TL, oy + GRID_LINE_TL,
TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL, bg);
/* More outlines for clue squares. */
if (flags & DS_CURSOR) {
@ -2389,8 +2393,8 @@ static void draw_square(drawing *dr, game_drawstate *ds,
(flags_drag & DS_NOTRACK) == DS_NOTRACK, &c);
if (flags_best) {
off = HALFSZ/2;
draw_line(dr, cx - off, cy - off, cx + off, cy + off, c);
draw_line(dr, cx - off, cy + off, cx + off, cy - off, c);
draw_thick_line(dr, LINE_THICK, cx - off, cy - off, cx + off, cy + off, c);
draw_thick_line(dr, LINE_THICK, cx - off, cy + off, cx + off, cy - off, c);
}
c = COL_TRACK;
@ -2404,8 +2408,8 @@ static void draw_square(drawing *dr, game_drawstate *ds,
cx += (d == R) ? t2 : (d == L) ? -t2 : 0;
cy += (d == D) ? t2 : (d == U) ? -t2 : 0;
draw_line(dr, cx - off, cy - off, cx + off, cy + off, c);
draw_line(dr, cx - off, cy + off, cx + off, cy - off, c);
draw_thick_line(dr, LINE_THICK, cx - off, cy - off, cx + off, cy + off, c);
draw_thick_line(dr, LINE_THICK, cx - off, cy + off, cx + off, cy - off, c);
}
}
@ -2426,12 +2430,14 @@ static void draw_clue(drawing *dr, game_drawstate *ds, int w, int clue, int i, i
cy = CENTERED_COORD(i-w);
}
draw_rect(dr, cx - tsz + BORDER, cy - tsz + BORDER,
TILE_SIZE - BORDER, TILE_SIZE - BORDER, COL_BACKGROUND);
draw_rect(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL,
TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL,
COL_BACKGROUND);
sprintf(buf, "%d", clue);
draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE,
col, buf);
draw_update(dr, cx - tsz, cy - tsz, TILE_SIZE, TILE_SIZE);
draw_update(dr, cx - tsz + GRID_LINE_TL, cy - tsz + GRID_LINE_TL,
TILE_SIZE - GRID_LINE_ALL, TILE_SIZE - GRID_LINE_ALL);
}
static void draw_loop_ends(drawing *dr, game_drawstate *ds,
@ -2498,8 +2504,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldst
draw_loop_ends(dr, ds, state, COL_CLUE);
draw_line(dr, COORD(ds->w), COORD(0), COORD(ds->w), COORD(ds->h), COL_GRID);
draw_line(dr, COORD(0), COORD(ds->h), COORD(ds->w), COORD(ds->h), COL_GRID);
draw_rect(dr, COORD(0) - GRID_LINE_BR, COORD(0) - GRID_LINE_BR,
ds->w * TILE_SIZE + GRID_LINE_ALL,
ds->h * TILE_SIZE + GRID_LINE_ALL, COL_GRID);
draw_update(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER);

View file

@ -27,7 +27,7 @@ while (<$desc>) {
'<span class="puzzle"><table>'.
'<tr><th align="center">%s</th></tr>'.
'<tr><td align="center">'.
'<img style="margin: 0.5em" alt="" title="%s" width=150 height=150 border=0 src="%s-web.png" />'.
'<a href="js/%s.html"><img style="margin: 0.5em" alt="" title="%s" width=150 height=150 border=0 src="%s-web.png" /></a>'.
'</td></tr>'.
'<tr><td align="center" style="font-size: 70%%"><code>[</code>'.
' <a href="java/%s.html">java</a> '.
@ -41,6 +41,7 @@ while (<$desc>) {
'<tr><td align="center">%s</td></tr></table></span>'.
"\n",
encode_entities($displayname),
encode_entities($id),
encode_entities($description),
encode_entities($id),
encode_entities($id),

View file

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

View file

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