Port of Simon Tatham's Puzzle Collection

Original revision: 5123b1bf68777ffa86e651f178046b26a87cf2d9

MIT Licensed. Some games still crash and others are unplayable due to
issues with controls. Still need a "real" polygon filling algorithm.

Currently builds one plugin per puzzle (about 40 in total, around 100K
each on ARM), but can easily be made to build a single monolithic
overlay (800K or so on ARM).

The following games are at least partially broken for various reasons,
and have been disabled on this commit:

Cube:     failed assertion with "Icosahedron" setting
Keen:     input issues
Mines:    weird stuff happens on target
Palisade: input issues
Solo:     input issues, occasional crash on target
Towers:   input issues
Undead:   input issues
Unequal:  input and drawing issues (concave polys)
Untangle: input issues

Features left to do:
 - In-game help system
 - Figure out the weird bugs

Change-Id: I7c69b6860ab115f973c8d76799502e9bb3d52368
This commit is contained in:
Franklin Wei 2016-11-20 15:16:41 -05:00 committed by Franklin Wei
parent 3ee79724f6
commit 1a6a8b52f7
289 changed files with 147273 additions and 0 deletions

View file

@ -78,6 +78,7 @@ gif,viewers
pong,games
ppm,viewers
properties,viewers
puzzles,games
random_folder_advance_config,apps
remote_control,apps
resistor,apps
@ -92,6 +93,45 @@ rockpaint,apps
search,viewers
searchengine,viewers
settings_dumper,apps
sgt-blackbox,games
sgt-bridges,games
sgt-cube,games
sgt-dominosa,games
sgt-fifteen,games
sgt-filling,games
sgt-flip,games
sgt-flood,games
sgt-galaxies,games
sgt-guess,games
sgt-inertia,games
sgt-keen,games
sgt-lightup,games
sgt-loopy,games
sgt-magnets,games
sgt-map,games
sgt-mines,games
sgt-net,games
sgt-netslide,games
sgt-palisade,games
sgt-pattern,games
sgt-pearl,games
sgt-pegs,games
sgt-range,games
sgt-rect,games
sgt-samegame,games
sgt-signpost,games
sgt-singles,games
sgt-sixteen,games
sgt-slant,games
sgt-solo,games
sgt-tents,games
sgt-towers,games
sgt-tracks,games
sgt-twiddle,games
sgt-undead,games
sgt-unequal,games
sgt-unruly,games
sgt-untangle,games
shopper,viewers
shortcuts_append,viewers
shortcuts_view,viewers

View file

@ -73,6 +73,10 @@ iriverify.c
#if (CONFIG_PLATFORM & PLATFORM_NATIVE) /* those plugins only run on hardware */
/* Overlays loaders */
/* use this if you want a monolithic 'puzzles' */
/*sgt-puzzles.c*/
#if PLUGIN_BUFFER_SIZE <= 0x20000 && defined(HAVE_LCD_BITMAP)
#if CONFIG_KEYPAD != ONDIO_PAD && CONFIG_KEYPAD != SANSA_M200_PAD \

View file

@ -16,6 +16,7 @@ clock
#if defined(HAVE_LCD_COLOR) && \
(!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE))
xworld
puzzles
#endif
#if (CONFIG_KEYPAD != ONDIO_PAD) /* not enough buttons */ \

View file

@ -0,0 +1,194 @@
# -*- sh -*-
# Build script to build Puzzles.
module puzzles
set Version $(!builddate).$(vcsid)
# Start by substituting the right version number in configure.ac.
in puzzles do perl -i~ -pe 's/6.66/$(Version)/' configure.ac
in puzzles do rm configure.ac~
# And put it into the documentation as a versionid.
# use perl to avoid inconsistent behaviour of echo '\v'
in puzzles do perl -e 'print "\n\\versionid Simon Tatham'\''s Portable Puzzle Collection, version $$ARGV[0]\n"' $(Version) >> puzzles.but
in puzzles do perl -e 'print "\n\\versionid Simon Tatham'\''s Portable Puzzle Collection, version $$ARGV[0]\n"' $(Version) >> devel.but
# Write out a version.h that contains the real version number.
in puzzles do echo '/* Generated by automated build script */' > version.h
in puzzles do echo '$#define VER "Version $(Version)"' >> version.h
# And do the same substitution in the OS X metadata. (This is a bit
# icky in principle because it presumes that my version numbers don't
# need XML escaping, but frankly, if they ever do then I should fix
# them!)
in puzzles do perl -i -pe 's/Unidentified build/$(Version)/' osx-info.plist
# First build some local binaries, to run the icon build.
in puzzles do perl mkfiles.pl -U
in puzzles do make
# Now build the screenshots and icons.
in puzzles/icons do xvfb-run -s "-screen 0 1024x768x24" make web winicons gtkicons
# Destroy the local binaries and autoconf detritus, mostly to avoid
# wasting network bandwidth by transferring them to the delegate
# servers.
in puzzles do make distclean
# Re-run mkfiles.pl now that it knows the icons are there.
in puzzles do perl mkfiles.pl
# Rebuild the configure script.
in puzzles do ./mkauto.sh
# Build the OS X .dmg archive.
delegate osx
in puzzles do make -f Makefile.osx clean
in puzzles do make -f Makefile.osx release VER=-DVER=$(Version)
return puzzles/Puzzles.dmg
enddelegate
# Build the Windows binaries and installer, and the CHM file.
in puzzles do make -f Makefile.doc clean
in puzzles do make -f Makefile.doc chm
in puzzles do make -f Makefile.doc # build help file 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
# Ignore the poorly controlled return value from HHC, and instead
# just test that the output file was generated.
in puzzles with htmlhelp do/win hhc puzzles.hhp & type puzzles.chm >nul
# 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)
# Code-sign the binaries, if the local bob config provides a script
# to do so. We assume here that the script accepts an -i option to
# provide a 'more info' URL, and an optional -n option to provide a
# program name, and that it can take multiple .exe filename
# arguments and sign them all in place.
ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i http://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 http://www.chiark.greenend.org.uk/~sgtatham/puzzles/ -n "Simon Tatham's Portable Puzzle Collection Installer" puzzles.msi Output/installer.exe
return puzzles/puzzles.chm
return puzzles/*.exe
return puzzles/Output/installer.exe
return puzzles/puzzles.msi
enddelegate
in puzzles do chmod +x *.exe
# Build the Pocket PC binaries and CAB.
#
# NOTE: This part of the build script requires the Windows delegate
# server to have the cabwiz program on its PATH. This will
# typically be at
#
# C:\Program Files\Windows CE Tools\WCE420\POCKET PC 2003\Tools
#
# but it might not be if you've installed it somewhere else, or
# have a different version.
#
# NOTE ALSO: This part of the build is commented out, for the
# moment, because cabwiz does unhelpful things when run from within
# a bob delegate process (or, more generally, when run from any
# terminal-based remote login to a Windows machine, including
# Cygwin opensshd and Windows Telnet). The symptom is that cabwiz
# just beeps and sits there. Until I figure out how to build the
# .cab from an automated process (and I'm willing to consider silly
# approaches such as a third-party CAB generator), I don't think I
# can sensibly enable this build.
#in puzzles do perl wceinf.pl gamedesc.txt > puzzles.inf
#delegate windows
# in puzzles do cmd /c 'wcearmv4 & nmake -f Makefile.wce clean'
# in puzzles do cmd /c 'wcearmv4 & nmake -f Makefile.wce VER=-DVER=$(Version)'
# # Nasty piece of sh here which saves the return code from cabwiz,
# # outputs its errors and/or warnings, and then propagates the
# # return code back to bob. If only cabwiz could output to
# # standard error LIKE EVERY OTHER COMMAND-LINE UTILITY IN THE
# # WORLD, I wouldn't have to do this.
# in puzzles do cat puzzles.inf
# in puzzles do cmd /c 'wcearmv4 & bash -c cabwiz puzzles.inf /err cabwiz.err /cpu ARMV4'; a=$$?; cat cabwiz.err; exit $$a
# return puzzles/puzzles.armv4.cab
#enddelegate
# Build the help file and the HTML docs.
in puzzles do make -f Makefile.doc clean # remove CHM-target HTML
in puzzles do make -f Makefile.doc # and rebuild help file...
in puzzles do mkdir doc
in puzzles do mkdir devel
in puzzles/doc do halibut --html -Chtml-contents-filename:index.html -Chtml-index-filename:indexpage.html -Chtml-template-filename:%k.html -Chtml-template-fragment:%k ../puzzles.but
in puzzles/devel do halibut --html -Chtml-contents-filename:index.html -Chtml-index-filename:indexpage.html -Chtml-template-filename:%k.html -Chtml-template-fragment:%k ../devel.but
# Move the deliver-worthy Windows binaries (those specified in
# gamedesc.txt, which is generated by mkfiles.pl and helpfully
# excludes the command-line auxiliary utilities such as solosolver,
# and nullgame.exe) into a subdirectory for easy access.
in puzzles do mkdir winbin
in puzzles do mv `cut -f2 -d: gamedesc.txt` winbin
# Make a zip file of the Windows binaries and help files.
in puzzles do zip -j puzzles.zip winbin/*.exe puzzles.chm puzzles.hlp puzzles.cnt
# Create the source archive. (That writes the archive into the
# _parent_ directory, so be careful when we deliver it.)
in puzzles do ./makedist.sh $(Version)
# Build the autogenerated pieces of the main web page.
in puzzles do perl webpage.pl
ifneq "$(JAVA_UNFINISHED)" "" in puzzles do perl -i~ -pe 'print "!srcdir unfinished/\n" if /!srcdir icons/' Recipe
ifneq "$(JAVA_UNFINISHED)" "" in puzzles do ln -s unfinished/group.R .
ifneq "$(JAVA_UNFINISHED)" "" in puzzles do perl mkfiles.pl
# Build the Java applets.
delegate nestedvm
in puzzles do make -f Makefile.nestedvm NESTEDVM="$$NESTEDVM" VER=-DVER=$(Version)
return puzzles/*.jar
enddelegate
# Build the Javascript applets. Since my master build machine doesn't
# have the right dependencies installed for Emscripten, I do this by a
# delegation.
in puzzles do mkdir js # so we can tell output .js files from emcc*.js
delegate emscripten
in puzzles do make -f Makefile.emcc OUTPREFIX=js/ clean
in puzzles do make -f Makefile.emcc OUTPREFIX=js/
return puzzles/js/*.js
enddelegate
# 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 $@
deliver puzzles/winbin/*.exe $@
deliver puzzles/.htaccess $@
deliver puzzles/doc/*.html doc/$@
deliver puzzles/devel/*.html devel/$@
deliver puzzles/Puzzles.dmg $@
deliver puzzles/puzzles.chm $@
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/html/*.html html/$@
deliver puzzles/html/*.pl html/$@
deliver puzzles/wwwspans.html $@
deliver puzzles/wwwlinks.html $@
# deliver puzzles/puzzles.armv4.cab $@ # (not built at the moment)
# This one isn't in the puzzles subdir, because makedist.sh left it
# one level up.
deliver puzzles*.tar.gz $@

View file

@ -0,0 +1,70 @@
Useful checklists
=================
Things to remember when adding a new puzzle
-------------------------------------------
Write the source file for the new puzzle (duhh).
Create a .R file for it which:
- defines a <puzzle>_EXTRA symbol for it if it requires auxiliary
object files (make sure that symbol doesn't contain the icon)
- adds it to the `ALL' definition, to ensure it is compiled into
the OS X binary
- adds it as a GTK build target, with the optional GTK icon
- adds it as a Windows build target, with the optional resource
file
- adds auxiliary solver binaries if any
- adds it to $(GAMES) in both the automake and GTK makefiles, for
`make install'
- adds it to list.c for the OS X binary
- adds it to gamedesc.txt, with its Windows executable name, display
name, and slightly longer description.
If the puzzle is by a new author, modify the copyright notice in
LICENCE and in puzzles.but. (Also in index.html, but that's listed
below under website changes.)
Double-check that the game structure name in the source file has
been renamed from `nullgame', so that it'll work on OS X. Actually
compiling it on OS X would be a good way to check this, if
convenient.
Add a documentation section in puzzles.but.
Make sure there's a Windows help topic name defined in puzzles.but,
and that it's referenced by the help topic field in the game
structure in the source file.
Check that REQUIRE_RBUTTON and/or REQUIRE_NUMPAD are set as
appropriate.
Add the new Unix binary name, and the names of any auxiliary solver
binaries, to .gitignore.
Write an instructions fragment for the webified puzzle pages, as
html/<puzzlename>.html .
Make a screenshot:
- create an appropriate save file in `icons'
- add the puzzle name to icons/Makefile
- set up a REDO property in icons/Makefile if the screenshot wants
to display a move halfway through an animation
- set up a CROP property in icons/Makefile if the icon wants to be
a sub-rectangle of the whole screenshot
Don't forget to `git add' the new source file, the new .R file and the
save file in `icons', the new .html file, and any other new files that
might have been involved.
Check in!
Put the puzzle on the web:
- run puzzlesnap.sh
- adjust the copyright in index-mid.html if the puzzle is by a new
author
- check that the new puzzle has appeared on the staging web page
- test both Windows binary links, the docs link, the Javascript
version and the Java version
- run webupdate
- test all those things once more on the live website

View file

@ -0,0 +1,25 @@
This software is copyright (c) 2004-2014 Simon Tatham.
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,617 @@
/*
* PuzzleApplet.java: NestedVM applet for the puzzle collection
*/
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.*;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.Timer;
import java.util.List;
import org.ibex.nestedvm.Runtime;
public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
private static final long serialVersionUID = 1L;
private static final int CFG_SETTINGS = 0, CFG_SEED = 1, CFG_DESC = 2,
LEFT_BUTTON = 0x0200, MIDDLE_BUTTON = 0x201, RIGHT_BUTTON = 0x202,
LEFT_DRAG = 0x203, MIDDLE_DRAG = 0x204, RIGHT_DRAG = 0x205,
LEFT_RELEASE = 0x206, CURSOR_UP = 0x209, CURSOR_DOWN = 0x20a,
CURSOR_LEFT = 0x20b, CURSOR_RIGHT = 0x20c, MOD_CTRL = 0x1000,
MOD_SHFT = 0x2000, MOD_NUM_KEYPAD = 0x4000, ALIGN_VCENTRE = 0x100,
ALIGN_HCENTRE = 0x001, ALIGN_HRIGHT = 0x002, C_STRING = 0,
C_CHOICES = 1, C_BOOLEAN = 2;
private JFrame mainWindow;
private JMenu typeMenu;
private JMenuItem solveCommand;
private Color[] colors;
private JLabel statusBar;
private PuzzlePanel pp;
private Runtime runtime;
private String[] puzzle_args;
private Graphics2D gg;
private Timer timer;
private int xarg1, xarg2, xarg3;
private int[] xPoints, yPoints;
private BufferedImage[] blitters = new BufferedImage[512];
private ConfigDialog dlg;
static {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void init() {
try {
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
runtime = (Runtime) Class.forName("PuzzleEngine").newInstance();
runtime.setCallJavaCB(this);
JMenuBar menubar = new JMenuBar();
JMenu jm;
menubar.add(jm = new JMenu("Game"));
addMenuItemWithKey(jm, "New", 'n');
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');
jm.addSeparator();
solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
solveCommand.setEnabled(false);
if (mainWindow != null) {
jm.addSeparator();
addMenuItemWithKey(jm, "Exit", 'q');
}
menubar.add(typeMenu = new JMenu("Type"));
typeMenu.setVisible(false);
menubar.add(jm = new JMenu("Help"));
addMenuItemCallback(jm, "About", "jcallback_about_event");
setJMenuBar(menubar);
cp.add(pp = new PuzzlePanel(), BorderLayout.CENTER);
pp.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
int key = -1;
int shift = e.isShiftDown() ? MOD_SHFT : 0;
int ctrl = e.isControlDown() ? MOD_CTRL : 0;
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
case KeyEvent.VK_KP_LEFT:
key = shift | ctrl | CURSOR_LEFT;
break;
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_KP_RIGHT:
key = shift | ctrl | CURSOR_RIGHT;
break;
case KeyEvent.VK_UP:
case KeyEvent.VK_KP_UP:
key = shift | ctrl | CURSOR_UP;
break;
case KeyEvent.VK_DOWN:
case KeyEvent.VK_KP_DOWN:
key = shift | ctrl | CURSOR_DOWN;
break;
case KeyEvent.VK_PAGE_UP:
key = shift | ctrl | MOD_NUM_KEYPAD | '9';
break;
case KeyEvent.VK_PAGE_DOWN:
key = shift | ctrl | MOD_NUM_KEYPAD | '3';
break;
case KeyEvent.VK_HOME:
key = shift | ctrl | MOD_NUM_KEYPAD | '7';
break;
case KeyEvent.VK_END:
key = shift | ctrl | MOD_NUM_KEYPAD | '1';
break;
default:
if (e.getKeyCode() >= KeyEvent.VK_NUMPAD0 && e.getKeyCode() <=KeyEvent.VK_NUMPAD9) {
key = MOD_NUM_KEYPAD | (e.getKeyCode() - KeyEvent.VK_NUMPAD0+'0');
}
break;
}
if (key != -1) {
runtimeCall("jcallback_key_event", new int[] {0, 0, key});
}
}
public void keyTyped(KeyEvent e) {
runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()});
}
});
pp.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
mousePressedReleased(e, true);
}
public void mousePressed(MouseEvent e) {
pp.requestFocus();
mousePressedReleased(e, false);
}
private void mousePressedReleased(MouseEvent e, boolean released) {
int button;
if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
button = MIDDLE_BUTTON;
else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
button = RIGHT_BUTTON;
else if ((e.getModifiers() & (InputEvent.BUTTON1_MASK)) != 0)
button = LEFT_BUTTON;
else
return;
if (released)
button += LEFT_RELEASE - LEFT_BUTTON;
runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
}
});
pp.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
int button;
if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
button = MIDDLE_DRAG;
else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
button = RIGHT_DRAG;
else
button = LEFT_DRAG;
runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
}
});
pp.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
handleResized();
}
});
pp.setFocusable(true);
pp.requestFocus();
timer = new Timer(20, new ActionListener() {
public void actionPerformed(ActionEvent e) {
runtimeCall("jcallback_timer_func", new int[0]);
}
});
String gameid;
try {
gameid = getParameter("game_id");
} catch (java.lang.NullPointerException ex) {
gameid = null;
}
if (gameid == null) {
puzzle_args = null;
} else {
puzzle_args = new String[2];
puzzle_args[0] = "puzzle";
puzzle_args[1] = gameid;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
runtime.start(puzzle_args);
runtime.execute();
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void destroy() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
runtime.execute();
if (mainWindow != null) {
mainWindow.dispose();
System.exit(0);
}
}
});
}
protected void handleResized() {
pp.createBackBuffer(pp.getWidth(), pp.getHeight(), colors[0]);
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});
}
private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback) {
return addMenuItemCallback(jm, name, callback, new int[0]);
}
private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int[] args) {
JMenuItem jmi;
if (jm == typeMenu)
typeMenu.add(jmi = new JCheckBoxMenuItem(name));
else
jm.add(jmi = new JMenuItem(name));
jmi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
runtimeCall(callback, args);
}
});
return jmi;
}
protected void runtimeCall(String func, int[] args) {
if (runtimeCallWithResult(func, args) == 42 && mainWindow != null) {
destroy();
}
}
protected int runtimeCallWithResult(String func, int[] args) {
try {
return runtime.call(func, args);
} catch (Runtime.CallException ex) {
ex.printStackTrace();
return 42;
}
}
private void buildConfigureMenuItem() {
if (typeMenu.isVisible()) {
typeMenu.addSeparator();
} else {
typeMenu.setVisible(true);
}
addMenuItemCallback(typeMenu, "Custom...", "jcallback_config_event", CFG_SETTINGS);
}
private void addTypeItem(String name, final int ptrGameParams) {
typeMenu.setVisible(true);
addMenuItemCallback(typeMenu, name, "jcallback_preset_event", ptrGameParams);
}
public int call(int cmd, int arg1, int arg2, int arg3) {
try {
switch(cmd) {
case 0: // initialize
if (mainWindow != null) mainWindow.setTitle(runtime.cstring(arg1));
if ((arg2 & 1) != 0) buildConfigureMenuItem();
if ((arg2 & 2) != 0) addStatusBar();
if ((arg2 & 4) != 0) solveCommand.setEnabled(true);
colors = new Color[arg3];
return 0;
case 1: // Type menu item
addTypeItem(runtime.cstring(arg1), arg2);
return 0;
case 2: // MessageBox
JOptionPane.showMessageDialog(this, runtime.cstring(arg2), runtime.cstring(arg1), arg3 == 0 ? JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE);
return 0;
case 3: // Resize
pp.setPreferredSize(new Dimension(arg1, arg2));
if (mainWindow != null) mainWindow.pack();
handleResized();
if (mainWindow != null) mainWindow.setVisible(true);
return 0;
case 4: // drawing tasks
switch(arg1) {
case 0:
String text = runtime.cstring(arg2);
if (text.equals("")) text = " ";
statusBar.setText(text);
break;
case 1:
gg = pp.backBuffer.createGraphics();
if (arg2 != 0 || arg3 != 0 ||
arg2 + xarg2 != getWidth() ||
arg3 + xarg3 != getHeight()) {
int left = arg2, right = arg2 + xarg2;
int top = arg3, bottom = arg3 + xarg3;
int width = getWidth(), height = getHeight();
gg.setColor(colors != null ? colors[0] : Color.black);
gg.fillRect(0, 0, left, height);
gg.fillRect(right, 0, width-right, height);
gg.fillRect(0, 0, width, top);
gg.fillRect(0, bottom, width, height-bottom);
gg.setClip(left, top, right-left, bottom-top);
}
break;
case 2: gg.dispose(); pp.repaint(); break;
case 3: gg.setClip(arg2, arg3, xarg1, xarg2); break;
case 4:
if (arg2 == 0 && arg3 == 0) {
gg.setClip(0, 0, getWidth(), getHeight());
} else {
gg.setClip(arg2, arg3, getWidth()-2*arg2, getHeight()-2*arg3);
}
break;
case 5:
gg.setColor(colors[xarg3]);
gg.fillRect(arg2, arg3, xarg1, xarg2);
break;
case 6:
gg.setColor(colors[xarg3]);
gg.drawLine(arg2, arg3, xarg1, xarg2);
break;
case 7:
xPoints = new int[arg2];
yPoints = new int[arg2];
break;
case 8:
if (arg3 != -1) {
gg.setColor(colors[arg3]);
gg.fillPolygon(xPoints, yPoints, xPoints.length);
}
gg.setColor(colors[arg2]);
gg.drawPolygon(xPoints, yPoints, xPoints.length);
break;
case 9:
if (arg3 != -1) {
gg.setColor(colors[arg3]);
gg.fillOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
}
gg.setColor(colors[arg2]);
gg.drawOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
break;
case 10:
for(int i=0; i<blitters.length; i++) {
if (blitters[i] == null) {
blitters[i] = new BufferedImage(arg2, arg3, BufferedImage.TYPE_3BYTE_BGR);
return i;
}
}
throw new RuntimeException("No free blitter found!");
case 11: blitters[arg2] = null; break;
case 12:
timer.start(); break;
case 13:
timer.stop(); break;
}
return 0;
case 5: // more arguments
xarg1 = arg1;
xarg2 = arg2;
xarg3 = arg3;
return 0;
case 6: // polygon vertex
xPoints[arg1]=arg2;
yPoints[arg1]=arg3;
return 0;
case 7: // string
gg.setColor(colors[arg2]);
{
String text = runtime.utfstring(arg3);
Font ft = new Font((xarg3 & 0x10) != 0 ? "Monospaced" : "Dialog",
Font.PLAIN, 100);
int height100 = this.getFontMetrics(ft).getHeight();
ft = ft.deriveFont(arg1 * 100 / (float)height100);
FontMetrics fm = this.getFontMetrics(ft);
int asc = fm.getAscent(), desc = fm.getDescent();
if ((xarg3 & ALIGN_VCENTRE) != 0)
xarg2 += asc - (asc+desc)/2;
int wid = fm.stringWidth(text);
if ((xarg3 & ALIGN_HCENTRE) != 0)
xarg1 -= wid / 2;
else if ((xarg3 & ALIGN_HRIGHT) != 0)
xarg1 -= wid;
gg.setFont(ft);
gg.drawString(text, xarg1, xarg2);
}
return 0;
case 8: // blitter_save
Graphics g2 = blitters[arg1].createGraphics();
g2.drawImage(pp.backBuffer, 0, 0, blitters[arg1].getWidth(), blitters[arg1].getHeight(),
arg2, arg3, arg2 + blitters[arg1].getWidth(), arg3 + blitters[arg1].getHeight(), this);
g2.dispose();
return 0;
case 9: // blitter_load
gg.drawImage(blitters[arg1], arg2, arg3, this);
return 0;
case 10: // dialog_init
dlg= new ConfigDialog(this, runtime.cstring(arg1));
return 0;
case 11: // dialog_add_control
{
int sval_ptr = arg1;
int ival = arg2;
int ptr = xarg1;
int type=xarg2;
String name = runtime.cstring(xarg3);
switch(type) {
case C_STRING:
dlg.addTextBox(ptr, name, runtime.cstring(sval_ptr));
break;
case C_BOOLEAN:
dlg.addCheckBox(ptr, name, ival != 0);
break;
case C_CHOICES:
dlg.addComboBox(ptr, name, runtime.cstring(sval_ptr), ival);
}
}
return 0;
case 12:
dlg.finish();
dlg = null;
return 0;
case 13: // tick a menu item
if (arg1 < 0) arg1 = typeMenu.getItemCount() - 1;
for (int i = 0; i < typeMenu.getItemCount(); i++) {
if (typeMenu.getMenuComponent(i) instanceof JCheckBoxMenuItem) {
((JCheckBoxMenuItem)typeMenu.getMenuComponent(i)).setSelected(arg1 == i);
}
}
return 0;
default:
if (cmd >= 1024 && cmd < 2048) { // palette
colors[cmd-1024] = new Color(arg1, arg2, arg3);
}
if (cmd == 1024) {
pp.setBackground(colors[0]);
if (statusBar != null) statusBar.setBackground(colors[0]);
this.setBackground(colors[0]);
}
return 0;
}
} catch (Throwable ex) {
ex.printStackTrace();
System.exit(-1);
return 0;
}
}
private void addStatusBar() {
statusBar = new JLabel("test");
statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));
getContentPane().add(BorderLayout.SOUTH,statusBar);
}
// Standalone runner
public static void main(String[] args) {
final PuzzleApplet a = new PuzzleApplet();
JFrame jf = new JFrame("Loading...");
jf.getContentPane().setLayout(new BorderLayout());
jf.getContentPane().add(a, BorderLayout.CENTER);
a.mainWindow=jf;
a.init();
a.start();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
a.stop();
a.destroy();
}
});
jf.setVisible(true);
}
public static class PuzzlePanel extends JPanel {
private static final long serialVersionUID = 1L;
protected BufferedImage backBuffer;
public PuzzlePanel() {
setPreferredSize(new Dimension(100,100));
createBackBuffer(100,100, Color.black);
}
public void createBackBuffer(int w, int h, Color bg) {
if (w > 0 && h > 0) {
backBuffer = new BufferedImage(w,h, BufferedImage.TYPE_3BYTE_BGR);
Graphics g = backBuffer.createGraphics();
g.setColor(bg);
g.fillRect(0, 0, w, h);
g.dispose();
}
}
protected void paintComponent(Graphics g) {
g.drawImage(backBuffer, 0, 0, this);
}
}
public static class ConfigComponent {
public int type;
public int configItemPointer;
public JComponent component;
public ConfigComponent(int type, int configItemPointer, JComponent component) {
this.type = type;
this.configItemPointer = configItemPointer;
this.component = component;
}
}
public class ConfigDialog extends JDialog {
private GridBagConstraints gbcLeft = new GridBagConstraints(
GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1,
0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(0, 0, 0, 0), 0, 0);
private GridBagConstraints gbcRight = new GridBagConstraints(
GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
GridBagConstraints.REMAINDER, 1, 1.0, 0,
GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5), 0, 0);
private GridBagConstraints gbcBottom = new GridBagConstraints(
GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
GridBagConstraints.REMAINDER, GridBagConstraints.REMAINDER,
1.0, 1.0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
private static final long serialVersionUID = 1L;
private List components = new ArrayList();
public ConfigDialog(JApplet parent, String title) {
super(JOptionPane.getFrameForComponent(parent), title, true);
getContentPane().setLayout(new GridBagLayout());
}
public void addTextBox(int ptr, String name, String value) {
getContentPane().add(new JLabel(name), gbcLeft);
JComponent c = new JTextField(value, 25);
getContentPane().add(c, gbcRight);
components.add(new ConfigComponent(C_STRING, ptr, c));
}
public void addCheckBox(int ptr, String name, boolean selected) {
JComponent c = new JCheckBox(name, selected);
getContentPane().add(c, gbcRight);
components.add(new ConfigComponent(C_BOOLEAN, ptr, c));
}
public void addComboBox(int ptr, String name, String values, int selected) {
getContentPane().add(new JLabel(name), gbcLeft);
StringTokenizer st = new StringTokenizer(values.substring(1), values.substring(0,1));
JComboBox c = new JComboBox();
c.setEditable(false);
while(st.hasMoreTokens())
c.addItem(st.nextToken());
c.setSelectedIndex(selected);
getContentPane().add(c, gbcRight);
components.add(new ConfigComponent(C_CHOICES, ptr, c));
}
public void finish() {
JPanel buttons = new JPanel(new GridLayout(1, 2, 5, 5));
getContentPane().add(buttons, gbcBottom);
JButton b;
buttons.add(b=new JButton("OK"));
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
save();
dispose();
}
});
getRootPane().setDefaultButton(b);
buttons.add(b=new JButton("Cancel"));
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private void save() {
for (int i = 0; i < components.size(); i++) {
ConfigComponent cc = (ConfigComponent) components.get(i);
switch(cc.type) {
case C_STRING:
JTextField jtf = (JTextField)cc.component;
runtimeCall("jcallback_config_set_string", new int[] {cc.configItemPointer, runtime.strdup(jtf.getText())});
break;
case C_BOOLEAN:
JCheckBox jcb = (JCheckBox)cc.component;
runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcb.isSelected()?1:0});
break;
case C_CHOICES:
JComboBox jcm = (JComboBox)cc.component;
runtimeCall("jcallback_config_set_choice", new int[] {cc.configItemPointer, jcm.getSelectedIndex()});
break;
}
}
runtimeCall("jcallback_config_ok", new int[0]);
}
}
}

View file

@ -0,0 +1,54 @@
This is the README accompanying the source code to Simon Tatham's
puzzle collection. The collection's web site is at
<http://www.chiark.greenend.org.uk/~sgtatham/puzzles/>.
If you've obtained the source code by downloading a .tar.gz archive
from the Puzzles web site, you should find several Makefiles in the
source code. However, if you've checked the source code out from the
Puzzles git repository, you won't find the Makefiles: they're
automatically generated by `mkfiles.pl', so run that to create them.
The Makefiles include:
- `Makefile.am', together with the static `configure.ac', is intended
as input to automake. Run `mkauto.sh' to turn these into a
configure script and Makefile.in, after which you can then run
`./configure' to create an actual Unix Makefile.
- `Makefile.vc' should work under MS Visual C++ on Windows. Run
'nmake /f Makefile.vc' in a Visual Studio command prompt.
- `Makefile.cyg' should work under Cygwin / MinGW. With appropriate
tweaks and setting of TOOLPATH, it should work for both compiling
on Windows and cross-compiling on Unix.
- `Makefile.osx' should work under Mac OS X, provided the Xcode
tools are installed. It builds a single monolithic OS X
application capable of running any of the puzzles, or even more
than one of them at a time.
- `Makefile.wce' should work under MS eMbedded Visual C++ on
Windows and the Pocket PC SDK; it builds Pocket PC binaries.
Many of these Makefiles build a program called `nullgame' in
addition to the actual game binaries. This program doesn't do
anything; it's just a template for people to start from when adding
a new game to the collection, and it's compiled every time to ensure
that it _does_ compile and link successfully (because otherwise it
wouldn't be much use as a template). Once it's built, you can run it
if you really want to (but it's very boring), and then you should
ignore it.
DO NOT EDIT THE MAKEFILES DIRECTLY, if you plan to send any changes
back to the maintainer. The makefiles are generated automatically by
the Perl script `mkfiles.pl' from the file `Recipe' and the various
.R files. If you need to change the makefiles as part of a patch,
you should change Recipe, *.R, and/or mkfiles.pl.
The manual is provided in Windows Help format for the Windows build;
in text format for anyone who needs it; and in HTML for the Mac OS X
application and for the web site. It is generated from a Halibut
source file (puzzles.but), which is the preferred form for
modification. To generate the manual in other formats, rebuild it,
or learn about Halibut, visit the Halibut website at
<http://www.chiark.greenend.org.uk/~sgtatham/halibut/>.

157
apps/plugins/puzzles/Recipe Normal file
View file

@ -0,0 +1,157 @@
# -*- makefile -*-
#
# This file describes which puzzle binaries are made up from which
# object and resource files. It is processed into the various
# Makefiles by means of a Perl script. Makefile changes should
# really be made by editing this file and/or the Perl script, not
# by editing the actual Makefiles.
!name puzzles
!makefile gtk Makefile.gtk
!makefile am Makefile.am
!makefile vc Makefile.vc
!makefile wce Makefile.wce
!makefile cygwin Makefile.cyg
!makefile osx Makefile.osx
!makefile gnustep Makefile.gnustep
!makefile nestedvm Makefile.nestedvm
!makefile emcc Makefile.emcc
!srcdir icons/
WINDOWS_COMMON = printing
+ user32.lib gdi32.lib comctl32.lib comdlg32.lib winspool.lib
WINDOWS = windows WINDOWS_COMMON
COMMON = midend drawing misc malloc random version
GTK = gtk printing ps
# Objects needed for auxiliary command-line programs.
STANDALONE = nullfe random misc malloc
ALL = list
# First half of list.c.
!begin >list.c
/*
* list.c: List of pointers to puzzle structures, for monolithic
* platforms.
*
* This file is automatically generated by mkfiles.pl. Do not edit
* it directly, or the changes will be lost next time mkfiles.pl runs.
* Instead, edit Recipe and/or its *.R subfiles.
*/
#include "puzzles.h"
#define GAMELIST(A) \
!end
# Now each .R file adds part of the macro definition of GAMELIST to list.c.
!include *.R
# Then we finish up list.c as follows:
!begin >list.c
#define DECL(x) extern const game x;
#define REF(x) &x,
GAMELIST(DECL)
const game *gamelist[] = { GAMELIST(REF) };
const int gamecount = lenof(gamelist);
!end
# Unix standalone application for special-purpose obfuscation.
obfusc : [U] obfusc STANDALONE
puzzles : [G] windows[COMBINED] WINDOWS_COMMON COMMON ALL noicon.res
# Mac OS X unified application containing all the puzzles.
Puzzles : [MX] osx osx.icns osx-info.plist COMMON ALL
# For OS X, we must create the online help and include it in the
# application bundle.) Also we add -DCOMBINED to the compiler flags
# so as to inform the code that we're building a single binary for
# all the puzzles. Then I've also got some code in here to build a
# distributable .dmg disk image.
!begin osx
Puzzles_extra = Puzzles.app/Contents/Resources/Help/index.html
Puzzles.app/Contents/Resources/Help/index.html: \
Puzzles.app/Contents/Resources/Help osx-help.but puzzles.but
cd Puzzles.app/Contents/Resources/Help; \
halibut --html ../../../../osx-help.but ../../../../puzzles.but
Puzzles.app/Contents/Resources/Help: Puzzles.app/Contents/Resources
mkdir -p Puzzles.app/Contents/Resources/Help
release: Puzzles.dmg
Puzzles.dmg: Puzzles
rm -f raw.dmg
hdiutil create -megabytes 5 -layout NONE raw.dmg
hdid -nomount raw.dmg > devicename
newfs_hfs -v "Simon Tatham's Puzzle Collection" `cat devicename`
hdiutil eject `cat devicename`
hdid raw.dmg | cut -f1 -d' ' > devicename
cp -R Puzzles.app /Volumes/"Simon Tatham's Puzzle Collection"
hdiutil eject `cat devicename`
rm -f Puzzles.dmg
hdiutil convert -format UDCO raw.dmg -o Puzzles.dmg
rm -f raw.dmg devicename
!end
!begin am
bin_PROGRAMS = $(GAMES)
!end
!begin am_begin
GAMES =
!end
# make install for Unix.
!begin gtk
install:
for i in $(GAMES); do \
$(INSTALL_PROGRAM) -m 755 $(BINPREFIX)$$i $(DESTDIR)$(gamesdir)/$(BINPREFIX)$$i \
|| exit 1; \
done
!end
!begin nestedvm
.PRECIOUS: %.class
%.class: %.mips
java -cp $(NESTEDVM)/build:$(NESTEDVM)/upstream/build/classgen/build \
org.ibex.nestedvm.Compiler -outformat class -d . \
PuzzleEngine $<
mv PuzzleEngine.class $@
org:
mkdir -p org/ibex/nestedvm/util
cp $(NESTEDVM)/build/org/ibex/nestedvm/Registers.class org/ibex/nestedvm
cp $(NESTEDVM)/build/org/ibex/nestedvm/UsermodeConstants.class org/ibex/nestedvm
cp $(NESTEDVM)/build/org/ibex/nestedvm/Runtime*.class org/ibex/nestedvm
cp $(NESTEDVM)/build/org/ibex/nestedvm/util/Platform*.class org/ibex/nestedvm/util
cp $(NESTEDVM)/build/org/ibex/nestedvm/util/Seekable*.class org/ibex/nestedvm/util
echo "Main-Class: PuzzleApplet" >applet.manifest
PuzzleApplet.class: PuzzleApplet.java org
javac -source 1.3 -target 1.3 PuzzleApplet.java
%.jar: %.class PuzzleApplet.class org
mv $< PuzzleEngine.class
jar cfm $@ applet.manifest PuzzleEngine.class PuzzleApplet*.class org
echo '<applet archive="'$@'" code="PuzzleApplet" width="700" height="500"></applet>' >$*.html
mv PuzzleEngine.class $<
!end
# A benchmarking and testing target for the GTK puzzles.
!begin gtk
test: benchmark.html benchmark.txt
benchmark.html: benchmark.txt benchmark.pl
./benchmark.pl benchmark.txt > $@
benchmark.txt: benchmark.sh $(GAMES)
./benchmark.sh > $@
!end
!begin am
test: benchmark.html benchmark.txt
benchmark.html: benchmark.txt benchmark.pl
./benchmark.pl benchmark.txt > $@
benchmark.txt: benchmark.sh $(GAMES)
./benchmark.sh > $@
!end

View file

@ -0,0 +1,26 @@
rockbox.c
rbwrappers.c
combi.c
divvy.c
drawing.c
dsf.c
findloop.c
grid.c
latin.c
laydomino.c
loopgen.c
malloc.c
maxflow.c
midend.c
misc.c
penrose.c
printing.c
random.c
tdq.c
tree234.c
version.c
#ifdef COMBINED
list.c
#endif

197
apps/plugins/puzzles/benchmark.pl Executable file
View file

@ -0,0 +1,197 @@
#!/usr/bin/perl
# Process the raw output from benchmark.sh into Javascript-ified HTML.
use strict;
use warnings;
my @presets = ();
my %presets = ();
my $maxval = 0;
while (<>) {
chomp;
if (/^(.*)(#.*): ([\d\.]+)$/) {
push @presets, $1 unless defined $presets{$1};
push @{$presets{$1}}, $3;
$maxval = $3 if $maxval < $3;
}
}
print <<EOF;
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
<title>Puzzle generation-time benchmarks</title>
<script type="text/javascript">
//<![CDATA[
function choose_scale_ticks(scale) {
var nscale = 1, j = 0, factors = [2,2.5,2];
while (scale / nscale > 20) {
nscale *= factors[j];
j = (j+1) % factors.length;
}
return nscale;
}
function initPlots() {
var canvases = document.getElementsByTagName('canvas');
for (var i = 0; i < canvases.length; i++) {
var canvas = canvases[i];
var scale = eval(canvas.getAttribute("data-scale"));
var add = 20.5, mult = (canvas.width - 2*add) / scale;
var data = eval(canvas.getAttribute("data-points"));
var ctx = canvas.getContext('2d');
ctx.lineWidth = '1px';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = ctx.fillStyle = '#000000';
if (data === "scale") {
// Draw scale.
ctx.font = "16px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "alphabetic";
var nscale = choose_scale_ticks(scale);
for (var x = 0; x <= scale; x += nscale) {
ctx.beginPath();
ctx.moveTo(add+mult*x, canvas.height);
ctx.lineTo(add+mult*x, canvas.height - 3);
ctx.stroke();
ctx.fillText(x + "s", add+mult*x, canvas.height - 6);
}
} else {
// Draw a box plot.
function quantile(x) {
var n = (data.length * x) | 0;
return (data[n-1] + data[n]) / 2;
}
var q1 = quantile(0.25), q2 = quantile(0.5), q3 = quantile(0.75);
var iqr = q3 - q1;
var top = 0.5, bot = canvas.height - 1.5, mid = (top+bot)/2;
var wlo = null, whi = null; // whisker ends
ctx.strokeStyle = '#bbbbbb';
var nscale = choose_scale_ticks(scale);
for (var x = 0; x <= scale; x += nscale) {
ctx.beginPath();
ctx.moveTo(add+mult*x, 0);
ctx.lineTo(add+mult*x, canvas.height);
ctx.stroke();
}
ctx.strokeStyle = '#000000';
for (var j in data) {
var x = data[j];
if (x >= q1 - 1.5 * iqr && x <= q3 + 1.5 * iqr) {
if (wlo === null || wlo > x)
wlo = x;
if (whi === null || whi < x)
whi = x;
} else {
ctx.beginPath();
ctx.arc(add+mult*x, mid, 2, 0, 2*Math.PI);
ctx.stroke();
if (x >= q1 - 3 * iqr && x <= q3 + 3 * iqr)
ctx.fill();
}
}
ctx.beginPath();
// Box
ctx.moveTo(add+mult*q1, top);
ctx.lineTo(add+mult*q3, top);
ctx.lineTo(add+mult*q3, bot);
ctx.lineTo(add+mult*q1, bot);
ctx.closePath();
// Line at median
ctx.moveTo(add+mult*q2, top);
ctx.lineTo(add+mult*q2, bot);
// Lower whisker
ctx.moveTo(add+mult*q1, mid);
ctx.lineTo(add+mult*wlo, mid);
ctx.moveTo(add+mult*wlo, top);
ctx.lineTo(add+mult*wlo, bot);
// Upper whisker
ctx.moveTo(add+mult*q3, mid);
ctx.lineTo(add+mult*whi, mid);
ctx.moveTo(add+mult*whi, top);
ctx.lineTo(add+mult*whi, bot);
ctx.stroke();
}
}
document.getElementById('sort_orig').onclick = function() {
sort(function(e) {
return parseFloat(e.getAttribute("data-index"));
});
};
document.getElementById('sort_median').onclick = function() {
sort(function(e) {
return -parseFloat(e.getAttribute("data-median"));
});
};
document.getElementById('sort_mean').onclick = function() {
sort(function(e) {
return -parseFloat(e.getAttribute("data-mean"));
});
};
}
function sort(keyfn) {
var rows = document.getElementsByTagName("tr");
var trs = [];
for (var i = 0; i < rows.length; i++)
trs.push(rows[i]);
trs.sort(function(a,b) {
var akey = keyfn(a);
var bkey = keyfn(b);
return akey < bkey ? -1 : akey > bkey ? +1 : 0;
});
var parent = trs[0].parentElement;
for (var i = 0; i < trs.length; i++)
parent.removeChild(trs[i]);
for (var i = 0; i < trs.length; i++)
parent.appendChild(trs[i]);
}
//]]>
</script>
</head>
<body onLoad="initPlots();">
<h1 align=center>Puzzle generation-time benchmarks</h1>
<p>Sort order:
<button id="sort_orig">Original</button>
<button id="sort_median">Median</button>
<button id="sort_mean">Mean</button>
<table>
<tr><th>Preset</th><td><canvas width=700 height=30 data-points='"scale"' data-scale="$maxval"></td></tr>
EOF
my $index = 0;
for my $preset (@presets) {
my @data = sort { $a <=> $b } @{$presets{$preset}};
my $median = ($#data % 2 ?
($data[($#data-1)/2]+$data[($#data+1)/2])/2 :
$data[$#data/2]);
my $mean = 0; map { $mean += $_ } @data; $mean /= @data;
print "<tr data-index=\"$index\" data-mean=\"$mean\" data-median=\"$median\"><td>", &escape($preset), "</td><td><canvas width=700 height=15 data-points=\"[";
print join ",", @data;
print "]\" data-scale=\"$maxval\"></td></tr>\n";
$index++;
}
print <<EOF;
</body>
</html>
EOF
sub escape {
my ($text) = @_;
$text =~ s/&/&amp;/g;
$text =~ s/</&lt;/g;
$text =~ s/>/&gt;/g;
return $text;
}

View file

@ -0,0 +1,27 @@
#!/bin/sh
# Run every puzzle in benchmarking mode, and generate a file of raw
# data that benchmark.pl will format into a web page.
# If any arguments are provided, use those as the list of games to
# benchmark. Otherwise, read the full list from gamedesc.txt.
if test $# = 0; then
set -- $(cut -f1 -d: < gamedesc.txt)
fi
failures=false
for game in "$@"; do
# Use 'env -i' to suppress any environment variables that might
# change the preset list for a puzzle (e.g. user-defined extras)
presets=$(env -i ./$game --list-presets | cut -f1 -d' ')
for preset in $presets; do
if ! env -i ./$game --test-solve --time-generation \
--generate 100 $preset;
then
echo "${game} ${preset} failed to generate" >&2
fi
done
done
if $failures; then exit 1; fi

View file

@ -0,0 +1,19 @@
# -*- makefile -*-
blackbox : [X] GTK COMMON blackbox blackbox-icon|no-icon
blackbox : [G] WINDOWS COMMON blackbox blackbox.res|noicon.res
ALL += blackbox[COMBINED]
!begin am gtk
GAMES += blackbox
!end
!begin >list.c
A(blackbox) \
!end
!begin >gamedesc.txt
blackbox:blackbox.exe:Black Box:Ball-finding puzzle:Find the hidden balls in the box by bouncing laser beams off them.
!end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
# -*- makefile -*-
BRIDGES_EXTRA = dsf findloop
bridges : [X] GTK COMMON bridges BRIDGES_EXTRA bridges-icon|no-icon
bridges : [G] WINDOWS COMMON bridges BRIDGES_EXTRA bridges.res|noicon.res
ALL += bridges[COMBINED] BRIDGES_EXTRA
!begin am gtk
GAMES += bridges
!end
!begin >list.c
A(bridges) \
!end
!begin >gamedesc.txt
bridges:bridges.exe:Bridges:Bridge-placing puzzle:Connect all the islands with a network of bridges.
!end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
\# 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

@ -0,0 +1,7 @@
/* Stylesheet for a Windows .CHM help file */
body { font-size: 75%; font-family: Verdana, Arial, Helvetica, Sans-Serif; }
h1 { font-weight: bold; font-size: 150%; }
h2 { font-weight: bold; font-size: 130%; }
h3 { font-weight: bold; font-size: 120%; }

View file

@ -0,0 +1,110 @@
#include "rbassert.h"
#include <string.h>
#include "puzzles.h"
/* horrific and doesn't check overflow. */
static long factx(long x, long y)
{
long acc = 1, i;
for (i = y; i <= x; i++)
acc *= i;
return acc;
}
void reset_combi(combi_ctx *combi)
{
int i;
combi->nleft = combi->total;
for (i = 0; i < combi->r; i++)
combi->a[i] = i;
}
combi_ctx *new_combi(int r, int n)
{
long nfr, nrf;
combi_ctx *combi;
assert(r <= n);
assert(n >= 1);
combi = snew(combi_ctx);
memset(combi, 0, sizeof(combi_ctx));
combi->r = r;
combi->n = n;
combi->a = snewn(r, int);
memset(combi->a, 0, r * sizeof(int));
nfr = factx(n, r+1);
nrf = factx(n-r, 1);
combi->total = (int)(nfr / nrf);
reset_combi(combi);
return combi;
}
/* returns NULL when we're done otherwise returns input. */
combi_ctx *next_combi(combi_ctx *combi)
{
int i = combi->r - 1, j;
if (combi->nleft == combi->total)
goto done;
else if (combi->nleft <= 0)
return NULL;
while (combi->a[i] == combi->n - combi->r + i)
i--;
combi->a[i] += 1;
for (j = i+1; j < combi->r; j++)
combi->a[j] = combi->a[i] + j - i;
done:
combi->nleft--;
return combi;
}
void free_combi(combi_ctx *combi)
{
sfree(combi->a);
sfree(combi);
}
/* compile this with:
* gcc -o combi.exe -DSTANDALONE_COMBI_TEST combi.c malloc.c
*/
#ifdef STANDALONE_COMBI_TEST
#include <stdio.h>
void fatal(char *fmt, ...)
{
abort();
}
int main(int argc, char *argv[])
{
combi_ctx *c;
int i, r, n;
if (argc < 3) {
fprintf(stderr, "Usage: combi R N\n");
exit(1);
}
r = atoi(argv[1]); n = atoi(argv[2]);
c = new_combi(r, n);
printf("combi %d of %d, %d elements.\n", c->r, c->n, c->total);
while (next_combi(c)) {
for (i = 0; i < c->r; i++) {
printf("%d ", c->a[i]);
}
printf("\n");
}
free_combi(c);
}
#endif

View file

@ -0,0 +1,85 @@
dnl Configure script for the Unix GTK build of puzzles.
AC_INIT([puzzles], [6.66], [anakin@pobox.com])
AC_CONFIG_SRCDIR([midend.c])
AM_INIT_AUTOMAKE([foreign])
AC_PROG_CC
AC_ARG_WITH([gtk],
[AS_HELP_STRING([--with-gtk=VER],
[specify GTK version to use (`2' or `3')])],
[gtk_version_desired="$withval"],
[gtk_version_desired="any"])
case "$gtk_version_desired" in
2 | 3 | any) ;;
yes) gtk_version_desired="any" ;;
*) AC_ERROR([Invalid GTK version specified])
esac
gtk=none
case "$gtk_version_desired:$gtk" in
3:none | any:none)
ifdef([AM_PATH_GTK_3_0],[
AM_PATH_GTK_3_0([3.0.0], [gtk=3], [])
],[AC_WARNING([generating configure script without GTK 3 autodetection])])
;;
esac
case "$gtk_version_desired:$gtk" in
2:none | any:none)
ifdef([AM_PATH_GTK_2_0],[
AM_PATH_GTK_2_0([2.0.0], [gtk=2], [])
],[AC_WARNING([generating configure script without GTK 2 autodetection])])
;;
esac
if test "$gtk" = "none"; then
AC_MSG_ERROR([cannot build without GTK 2 or GTK 3])
fi
if test "x$GCC" = "xyes"; then
AC_MSG_CHECKING([for usable gcc warning flags])
gccwarningflags=
for flag in -Wall -Werror -std=c89 -pedantic; do
ac_save_CFLAGS="$CFLAGS"
ac_save_LIBS="$LIBS"
CFLAGS="$CFLAGS$gccwarningflags $flag $GTK_CFLAGS"
LIBS="$GTK_LIBS $LIBS"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
],[
return 0;
])], [gccwarningflags="$gccwarningflags $flag"], [])
CFLAGS="$ac_save_CFLAGS"
LIBS="$ac_save_LIBS"
done
AC_MSG_RESULT($gccwarningflags)
CFLAGS="$CFLAGS$gccwarningflags"
fi
AC_PROG_RANLIB
AC_PROG_INSTALL
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

View file

@ -0,0 +1,19 @@
# -*- makefile -*-
cube : [X] GTK COMMON cube cube-icon|no-icon
cube : [G] WINDOWS COMMON cube cube.res|noicon.res
ALL += cube[COMBINED]
!begin am gtk
GAMES += cube
!end
!begin >list.c
A(cube) \
!end
!begin >gamedesc.txt
cube:cube.exe:Cube:Rolling cube puzzle:Pick up all the blue squares by rolling the cube over them.
!end

1774
apps/plugins/puzzles/cube.c Normal file

File diff suppressed because it is too large Load diff

52
apps/plugins/puzzles/desktop.pl Executable file
View file

@ -0,0 +1,52 @@
#!/usr/bin/perl
# Make .desktop files for the puzzles.
#
# At present, this script is intended for developer usage: if you're
# working on the puzzles and want to play your bleeding-edge locally
# modified and compiled versions, run this script and it will create a
# collection of desktop files in ~/.local/share/applications where
# XFCE can pick them up and add them to its main menu. (Be sure to run
# 'xfdesktop --reload' after running this.)
#
# (If you don't use XFCE, patches to support other desktop
# environments are welcome :-)
use strict;
use warnings;
use Cwd 'abs_path';
die "usage: desktop.pl [<outdir> [<bindir> <icondir>]]\n"
unless @ARGV == 0 or @ARGV == 1 or @ARGV == 3;
my ($outdir, $bindir, $icondir) = @ARGV;
$outdir = $ENV{'HOME'}."/.local/share/applications" unless defined $outdir;
$bindir = "." unless defined $bindir;
$icondir = "./icons" unless defined $icondir;
$bindir = abs_path($bindir);
$icondir = abs_path($icondir);
open my $desc, "<", "gamedesc.txt"
or die "gamedesc.txt: open: $!\n";
while (<$desc>) {
chomp;
my ($id, $win, $displayname, $description, $summary) = split /:/, $_;
open my $desktop, ">", "$outdir/$id.desktop"
or die "$outdir/$id.desktop: open: $!\n";
print $desktop "[Desktop Entry]\n";
print $desktop "Version=1.0\n";
print $desktop "Type=Application\n";
print $desktop "Name=$displayname\n";
print $desktop "Comment=$description\n";
print $desktop "Exec=$bindir/$id\n";
print $desktop "Icon=$icondir/$id-48d24.png\n";
print $desktop "StartupNotify=false\n";
print $desktop "Categories=Game;\n";
print $desktop "Terminal=false\n";
close $desktop
or die "$outdir/$id.desktop: close: $!\n";
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,781 @@
/*
* Library code to divide up a rectangle into a number of equally
* sized ominoes, in a random fashion.
*
* Could use this for generating solved grids of
* http://www.nikoli.co.jp/ja/puzzles/block_puzzle/
* or for generating the playfield for Jigsaw Sudoku.
*/
/*
* This code is restricted to simply connected solutions: that is,
* no single polyomino may completely surround another (not even
* with a corner visible to the outside world, in the sense that a
* 7-omino can `surround' a single square).
*
* It's tempting to think that this is a natural consequence of
* all the ominoes being the same size - after all, a division of
* anything into 7-ominoes must necessarily have all of them
* simply connected, because if one was not then the 1-square
* space in the middle could not be part of any 7-omino - but in
* fact, for sufficiently large k, it is perfectly possible for a
* k-omino to completely surround another k-omino. A simple
* example is this one with two 25-ominoes:
*
* +--+--+--+--+--+--+--+
* | |
* + +--+--+--+--+--+ +
* | | | |
* + + + +
* | | | |
* + + + +--+
* | | | |
* + + + +--+
* | | | |
* + + + +
* | | | |
* + +--+--+--+--+--+ +
* | |
* +--+--+--+--+--+--+--+
*
* I claim the smallest k which can manage this is 23. More
* formally:
*
* If a k-omino P is completely surrounded by another k-omino Q,
* such that every edge of P borders on Q, then k >= 23.
*
* Proof:
*
* It's relatively simple to find the largest _rectangle_ a
* k-omino can enclose. So I'll construct my proof in two parts:
* firstly, show that no 22-omino or smaller can enclose a
* rectangle as large as itself, and secondly, show that no
* polyomino can enclose a larger non-rectangle than a rectangle.
*
* The first of those claims:
*
* To surround an m x n rectangle, a polyomino must have 2m
* squares along the two m-sides of the rectangle, 2n squares
* along the two n-sides, and must fill in at least three of the
* corners in order to be connected. Thus, 2(m+n)+3 <= k. We wish
* to find the largest value of mn subject to that constraint, and
* it's clear that this is achieved when m and n are as close to
* equal as possible. (If they aren't, WLOG suppose m < n; then
* (m+1)(n-1) = mn + n - m - 1 >= mn, with equality only when
* m=n-1.)
*
* So the area of the largest rectangle which can be enclosed by a
* k-omino is given by floor(k'/2) * ceil(k'/2), where k' =
* (k-3)/2. This is a monotonic function in k, so there will be a
* unique point at which it goes from being smaller than k to
* being larger than k. That point is between 22 (maximum area 20)
* and 23 (maximum area 25).
*
* The second claim:
*
* Suppose we have an inner polyomino P surrounded by an outer
* polyomino Q. I seek to show that if P is non-rectangular, then
* P is also non-maximal, in the sense that we can transform P and
* Q into a new pair of polyominoes in which P is larger and Q is
* at most the same size.
*
* Consider walking along the boundary of P in a clockwise
* direction. (We may assume, of course, that there is only _one_
* boundary of P, i.e. P has no hole in the middle. If it does
* have a hole in the middle, it's _trivially_ non-maximal because
* we can just fill the hole in!) Our walk will take us along many
* edges between squares; sometimes we might turn left, and
* certainly sometimes we will turn right. Always there will be a
* square of P on our right, and a square of Q on our left.
*
* The net angle through which we turn during the entire walk must
* add up to 360 degrees rightwards. So if there are no left
* turns, then we must turn right exactly four times, meaning we
* have described a rectangle. Hence, if P is _not_ rectangular,
* then there must have been a left turn at some point. A left
* turn must mean we walk along two edges of the same square of Q.
*
* Thus, there is some square X in Q which is adjacent to two
* diagonally separated squares in P. Let us call those two
* squares N and E; let us refer to the other two neighbours of X
* as S and W; let us refer to the other mutual neighbour of S and
* W as D; and let us refer to the other mutual neighbour of S and
* E as Y. In other words, we have named seven squares, arranged
* thus:
*
* N
* W X E
* D S Y
*
* where N and E are in P, and X is in Q.
*
* Clearly at least one of W and S must be in Q (because otherwise
* X would not be connected to any other square in Q, and would
* hence have to be the whole of Q; and evidently if Q were a
* 1-omino it could not enclose _anything_). So we divide into
* cases:
*
* If both W and S are in Q, then we take X out of Q and put it in
* P, which does not expose any edge of P. If this disconnects Q,
* then we can reconnect it by adding D to Q.
*
* If only one of W and S is in Q, then wlog let it be W. If S is
* in _P_, then we have a particularly easy case: we can simply
* take X out of Q and add it to P, and this cannot disconnect X
* since X was a leaf square of Q.
*
* Our remaining case is that W is in Q and S is in neither P nor
* Q. Again we take X out of Q and put it in P; we also add S to
* Q. This ensures we do not expose an edge of P, but we must now
* prove that S is adjacent to some other existing square of Q so
* that we haven't disconnected Q by adding it.
*
* To do this, we recall that we walked along the edge XE, and
* then turned left to walk along XN. So just before doing all
* that, we must have reached the corner XSE, and we must have
* done it by walking along one of the three edges meeting at that
* corner which are _not_ XE. It can't have been SY, since S would
* then have been on our left and it isn't in Q; and it can't have
* been XS, since S would then have been on our right and it isn't
* in P. So it must have been YE, in which case Y was on our left,
* and hence is in Q.
*
* So in all cases we have shown that we can take X out of Q and
* add it to P, and add at most one square to Q to restore the
* containment and connectedness properties. Hence, we can keep
* doing this until we run out of left turns and P becomes
* rectangular. []
*
* ------------
*
* Anyway, that entire proof was a bit of a sidetrack. The point
* is, although constructions of this type are possible for
* sufficiently large k, divvy_rectangle() will never generate
* them. This could be considered a weakness for some purposes, in
* the sense that we can't generate all possible divisions.
* However, there are many divisions which we are highly unlikely
* to generate anyway, so in practice it probably isn't _too_ bad.
*
* If I wanted to fix this issue, I would have to make the rules
* more complicated for determining when a square can safely be
* _removed_ from a polyomino. Adding one becomes easier (a square
* may be added to a polyomino iff it is 4-adjacent to any square
* currently part of the polyomino, and the current test for loop
* formation may be dispensed with), but to determine which
* squares may be removed we must now resort to analysis of the
* overall structure of the polyomino rather than the simple local
* properties we can currently get away with measuring.
*/
/*
* Possible improvements which might cut the fail rate:
*
* - instead of picking one omino to extend in an iteration, try
* them all in succession (in a randomised order)
*
* - (for real rigour) instead of bfsing over ominoes, bfs over
* the space of possible _removed squares_. That way we aren't
* limited to randomly choosing a single square to remove from
* an omino and failing if that particular square doesn't
* happen to work.
*
* However, I don't currently think it's necessary to do either of
* these, because the failure rate is already low enough to be
* easily tolerable, under all circumstances I've been able to
* think of.
*/
#include "rbassert.h"
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include "puzzles.h"
/*
* Subroutine which implements a function used in computing both
* whether a square can safely be added to an omino, and whether
* it can safely be removed.
*
* We enumerate the eight squares 8-adjacent to this one, in
* cyclic order. We go round that loop and count the number of
* times we find a square owned by the target omino next to one
* not owned by it. We then return success iff that count is 2.
*
* When adding a square to an omino, this is precisely the
* criterion which tells us that adding the square won't leave a
* hole in the middle of the omino. (If it did, then things get
* more complicated; see above.)
*
* When removing a square from an omino, the _same_ criterion
* tells us that removing the square won't disconnect the omino.
* (This only works _because_ we've ensured the omino is simply
* connected.)
*/
static int addremcommon(int w, int h, int x, int y, int *own, int val)
{
int neighbours[8];
int dir, count;
for (dir = 0; dir < 8; dir++) {
int dx = ((dir & 3) == 2 ? 0 : dir > 2 && dir < 6 ? +1 : -1);
int dy = ((dir & 3) == 0 ? 0 : dir < 4 ? -1 : +1);
int sx = x+dx, sy = y+dy;
if (sx < 0 || sx >= w || sy < 0 || sy >= h)
neighbours[dir] = -1; /* outside the grid */
else
neighbours[dir] = own[sy*w+sx];
}
/*
* To begin with, check 4-adjacency.
*/
if (neighbours[0] != val && neighbours[2] != val &&
neighbours[4] != val && neighbours[6] != val)
return FALSE;
count = 0;
for (dir = 0; dir < 8; dir++) {
int next = (dir + 1) & 7;
int gotthis = (neighbours[dir] == val);
int gotnext = (neighbours[next] == val);
if (gotthis != gotnext)
count++;
}
return (count == 2);
}
/*
* w and h are the dimensions of the rectangle.
*
* k is the size of the required ominoes. (So k must divide w*h,
* of course.)
*
* The returned result is a w*h-sized dsf.
*
* In both of the above suggested use cases, the user would
* probably want w==h==k, but that isn't a requirement.
*/
static int *divvy_internal(int w, int h, int k, random_state *rs)
{
int *order, *queue, *tmp, *own, *sizes, *addable, *removable, *retdsf;
int wh = w*h;
int i, j, n, x, y, qhead, qtail;
n = wh / k;
assert(wh == k*n);
order = snewn(wh, int);
tmp = snewn(wh, int);
own = snewn(wh, int);
sizes = snewn(n, int);
queue = snewn(n, int);
addable = snewn(wh*4, int);
removable = snewn(wh, int);
/*
* Permute the grid squares into a random order, which will be
* used for iterating over the grid whenever we need to search
* for something. This prevents directional bias and arranges
* for the answer to be non-deterministic.
*/
for (i = 0; i < wh; i++)
order[i] = i;
shuffle(order, wh, sizeof(*order), rs);
/*
* Begin by choosing a starting square at random for each
* omino.
*/
for (i = 0; i < wh; i++) {
own[i] = -1;
}
for (i = 0; i < n; i++) {
own[order[i]] = i;
sizes[i] = 1;
}
/*
* Now repeatedly pick a random omino which isn't already at
* the target size, and find a way to expand it by one. This
* may involve stealing a square from another omino, in which
* case we then re-expand that omino, forming a chain of
* square-stealing which terminates in an as yet unclaimed
* square. Hence every successful iteration around this loop
* causes the number of unclaimed squares to drop by one, and
* so the process is bounded in duration.
*/
while (1) {
#ifdef DIVVY_DIAGNOSTICS
{
int x, y;
printf("Top of loop. Current grid:\n");
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++)
printf("%3d", own[y*w+x]);
printf("\n");
}
}
#endif
/*
* Go over the grid and figure out which squares can
* safely be added to, or removed from, each omino. We
* don't take account of other ominoes in this process, so
* we will often end up knowing that a square can be
* poached from one omino by another.
*
* For each square, there may be up to four ominoes to
* which it can be added (those to which it is
* 4-adjacent).
*/
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
int yx = y*w+x;
int curr = own[yx];
int dir;
if (curr < 0) {
removable[yx] = FALSE; /* can't remove if not owned! */
} else if (sizes[curr] == 1) {
removable[yx] = TRUE; /* can always remove a singleton */
} else {
/*
* See if this square can be removed from its
* omino without disconnecting it.
*/
removable[yx] = addremcommon(w, h, x, y, own, curr);
}
for (dir = 0; dir < 4; dir++) {
int dx = (dir == 0 ? -1 : dir == 1 ? +1 : 0);
int dy = (dir == 2 ? -1 : dir == 3 ? +1 : 0);
int sx = x + dx, sy = y + dy;
int syx = sy*w+sx;
addable[yx*4+dir] = -1;
if (sx < 0 || sx >= w || sy < 0 || sy >= h)
continue; /* no omino here! */
if (own[syx] < 0)
continue; /* also no omino here */
if (own[syx] == own[yx])
continue; /* we already got one */
if (!addremcommon(w, h, x, y, own, own[syx]))
continue; /* would non-simply connect the omino */
addable[yx*4+dir] = own[syx];
}
}
}
for (i = j = 0; i < n; i++)
if (sizes[i] < k)
tmp[j++] = i;
if (j == 0)
break; /* all ominoes are complete! */
j = tmp[random_upto(rs, j)];
#ifdef DIVVY_DIAGNOSTICS
printf("Trying to extend %d\n", j);
#endif
/*
* So we're trying to expand omino j. We breadth-first
* search out from j across the space of ominoes.
*
* For bfs purposes, we use two elements of tmp per omino:
* tmp[2*i+0] tells us which omino we got to i from, and
* tmp[2*i+1] numbers the grid square that omino stole
* from us.
*
* This requires that wh (the size of tmp) is at least 2n,
* i.e. k is at least 2. There would have been nothing to
* stop a user calling this function with k=1, but if they
* did then we wouldn't have got to _here_ in the code -
* we would have noticed above that all ominoes were
* already at their target sizes, and terminated :-)
*/
assert(wh >= 2*n);
for (i = 0; i < n; i++)
tmp[2*i] = tmp[2*i+1] = -1;
qhead = qtail = 0;
queue[qtail++] = j;
tmp[2*j] = tmp[2*j+1] = -2; /* special value: `starting point' */
while (qhead < qtail) {
int tmpsq;
j = queue[qhead];
/*
* We wish to expand omino j. However, we might have
* got here by omino j having a square stolen from it,
* so first of all we must temporarily mark that
* square as not belonging to j, so that our adjacency
* calculations don't assume j _does_ belong to us.
*/
tmpsq = tmp[2*j+1];
if (tmpsq >= 0) {
assert(own[tmpsq] == j);
own[tmpsq] = -3;
}
/*
* OK. Now begin by seeing if we can find any
* unclaimed square into which we can expand omino j.
* If we find one, the entire bfs terminates.
*/
for (i = 0; i < wh; i++) {
int dir;
if (own[order[i]] != -1)
continue; /* this square is claimed */
/*
* Special case: if our current omino was size 1
* and then had a square stolen from it, it's now
* size zero, which means it's valid to `expand'
* it into _any_ unclaimed square.
*/
if (sizes[j] == 1 && tmpsq >= 0)
break; /* got one */
/*
* Failing that, we must do the full test for
* addability.
*/
for (dir = 0; dir < 4; dir++)
if (addable[order[i]*4+dir] == j) {
/*
* We know this square is addable to this
* omino with the grid in the state it had
* at the top of the loop. However, we
* must now check that it's _still_
* addable to this omino when the omino is
* missing a square. To do this it's only
* necessary to re-check addremcommon.
*/
if (!addremcommon(w, h, order[i]%w, order[i]/w,
own, j))
continue;
break;
}
if (dir == 4)
continue; /* we can't add this square to j */
break; /* got one! */
}
if (i < wh) {
i = order[i];
/*
* Restore the temporarily removed square _before_
* we start shifting ownerships about.
*/
if (tmpsq >= 0)
own[tmpsq] = j;
/*
* We are done. We can add square i to omino j,
* and then backtrack along the trail in tmp
* moving squares between ominoes, ending up
* expanding our starting omino by one.
*/
#ifdef DIVVY_DIAGNOSTICS
printf("(%d,%d)", i%w, i/w);
#endif
while (1) {
own[i] = j;
#ifdef DIVVY_DIAGNOSTICS
printf(" -> %d", j);
#endif
if (tmp[2*j] == -2)
break;
i = tmp[2*j+1];
j = tmp[2*j];
#ifdef DIVVY_DIAGNOSTICS
printf("; (%d,%d)", i%w, i/w);
#endif
}
#ifdef DIVVY_DIAGNOSTICS
printf("\n");
#endif
/*
* Increment the size of the starting omino.
*/
sizes[j]++;
/*
* Terminate the bfs loop.
*/
break;
}
/*
* If we get here, we haven't been able to expand
* omino j into an unclaimed square. So now we begin
* to investigate expanding it into squares which are
* claimed by ominoes the bfs has not yet visited.
*/
for (i = 0; i < wh; i++) {
int dir, nj;
nj = own[order[i]];
if (nj < 0 || tmp[2*nj] != -1)
continue; /* unclaimed, or owned by wrong omino */
if (!removable[order[i]])
continue; /* its omino won't let it go */
for (dir = 0; dir < 4; dir++)
if (addable[order[i]*4+dir] == j) {
/*
* As above, re-check addremcommon.
*/
if (!addremcommon(w, h, order[i]%w, order[i]/w,
own, j))
continue;
/*
* We have found a square we can use to
* expand omino j, at the expense of the
* as-yet unvisited omino nj. So add this
* to the bfs queue.
*/
assert(qtail < n);
queue[qtail++] = nj;
tmp[2*nj] = j;
tmp[2*nj+1] = order[i];
/*
* Now terminate the loop over dir, to
* ensure we don't accidentally add the
* same omino twice to the queue.
*/
break;
}
}
/*
* Restore the temporarily removed square.
*/
if (tmpsq >= 0)
own[tmpsq] = j;
/*
* Advance the queue head.
*/
qhead++;
}
if (qhead == qtail) {
/*
* We have finished the bfs and not found any way to
* expand omino j. Panic, and return failure.
*
* FIXME: or should we loop over all ominoes before we
* give up?
*/
#ifdef DIVVY_DIAGNOSTICS
printf("FAIL!\n");
#endif
retdsf = NULL;
goto cleanup;
}
}
#ifdef DIVVY_DIAGNOSTICS
{
int x, y;
printf("SUCCESS! Final grid:\n");
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++)
printf("%3d", own[y*w+x]);
printf("\n");
}
}
#endif
/*
* Construct the output dsf.
*/
for (i = 0; i < wh; i++) {
assert(own[i] >= 0 && own[i] < n);
tmp[own[i]] = i;
}
retdsf = snew_dsf(wh);
for (i = 0; i < wh; i++) {
dsf_merge(retdsf, i, tmp[own[i]]);
}
/*
* Construct the output dsf a different way, to verify that
* the ominoes really are k-ominoes and we haven't
* accidentally split one into two disconnected pieces.
*/
dsf_init(tmp, wh);
for (y = 0; y < h; y++)
for (x = 0; x+1 < w; x++)
if (own[y*w+x] == own[y*w+(x+1)])
dsf_merge(tmp, y*w+x, y*w+(x+1));
for (x = 0; x < w; x++)
for (y = 0; y+1 < h; y++)
if (own[y*w+x] == own[(y+1)*w+x])
dsf_merge(tmp, y*w+x, (y+1)*w+x);
for (i = 0; i < wh; i++) {
j = dsf_canonify(retdsf, i);
assert(dsf_canonify(tmp, j) == dsf_canonify(tmp, i));
}
cleanup:
/*
* Free our temporary working space.
*/
sfree(order);
sfree(tmp);
sfree(own);
sfree(sizes);
sfree(queue);
sfree(addable);
sfree(removable);
/*
* And we're done.
*/
return retdsf;
}
#ifdef TESTMODE
static int fail_counter = 0;
#endif
int *divvy_rectangle(int w, int h, int k, random_state *rs)
{
int *ret;
do {
ret = divvy_internal(w, h, k, rs);
#ifdef TESTMODE
if (!ret)
fail_counter++;
#endif
} while (!ret);
return ret;
}
#ifdef TESTMODE
/*
* gcc -g -O0 -DTESTMODE -I.. -o divvy divvy.c ../random.c ../malloc.c ../dsf.c ../misc.c ../nullfe.c
*
* or to debug
*
* gcc -g -O0 -DDIVVY_DIAGNOSTICS -DTESTMODE -I.. -o divvy divvy.c ../random.c ../malloc.c ../dsf.c ../misc.c ../nullfe.c
*/
int main(int argc, char **argv)
{
int *dsf;
int i;
int w = 9, h = 4, k = 6, tries = 100;
random_state *rs;
rs = random_new("123456", 6);
if (argc > 1)
w = atoi(argv[1]);
if (argc > 2)
h = atoi(argv[2]);
if (argc > 3)
k = atoi(argv[3]);
if (argc > 4)
tries = atoi(argv[4]);
for (i = 0; i < tries; i++) {
int x, y;
dsf = divvy_rectangle(w, h, k, rs);
assert(dsf);
for (y = 0; y <= 2*h; y++) {
for (x = 0; x <= 2*w; x++) {
int miny = y/2 - 1, maxy = y/2;
int minx = x/2 - 1, maxx = x/2;
int classes[4], tx, ty;
for (ty = 0; ty < 2; ty++)
for (tx = 0; tx < 2; tx++) {
int cx = minx+tx, cy = miny+ty;
if (cx < 0 || cx >= w || cy < 0 || cy >= h)
classes[ty*2+tx] = -1;
else
classes[ty*2+tx] = dsf_canonify(dsf, cy*w+cx);
}
switch (y%2 * 2 + x%2) {
case 0: /* corner */
/*
* Cases for the corner:
*
* - if all four surrounding squares belong
* to the same omino, we print a space.
*
* - if the top two are the same and the
* bottom two are the same, we print a
* horizontal line.
*
* - if the left two are the same and the
* right two are the same, we print a
* vertical line.
*
* - otherwise, we print a cross.
*/
if (classes[0] == classes[1] &&
classes[1] == classes[2] &&
classes[2] == classes[3])
printf(" ");
else if (classes[0] == classes[1] &&
classes[2] == classes[3])
printf("-");
else if (classes[0] == classes[2] &&
classes[1] == classes[3])
printf("|");
else
printf("+");
break;
case 1: /* horiz edge */
if (classes[1] == classes[3])
printf(" ");
else
printf("--");
break;
case 2: /* vert edge */
if (classes[2] == classes[3])
printf(" ");
else
printf("|");
break;
case 3: /* square centre */
printf(" ");
break;
}
}
printf("\n");
}
printf("\n");
sfree(dsf);
}
printf("%d retries needed for %d successes\n", fail_counter, tries);
return 0;
}
#endif

View file

@ -0,0 +1,21 @@
# -*- makefile -*-
DOMINOSA_EXTRA = laydomino
dominosa : [X] GTK COMMON dominosa DOMINOSA_EXTRA dominosa-icon|no-icon
dominosa : [G] WINDOWS COMMON dominosa DOMINOSA_EXTRA dominosa.res|noicon.res
ALL += dominosa[COMBINED] DOMINOSA_EXTRA
!begin am gtk
GAMES += dominosa
!end
!begin >list.c
A(dominosa) \
!end
!begin >gamedesc.txt
dominosa:dominosa.exe:Dominosa:Domino tiling puzzle:Tile the rectangle with a full set of dominoes.
!end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,351 @@
/*
* drawing.c: Intermediary between the drawing interface as
* presented to the back end, and that implemented by the front
* end.
*
* Mostly just looks up calls in a vtable and passes them through
* unchanged. However, on the printing side it tracks print colours
* so the front end API doesn't have to.
*
* FIXME:
*
* - I'd _like_ to do automatic draw_updates, but it's a pain for
* draw_text in particular. I'd have to invent a front end API
* which retrieved the text bounds.
* + that might allow me to do the alignment centrally as well?
* * perhaps not, because PS can't return this information,
* so there would have to be a special case for it.
* + however, that at least doesn't stand in the way of using
* the text bounds for draw_update, because PS doesn't need
* draw_update since it's printing-only. Any _interactive_
* drawing API couldn't get away with refusing to tell you
* what parts of the screen a text draw had covered, because
* you would inevitably need to erase it later on.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rbassert.h"
#include <math.h>
#include "puzzles.h"
struct print_colour {
int hatch;
int hatch_when; /* 0=never 1=only-in-b&w 2=always */
float r, g, b;
float grey;
};
struct drawing {
const drawing_api *api;
void *handle;
struct print_colour *colours;
int ncolours, coloursize;
float scale;
/* `me' is only used in status_bar(), so print-oriented instances of
* this may set it to NULL. */
midend *me;
char *laststatus;
};
drawing *drawing_new(const drawing_api *api, midend *me, void *handle)
{
drawing *dr = snew(drawing);
dr->api = api;
dr->handle = handle;
dr->colours = NULL;
dr->ncolours = dr->coloursize = 0;
dr->scale = 1.0F;
dr->me = me;
dr->laststatus = NULL;
return dr;
}
void drawing_free(drawing *dr)
{
sfree(dr->laststatus);
sfree(dr->colours);
sfree(dr);
}
void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
int align, int colour, char *text)
{
dr->api->draw_text(dr->handle, x, y, fonttype, fontsize, align,
colour, text);
}
void draw_rect(drawing *dr, int x, int y, int w, int h, int colour)
{
dr->api->draw_rect(dr->handle, x, y, w, h, colour);
}
void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour)
{
dr->api->draw_line(dr->handle, x1, y1, x2, y2, colour);
}
void draw_thick_line(drawing *dr, float thickness,
float x1, float y1, float x2, float y2, int colour)
{
if (dr->api->draw_thick_line) {
dr->api->draw_thick_line(dr->handle, thickness,
x1, y1, x2, y2, colour);
} else {
/* We'll fake it up with a filled polygon. The tweak to the
* thickness empirically compensates for rounding errors, because
* polygon rendering uses integer coordinates.
*/
float len = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
float tvhatx = (x2 - x1)/len * (thickness/2 - 0.2);
float tvhaty = (y2 - y1)/len * (thickness/2 - 0.2);
int p[8];
p[0] = x1 - tvhaty;
p[1] = y1 + tvhatx;
p[2] = x2 - tvhaty;
p[3] = y2 + tvhatx;
p[4] = x2 + tvhaty;
p[5] = y2 - tvhatx;
p[6] = x1 + tvhaty;
p[7] = y1 - tvhatx;
dr->api->draw_polygon(dr->handle, p, 4, colour, colour);
}
}
void draw_polygon(drawing *dr, int *coords, int npoints,
int fillcolour, int outlinecolour)
{
dr->api->draw_polygon(dr->handle, coords, npoints, fillcolour,
outlinecolour);
}
void draw_circle(drawing *dr, int cx, int cy, int radius,
int fillcolour, int outlinecolour)
{
dr->api->draw_circle(dr->handle, cx, cy, radius, fillcolour,
outlinecolour);
}
void draw_update(drawing *dr, int x, int y, int w, int h)
{
if (dr->api->draw_update)
dr->api->draw_update(dr->handle, x, y, w, h);
}
void clip(drawing *dr, int x, int y, int w, int h)
{
dr->api->clip(dr->handle, x, y, w, h);
}
void unclip(drawing *dr)
{
dr->api->unclip(dr->handle);
}
void start_draw(drawing *dr)
{
dr->api->start_draw(dr->handle);
}
void end_draw(drawing *dr)
{
dr->api->end_draw(dr->handle);
}
char *text_fallback(drawing *dr, const char *const *strings, int nstrings)
{
int i;
/*
* If the drawing implementation provides one of these, use it.
*/
if (dr && dr->api->text_fallback)
return dr->api->text_fallback(dr->handle, strings, nstrings);
/*
* Otherwise, do the simple thing and just pick the first string
* that fits in plain ASCII. It will then need no translation
* out of UTF-8.
*/
for (i = 0; i < nstrings; i++) {
const char *p;
for (p = strings[i]; *p; p++)
if (*p & 0x80)
break;
if (!*p)
return dupstr(strings[i]);
}
/*
* The caller was responsible for making sure _some_ string in
* the list was in plain ASCII.
*/
assert(!"Should never get here");
return NULL; /* placate optimiser */
}
void status_bar(drawing *dr, char *text)
{
char *rewritten;
if (!dr->api->status_bar)
return;
assert(dr->me);
rewritten = midend_rewrite_statusbar(dr->me, text);
if (!dr->laststatus || strcmp(rewritten, dr->laststatus)) {
dr->api->status_bar(dr->handle, rewritten);
sfree(dr->laststatus);
dr->laststatus = rewritten;
} else {
sfree(rewritten);
}
}
blitter *blitter_new(drawing *dr, int w, int h)
{
return dr->api->blitter_new(dr->handle, w, h);
}
void blitter_free(drawing *dr, blitter *bl)
{
dr->api->blitter_free(dr->handle, bl);
}
void blitter_save(drawing *dr, blitter *bl, int x, int y)
{
dr->api->blitter_save(dr->handle, bl, x, y);
}
void blitter_load(drawing *dr, blitter *bl, int x, int y)
{
dr->api->blitter_load(dr->handle, bl, x, y);
}
void print_begin_doc(drawing *dr, int pages)
{
dr->api->begin_doc(dr->handle, pages);
}
void print_begin_page(drawing *dr, int number)
{
dr->api->begin_page(dr->handle, number);
}
void print_begin_puzzle(drawing *dr, float xm, float xc,
float ym, float yc, int pw, int ph, float wmm,
float scale)
{
dr->scale = scale;
dr->ncolours = 0;
dr->api->begin_puzzle(dr->handle, xm, xc, ym, yc, pw, ph, wmm);
}
void print_end_puzzle(drawing *dr)
{
dr->api->end_puzzle(dr->handle);
dr->scale = 1.0F;
}
void print_end_page(drawing *dr, int number)
{
dr->api->end_page(dr->handle, number);
}
void print_end_doc(drawing *dr)
{
dr->api->end_doc(dr->handle);
}
void print_get_colour(drawing *dr, int colour, int printing_in_colour,
int *hatch, float *r, float *g, float *b)
{
assert(colour >= 0 && colour < dr->ncolours);
if (dr->colours[colour].hatch_when == 2 ||
(dr->colours[colour].hatch_when == 1 && !printing_in_colour)) {
*hatch = dr->colours[colour].hatch;
} else {
*hatch = -1;
if (printing_in_colour) {
*r = dr->colours[colour].r;
*g = dr->colours[colour].g;
*b = dr->colours[colour].b;
} else {
*r = *g = *b = dr->colours[colour].grey;
}
}
}
static int print_generic_colour(drawing *dr, float r, float g, float b,
float grey, int hatch, int hatch_when)
{
if (dr->ncolours >= dr->coloursize) {
dr->coloursize = dr->ncolours + 16;
dr->colours = sresize(dr->colours, dr->coloursize,
struct print_colour);
}
dr->colours[dr->ncolours].hatch = hatch;
dr->colours[dr->ncolours].hatch_when = hatch_when;
dr->colours[dr->ncolours].r = r;
dr->colours[dr->ncolours].g = g;
dr->colours[dr->ncolours].b = b;
dr->colours[dr->ncolours].grey = grey;
return dr->ncolours++;
}
int print_mono_colour(drawing *dr, int grey)
{
return print_generic_colour(dr, grey, grey, grey, grey, -1, 0);
}
int print_grey_colour(drawing *dr, float grey)
{
return print_generic_colour(dr, grey, grey, grey, grey, -1, 0);
}
int print_hatched_colour(drawing *dr, int hatch)
{
return print_generic_colour(dr, 0, 0, 0, 0, hatch, 2);
}
int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int grey)
{
return print_generic_colour(dr, r, g, b, grey, -1, 0);
}
int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey)
{
return print_generic_colour(dr, r, g, b, grey, -1, 0);
}
int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, int hatch)
{
return print_generic_colour(dr, r, g, b, 0, hatch, 1);
}
void print_line_width(drawing *dr, int width)
{
/*
* I don't think it's entirely sensible to have line widths be
* entirely relative to the puzzle size; there is a point
* beyond which lines are just _stupidly_ thick. On the other
* hand, absolute line widths aren't particularly nice either
* because they start to feel a bit feeble at really large
* scales.
*
* My experimental answer is to scale line widths as the
* _square root_ of the main puzzle scale. Double the puzzle
* size, and the line width multiplies by 1.4.
*/
dr->api->line_width(dr->handle, (float)sqrt(dr->scale) * width);
}
void print_line_dotted(drawing *dr, int dotted)
{
dr->api->line_dotted(dr->handle, dotted);
}

192
apps/plugins/puzzles/dsf.c Normal file
View file

@ -0,0 +1,192 @@
/*
* dsf.c: some functions to handle a disjoint set forest,
* which is a data structure useful in any solver which has to
* worry about avoiding closed loops.
*/
#include "rbassert.h"
#include <string.h>
#include "puzzles.h"
/*void print_dsf(int *dsf, int size)
{
int *printed_elements = snewn(size, int);
int *equal_elements = snewn(size, int);
int *inverse_elements = snewn(size, int);
int printed_count = 0, equal_count, inverse_count;
int i, n, inverse;
memset(printed_elements, -1, sizeof(int) * size);
while (1) {
equal_count = 0;
inverse_count = 0;
for (i = 0; i < size; ++i) {
if (!memchr(printed_elements, i, sizeof(int) * size))
break;
}
if (i == size)
goto done;
i = dsf_canonify(dsf, i);
for (n = 0; n < size; ++n) {
if (edsf_canonify(dsf, n, &inverse) == i) {
if (inverse)
inverse_elements[inverse_count++] = n;
else
equal_elements[equal_count++] = n;
}
}
for (n = 0; n < equal_count; ++n) {
fprintf(stderr, "%d ", equal_elements[n]);
printed_elements[printed_count++] = equal_elements[n];
}
if (inverse_count) {
fprintf(stderr, "!= ");
for (n = 0; n < inverse_count; ++n) {
fprintf(stderr, "%d ", inverse_elements[n]);
printed_elements[printed_count++] = inverse_elements[n];
}
}
fprintf(stderr, "\n");
}
done:
sfree(printed_elements);
sfree(equal_elements);
sfree(inverse_elements);
}*/
void dsf_init(int *dsf, int size)
{
int i;
for (i = 0; i < size; i++) dsf[i] = 6;
/* Bottom bit of each element of this array stores whether that
* element is opposite to its parent, which starts off as
* false. Second bit of each element stores whether that element
* is the root of its tree or not. If it's not the root, the
* remaining 30 bits are the parent, otherwise the remaining 30
* bits are the number of elements in the tree. */
}
int *snew_dsf(int size)
{
int *ret;
ret = snewn(size, int);
dsf_init(ret, size);
/*print_dsf(ret, size); */
return ret;
}
int dsf_canonify(int *dsf, int index)
{
return edsf_canonify(dsf, index, NULL);
}
void dsf_merge(int *dsf, int v1, int v2)
{
edsf_merge(dsf, v1, v2, FALSE);
}
int dsf_size(int *dsf, int index) {
return dsf[dsf_canonify(dsf, index)] >> 2;
}
int edsf_canonify(int *dsf, int index, int *inverse_return)
{
int start_index = index, canonical_index;
int inverse = 0;
/* fprintf(stderr, "dsf = %p\n", dsf); */
/* fprintf(stderr, "Canonify %2d\n", index); */
assert(index >= 0);
/* Find the index of the canonical element of the 'equivalence class' of
* which start_index is a member, and figure out whether start_index is the
* same as or inverse to that. */
while ((dsf[index] & 2) == 0) {
inverse ^= (dsf[index] & 1);
index = dsf[index] >> 2;
/* fprintf(stderr, "index = %2d, ", index); */
/* fprintf(stderr, "inverse = %d\n", inverse); */
}
canonical_index = index;
if (inverse_return)
*inverse_return = inverse;
/* Update every member of this 'equivalence class' to point directly at the
* canonical member. */
index = start_index;
while (index != canonical_index) {
int nextindex = dsf[index] >> 2;
int nextinverse = inverse ^ (dsf[index] & 1);
dsf[index] = (canonical_index << 2) | inverse;
inverse = nextinverse;
index = nextindex;
}
assert(inverse == 0);
/* fprintf(stderr, "Return %2d\n", index); */
return index;
}
void edsf_merge(int *dsf, int v1, int v2, int inverse)
{
int i1, i2;
/* fprintf(stderr, "dsf = %p\n", dsf); */
/* fprintf(stderr, "Merge [%2d,%2d], %d\n", v1, v2, inverse); */
v1 = edsf_canonify(dsf, v1, &i1);
assert(dsf[v1] & 2);
inverse ^= i1;
v2 = edsf_canonify(dsf, v2, &i2);
assert(dsf[v2] & 2);
inverse ^= i2;
/* fprintf(stderr, "Doing [%2d,%2d], %d\n", v1, v2, inverse); */
if (v1 == v2)
assert(!inverse);
else {
assert(inverse == 0 || inverse == 1);
/*
* We always make the smaller of v1 and v2 the new canonical
* element. This ensures that the canonical element of any
* class in this structure is always the first element in
* it. 'Keen' depends critically on this property.
*
* (Jonas Koelker previously had this code choosing which
* way round to connect the trees by examining the sizes of
* the classes being merged, so that the root of the
* larger-sized class became the new root. This gives better
* asymptotic performance, but I've changed it to do it this
* way because I like having a deterministic canonical
* element.)
*/
if (v1 > v2) {
int v3 = v1;
v1 = v2;
v2 = v3;
}
dsf[v1] += (dsf[v2] >> 2) << 2;
dsf[v2] = (v1 << 2) | !!inverse;
}
v2 = edsf_canonify(dsf, v2, &i2);
assert(v2 == v1);
assert(i2 == inverse);
/* fprintf(stderr, "dsf[%2d] = %2d\n", v2, dsf[v2]); */
}

867
apps/plugins/puzzles/emcc.c Normal file
View file

@ -0,0 +1,867 @@
/*
* emcc.c: the C component of an Emscripten-based web/Javascript front
* end for Puzzles.
*
* The Javascript parts of this system live in emcclib.js and
* emccpre.js. It also depends on being run in the context of a web
* page containing an appropriate collection of bits and pieces (a
* canvas, some buttons and links etc), which is generated for each
* puzzle by the script html/jspage.pl.
*/
/*
* Further thoughts on possible enhancements:
*
* - I think it might be feasible to have these JS puzzles permit
* loading and saving games in disk files. Saving would be done by
* constructing a data: URI encapsulating the save file, and then
* telling the browser to visit that URI with the effect that it
* would naturally pop up a 'where would you like to save this'
* dialog box. Loading, more or less similarly, might be feasible
* by using the DOM File API to ask the user to select a file and
* permit us to see its contents.
*
* - I should think about whether these webified puzzles can support
* touchscreen-based tablet browsers (assuming there are any that
* can cope with the reasonably modern JS and run it fast enough to
* be worthwhile).
*
* - think about making use of localStorage. It might be useful to
* let the user save games into there as an alternative to disk
* files - disk files are all very well for getting the save right
* out of your browser to (e.g.) email to me as a bug report, but
* for just resuming a game you were in the middle of, you'd
* probably rather have a nice simple 'quick save' and 'quick load'
* button pair. Also, that might be a useful place to store
* preferences, if I ever get round to writing a preferences UI.
*
* - some CSS to make the button bar and configuration dialogs a
* little less ugly would probably not go amiss.
*
* - this is a downright silly idea, but it does occur to me that if
* I were to write a PDF output driver for the Puzzles printing
* API, then I might be able to implement a sort of 'printing'
* feature in this front end, using data: URIs again. (Ask the user
* exactly what they want printed, then construct an appropriate
* PDF and embed it in a gigantic data: URI. Then they can print
* that using whatever they normally use to print PDFs!)
*/
#include "rbassert.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "puzzles.h"
/*
* Extern references to Javascript functions provided in emcclib.js.
*/
extern void js_debug(const char *);
extern void js_error_box(const char *message);
extern void js_remove_type_dropdown(void);
extern void js_remove_solve_button(void);
extern void js_add_preset(const char *name);
extern int js_get_selected_preset(void);
extern void js_select_preset(int n);
extern void js_get_date_64(unsigned *p);
extern void js_update_permalinks(const char *desc, const char *seed);
extern void js_enable_undo_redo(int undo, int redo);
extern void js_activate_timer();
extern void js_deactivate_timer();
extern void js_canvas_start_draw(void);
extern void js_canvas_draw_update(int x, int y, int w, int h);
extern void js_canvas_end_draw(void);
extern void js_canvas_draw_rect(int x, int y, int w, int h,
const char *colour);
extern void js_canvas_clip_rect(int x, int y, int w, int h);
extern void js_canvas_unclip(void);
extern void js_canvas_draw_line(float x1, float y1, float x2, float y2,
int width, const char *colour);
extern void js_canvas_draw_poly(int *points, int npoints,
const char *fillcolour,
const char *outlinecolour);
extern void js_canvas_draw_circle(int x, int y, int r,
const char *fillcolour,
const char *outlinecolour);
extern int js_canvas_find_font_midpoint(int height, const char *fontptr);
extern void js_canvas_draw_text(int x, int y, int halign,
const char *colptr, const char *fontptr,
const char *text);
extern int js_canvas_new_blitter(int w, int h);
extern void js_canvas_free_blitter(int id);
extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
extern void js_canvas_make_statusbar(void);
extern void js_canvas_set_statusbar(const char *text);
extern void js_canvas_set_size(int w, int h);
extern void js_dialog_init(const char *title);
extern void js_dialog_string(int i, const char *title, const char *initvalue);
extern void js_dialog_choices(int i, const char *title, const char *choicelist,
int initvalue);
extern void js_dialog_boolean(int i, const char *title, int initvalue);
extern void js_dialog_launch(void);
extern void js_dialog_cleanup(void);
extern void js_focus_canvas(void);
/*
* Call JS to get the date, and use that to initialise our random
* number generator to invent the first game seed.
*/
void get_random_seed(void **randseed, int *randseedsize)
{
unsigned *ret = snewn(2, unsigned);
js_get_date_64(ret);
*randseed = ret;
*randseedsize = 2*sizeof(unsigned);
}
/*
* Fatal error, called in cases of complete despair such as when
* malloc() has returned NULL.
*/
void fatal(char *fmt, ...)
{
char buf[512];
va_list ap;
strcpy(buf, "puzzle fatal error: ");
va_start(ap, fmt);
vsnprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), fmt, ap);
va_end(ap);
js_error_box(buf);
}
void debug_printf(char *fmt, ...)
{
char buf[512];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
js_debug(buf);
}
/*
* Helper function that makes it easy to test strings that might be
* NULL.
*/
int strnullcmp(const char *a, const char *b)
{
if (a == NULL || b == NULL)
return a != NULL ? +1 : b != NULL ? -1 : 0;
return strcmp(a, b);
}
/*
* HTMLish names for the colours allocated by the puzzle.
*/
char **colour_strings;
int ncolours;
/*
* The global midend object.
*/
midend *me;
/* ----------------------------------------------------------------------
* Timing functions.
*/
int timer_active = FALSE;
void deactivate_timer(frontend *fe)
{
js_deactivate_timer();
timer_active = FALSE;
}
void activate_timer(frontend *fe)
{
if (!timer_active) {
js_activate_timer();
timer_active = TRUE;
}
}
void timer_callback(double tplus)
{
if (timer_active)
midend_timer(me, tplus);
}
/* ----------------------------------------------------------------------
* Helper functions to resize the canvas, and variables to remember
* its size for other functions (e.g. trimming blitter rectangles).
*/
static int canvas_w, canvas_h;
/* Called when we resize as a result of changing puzzle settings */
static void resize(void)
{
int w, h;
w = h = INT_MAX;
midend_size(me, &w, &h, FALSE);
js_canvas_set_size(w, h);
canvas_w = w;
canvas_h = h;
}
/* Called from JS when the user uses the resize handle */
void resize_puzzle(int w, int h)
{
midend_size(me, &w, &h, TRUE);
if (canvas_w != w || canvas_h != h) {
js_canvas_set_size(w, h);
canvas_w = w;
canvas_h = h;
midend_force_redraw(me);
}
}
/* Called from JS when the user uses the restore button */
void restore_puzzle_size(int w, int h)
{
midend_reset_tilesize(me);
resize();
midend_force_redraw(me);
}
/*
* HTML doesn't give us a default frontend colour of its own, so we
* just make up a lightish grey ourselves.
*/
void frontend_default_colour(frontend *fe, float *output)
{
output[0] = output[1] = output[2] = 0.9F;
}
/*
* Helper function called from all over the place to ensure the undo
* and redo buttons get properly enabled and disabled after every move
* or undo or new-game event.
*/
static void update_undo_redo(void)
{
js_enable_undo_redo(midend_can_undo(me), midend_can_redo(me));
}
/*
* Mouse event handlers called from JS.
*/
void mousedown(int x, int y, int button)
{
button = (button == 0 ? LEFT_BUTTON :
button == 1 ? MIDDLE_BUTTON : RIGHT_BUTTON);
midend_process_key(me, x, y, button);
update_undo_redo();
}
void mouseup(int x, int y, int button)
{
button = (button == 0 ? LEFT_RELEASE :
button == 1 ? MIDDLE_RELEASE : RIGHT_RELEASE);
midend_process_key(me, x, y, button);
update_undo_redo();
}
void mousemove(int x, int y, int buttons)
{
int button = (buttons & 2 ? MIDDLE_DRAG :
buttons & 4 ? RIGHT_DRAG : LEFT_DRAG);
midend_process_key(me, x, y, button);
update_undo_redo();
}
/*
* Keyboard handler called from JS.
*/
void key(int keycode, int charcode, const char *key, const char *chr,
int shift, int ctrl)
{
int keyevent = -1;
if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Del") ||
keycode == 8 || keycode == 46) {
keyevent = 127; /* Backspace / Delete */
} else if (!strnullcmp(key, "Enter") || keycode == 13) {
keyevent = 13; /* return */
} else if (!strnullcmp(key, "Left") || keycode == 37) {
keyevent = CURSOR_LEFT;
} else if (!strnullcmp(key, "Up") || keycode == 38) {
keyevent = CURSOR_UP;
} else if (!strnullcmp(key, "Right") || keycode == 39) {
keyevent = CURSOR_RIGHT;
} else if (!strnullcmp(key, "Down") || keycode == 40) {
keyevent = CURSOR_DOWN;
} else if (!strnullcmp(key, "End") || keycode == 35) {
/*
* We interpret Home, End, PgUp and PgDn as numeric keypad
* controls regardless of whether they're the ones on the
* numeric keypad (since we can't tell). The effect of
* this should only be that the non-numeric-pad versions
* of those keys generate directions in 8-way movement
* puzzles like Cube and Inertia.
*/
keyevent = MOD_NUM_KEYPAD | '1';
} else if (!strnullcmp(key, "PageDown") || keycode==34) {
keyevent = MOD_NUM_KEYPAD | '3';
} else if (!strnullcmp(key, "Home") || keycode==36) {
keyevent = MOD_NUM_KEYPAD | '7';
} else if (!strnullcmp(key, "PageUp") || keycode==33) {
keyevent = MOD_NUM_KEYPAD | '9';
} else if (chr && chr[0] && !chr[1]) {
keyevent = chr[0] & 0xFF;
} else if (keycode >= 96 && keycode < 106) {
keyevent = MOD_NUM_KEYPAD | ('0' + keycode - 96);
} else if (keycode >= 65 && keycode <= 90) {
keyevent = keycode + (shift ? 0 : 32);
} else if (keycode >= 48 && keycode <= 57) {
keyevent = keycode;
} else if (keycode == 32) { /* space / CURSOR_SELECT2 */
keyevent = keycode;
}
if (keyevent >= 0) {
if (shift && keyevent >= 0x100)
keyevent |= MOD_SHFT;
if (ctrl) {
if (keyevent >= 0x100)
keyevent |= MOD_CTRL;
else
keyevent &= 0x1F;
}
midend_process_key(me, 0, 0, keyevent);
update_undo_redo();
}
}
/*
* Helper function called from several places to update the permalinks
* whenever a new game is created.
*/
static void update_permalinks(void)
{
char *desc, *seed;
desc = midend_get_game_id(me);
seed = midend_get_random_seed(me);
js_update_permalinks(desc, seed);
sfree(desc);
sfree(seed);
}
/*
* Callback from the midend when the game ids change, so we can update
* the permalinks.
*/
static void ids_changed(void *ignored)
{
update_permalinks();
}
/* ----------------------------------------------------------------------
* Implementation of the drawing API by calling Javascript canvas
* drawing functions. (Well, half of it; the other half is on the JS
* side.)
*/
static void js_start_draw(void *handle)
{
js_canvas_start_draw();
}
static void js_clip(void *handle, int x, int y, int w, int h)
{
js_canvas_clip_rect(x, y, w, h);
}
static void js_unclip(void *handle)
{
js_canvas_unclip();
}
static void js_draw_text(void *handle, int x, int y, int fonttype,
int fontsize, int align, int colour, char *text)
{
char fontstyle[80];
int halign;
sprintf(fontstyle, "%dpx %s", fontsize,
fonttype == FONT_FIXED ? "monospace" : "sans-serif");
if (align & ALIGN_VCENTRE)
y += js_canvas_find_font_midpoint(fontsize, fontstyle);
if (align & ALIGN_HCENTRE)
halign = 1;
else if (align & ALIGN_HRIGHT)
halign = 2;
else
halign = 0;
js_canvas_draw_text(x, y, halign, colour_strings[colour], fontstyle, text);
}
static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour)
{
js_canvas_draw_rect(x, y, w, h, colour_strings[colour]);
}
static void js_draw_line(void *handle, int x1, int y1, int x2, int y2,
int colour)
{
js_canvas_draw_line(x1, y1, x2, y2, 1, colour_strings[colour]);
}
static void js_draw_thick_line(void *handle, float thickness,
float x1, float y1, float x2, float y2,
int colour)
{
js_canvas_draw_line(x1, y1, x2, y2, thickness, colour_strings[colour]);
}
static void js_draw_poly(void *handle, int *coords, int npoints,
int fillcolour, int outlinecolour)
{
js_canvas_draw_poly(coords, npoints,
fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
colour_strings[outlinecolour]);
}
static void js_draw_circle(void *handle, int cx, int cy, int radius,
int fillcolour, int outlinecolour)
{
js_canvas_draw_circle(cx, cy, radius,
fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
colour_strings[outlinecolour]);
}
struct blitter {
int id; /* allocated on the js side */
int w, h; /* easier to retain here */
};
static blitter *js_blitter_new(void *handle, int w, int h)
{
blitter *bl = snew(blitter);
bl->w = w;
bl->h = h;
bl->id = js_canvas_new_blitter(w, h);
return bl;
}
static void js_blitter_free(void *handle, blitter *bl)
{
js_canvas_free_blitter(bl->id);
sfree(bl);
}
static void trim_rect(int *x, int *y, int *w, int *h)
{
int x0, x1, y0, y1;
/*
* Reduce the size of the copied rectangle to stop it going
* outside the bounds of the canvas.
*/
/* Transform from x,y,w,h form into coordinates of all edges */
x0 = *x;
y0 = *y;
x1 = *x + *w;
y1 = *y + *h;
/* Clip each coordinate at both extremes of the canvas */
x0 = (x0 < 0 ? 0 : x0 > canvas_w ? canvas_w : x0);
x1 = (x1 < 0 ? 0 : x1 > canvas_w ? canvas_w : x1);
y0 = (y0 < 0 ? 0 : y0 > canvas_h ? canvas_h : y0);
y1 = (y1 < 0 ? 0 : y1 > canvas_h ? canvas_h : y1);
/* Transform back into x,y,w,h to return */
*x = x0;
*y = y0;
*w = x1 - x0;
*h = y1 - y0;
}
static void js_blitter_save(void *handle, blitter *bl, int x, int y)
{
int w = bl->w, h = bl->h;
trim_rect(&x, &y, &w, &h);
if (w > 0 && h > 0)
js_canvas_copy_to_blitter(bl->id, x, y, w, h);
}
static void js_blitter_load(void *handle, blitter *bl, int x, int y)
{
int w = bl->w, h = bl->h;
trim_rect(&x, &y, &w, &h);
if (w > 0 && h > 0)
js_canvas_copy_from_blitter(bl->id, x, y, w, h);
}
static void js_draw_update(void *handle, int x, int y, int w, int h)
{
trim_rect(&x, &y, &w, &h);
if (w > 0 && h > 0)
js_canvas_draw_update(x, y, w, h);
}
static void js_end_draw(void *handle)
{
js_canvas_end_draw();
}
static void js_status_bar(void *handle, char *text)
{
js_canvas_set_statusbar(text);
}
static char *js_text_fallback(void *handle, const char *const *strings,
int nstrings)
{
return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */
}
const struct drawing_api js_drawing = {
js_draw_text,
js_draw_rect,
js_draw_line,
js_draw_poly,
js_draw_circle,
js_draw_update,
js_clip,
js_unclip,
js_start_draw,
js_end_draw,
js_status_bar,
js_blitter_new,
js_blitter_free,
js_blitter_save,
js_blitter_load,
NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
NULL, NULL, /* line_width, line_dotted */
js_text_fallback,
js_draw_thick_line,
};
/* ----------------------------------------------------------------------
* Presets and game-configuration dialog support.
*/
static game_params **presets;
static int npresets;
int have_presets_dropdown;
void select_appropriate_preset(void)
{
if (have_presets_dropdown) {
int preset = midend_which_preset(me);
js_select_preset(preset < 0 ? -1 : preset);
}
}
static config_item *cfg = NULL;
static int cfg_which;
/*
* Set up a dialog box. This is pretty easy on the C side; most of the
* work is done in JS.
*/
static void cfg_start(int which)
{
char *title;
int i;
cfg = midend_get_config(me, which, &title);
cfg_which = which;
js_dialog_init(title);
sfree(title);
for (i = 0; cfg[i].type != C_END; i++) {
switch (cfg[i].type) {
case C_STRING:
js_dialog_string(i, cfg[i].name, cfg[i].sval);
break;
case C_BOOLEAN:
js_dialog_boolean(i, cfg[i].name, cfg[i].ival);
break;
case C_CHOICES:
js_dialog_choices(i, cfg[i].name, cfg[i].sval, cfg[i].ival);
break;
}
}
js_dialog_launch();
}
/*
* Callbacks from JS when the OK button is clicked, to return the
* final state of each control.
*/
void dlg_return_sval(int index, const char *val)
{
sfree(cfg[index].sval);
cfg[index].sval = dupstr(val);
}
void dlg_return_ival(int index, int val)
{
cfg[index].ival = val;
}
/*
* Called when the user clicks OK or Cancel. use_results will be TRUE
* or FALSE respectively, in those cases. We terminate the dialog box,
* unless the user selected an invalid combination of parameters.
*/
static void cfg_end(int use_results)
{
if (use_results) {
/*
* User hit OK.
*/
char *err = midend_set_config(me, cfg_which, cfg);
if (err) {
/*
* The settings were unacceptable, so leave the config box
* open for the user to adjust them and try again.
*/
js_error_box(err);
} else {
/*
* New settings are fine; start a new game and close the
* dialog.
*/
select_appropriate_preset();
midend_new_game(me);
resize();
midend_redraw(me);
free_cfg(cfg);
js_dialog_cleanup();
}
} else {
/*
* User hit Cancel. Close the dialog, but also we must still
* reselect the right element of the dropdown list.
*
* (Because: imagine you have a preset selected, and then you
* select Custom from the list, but change your mind and hit
* Esc. The Custom option will now still be selected in the
* list, whereas obviously it should show the preset you still
* _actually_ have selected. Worse still, it'll be the visible
* rather than invisible Custom option - see the comment in
* js_add_preset in emcclib.js - so you won't even be able to
* select Custom without a faffy workaround.)
*/
select_appropriate_preset();
free_cfg(cfg);
js_dialog_cleanup();
}
}
/* ----------------------------------------------------------------------
* Called from JS when a command is given to the puzzle by clicking a
* button or control of some sort.
*/
void command(int n)
{
switch (n) {
case 0: /* specific game ID */
cfg_start(CFG_DESC);
break;
case 1: /* random game seed */
cfg_start(CFG_SEED);
break;
case 2: /* game parameter dropdown changed */
{
int i = js_get_selected_preset();
if (i < 0) {
/*
* The user selected 'Custom', so launch the config
* box.
*/
if (thegame.can_configure) /* (double-check just in case) */
cfg_start(CFG_SETTINGS);
} else {
/*
* The user selected a preset, so just switch straight
* to that.
*/
assert(i < npresets);
midend_set_params(me, presets[i]);
midend_new_game(me);
resize();
midend_redraw(me);
update_undo_redo();
js_focus_canvas();
select_appropriate_preset(); /* sort out Custom/Customise */
}
}
break;
case 3: /* OK clicked in a config box */
cfg_end(TRUE);
update_undo_redo();
break;
case 4: /* Cancel clicked in a config box */
cfg_end(FALSE);
update_undo_redo();
break;
case 5: /* New Game */
midend_process_key(me, 0, 0, 'n');
update_undo_redo();
js_focus_canvas();
break;
case 6: /* Restart */
midend_restart_game(me);
update_undo_redo();
js_focus_canvas();
break;
case 7: /* Undo */
midend_process_key(me, 0, 0, 'u');
update_undo_redo();
js_focus_canvas();
break;
case 8: /* Redo */
midend_process_key(me, 0, 0, 'r');
update_undo_redo();
js_focus_canvas();
break;
case 9: /* Solve */
if (thegame.can_solve) {
char *msg = midend_solve(me);
if (msg)
js_error_box(msg);
}
update_undo_redo();
js_focus_canvas();
break;
}
}
/* ----------------------------------------------------------------------
* Setup function called at page load time. It's called main() because
* that's the most convenient thing in Emscripten, but it's not main()
* in the usual sense of bounding the program's entire execution.
* Instead, this function returns once the initial puzzle is set up
* and working, and everything thereafter happens by means of JS event
* handlers sending us callbacks.
*/
int main(int argc, char **argv)
{
char *param_err;
float *colours;
int i;
/*
* Instantiate a midend.
*/
me = midend_new(NULL, &thegame, &js_drawing, NULL);
/*
* Chuck in the HTML fragment ID if we have one (trimming the
* leading # off the front first). If that's invalid, we retain
* the error message and will display it at the end, after setting
* up a random puzzle as usual.
*/
if (argc > 1 && argv[1][0] == '#' && argv[1][1] != '\0')
param_err = midend_game_id(me, argv[1] + 1);
else
param_err = NULL;
/*
* Create either a random game or the specified one, and set the
* canvas size appropriately.
*/
midend_new_game(me);
resize();
/*
* Create a status bar, if needed.
*/
if (midend_wants_statusbar(me))
js_canvas_make_statusbar();
/*
* Set up the game-type dropdown with presets and/or the Custom
* option.
*/
npresets = midend_num_presets(me);
if (npresets == 0) {
/*
* This puzzle doesn't have selectable game types at all.
* Completely remove the drop-down list from the page.
*/
js_remove_type_dropdown();
have_presets_dropdown = FALSE;
} else {
presets = snewn(npresets, game_params *);
for (i = 0; i < npresets; i++) {
char *name;
midend_fetch_preset(me, i, &name, &presets[i]);
js_add_preset(name);
}
if (thegame.can_configure)
js_add_preset(NULL); /* the 'Custom' entry in the dropdown */
have_presets_dropdown = TRUE;
/*
* Now ensure the appropriate element of the presets menu
* starts off selected, in case it isn't the first one in the
* list (e.g. Slant).
*/
select_appropriate_preset();
}
/*
* Remove the Solve button if the game doesn't support it.
*/
if (!thegame.can_solve)
js_remove_solve_button();
/*
* Retrieve the game's colours, and convert them into #abcdef type
* hex ID strings.
*/
colours = midend_colours(me, &ncolours);
colour_strings = snewn(ncolours, char *);
for (i = 0; i < ncolours; i++) {
char col[40];
sprintf(col, "#%02x%02x%02x",
(unsigned)(0.5 + 255 * colours[i*3+0]),
(unsigned)(0.5 + 255 * colours[i*3+1]),
(unsigned)(0.5 + 255 * colours[i*3+2]));
colour_strings[i] = dupstr(col);
}
/*
* Request notification when the game ids change (e.g. if the user
* presses 'n', and also when Mines supersedes its game
* description), so that we can proactively update the permalink.
*/
midend_request_id_changes(me, ids_changed, NULL);
/*
* Draw the puzzle's initial state, and set up the permalinks and
* undo/redo greying out.
*/
midend_redraw(me);
update_permalinks();
update_undo_redo();
/*
* If we were given an erroneous game ID in argv[1], now's the
* time to put up the error box about it, after we've fully set up
* a random puzzle. Then when the user clicks 'ok', we have a
* puzzle for them.
*/
if (param_err)
js_error_box(param_err);
/*
* Done. Return to JS, and await callbacks!
*/
return 0;
}

View file

@ -0,0 +1,757 @@
/*
* emcclib.js: one of the Javascript components of an Emscripten-based
* web/Javascript front end for Puzzles.
*
* The other parts of this system live in emcc.c and emccpre.js. It
* also depends on being run in the context of a web page containing
* an appropriate collection of bits and pieces (a canvas, some
* buttons and links etc), which is generated for each puzzle by the
* script html/jspage.pl.
*
* This file contains a set of Javascript functions which we insert
* into Emscripten's library object via the --js-library option; this
* allows us to provide JS code which can be called from the
* Emscripten-compiled C, mostly dealing with UI interaction of
* various kinds.
*/
mergeInto(LibraryManager.library, {
/*
* void js_debug(const char *message);
*
* A function to write a diagnostic to the Javascript console.
* Unused in production, but handy in development.
*/
js_debug: function(ptr) {
console.log(Pointer_stringify(ptr));
},
/*
* void js_error_box(const char *message);
*
* A wrapper around Javascript's alert(), so the C code can print
* simple error message boxes (e.g. when invalid data is entered
* in a configuration dialog).
*/
js_error_box: function(ptr) {
alert(Pointer_stringify(ptr));
},
/*
* void js_remove_type_dropdown(void);
*
* Get rid of the drop-down list on the web page for selecting
* game presets. Called at setup time if the game back end
* provides neither presets nor configurability.
*/
js_remove_type_dropdown: function() {
document.getElementById("gametype").style.display = "none";
},
/*
* void js_remove_solve_button(void);
*
* Get rid of the Solve button on the web page. Called at setup
* time if the game doesn't support an in-game solve function.
*/
js_remove_solve_button: function() {
document.getElementById("solve").style.display = "none";
},
/*
* void js_add_preset(const char *name);
*
* Add a preset to the drop-down types menu. The provided text is
* the name of the preset. (The corresponding game_params stays on
* the C side and never comes out this far; we just pass a numeric
* index back to the C code when a selection is made.)
*
* The special 'Custom' preset is requested by passing NULL to
* this function, rather than the string "Custom", since in that
* case we need to do something special - see below.
*/
js_add_preset: function(ptr) {
var name = (ptr == 0 ? "Customise..." : Pointer_stringify(ptr));
var value = gametypeoptions.length;
var option = document.createElement("option");
option.value = value;
option.appendChild(document.createTextNode(name));
gametypeselector.appendChild(option);
gametypeoptions.push(option);
if (ptr == 0) {
// The option we've just created is the one for inventing
// a new custom setup.
gametypenewcustom = option;
option.value = -1;
// Now create another element called 'Custom', which will
// be auto-selected by us to indicate the custom settings
// you've previously selected. However, we don't add it to
// the game type selector; it will only appear when the
// user actually has custom settings selected.
option = document.createElement("option");
option.value = -2;
option.appendChild(document.createTextNode("Custom"));
gametypethiscustom = option;
}
},
/*
* int js_get_selected_preset(void);
*
* Return the index of the currently selected value in the type
* dropdown.
*/
js_get_selected_preset: function() {
for (var i in gametypeoptions) {
if (gametypeoptions[i].selected) {
return gametypeoptions[i].value;
}
}
return 0;
},
/*
* void js_select_preset(int n);
*
* Cause a different value to be selected in the type dropdown
* (for when the user selects values from the Custom configurer
* which turn out to exactly match a preset).
*/
js_select_preset: function(n) {
if (gametypethiscustom !== null) {
// Fiddle with the Custom/Customise options. If we're
// about to select the Custom option, then it should be in
// the menu, and the other one should read "Re-customise";
// if we're about to select another one, then the static
// Custom option should disappear and the other one should
// read "Customise".
if (gametypethiscustom.parentNode == gametypeselector)
gametypeselector.removeChild(gametypethiscustom);
if (gametypenewcustom.parentNode == gametypeselector)
gametypeselector.removeChild(gametypenewcustom);
if (n < 0) {
gametypeselector.appendChild(gametypethiscustom);
gametypenewcustom.lastChild.data = "Re-customise...";
} else {
gametypenewcustom.lastChild.data = "Customise...";
}
gametypeselector.appendChild(gametypenewcustom);
gametypenewcustom.selected = false;
}
if (n < 0) {
gametypethiscustom.selected = true;
} else {
gametypeoptions[n].selected = true;
}
},
/*
* void js_get_date_64(unsigned *p);
*
* Return the current date, in milliseconds since the epoch
* (Javascript's native format), as a 64-bit integer. Used to
* invent an initial random seed for puzzle generation.
*/
js_get_date_64: function(ptr) {
var d = (new Date()).valueOf();
setValue(ptr, d, 'i64');
},
/*
* void js_update_permalinks(const char *desc, const char *seed);
*
* Update the permalinks on the web page for a new game
* description and optional random seed. desc can never be NULL,
* but seed might be (if the game was generated by entering a
* descriptive id by hand), in which case we suppress display of
* the random seed permalink.
*/
js_update_permalinks: function(desc, seed) {
desc = Pointer_stringify(desc);
permalink_desc.href = "#" + desc;
if (seed == 0) {
permalink_seed.style.display = "none";
} else {
seed = Pointer_stringify(seed);
permalink_seed.href = "#" + seed;
permalink_seed.style.display = "inline";
}
},
/*
* void js_enable_undo_redo(int undo, int redo);
*
* Set the enabled/disabled states of the undo and redo buttons,
* after a move.
*/
js_enable_undo_redo: function(undo, redo) {
undo_button.disabled = (undo == 0);
redo_button.disabled = (redo == 0);
},
/*
* void js_activate_timer();
*
* Start calling the C timer_callback() function every 20ms.
*/
js_activate_timer: function() {
if (timer === null) {
timer_reference_date = (new Date()).valueOf();
timer = setInterval(function() {
var now = (new Date()).valueOf();
timer_callback((now - timer_reference_date) / 1000.0);
timer_reference_date = now;
return true;
}, 20);
}
},
/*
* void js_deactivate_timer();
*
* Stop calling the C timer_callback() function every 20ms.
*/
js_deactivate_timer: function() {
if (timer !== null) {
clearInterval(timer);
timer = null;
}
},
/*
* void js_canvas_start_draw(void);
*
* Prepare to do some drawing on the canvas.
*/
js_canvas_start_draw: function() {
ctx = offscreen_canvas.getContext('2d');
update_xmin = update_xmax = update_ymin = update_ymax = undefined;
},
/*
* void js_canvas_draw_update(int x, int y, int w, int h);
*
* Mark a rectangle of the off-screen canvas as needing to be
* copied to the on-screen one.
*/
js_canvas_draw_update: function(x, y, w, h) {
/*
* Currently we do this in a really simple way, just by taking
* the smallest rectangle containing all updates so far. We
* could instead keep the data in a richer form (e.g. retain
* multiple smaller rectangles needing update, and only redraw
* the whole thing beyond a certain threshold) but this will
* do for now.
*/
if (update_xmin === undefined || update_xmin > x) update_xmin = x;
if (update_ymin === undefined || update_ymin > y) update_ymin = y;
if (update_xmax === undefined || update_xmax < x+w) update_xmax = x+w;
if (update_ymax === undefined || update_ymax < y+h) update_ymax = y+h;
},
/*
* void js_canvas_end_draw(void);
*
* Finish the drawing, by actually copying the newly drawn stuff
* to the on-screen canvas.
*/
js_canvas_end_draw: function() {
if (update_xmin !== undefined) {
var onscreen_ctx = onscreen_canvas.getContext('2d');
onscreen_ctx.drawImage(offscreen_canvas,
update_xmin, update_ymin,
update_xmax - update_xmin,
update_ymax - update_ymin,
update_xmin, update_ymin,
update_xmax - update_xmin,
update_ymax - update_ymin);
}
ctx = null;
},
/*
* void js_canvas_draw_rect(int x, int y, int w, int h,
* const char *colour);
*
* Draw a rectangle.
*/
js_canvas_draw_rect: function(x, y, w, h, colptr) {
ctx.fillStyle = Pointer_stringify(colptr);
ctx.fillRect(x, y, w, h);
},
/*
* void js_canvas_clip_rect(int x, int y, int w, int h);
*
* Set a clipping rectangle.
*/
js_canvas_clip_rect: function(x, y, w, h) {
ctx.save();
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.clip();
},
/*
* void js_canvas_unclip(void);
*
* Reset to no clipping.
*/
js_canvas_unclip: function() {
ctx.restore();
},
/*
* void js_canvas_draw_line(float x1, float y1, float x2, float y2,
* int width, const char *colour);
*
* Draw a line. We must adjust the coordinates by 0.5 because
* Javascript's canvas coordinates appear to be pixel corners,
* whereas we want pixel centres. Also, we manually draw the pixel
* at each end of the line, which our clients will expect but
* Javascript won't reliably do by default (in common with other
* Postscriptish drawing frameworks).
*/
js_canvas_draw_line: function(x1, y1, x2, y2, width, colour) {
colour = Pointer_stringify(colour);
ctx.beginPath();
ctx.moveTo(x1 + 0.5, y1 + 0.5);
ctx.lineTo(x2 + 0.5, y2 + 0.5);
ctx.lineWidth = width;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = colour;
ctx.stroke();
ctx.fillStyle = colour;
ctx.fillRect(x1, y1, 1, 1);
ctx.fillRect(x2, y2, 1, 1);
},
/*
* void js_canvas_draw_poly(int *points, int npoints,
* const char *fillcolour,
* const char *outlinecolour);
*
* Draw a polygon.
*/
js_canvas_draw_poly: function(pointptr, npoints, fill, outline) {
ctx.beginPath();
ctx.moveTo(getValue(pointptr , 'i32') + 0.5,
getValue(pointptr+4, 'i32') + 0.5);
for (var i = 1; i < npoints; i++)
ctx.lineTo(getValue(pointptr+8*i , 'i32') + 0.5,
getValue(pointptr+8*i+4, 'i32') + 0.5);
ctx.closePath();
if (fill != 0) {
ctx.fillStyle = Pointer_stringify(fill);
ctx.fill();
}
ctx.lineWidth = '1';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = Pointer_stringify(outline);
ctx.stroke();
},
/*
* void js_canvas_draw_circle(int x, int y, int r,
* const char *fillcolour,
* const char *outlinecolour);
*
* Draw a circle.
*/
js_canvas_draw_circle: function(x, y, r, fill, outline) {
ctx.beginPath();
ctx.arc(x + 0.5, y + 0.5, r, 0, 2*Math.PI);
if (fill != 0) {
ctx.fillStyle = Pointer_stringify(fill);
ctx.fill();
}
ctx.lineWidth = '1';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = Pointer_stringify(outline);
ctx.stroke();
},
/*
* int js_canvas_find_font_midpoint(int height, const char *fontptr);
*
* Return the adjustment required for text displayed using
* ALIGN_VCENTRE. We want to place the midpoint between the
* baseline and the cap-height at the specified position; so this
* function returns the adjustment which, when added to the
* desired centre point, returns the y-coordinate at which you
* should put the baseline.
*
* There is no sensible method of querying this kind of font
* metric in Javascript, so instead we render a piece of test text
* to a throwaway offscreen canvas and then read the pixel data
* back out to find the highest and lowest pixels. That's good
* _enough_ (in that we only needed the answer to the nearest
* pixel anyway), but rather disgusting!
*
* Since this is a very expensive operation, we cache the results
* per (font,height) pair.
*/
js_canvas_find_font_midpoint: function(height, font) {
font = Pointer_stringify(font);
// Reuse cached value if possible
if (midpoint_cache[font] !== undefined)
return midpoint_cache[font];
// Find the width of the string
var ctx1 = onscreen_canvas.getContext('2d');
ctx1.font = font;
var width = (ctx1.measureText(midpoint_test_str).width + 1) | 0;
// Construct a test canvas of appropriate size, initialise it to
// black, and draw the string on it in white
var measure_canvas = document.createElement('canvas');
var ctx2 = measure_canvas.getContext('2d');
ctx2.canvas.width = width;
ctx2.canvas.height = 2*height;
ctx2.fillStyle = "#000000";
ctx2.fillRect(0, 0, width, 2*height);
var baseline = (1.5*height) | 0;
ctx2.fillStyle = "#ffffff";
ctx2.font = font;
ctx2.fillText(midpoint_test_str, 0, baseline);
// Scan the contents of the test canvas to find the top and bottom
// set pixels.
var pixels = ctx2.getImageData(0, 0, width, 2*height).data;
var ymin = 2*height, ymax = -1;
for (var y = 0; y < 2*height; y++) {
for (var x = 0; x < width; x++) {
if (pixels[4*(y*width+x)] != 0) {
if (ymin > y) ymin = y;
if (ymax < y) ymax = y;
break;
}
}
}
var ret = (baseline - (ymin + ymax) / 2) | 0;
midpoint_cache[font] = ret;
return ret;
},
/*
* void js_canvas_draw_text(int x, int y, int halign,
* const char *colptr, const char *fontptr,
* const char *text);
*
* Draw text. Vertical alignment has been taken care of on the C
* side, by optionally calling the above function. Horizontal
* alignment is handled here, since we can get the canvas draw
* function to do it for us with almost no extra effort.
*/
js_canvas_draw_text: function(x, y, halign, colptr, fontptr, text) {
ctx.font = Pointer_stringify(fontptr);
ctx.fillStyle = Pointer_stringify(colptr);
ctx.textAlign = (halign == 0 ? 'left' :
halign == 1 ? 'center' : 'right');
ctx.textBaseline = 'alphabetic';
ctx.fillText(Pointer_stringify(text), x, y);
},
/*
* int js_canvas_new_blitter(int w, int h);
*
* Create a new blitter object, which is just an offscreen canvas
* of the specified size.
*/
js_canvas_new_blitter: function(w, h) {
var id = blittercount++;
blitters[id] = document.createElement("canvas");
blitters[id].width = w;
blitters[id].height = h;
return id;
},
/*
* void js_canvas_free_blitter(int id);
*
* Free a blitter (or rather, destroy our reference to it so JS
* can garbage-collect it, and also enforce that we don't
* accidentally use it again afterwards).
*/
js_canvas_free_blitter: function(id) {
blitters[id] = null;
},
/*
* void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
*
* Copy from the puzzle image to a blitter. The size is passed to
* us, partly so we don't have to remember the size of each
* blitter, but mostly so that the C side can adjust the copy
* rectangle in the case where it partially overlaps the edge of
* the screen.
*/
js_canvas_copy_to_blitter: function(id, x, y, w, h) {
var blitter_ctx = blitters[id].getContext('2d');
blitter_ctx.drawImage(offscreen_canvas,
x, y, w, h,
0, 0, w, h);
},
/*
* void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
*
* Copy from a blitter back to the puzzle image. As above, the
* size of the copied rectangle is passed to us from the C side
* and may already have been modified.
*/
js_canvas_copy_from_blitter: function(id, x, y, w, h) {
ctx.drawImage(blitters[id],
0, 0, w, h,
x, y, w, h);
},
/*
* void js_canvas_make_statusbar(void);
*
* Cause a status bar to exist. Called at setup time if the puzzle
* back end turns out to want one.
*/
js_canvas_make_statusbar: function() {
var statusholder = document.getElementById("statusbarholder");
statusbar = document.createElement("div");
statusbar.style.overflow = "hidden";
statusbar.style.width = (onscreen_canvas.width - 4) + "px";
statusholder.style.width = onscreen_canvas.width + "px";
statusbar.style.height = "1.2em";
statusbar.style.textAlign = "left";
statusbar.style.background = "#d8d8d8";
statusbar.style.borderLeft = '2px solid #c8c8c8';
statusbar.style.borderTop = '2px solid #c8c8c8';
statusbar.style.borderRight = '2px solid #e8e8e8';
statusbar.style.borderBottom = '2px solid #e8e8e8';
statusbar.appendChild(document.createTextNode(" "));
statusholder.appendChild(statusbar);
},
/*
* void js_canvas_set_statusbar(const char *text);
*
* Set the text in the status bar.
*/
js_canvas_set_statusbar: function(ptr) {
var text = Pointer_stringify(ptr);
statusbar.replaceChild(document.createTextNode(text),
statusbar.lastChild);
},
/*
* void js_canvas_set_size(int w, int h);
*
* Set the size of the puzzle canvas. Called at setup, and every
* time the user picks new puzzle settings requiring a different
* size.
*/
js_canvas_set_size: function(w, h) {
onscreen_canvas.width = w;
offscreen_canvas.width = w;
if (statusbar !== null) {
statusbar.style.width = (w - 4) + "px";
document.getElementById("statusbarholder").style.width = w + "px";
}
resizable_div.style.width = w + "px";
onscreen_canvas.height = h;
offscreen_canvas.height = h;
},
/*
* void js_dialog_init(const char *title);
*
* Begin constructing a 'dialog box' which will be popped up in an
* 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;
},
/*
* void js_dialog_string(int i, const char *title, const char *initvalue);
*
* Add a string control (that is, an edit box) to the dialog under
* construction.
*/
js_dialog_string: function(index, title, initialtext) {
dlg_form.appendChild(document.createTextNode(Pointer_stringify(title)));
var editbox = document.createElement("input");
editbox.type = "text";
editbox.value = Pointer_stringify(initialtext);
dlg_form.appendChild(editbox);
dlg_form.appendChild(document.createElement("br"));
dlg_return_funcs.push(function() {
dlg_return_sval(index, editbox.value);
});
},
/*
* void js_dialog_choices(int i, const char *title, const char *choicelist,
* int initvalue);
*
* Add a choices control (i.e. a drop-down list) to the dialog
* under construction. The 'choicelist' parameter is unchanged
* from the way the puzzle back end will have supplied it: i.e.
* it's still encoded as a single string whose first character
* gives the separator.
*/
js_dialog_choices: function(index, title, choicelist, initvalue) {
dlg_form.appendChild(document.createTextNode(Pointer_stringify(title)));
var dropdown = document.createElement("select");
var choicestr = Pointer_stringify(choicelist);
var items = choicestr.slice(1).split(choicestr[0]);
var options = [];
for (var i in items) {
var option = document.createElement("option");
option.value = i;
option.appendChild(document.createTextNode(items[i]));
if (i == initvalue) option.selected = true;
dropdown.appendChild(option);
options.push(option);
}
dlg_form.appendChild(dropdown);
dlg_form.appendChild(document.createElement("br"));
dlg_return_funcs.push(function() {
var val = 0;
for (var i in options) {
if (options[i].selected) {
val = options[i].value;
break;
}
}
dlg_return_ival(index, val);
});
},
/*
* void js_dialog_boolean(int i, const char *title, int initvalue);
*
* Add a boolean control (a checkbox) to the dialog under
* construction. Checkboxes are generally expected to be sensitive
* on their label text as well as the box itself, so for this
* control we create an actual label rather than merely a text
* node (and hence we must allocate an id to the checkbox so that
* the label can refer to it).
*/
js_dialog_boolean: function(index, title, initvalue) {
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = "cb" + String(dlg_next_id++);
checkbox.checked = (initvalue != 0);
dlg_form.appendChild(checkbox);
var checkboxlabel = document.createElement("label");
checkboxlabel.setAttribute("for", checkbox.id);
checkboxlabel.textContent = Pointer_stringify(title);
dlg_form.appendChild(checkboxlabel);
dlg_form.appendChild(document.createElement("br"));
dlg_return_funcs.push(function() {
dlg_return_ival(index, checkbox.checked ? 1 : 0);
});
},
/*
* void js_dialog_launch(void);
*
* Finish constructing a dialog, and actually display it, dimming
* 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) {
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);
},
/*
* void js_dialog_cleanup(void);
*
* Stop displaying a dialog, and clean up the internal state
* 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();
},
/*
* void js_focus_canvas(void);
*
* Return keyboard focus to the puzzle canvas. Called after a
* puzzle-control button is pressed, which tends to have the side
* effect of taking focus away from the canvas.
*/
js_focus_canvas: function() {
onscreen_canvas.focus();
}
});

View file

@ -0,0 +1,364 @@
/*
* emccpre.js: one of the Javascript components of an Emscripten-based
* web/Javascript front end for Puzzles.
*
* The other parts of this system live in emcc.c and emcclib.js. It
* also depends on being run in the context of a web page containing
* an appropriate collection of bits and pieces (a canvas, some
* buttons and links etc), which is generated for each puzzle by the
* script html/jspage.pl.
*
* This file contains the Javascript code which is prefixed unmodified
* to Emscripten's output via the --pre-js option. It declares all our
* global variables, and provides the puzzle init function and a
* couple of other helper functions.
*/
// To avoid flicker while doing complicated drawing, we use two
// canvases, the same size. One is actually on the web page, and the
// other is off-screen. We do all our drawing on the off-screen one
// first, and then copy rectangles of it to the on-screen canvas in
// response to draw_update() calls by the game backend.
var onscreen_canvas, offscreen_canvas;
// A persistent drawing context for the offscreen canvas, to save
// constructing one per individual graphics operation.
var ctx;
// Bounding rectangle for the copy to the onscreen canvas that will be
// done at drawing end time. Updated by js_canvas_draw_update and used
// by js_canvas_end_draw.
var update_xmin, update_xmax, update_ymin, update_ymax;
// Module object for Emscripten. We fill in these parameters to ensure
// that Module.run() won't be called until we're ready (we want to do
// our own init stuff first), and that when main() returns nothing
// will get cleaned up so we remain able to call the puzzle's various
// callbacks.
var Module = {
'noInitialRun': true,
'noExitRuntime': true
};
// Variables used by js_canvas_find_font_midpoint().
var midpoint_test_str = "ABCDEFGHIKLMNOPRSTUVWXYZ0123456789";
var midpoint_cache = [];
// Variables used by js_activate_timer() and js_deactivate_timer().
var timer = null;
var timer_reference_date;
// void timer_callback(double tplus);
//
// Called every 20ms while timing is active.
var timer_callback;
// The status bar object, if we create one.
var statusbar = null;
// Currently live blitters. We keep an integer id for each one on the
// JS side; the C side, which expects a blitter to look like a struct,
// simply defines the struct to contain that integer id.
var blittercount = 0;
var blitters = [];
// State for the dialog-box mechanism. dlg_dimmer and dlg_form are the
// page-darkening overlay and the actual dialog box respectively;
// dlg_next_id is used to allocate each checkbox a unique id to use
// for linking its label to it (see js_dialog_boolean);
// dlg_return_funcs is a list of JS functions to be called when the OK
// button is pressed, to pass the results back to C.
var dlg_dimmer = null, dlg_form = null;
var dlg_next_id = 0;
var dlg_return_funcs = null;
// void dlg_return_sval(int index, const char *val);
// void dlg_return_ival(int index, int val);
//
// C-side entry points called by functions in dlg_return_funcs, to
// pass back the final value in each dialog control.
var dlg_return_sval, dlg_return_ival;
// The <select> object implementing the game-type drop-down, and a
// list of the <option> objects inside it. Used by js_add_preset(),
// js_get_selected_preset() and js_select_preset().
//
// gametypethiscustom is an option which indicates some custom game
// params you've already set up, and which will be auto-selected on
// return from the customisation dialog; gametypenewcustom is an
// option which you select to indicate that you want to bring up the
// customisation dialog and select a new configuration. Ideally I'd do
// this with just one option serving both purposes, but instead we
// have to do this a bit oddly because browsers don't send 'onchange'
// events for a select element if you reselect the same one - so if
// you've picked a custom setup and now want to change it, you need a
// way to specify that.
var gametypeselector = null, gametypeoptions = [];
var gametypethiscustom = null, gametypehiddencustom = null;
// The two anchors used to give permalinks to the current puzzle. Used
// by js_update_permalinks().
var permalink_seed, permalink_desc;
// The undo and redo buttons. Used by js_enable_undo_redo().
var undo_button, redo_button;
// A div element enclosing both the puzzle and its status bar, used
// for positioning the resize handle.
var resizable_div;
// Helper function to find the absolute position of a given DOM
// element on a page, by iterating upwards through the DOM finding
// each element's offset from its parent, and thus calculating the
// page-relative position of the target element.
function element_coords(element) {
var ex = 0, ey = 0;
while (element.offsetParent) {
ex += element.offsetLeft;
ey += element.offsetTop;
element = element.offsetParent;
}
return {x: ex, y:ey};
}
// Helper function which is passed a mouse event object and a DOM
// element, and returns the coordinates of the mouse event relative to
// the top left corner of the element by subtracting element_coords
// from event.page{X,Y}.
function relative_mouse_coords(event, element) {
var ecoords = element_coords(element);
return {x: event.pageX - ecoords.x,
y: event.pageY - ecoords.y};
}
// Init function called from body.onload.
function initPuzzle() {
// Construct the off-screen canvas used for double buffering.
onscreen_canvas = document.getElementById("puzzlecanvas");
offscreen_canvas = document.createElement("canvas");
offscreen_canvas.width = onscreen_canvas.width;
offscreen_canvas.height = onscreen_canvas.height;
// Stop right-clicks on the puzzle from popping up a context menu.
// We need those right-clicks!
onscreen_canvas.oncontextmenu = function(event) { return false; }
// Set up mouse handlers. We do a bit of tracking of the currently
// pressed mouse buttons, to avoid sending mousemoves with no
// button down (our puzzles don't want those events).
mousedown = Module.cwrap('mousedown', 'void',
['number', 'number', 'number']);
buttons_down = 0;
onscreen_canvas.onmousedown = function(event) {
var xy = relative_mouse_coords(event, onscreen_canvas);
mousedown(xy.x, xy.y, event.button);
buttons_down |= 1 << event.button;
onscreen_canvas.setCapture(true);
};
mousemove = Module.cwrap('mousemove', 'void',
['number', 'number', 'number']);
onscreen_canvas.onmousemove = function(event) {
if (buttons_down) {
var xy = relative_mouse_coords(event, onscreen_canvas);
mousemove(xy.x, xy.y, buttons_down);
}
};
mouseup = Module.cwrap('mouseup', 'void',
['number', 'number', 'number']);
onscreen_canvas.onmouseup = function(event) {
if (buttons_down & (1 << event.button)) {
buttons_down ^= 1 << event.button;
var xy = relative_mouse_coords(event, onscreen_canvas);
mouseup(xy.x, xy.y, event.button);
}
};
// Set up keyboard handlers. We do all the actual keyboard
// handling in onkeydown; but we also call event.preventDefault()
// in both the keydown and keypress handlers. This means that
// while the canvas itself has focus, _all_ keypresses go only to
// the puzzle - so users of this puzzle collection in other media
// can indulge their instinct to press ^R for redo, for example,
// without accidentally reloading the page.
key = Module.cwrap('key', 'void', ['number', 'number', 'string',
'string', 'number', 'number']);
onscreen_canvas.onkeydown = function(event) {
key(event.keyCode, event.charCode, event.key, event.char,
event.shiftKey ? 1 : 0, event.ctrlKey ? 1 : 0);
event.preventDefault();
};
onscreen_canvas.onkeypress = function(event) {
event.preventDefault();
};
// command() is a C function called to pass back events which
// don't fall into other categories like mouse and key events.
// Mostly those are button presses, but there's also one for the
// game-type dropdown having been changed.
command = Module.cwrap('command', 'void', ['number']);
// Event handlers for buttons and things, which call command().
document.getElementById("specific").onclick = function(event) {
// Ensure we don't accidentally process these events when a
// dialog is actually active, e.g. because the button still
// has keyboard focus
if (dlg_dimmer === null)
command(0);
};
document.getElementById("random").onclick = function(event) {
if (dlg_dimmer === null)
command(1);
};
document.getElementById("new").onclick = function(event) {
if (dlg_dimmer === null)
command(5);
};
document.getElementById("restart").onclick = function(event) {
if (dlg_dimmer === null)
command(6);
};
undo_button = document.getElementById("undo");
undo_button.onclick = function(event) {
if (dlg_dimmer === null)
command(7);
};
redo_button = document.getElementById("redo");
redo_button.onclick = function(event) {
if (dlg_dimmer === null)
command(8);
};
document.getElementById("solve").onclick = function(event) {
if (dlg_dimmer === null)
command(9);
};
gametypeselector = document.getElementById("gametype");
gametypeselector.onchange = function(event) {
if (dlg_dimmer === null)
command(2);
};
// In IE, the canvas doesn't automatically gain focus on a mouse
// click, so make sure it does
onscreen_canvas.addEventListener("mousedown", function(event) {
onscreen_canvas.focus();
});
// In our dialog boxes, Return and Escape should be like pressing
// OK and Cancel respectively
document.addEventListener("keydown", function(event) {
if (dlg_dimmer !== null && event.keyCode == 13) {
for (var i in dlg_return_funcs)
dlg_return_funcs[i]();
command(3);
}
if (dlg_dimmer !== null && event.keyCode == 27)
command(4);
});
// Set up the function pointers we haven't already grabbed.
dlg_return_sval = Module.cwrap('dlg_return_sval', 'void',
['number','string']);
dlg_return_ival = Module.cwrap('dlg_return_ival', 'void',
['number','number']);
timer_callback = Module.cwrap('timer_callback', 'void', ['number']);
// Save references to the two permalinks.
permalink_desc = document.getElementById("permalink-desc");
permalink_seed = document.getElementById("permalink-seed");
// Default to giving keyboard focus to the puzzle.
onscreen_canvas.focus();
// Create the resize handle.
var resize_handle = document.createElement("canvas");
resize_handle.width = 10;
resize_handle.height = 10;
{
var ctx = resize_handle.getContext("2d");
ctx.beginPath();
for (var i = 1; i <= 7; i += 3) {
ctx.moveTo(8.5, i + 0.5);
ctx.lineTo(i + 0.5, 8.5);
}
ctx.lineWidth = '1px';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = '#000000';
ctx.stroke();
}
resizable_div = document.getElementById("resizable");
resizable_div.appendChild(resize_handle);
resize_handle.style.position = 'absolute';
resize_handle.style.zIndex = 98;
resize_handle.style.bottom = "0";
resize_handle.style.right = "0";
resize_handle.style.cursor = "se-resize";
resize_handle.title = "Drag to resize the puzzle. Right-click to restore the default size.";
var resize_xbase = null, resize_ybase = null, restore_pending = false;
var resize_xoffset = null, resize_yoffset = null;
var resize_puzzle = Module.cwrap('resize_puzzle',
'void', ['number', 'number']);
var restore_puzzle_size = Module.cwrap('restore_puzzle_size', 'void', []);
resize_handle.oncontextmenu = function(event) { return false; }
resize_handle.onmousedown = function(event) {
if (event.button == 0) {
var xy = element_coords(onscreen_canvas);
resize_xbase = xy.x + onscreen_canvas.width / 2;
resize_ybase = xy.y;
resize_xoffset = xy.x + onscreen_canvas.width - event.pageX;
resize_yoffset = xy.y + onscreen_canvas.height - event.pageY;
} else {
restore_pending = true;
}
resize_handle.setCapture(true);
event.preventDefault();
};
window.addEventListener("mousemove", function(event) {
if (resize_xbase !== null && resize_ybase !== null) {
resize_puzzle((event.pageX + resize_xoffset - resize_xbase) * 2,
(event.pageY + resize_yoffset - resize_ybase));
event.preventDefault();
// Chrome insists on selecting text during a resize drag
// no matter what I do
if (window.getSelection)
window.getSelection().removeAllRanges();
else
document.selection.empty(); }
});
window.addEventListener("mouseup", function(event) {
if (resize_xbase !== null && resize_ybase !== null) {
resize_xbase = null;
resize_ybase = null;
onscreen_canvas.focus(); // return focus to the puzzle
event.preventDefault();
} else if (restore_pending) {
// If you have the puzzle at larger than normal size and
// then right-click to restore, I haven't found any way to
// stop Chrome and IE popping up a context menu on the
// revealed piece of document when you release the button
// except by putting the actual restore into a setTimeout.
// Gah.
setTimeout(function() {
restore_pending = false;
restore_puzzle_size();
onscreen_canvas.focus();
}, 20);
event.preventDefault();
}
});
// Run the C setup function, passing argv[1] as the fragment
// identifier (so that permalinks of the form puzzle.html#game-id
// can launch the specified id).
Module.callMain([location.hash]);
// And if we get here with everything having gone smoothly, i.e.
// we haven't crashed for one reason or another during setup, then
// it's probably safe to hide the 'sorry, no puzzle here' div and
// show the div containing the actual puzzle.
document.getElementById("apology").style.display = "none";
document.getElementById("puzzle").style.display = "inline";
}

View file

@ -0,0 +1,29 @@
// -*- js -*-
//
// List of entry points exported by the C side of the Emscripten
// puzzle builds. Passed in to emcc via the option '-s
// EXPORTED_FUNCTIONS=[list]'.
//
// This file isn't actually a valid list in its current state, since
// emcc doesn't like comments or newlines. However, it's a nicer
// source form to keep the comments and newlines in, so we sed them
// away at compile time.
[
// Event handlers for mouse and keyboard input
'_mouseup',
'_mousedown',
'_mousemove',
'_key',
// Callback when the program activates timing
'_timer_callback',
// Callback from button presses in the UI outside the canvas
'_command',
// Callbacks to return values from dialog boxes
'_dlg_return_sval',
'_dlg_return_ival',
// Callbacks when the resizing controls are used
'_resize_puzzle',
'_restore_puzzle_size',
// Main program, run at initialisation time
'_main'
]

View file

@ -0,0 +1,22 @@
# -*- makefile -*-
fifteen : [X] GTK COMMON fifteen fifteen-icon|no-icon
fifteen : [G] WINDOWS COMMON fifteen fifteen.res|noicon.res
fifteensolver : [U] fifteen[STANDALONE_SOLVER] STANDALONE
fifteensolver : [C] fifteen[STANDALONE_SOLVER] STANDALONE
ALL += fifteen[COMBINED]
!begin am gtk
GAMES += fifteen
!end
!begin >list.c
A(fifteen) \
!end
!begin >gamedesc.txt
fifteen:fifteen.exe:Fifteen:Sliding block puzzle:Slide the tiles around to arrange them into order.
!end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
# -*- makefile -*-
FILLING_EXTRA = dsf
fillingsolver : [U] filling[STANDALONE_SOLVER] FILLING_EXTRA STANDALONE
fillingsolver : [C] filling[STANDALONE_SOLVER] FILLING_EXTRA STANDALONE
filling : [X] GTK COMMON filling FILLING_EXTRA filling-icon|no-icon
filling : [G] WINDOWS COMMON filling FILLING_EXTRA filling.res|noicon.res
ALL += filling[COMBINED] FILLING_EXTRA
!begin am gtk
GAMES += filling
!end
!begin >list.c
A(filling) \
!end
!begin >gamedesc.txt
filling:filling.exe:Filling:Polyomino puzzle:Mark every square with the area of its containing region.
!end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,500 @@
/*
* Routine for finding loops in graphs, reusable across multiple
* puzzles.
*
* The strategy is Tarjan's bridge-finding algorithm, which is
* designed to list all edges whose removal would disconnect a
* previously connected component of the graph. We're interested in
* exactly the reverse - edges that are part of a loop in the graph
* are precisely those which _wouldn't_ disconnect anything if removed
* (individually) - but of course flipping the sense of the output is
* easy.
*/
#include "puzzles.h"
struct findloopstate {
int parent, child, sibling, visited;
int index, minindex, maxindex;
int minreachable, maxreachable;
int bridge;
};
struct findloopstate *findloop_new_state(int nvertices)
{
/*
* Allocate a findloopstate structure for each vertex, and one
* extra one at the end which will be the overall root of a
* 'super-tree', which links the whole graph together to make it
* as easy as possible to iterate over all the connected
* components.
*/
return snewn(nvertices + 1, struct findloopstate);
}
void findloop_free_state(struct findloopstate *state)
{
sfree(state);
}
int findloop_is_loop_edge(struct findloopstate *pv, int u, int v)
{
/*
* Since the algorithm is intended for finding bridges, and a
* bridge must be part of any spanning tree, it follows that there
* is at most one bridge per vertex.
*
* Furthermore, by finding a _rooted_ spanning tree (so that each
* bridge is a parent->child link), you can find an injection from
* bridges to vertices (namely, map each bridge to the vertex at
* its child end).
*
* So if the u-v edge is a bridge, then either v was u's parent
* when the algorithm ran and we set pv[u].bridge = v, or vice
* versa.
*/
return !(pv[u].bridge == v || pv[v].bridge == u);
}
int findloop_run(struct findloopstate *pv, int nvertices,
neighbour_fn_t neighbour, void *ctx)
{
int u, v, w, root, index;
int nbridges, nedges;
root = nvertices;
/*
* First pass: organise the graph into a rooted spanning forest.
* That is, a tree structure with a clear up/down orientation -
* every node has exactly one parent (which may be 'root') and
* zero or more children, and every parent-child link corresponds
* to a graph edge.
*
* (A side effect of this is to find all the connected components,
* which of course we could do less confusingly with a dsf - but
* then we'd have to do that *and* build the tree, so it's less
* effort to do it all at once.)
*/
for (v = 0; v <= nvertices; v++) {
pv[v].parent = root;
pv[v].child = -2;
pv[v].sibling = -1;
pv[v].visited = FALSE;
}
pv[root].child = -1;
nedges = 0;
debug(("------------- new find_loops, nvertices=%d\n", nvertices));
for (v = 0; v < nvertices; v++) {
if (pv[v].parent == root) {
/*
* Found a new connected component. Enumerate and treeify
* it.
*/
pv[v].sibling = pv[root].child;
pv[root].child = v;
debug(("%d is new child of root\n", v));
u = v;
while (1) {
if (!pv[u].visited) {
pv[u].visited = TRUE;
/*
* Enumerate the neighbours of u, and any that are
* as yet not in the tree structure (indicated by
* child==-2, and distinct from the 'visited'
* flag) become children of u.
*/
debug((" component pass: processing %d\n", u));
for (w = neighbour(u, ctx); w >= 0;
w = neighbour(-1, ctx)) {
debug((" edge %d-%d\n", u, w));
if (pv[w].child == -2) {
debug((" -> new child\n"));
pv[w].child = -1;
pv[w].sibling = pv[u].child;
pv[w].parent = u;
pv[u].child = w;
}
/* While we're here, count the edges in the whole
* graph, so that we can easily check at the end
* whether all of them are bridges, i.e. whether
* no loop exists at all. */
if (w > u) /* count each edge only in one direction */
nedges++;
}
/*
* Now descend in depth-first search.
*/
if (pv[u].child >= 0) {
u = pv[u].child;
debug((" descending to %d\n", u));
continue;
}
}
if (u == v) {
debug((" back at %d, done this component\n", u));
break;
} else if (pv[u].sibling >= 0) {
u = pv[u].sibling;
debug((" sideways to %d\n", u));
} else {
u = pv[u].parent;
debug((" ascending to %d\n", u));
}
}
}
}
/*
* Second pass: index all the vertices in such a way that every
* subtree has a contiguous range of indices. (Easily enough done,
* by iterating through the tree structure we just built and
* numbering its elements as if they were those of a sorted list.)
*
* For each vertex, we compute the min and max index of the
* subtree starting there.
*
* (We index the vertices in preorder, per Tarjan's original
* description, so that each vertex's min subtree index is its own
* index; but that doesn't actually matter; either way round would
* do. The important thing is that we have a simple arithmetic
* criterion that tells us whether a vertex is in a given subtree
* or not.)
*/
debug(("--- begin indexing pass\n"));
index = 0;
for (v = 0; v < nvertices; v++)
pv[v].visited = FALSE;
pv[root].visited = TRUE;
u = pv[root].child;
while (1) {
if (!pv[u].visited) {
pv[u].visited = TRUE;
/*
* Index this node.
*/
pv[u].minindex = pv[u].index = index;
debug((" vertex %d <- index %d\n", u, index));
index++;
/*
* Now descend in depth-first search.
*/
if (pv[u].child >= 0) {
u = pv[u].child;
debug((" descending to %d\n", u));
continue;
}
}
if (u == root) {
debug((" back at %d, done indexing\n", u));
break;
}
/*
* As we re-ascend to here from its children (or find that we
* had no children to descend to in the first place), fill in
* its maxindex field.
*/
pv[u].maxindex = index-1;
debug((" vertex %d <- maxindex %d\n", u, pv[u].maxindex));
if (pv[u].sibling >= 0) {
u = pv[u].sibling;
debug((" sideways to %d\n", u));
} else {
u = pv[u].parent;
debug((" ascending to %d\n", u));
}
}
/*
* We're ready to generate output now, so initialise the output
* fields.
*/
for (v = 0; v < nvertices; v++)
pv[v].bridge = -1;
/*
* Final pass: determine the min and max index of the vertices
* reachable from every subtree, not counting the link back to
* each vertex's parent. Then our criterion is: given a vertex u,
* defining a subtree consisting of u and all its descendants, we
* compare the range of vertex indices _in_ that subtree (which is
* just the minindex and maxindex of u) with the range of vertex
* indices in the _neighbourhood_ of the subtree (computed in this
* final pass, and not counting u's own edge to its parent), and
* if the latter includes anything outside the former, then there
* must be some path from u to outside its subtree which does not
* go through the parent edge - i.e. the edge from u to its parent
* is part of a loop.
*/
debug(("--- begin min-max pass\n"));
nbridges = 0;
for (v = 0; v < nvertices; v++)
pv[v].visited = FALSE;
u = pv[root].child;
pv[root].visited = TRUE;
while (1) {
if (!pv[u].visited) {
pv[u].visited = TRUE;
/*
* Look for vertices reachable directly from u, including
* u itself.
*/
debug((" processing vertex %d\n", u));
pv[u].minreachable = pv[u].maxreachable = pv[u].minindex;
for (w = neighbour(u, ctx); w >= 0; w = neighbour(-1, ctx)) {
debug((" edge %d-%d\n", u, w));
if (w != pv[u].parent) {
int i = pv[w].index;
if (pv[u].minreachable > i)
pv[u].minreachable = i;
if (pv[u].maxreachable < i)
pv[u].maxreachable = i;
}
}
debug((" initial min=%d max=%d\n",
pv[u].minreachable, pv[u].maxreachable));
/*
* Now descend in depth-first search.
*/
if (pv[u].child >= 0) {
u = pv[u].child;
debug((" descending to %d\n", u));
continue;
}
}
if (u == root) {
debug((" back at %d, done min-maxing\n", u));
break;
}
/*
* As we re-ascend to this vertex, go back through its
* immediate children and do a post-update of its min/max.
*/
for (v = pv[u].child; v >= 0; v = pv[v].sibling) {
if (pv[u].minreachable > pv[v].minreachable)
pv[u].minreachable = pv[v].minreachable;
if (pv[u].maxreachable < pv[v].maxreachable)
pv[u].maxreachable = pv[v].maxreachable;
}
debug((" postorder update of %d: min=%d max=%d (indices %d-%d)\n", u,
pv[u].minreachable, pv[u].maxreachable,
pv[u].minindex, pv[u].maxindex));
/*
* And now we know whether each to our own parent is a bridge.
*/
if ((v = pv[u].parent) != root) {
if (pv[u].minreachable >= pv[u].minindex &&
pv[u].maxreachable <= pv[u].maxindex) {
/* Yes, it's a bridge. */
pv[u].bridge = v;
nbridges++;
debug((" %d-%d is a bridge\n", v, u));
} else {
debug((" %d-%d is not a bridge\n", v, u));
}
}
if (pv[u].sibling >= 0) {
u = pv[u].sibling;
debug((" sideways to %d\n", u));
} else {
u = pv[u].parent;
debug((" ascending to %d\n", u));
}
}
debug(("finished, nedges=%d nbridges=%d\n", nedges, nbridges));
/*
* Done.
*/
return nbridges < nedges;
}
/*
* Appendix: the long and painful history of loop detection in these puzzles
* =========================================================================
*
* For interest, I thought I'd write up the five loop-finding methods
* I've gone through before getting to this algorithm. It's a case
* study in all the ways you can solve this particular problem
* wrongly, and also how much effort you can waste by not managing to
* find the existing solution in the literature :-(
*
* Vertex dsf
* ----------
*
* Initially, in puzzles where you need to not have any loops in the
* solution graph, I detected them by using a dsf to track connected
* components of vertices. Iterate over each edge unifying the two
* vertices it connects; but before that, check if the two vertices
* are _already_ known to be connected. If so, then the new edge is
* providing a second path between them, i.e. a loop exists.
*
* That's adequate for automated solvers, where you just need to know
* _whether_ a loop exists, so as to rule out that move and do
* something else. But during play, you want to do better than that:
* you want to _point out_ the loops with error highlighting.
*
* Graph pruning
* -------------
*
* So my second attempt worked by iteratively pruning the graph. Find
* a vertex with degree 1; remove that edge; repeat until you can't
* find such a vertex any more. This procedure will remove *every*
* edge of the graph if and only if there were no loops; so if there
* are any edges remaining, highlight them.
*
* This successfully highlights loops, but not _only_ loops. If the
* graph contains a 'dumb-bell' shaped subgraph consisting of two
* loops connected by a path, then we'll end up highlighting the
* connecting path as well as the loops. That's not what we wanted.
*
* Vertex dsf with ad-hoc loop tracing
* -----------------------------------
*
* So my third attempt was to go back to the dsf strategy, only this
* time, when you detect that a particular edge connects two
* already-connected vertices (and hence is part of a loop), you try
* to trace round that loop to highlight it - before adding the new
* edge, search for a path between its endpoints among the edges the
* algorithm has already visited, and when you find one (which you
* must), highlight the loop consisting of that path plus the new
* edge.
*
* This solves the dumb-bell problem - we definitely now cannot
* accidentally highlight any edge that is *not* part of a loop. But
* it's far from clear that we'll highlight *every* edge that *is*
* part of a loop - what if there were multiple paths between the two
* vertices? It would be difficult to guarantee that we'd always catch
* every single one.
*
* On the other hand, it is at least guaranteed that we'll highlight
* _something_ if any loop exists, and in other error highlighting
* situations (see in particular the Tents connected component
* analysis) I've been known to consider that sufficient. So this
* version hung around for quite a while, until I had a better idea.
*
* Face dsf
* --------
*
* Round about the time Loopy was being revamped to include non-square
* grids, I had a much cuter idea, making use of the fact that the
* graph is planar, and hence has a concept of faces.
*
* In Loopy, there are really two graphs: the 'grid', consisting of
* all the edges that the player *might* fill in, and the solution
* graph of the edges the player actually *has* filled in. The
* algorithm is: set up a dsf on the *faces* of the grid. Iterate over
* each edge of the grid which is _not_ marked by the player as an
* edge of the solution graph, unifying the faces on either side of
* that edge. This groups the faces into connected components. Now,
* there is more than one connected component iff a loop exists, and
* moreover, an edge of the solution graph is part of a loop iff the
* faces on either side of it are in different connected components!
*
* This is the first algorithm I came up with that I was confident
* would successfully highlight exactly the correct set of edges in
* all cases. It's also conceptually elegant, and very easy to
* implement and to be confident you've got it right (since it just
* consists of two very simple loops over the edge set, one building
* the dsf and one reading it off). I was very pleased with it.
*
* Doing the same thing in Slant is slightly more difficult because
* the set of edges the user can fill in do not form a planar graph
* (the two potential edges in each square cross in the middle). But
* you can still apply the same principle by considering the 'faces'
* to be diamond-shaped regions of space around each horizontal or
* vertical grid line. Equivalently, pretend each edge added by the
* player is really divided into two edges, each from a square-centre
* to one of the square's corners, and now the grid graph is planar
* again.
*
* However, it fell down when - much later - I tried to implement the
* same algorithm in Net.
*
* Net doesn't *absolutely need* loop detection, because of its system
* of highlighting squares connected to the source square: an argument
* involving counting vertex degrees shows that if any loop exists,
* then it must be counterbalanced by some disconnected square, so
* there will be _some_ error highlight in any invalid grid even
* without loop detection. However, in large complicated cases, it's
* still nice to highlight the loop itself, so that once the player is
* clued in to its existence by a disconnected square elsewhere, they
* don't have to spend forever trying to find it.
*
* The new wrinkle in Net, compared to other loop-disallowing puzzles,
* is that it can be played with wrapping walls, or - topologically
* speaking - on a torus. And a torus has a property that algebraic
* topologists would know of as a 'non-trivial H_1 homology group',
* which essentially means that there can exist a loop on a torus
* which *doesn't* separate the surface into two regions disconnected
* from each other.
*
* In other words, using this algorithm in Net will do fine at finding
* _small_ localised loops, but a large-scale loop that goes (say) off
* the top of the grid, back on at the bottom, and meets up in the
* middle again will not be detected.
*
* Footpath dsf
* ------------
*
* To solve this homology problem in Net, I hastily thought up another
* dsf-based algorithm.
*
* This time, let's consider each edge of the graph to be a road, with
* a separate pedestrian footpath down each side. We'll form a dsf on
* those imaginary segments of footpath.
*
* At each vertex of the graph, we go round the edges leaving that
* vertex, in order around the vertex. For each pair of edges adjacent
* in this order, we unify their facing pair of footpaths (e.g. if
* edge E appears anticlockwise of F, then we unify the anticlockwise
* footpath of F with the clockwise one of E) . In particular, if a
* vertex has degree 1, then the two footpaths on either side of its
* single edge are unified.
*
* Then, an edge is part of a loop iff its two footpaths are not
* reachable from one another.
*
* This algorithm is almost as simple to implement as the face dsf,
* and it works on a wider class of graphs embedded in plane-like
* surfaces; in particular, it fixes the torus bug in the face-dsf
* approach. However, it still depends on the graph having _some_ sort
* of embedding in a 2-manifold, because it relies on there being a
* meaningful notion of 'order of edges around a vertex' in the first
* place, so you couldn't use it on a wildly nonplanar graph like the
* diamond lattice. Also, more subtly, it depends on the graph being
* embedded in an _orientable_ surface - and that's a thing that might
* much more plausibly change in future puzzles, because it's not at
* all unlikely that at some point I might feel moved to implement a
* puzzle that can be played on the surface of a Mobius strip or a
* Klein bottle. And then even this algorithm won't work.
*
* Tarjan's bridge-finding algorithm
* ---------------------------------
*
* And so, finally, we come to the algorithm above. This one is pure
* graph theory: it doesn't depend on any concept of 'faces', or 'edge
* ordering around a vertex', or any other trapping of a planar or
* quasi-planar graph embedding. It should work on any graph
* whatsoever, and reliably identify precisely the set of edges that
* form part of some loop. So *hopefully* this long string of failures
* has finally come to an end...
*/

View file

@ -0,0 +1,21 @@
# -*- makefile -*-
FLIP_EXTRA = tree234
flip : [X] GTK COMMON flip FLIP_EXTRA flip-icon|no-icon
flip : [G] WINDOWS COMMON flip FLIP_EXTRA flip.res|noicon.res
ALL += flip[COMBINED] FLIP_EXTRA
!begin am gtk
GAMES += flip
!end
!begin >list.c
A(flip) \
!end
!begin >gamedesc.txt
flip:flip.exe:Flip:Tile inversion puzzle:Flip groups of squares to light them all up at once.
!end

1349
apps/plugins/puzzles/flip.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
# -*- makefile -*-
flood : [X] GTK COMMON flood flood-icon|no-icon
flood : [G] WINDOWS COMMON flood flood.res|noicon.res
ALL += flood[COMBINED]
!begin am gtk
GAMES += flood
!end
!begin >list.c
A(flood) \
!end
!begin >gamedesc.txt
flood:flood.exe:Flood:Flood-filling puzzle:Turn the grid the same colour in as few flood fills as possible.
!end

1372
apps/plugins/puzzles/flood.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
# -*- makefile -*-
GALAXIES_EXTRA = dsf
galaxies : [X] GTK COMMON galaxies GALAXIES_EXTRA galaxies-icon|no-icon
galaxies : [G] WINDOWS COMMON galaxies GALAXIES_EXTRA galaxies.res|noicon.res
galaxiessolver : [U] galaxies[STANDALONE_SOLVER] GALAXIES_EXTRA STANDALONE m.lib
galaxiessolver : [C] galaxies[STANDALONE_SOLVER] GALAXIES_EXTRA STANDALONE
galaxiespicture : [U] galaxies[STANDALONE_PICTURE_GENERATOR] GALAXIES_EXTRA STANDALONE
+ m.lib
galaxiespicture : [C] galaxies[STANDALONE_PICTURE_GENERATOR] GALAXIES_EXTRA STANDALONE
ALL += galaxies[COMBINED] GALAXIES_EXTRA
!begin am gtk
GAMES += galaxies
!end
!begin >list.c
A(galaxies) \
!end
!begin >gamedesc.txt
galaxies:galaxies.exe:Galaxies:Symmetric polyomino puzzle:Divide the grid into rotationally symmetric regions each centred on a dot.
!end

File diff suppressed because it is too large Load diff

2896
apps/plugins/puzzles/grid.c Normal file

File diff suppressed because it is too large Load diff

132
apps/plugins/puzzles/grid.h Normal file
View file

@ -0,0 +1,132 @@
/*
* (c) Lambros Lambrou 2008
*
* Code for working with general grids, which can be any planar graph
* with faces, edges and vertices (dots). Includes generators for a few
* types of grid, including square, hexagonal, triangular and others.
*/
#ifndef PUZZLES_GRID_H
#define PUZZLES_GRID_H
#include "puzzles.h" /* for random_state */
/* Useful macros */
#define SQ(x) ( (x) * (x) )
/* ----------------------------------------------------------------------
* Grid structures:
* A grid is made up of faces, edges and dots. These structures hold
* the incidence relationships between these types. For example, an
* edge always joins two dots, and is adjacent to two faces.
* The "grid_xxx **" members are lists of pointers which are dynamically
* allocated during grid generation.
* A pointer to a face/edge/dot will always point somewhere inside one of the
* three lists of the main "grid" structure: faces, edges, dots.
* Could have used integer offsets into these lists, but using actual
* pointers instead gives us type-safety.
*/
/* Need forward declarations */
typedef struct grid_face grid_face;
typedef struct grid_edge grid_edge;
typedef struct grid_dot grid_dot;
struct grid_face {
int order; /* Number of edges, also the number of dots */
grid_edge **edges; /* edges around this face */
grid_dot **dots; /* corners of this face */
/*
* For each face, we optionally compute and store its 'incentre'.
* The incentre of a triangle is the centre of a circle tangent to
* all three edges; I generalise the concept to arbitrary polygons
* by defining it to be the centre of the largest circle you can fit
* anywhere in the polygon. It's a useful thing to know because if
* you want to draw any symbol or text in the face (e.g. clue
* numbers in Loopy), that's the place it will most easily fit.
*
* When a grid is first generated, no face has this information
* computed, because it's fiddly to do. You can call
* grid_find_incentre() on a face, and it will fill in ix,iy below
* and set has_incentre to indicate that it's done so.
*/
int has_incentre;
int ix, iy; /* incentre (centre of largest inscribed circle) */
};
struct grid_edge {
grid_dot *dot1, *dot2;
grid_face *face1, *face2; /* Use NULL for the infinite outside face */
};
struct grid_dot {
int order;
grid_edge **edges;
grid_face **faces; /* A NULL grid_face* means infinite outside face */
/* Position in some fairly arbitrary (Cartesian) coordinate system.
* Use large enough values such that we can get away with
* integer arithmetic, but small enough such that arithmetic
* won't overflow. */
int x, y;
};
typedef struct grid {
/* These are (dynamically allocated) arrays of all the
* faces, edges, dots that are in the grid. */
int num_faces; grid_face *faces;
int num_edges; grid_edge *edges;
int num_dots; grid_dot *dots;
/* Cache the bounding-box of the grid, so the drawing-code can quickly
* figure out the proper scaling to draw onto a given area. */
int lowest_x, lowest_y, highest_x, highest_y;
/* A measure of tile size for this grid (in grid coordinates), to help
* the renderer decide how large to draw the grid.
* Roughly the size of a single tile - for example the side-length
* of a square cell. */
int tilesize;
/* We really don't want to copy this monstrosity!
* A grid is immutable once generated.
*/
int refcount;
} grid;
/* Grids are specified by type: GRID_SQUARE, GRID_KITE, etc. */
#define GRIDGEN_LIST(A) \
A(SQUARE,square) \
A(HONEYCOMB,honeycomb) \
A(TRIANGULAR,triangular) \
A(SNUBSQUARE,snubsquare) \
A(CAIRO,cairo) \
A(GREATHEXAGONAL,greathexagonal) \
A(OCTAGONAL,octagonal) \
A(KITE,kites) \
A(FLORET,floret) \
A(DODECAGONAL,dodecagonal) \
A(GREATDODECAGONAL,greatdodecagonal) \
A(PENROSE_P2,penrose_p2_kite) \
A(PENROSE_P3,penrose_p3_thick)
#define ENUM(upper,lower) GRID_ ## upper,
typedef enum grid_type { GRIDGEN_LIST(ENUM) GRID_TYPE_MAX } grid_type;
#undef ENUM
/* Free directly after use if non-NULL. Will never contain an underscore
* (so clients can safely use that as a separator). */
char *grid_new_desc(grid_type type, int width, int height, random_state *rs);
char *grid_validate_desc(grid_type type, int width, int height,
const char *desc);
grid *grid_new(grid_type type, int width, int height, const char *desc);
void grid_free(grid *g);
grid_edge *grid_nearest_edge(grid *g, int x, int y);
void grid_compute_size(grid_type type, int width, int height,
int *tilesize, int *xextent, int *yextent);
void grid_find_incentre(grid_face *f);
#endif /* PUZZLES_GRID_H */

3215
apps/plugins/puzzles/gtk.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
# -*- makefile -*-
guess : [X] GTK COMMON guess guess-icon|no-icon
guess : [G] WINDOWS COMMON guess guess.res|noicon.res
ALL += guess[COMBINED]
!begin am gtk
GAMES += guess
!end
!begin >list.c
A(guess) \
!end
!begin >gamedesc.txt
guess:guess.exe:Guess:Combination-guessing puzzle:Guess the hidden combination of colours.
!end

1518
apps/plugins/puzzles/guess.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
Black Box
<p>
Determine where the hidden balls are in the box, by observing the
behaviour of light beams fired into the box from the sides.
<p>
Click in a square around the edge of the box to send a beam into the
box. Possible results are 'H' (the beam hit a ball dead-on and
stopped), 'R' (the beam was either reflected back the way it came or
there was a ball just to one side of its entry point) or a number
appearing in two squares (indicating that the beam entered one of
those squares and emerged from the other).
<p>
Click in the middle of the box to place your guessed ball positions.
When you have placed enough, a green button will appear in the top
left; click that to indicate that you think you have the answer.
You can also right-click to mark squares as definitely known.

View file

@ -0,0 +1,13 @@
Bridges
<p>
Draw horizontal or vertical bridges to link up all the islands.
Bridges may be single or double; they may not cross; the islands
must all end up connected to each other; the number in each island
must match the number of bridges that end at that island (counting
double bridges as two). Note that loops of bridges are permitted.
<p>
Click on an island and drag left, right, up or down to draw a bridge
to the next island in that direction. Do the same again to create a
double bridge, and again to remove the bridge if you change your
mind. Click on an island without dragging to mark the island as
completed once you think you have placed all its bridges.

View file

@ -0,0 +1,14 @@
Cube
<p>
Roll the cube around the grid, picking up the blue squares on its
faces. Try to get all the blue squares on to the object at the same
time, in as few moves as possible.
<p>
Use the arrow keys to roll the cube, or click the mouse where you
want it to roll towards. After every roll, the grid square and cube
face that you brought into contact swap their colours, so that a
non-blue cube face can pick up a blue square, but a blue face rolled
on to a non-blue square puts it down again.
<p>
When you have mastered the cube, use the Type menu to select other
regular solids!

View file

@ -0,0 +1,10 @@
Dominosa
<p>
Tile the rectangle with dominoes (1&times;2 rectangles) so that
every possible domino appears exactly once (that is, every possible
pair of numbers, including doubles).
<p>
Click between two adjacent numbers to place or remove a domino.
Right-click to place a line between numbers if you think a domino
definitely cannot go there. Dominoes light up red if two identical
ones appear on the grid.

View file

@ -0,0 +1,6 @@
Fifteen
<p>
Slide the tiles around the box until they appear in numerical order
from the top left, with the hole in the bottom right corner.
<p>
Click on a tile to slide it towards the hole.

View file

@ -0,0 +1,12 @@
Filling
<p>
Write a number in every blank square of the grid. When the grid is
full, every orthogonally connected group of identical numbers should
have an area equal to that number: so 1s always appear alone, 2s in
pairs, and so on.
<p>
To place a number, click the mouse in a blank square to select it,
then type the number you want on the keyboard. You can also drag to
select multiple squares, and then type a number to place it in all
of them. To erase numbers, select one or more squares in the same
way and then press Backspace.

View file

@ -0,0 +1,10 @@
Flip
<p>
Try to light up all the squares in the grid by flipping combinations
of them.
<p>
Click in a square to flip it and some of its neighbours. The diagram
in each square indicates which other squares will flip.
<p>
Select one of the 'Random' settings from the Type menu for more
varied puzzles.

View file

@ -0,0 +1,8 @@
Flood
<p>
Try to get the whole grid to be the same colour within the given
number of moves, by repeatedly flood-filling the top left corner in
different colours.
<p>
Click in a square to flood-fill the top left corner with that square's
colour.

View file

@ -0,0 +1,11 @@
Galaxies
<p>
Draw lines along grid edges so as to divide the grid up into
regions. Every region should have two-way rotational symmetry, and
should contain exactly one dot which is in its centre.
<p>
Click on a grid edge to add or remove a line. Right-click on a dot
and drag the mouse to place an arrow in a grid square pointing to
that dot, to indicate that you think that square must belong in the
same region as that dot. Right-drag an existing arrow to move it, or
drop it off the edge of the grid to remove it.

View file

@ -0,0 +1,52 @@
unfinished:Group
<p>
Fill in the grid with the letters shown to the top and left of it, so
that the full grid is a valid
<a href="http://en.wikipedia.org/wiki/Cayley_table">Cayley table</a>
for a
<a href="http://en.wikipedia.org/wiki/Group_(mathematics)">group</a>.
<p>
If you don't already know what a group is, I don't really recommend
trying to play this game. But if you want to try anyway, the above is
equivalent to saying that the following conditions must be satisfied:
<ul>
<li>
<strong>Latin square</strong>. Every row and column must contain
exactly one of each letter.
<li>
<strong>Identity</strong>. There must be some letter <i>e</i> such
that, for all <i>a</i>, the letter in row <i>e</i> column <i>a</i> and
the one in row <i>a</i> column <i>e</i> are both <i>a</i>. In the
default mode, this letter is always <i>e</i> and its row and column
are filled in for you; by reconfiguring the game using the Type menu,
you can select a mode in which you have to work out which letter is
the identity.
<li>
<strong>Inverses</strong>. For every letter <i>a</i>, there must be
some letter <i>b</i> (which may sometimes be the same letter
as <i>a</i>) such that the letters in row <i>a</i> column <i>b</i> and
in row <i>b</i> column <i>a</i> are both the identity letter (as
defined above).
<li>
<strong>Associativity</strong>. For every combination of
letters <i>a</i>, <i>b</i>, and <i>c</i>, denote the letter in
row <i>a</i> column <i>b</i> by <i>d</i>, and the one in row <i>b</i>
column <i>c</i> by <i>e</i>. Then the letters in row <i>d</i>
column <i>c</i> and in row <i>a</i> column <i>e</i> must be the same.
</ul>
<p>
To place a letter, click in a square to select it, then type the
letter on the keyboard. To erase a letter, click to select a square
and then press Backspace.
<p>
Right-click in a square and then type a letter to add or remove the
number as a pencil mark, indicating letters that you think
<em>might</em> go in that square.
<p>
You can rearrange the order of elements in the rows and columns by
dragging the column or row headings back and forth. (The rows and
columns will stay in sync with each other.) Also,
left-clicking <em>between</em> two row or column headings will add or
remove a thick line between those two rows and the corresponding pair
of columns (which is useful if you're considering a subgroup and its
cosets).

View file

@ -0,0 +1,12 @@
Guess
<p>
Try to guess the hidden combination of colours. You will be given
limited information about each guess you make, enabling you to
refine the next guess.
<p>
Drag from the colours on the left into the topmost unfilled row to
make a guess; then click on the small circles to submit that guess.
The small circles give you your feedback: black pegs indicate how
many of the colours you guessed were the right colour in the right
place, and white pegs indicate how many of the rest were the right
colours but in the wrong place.

View file

@ -0,0 +1,14 @@
Inertia
<p>
Slide the ball around the grid picking up the gems. Every time the
ball moves, it will keep sliding until it either hits a wall, or
stops on a stop square (the broken circles). Try to collect every
gem without running into any of the mines.
<p>
Use the numeric keypad to slide the ball horizontally, vertically or
diagonally. Alternatively, click on the grid to make the ball move
towards where you clicked.
<p>
If you hit a mine and explode, you can select Undo from the Game
menu and continue playing; the game will track how many times you
died.

View file

@ -0,0 +1,104 @@
#!/usr/bin/perl
use strict;
use warnings;
open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n";
my $footer = "";
$footer .= $_ while <$footerfile>;
close $footerfile;
for my $arg (@ARGV) {
$arg =~ /(.*\/)?([^\/]+)\.html$/ or die;
my $filename = $2;
open my $gamefile, "<", $arg or die "$arg: open: $!\n";
my $unfinished = 0;
my $docname = $filename;
chomp(my $puzzlename = <$gamefile>);
while ($puzzlename =~ s/^([^:=]+)(=([^:]+))?://) {
if ($1 eq "unfinished") {
$unfinished = 1;
} elsif ($1 eq "docname") {
$docname = $3;
} else {
die "$arg: unknown keyword '$1'\n";
}
}
my $instructions = "";
$instructions .= $_ while <$gamefile>;
close $gamefile;
open my $outpage, ">", "${filename}.html";
my $unfinishedtitlefragment = $unfinished ? "an unfinished puzzle " : "";
my $unfinishedheading = $unfinished ? "<h2 align=center>an unfinished puzzle</h2>\n" : "";
my $unfinishedpara;
my $links;
if ($unfinished) {
$unfinishedpara = <<EOF;
<p>
You have found your way to a page containing an <em>unfinished</em>
puzzle in my collection, not linked from the <a href="../">main
puzzles page</a>. Don't be surprised if things are hard to understand
or don't work as you expect.
EOF
$links = <<EOF;
<p align="center">
<a href="../">Back to main puzzles page</a> (which does not link to this)
EOF
} else {
$unfinishedpara = "";
$links = <<EOF;
<p align="center">
<a href="../doc/${docname}.html#${docname}">Full instructions</a>
|
<a href="../">Back to main puzzles page</a>
EOF
}
print $outpage <<EOF;
<html>
<head>
<title>${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection</title>
<link rel="stylesheet" type="text/css" href="../../sitestyle.css" name="Simon Tatham's Home Page Style">
<script type="text/javascript" src="resize-puzzle-applet.js"></script>
</head>
<body onLoad="initResizablePuzzleApplet();">
<h1 align=center>${puzzlename}</h1>
${unfinishedheading}
<h2 align=center>from Simon Tatham's Portable Puzzle Collection</h2>
${unfinishedpara}
<p align="center">
<table cellpadding="0">
<tr>
<td>
<applet id="applet" archive="${filename}.jar" code="PuzzleApplet"
width="700" height="500">
</applet>
</td>
<td>
<div id="eresize" style="width:5px;height:500px;cursor:e-resize;"></div>
</td>
</tr>
<td>
<div id="sresize" style="width:700px;height:5px;cursor:s-resize;"></div>
</td>
<td>
<div id="seresize" style="width:5px;height:5px;cursor:se-resize;"></div>
</td>
</tr>
</table>
${instructions}
${links}
${footer}
</body>
</html>
EOF
close $outpage;
}

View file

@ -0,0 +1,120 @@
#!/usr/bin/perl
use strict;
use warnings;
open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n";
my $footer = "";
$footer .= $_ while <$footerfile>;
close $footerfile;
for my $arg (@ARGV) {
$arg =~ /(.*\/)?([^\/]+)\.html$/ or die;
my $filename = $2;
open my $gamefile, "<", $arg or die "$arg: open: $!\n";
my $unfinished = 0;
my $docname = $filename;
chomp(my $puzzlename = <$gamefile>);
while ($puzzlename =~ s/^([^:=]+)(=([^:]+))?://) {
if ($1 eq "unfinished") {
$unfinished = 1;
} elsif ($1 eq "docname") {
$docname = $3;
} else {
die "$arg: unknown keyword '$1'\n";
}
}
my $instructions = "";
$instructions .= $_ while <$gamefile>;
close $gamefile;
open my $outpage, ">", "${filename}.html";
my $unfinishedtitlefragment = $unfinished ? "an unfinished puzzle " : "";
my $unfinishedheading = $unfinished ? "<h2 align=center>an unfinished puzzle</h2>\n" : "";
my $unfinishedpara;
my $links;
if ($unfinished) {
$unfinishedpara = <<EOF;
<p>
You have found your way to a page containing an <em>unfinished</em>
puzzle in my collection, not linked from the <a href="../">main
puzzles page</a>. Don't be surprised if things are hard to understand
or don't work as you expect.
EOF
$links = <<EOF;
<p align="center">
<a href="../">Back to main puzzles page</a> (which does not link to this)
EOF
} else {
$unfinishedpara = "";
$links = <<EOF;
<p align="center">
<a href="../doc/${docname}.html#${docname}">Full instructions</a>
|
<a href="../">Back to main puzzles page</a>
EOF
}
print $outpage <<EOF;
<!DOCTYPE html>
<html>
<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>
</head>
<body onLoad="initPuzzle();">
<h1 align=center>${puzzlename}</h1>
${unfinishedheading}
<h2 align=center>from Simon Tatham's Portable Puzzle Collection</h2>
${unfinishedpara}
<hr>
<div id="puzzle" style="display: none">
<p align=center>
<input type="button" id="new" value="New game">
<input type="button" id="restart" value="Restart game">
<input type="button" id="undo" value="Undo move">
<input type="button" id="redo" value="Redo move">
<input type="button" id="solve" value="Solve game">
<input type="button" id="specific" value="Enter game ID">
<input type="button" id="random" value="Enter random seed">
<select id="gametype"></select>
</p>
<div align=center>
<div id="resizable" style="position:relative; left:0; top:0">
<canvas style="display: block" id="puzzlecanvas" width="1px" height="1px" tabindex="1">
</canvas>
<div id="statusbarholder" style="display: block">
</div>
</div>
<p>
Link to this puzzle:
<a id="permalink-desc">by game ID</a>
<a id="permalink-seed">by random seed</a>
</p>
</div>
</div>
<div id="apology">
Sorry, this Javascript puzzle doesn't seem to work in your web
browser. Perhaps you have Javascript disabled, or perhaps your browser
doesn't provide a feature that the puzzle code requires (such as
<a href="https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays">typed arrays</a>).
These puzzles have been successfully run in Firefox 19, Chrome 26,
Internet Explorer 10 and Safari 6.
</div>
<hr>
${instructions}
${links}
${footer}
</body>
</html>
EOF
close $outpage;
}

View file

@ -0,0 +1,15 @@
Keen
<p>
Fill in the grid with digits from 1 to the grid size, so that every
digit appears exactly once in each row and column, and so that all
the arithmetic clues are satisfied (i.e. the clue number in each
thick box should be possible to construct from the digits in the box
using the specified arithmetic operation).
<p>
To place a number, click in a square to select it, then type the
number on the keyboard. To erase a number, click to select a square
and then press Backspace.
<p>
Right-click in a square and then type a number to add or remove the
number as a pencil mark, indicating numbers that you think
<em>might</em> go in that square.

View file

@ -0,0 +1,10 @@
Light Up
<p>
Place light bulbs in the grid so as to light up all the blank
squares. A light illuminates its own square and all the squares in
the same row or column unless blocked by walls (black squares).
Lights may not illuminate each other. Each numbered square must be
orthogonally adjacent to exactly the given number of lights.
<p>
Click on a square to place or remove a light. Right-click to place a
dot indicating that you think there is no light in that square.

View file

@ -0,0 +1,13 @@
Loopy
<p>
Form a single closed loop out of the grid edges, in such a way that
every numbered square has exactly that many of its edges included in
the loop.
<p>
Click on a grid edge to mark it as part of the loop (black), and
again to return to marking it as undecided (yellow). Right-click on
a grid edge to mark it as definitely not part of the loop (faint
grey), and again to mark it as undecided again.
<p>
When you have mastered the square grid, look in the Type menu for
many other types of tiling!

View file

@ -0,0 +1,17 @@
Magnets
<p>
Fill each domino shape with either a magnet (consisting of a + and
&#8722; pole) or a neutral domino (green).
<p>
The number of + poles that in each row and column must match the
numbers along the top and left; the number of &#8722; poles must
match the numbers along the bottom and right. Two + poles may not be
orthogonally adjacent to each other, and similarly two &#8722; poles.
<p>
Left-click a domino to toggle it between being empty and being a
magnet (the + is placed in the end you click). Right-click to toggle
between empty, neutral, and a ?? mark indicating that you're sure
it's a magnet but don't yet know which way round it goes.
<p>
Left-click a clue to mark it as done (grey it out). To unmark a clue
as done, left-click it again.

View file

@ -0,0 +1,15 @@
Map
<p>
Colour the map with four colours, so that no two adjacent regions
have the same colour. (Regions touching at only one corner do not
count as adjacent.) There is a unique colouring consistent with the
coloured regions you are already given.
<p>
Drag from a coloured region to a blank one to colour the latter the
same colour as the former. Drag from outside the grid into a region
to erase its colour. (You cannot change the colours of the regions
you are given at the start of the game.)
<p>
Right-drag from a coloured region to a blank one to add dots marking
the latter region as <em>possibly</em> the same colour as the
former, or to remove those dots again.

View file

@ -0,0 +1,18 @@
Mines
<p>
Try to expose every square in the grid that is not one of the hidden
mines, without opening any square that is a mine.
<p>
Click in a square to open it. Every opened square are marked with
the number of mines in the surrounding 8 squares, if there are any;
if not, all the surrounding squares are automatically opened.
<p>
Right-click in a square to mark it with a flag if you think it is a
mine. If a numbered square has exactly the right number of flags
around it, you can click in it to open all the squares around it
that are not flagged.
<p>
The first square you open is guaranteed to be safe, and (by default)
you are guaranteed to be able to solve the whole grid by deduction
rather than guesswork. (Deductions may require you to think about
the total number of mines.)

View file

@ -0,0 +1,17 @@
Net
<p>
Rotate the grid squares so that they all join up into a single
connected network with no loops.
<p>
Left-click in a square to rotate it anticlockwise. Right-click to
rotate it clockwise. Middle-click, or shift-left-click if you have
no middle mouse button, to lock a square once you think it is
correct (so you don't accidentally rotate it again); do the same
again to unlock it if you change your mind.
<p>
Squares connected to the middle square are lit up. Aim to light up
every square in the grid (not just the endpoint blobs).
<p>
When this gets too easy, select a 'wrapping' variant from the Type
menu to enable grid lines to run off one edge of the playing area
and come back on the opposite edge!

View file

@ -0,0 +1,14 @@
Netslide
<p>
Slide the grid squares around so that they all join up into a single
connected network with no loops.
<p>
Click on the arrows at the edges of the grid to move a row or column
left, right, up or down. The square that falls off the end of the
row comes back on the other end.
<p>
Squares connected to the middle square are lit up. Aim to light up
every square in the grid (not just the endpoint blobs).
<p>
Connecting across a red barrier line is forbidden. On harder levels,
there are fewer barriers, which makes it harder rather than easier!

View file

@ -0,0 +1,11 @@
Palisade
<p>
Draw lines along the grid edges, in such a way that the grid is
divided into connected regions, all of the size shown in the status
line. Also, each square containing a number should have that many of
its edges drawn in.
<p>
Click on a grid edge to mark it as a division between regions (black),
and again to return to marking it as undecided (yellow). Right-click
on a grid edge to mark it as definitely not part of the loop (faint
grey), and again to mark it as undecided again.

View file

@ -0,0 +1,12 @@
Pattern
<p>
Fill in the grid with a pattern of black and white squares, so that
the numbers in each row and column match the lengths of consecutive
runs of black squares.
<p>
Left-click in a square to mark it black; right-click (or hold Ctrl
while left-clicking) to mark it white. Click and drag along a row or
column to mark multiple squares black or white at once. Middle-click
(or hold Shift while left-clicking) to return a square to grey
(meaning undecided): dragging like that can erase a whole rectangle,
not just a row or column.

View file

@ -0,0 +1,13 @@
Pearl
<p>
Draw a single closed loop by connecting together the centres of
adjacent grid squares, so that some squares end up as corners, some as
straights (horizontal or vertical), and some may be empty. Every
square containing a black circle must be a corner not connected
directly to another corner; every square containing a white circle
must be a straight which is connected to <em>at least one</em> corner.
<p>
Drag between squares to draw or undraw pieces of the loop.
Alternatively, left-click the edge between two squares to turn it on
or off. Right-click an edge to mark it with a cross indicating that
you are sure the loop does not go through it.

View file

@ -0,0 +1,8 @@
Pegs
<p>
Jump one peg over another to remove the one you jumped over. Try to
remove all but one peg.
<p>
Drag a peg into an empty space to make a move. The target space must
be exactly two holes away from the starting peg, in an orthogonal
direction, and there must be a peg in the hole in between.

View file

@ -0,0 +1,21 @@
Range
<p>
Colour some squares black, so as to meet the following conditions:
<ul>
<li>
No two black squares are orthogonally adjacent.
<li>
No group of white squares is separated from the rest of the grid by
black squares.
<li>
Each numbered cell can see precisely that many white squares in
total by looking in all four orthogonal directions, counting itself.
(Black squares block the view. So, for example, a 2 clue must be
adjacent to three black squares or grid edges, and in the fourth
direction there must be one white square and then a black one beyond
it.)
</ul>
<p>
Left-click to colour a square black. Right-click to mark a square
with a dot, if you know it should not be black.

View file

@ -0,0 +1,10 @@
docname=rectangles:Rectangles
<p>
Draw lines along the grid edges to divide the grid into rectangles,
so that each rectangle contains exactly one numbered square and its
area is equal to the number written in that square.
<p>
Click and drag from one grid corner to another, or from one square
centre to another, to draw a rectangle. You can also drag along a
grid line to just draw a line at a time, or just click on a single
grid edge to draw or erase it.

View file

@ -0,0 +1,14 @@
Same Game
<p>
Try to empty the playing area completely, by removing connected
groups of two or more squares of the same colour. Then try to score
as much as possible, by removing large groups at a time instead of
small ones.
<p>
Click on a coloured square to highlight the rest of its connected
group. The status line will print the number of squares selected,
and the score you would gain by removing them. Click again to remove
the group; other squares will fall down to fill the space, and if
you empty a whole column then the other columns will move up. You
cannot remove a single isolated square: try to avoid dead-end
positions where all remaining squares are isolated.

View file

@ -0,0 +1,14 @@
Signpost
<p>
Connect all the squares together into a sequence, so that every
square's arrow points towards the square that follows it (though the
next square can be any distance away in that direction).
<p>
Left-drag from a square to the square that should follow it, or
right-drag from a square to the square that should precede it.
<p>
Left-drag a square off the grid to break all links to it. Right-drag
a square off the grid to break all links to it and everything else
in its connected chain.

View file

@ -0,0 +1,11 @@
Singles
<p>
Black out some of the squares, in such a way that:
<ul><li>no number appears twice in any row or column
<li>no two black squares are adjacent
<li>the white squares form a single connected group (connections
along diagonals do not count).</ul>
<p>
Click in a square to black it out, and again to uncover it.
Right-click in a square to mark it with a circle, indicating that
you're sure it should <em>not</em> be blacked out.

View file

@ -0,0 +1,8 @@
Sixteen
<p>
Slide the grid squares around so that the numbers end up in
consecutive order from the top left corner.
<p>
Click on the arrows at the edges of the grid to move a row or column
left, right, up or down. The square that falls off the end of the
row comes back on the other end.

View file

@ -0,0 +1,9 @@
Slant
<p>
Fill in a diagonal line in every grid square so that there are no
loops in the grid, and so that every numbered point has that many
lines meeting at it.
<p>
Left-click in a square to mark it with a <code>\</code>; right-click
to mark it with a <code>/</code>. Keep clicking in a square to
cycle it between <code>\</code>, <code>/</code> and empty.

View file

@ -0,0 +1,20 @@
Solo
<p>
Fill in a number in every square so that every number appears
exactly once in each row, each column and each block marked by thick
lines.
<p>
To place a number, click in a square to select it, then type the
number on the keyboard. To erase a number, click to select a square
and then press Backspace.
<p>
Right-click in a square and then type a number to add or remove the
number as a pencil mark, indicating numbers that you think
<em>might</em> go in that square.
<p>
When you master the basic game, try Jigsaw mode (irregularly shaped
blocks), X mode (the two main diagonals of the grid must also
contain every number once), Killer mode (instead of single-cell
clues you are given regions of the grid each of which must add up to
a given total, again without reusing any digits), or all of those at
once!

View file

@ -0,0 +1,20 @@
Tents
<p>
Place tents in the empty squares in such a way that:
<ul>
<li>no two tents are adjacent, even diagonally
<li>the number of tents in each row and column matches the numbers
around the edge of the grid
<li>it is possible to match tents to trees so that each tree is
orthogonally adjacent to its own tent (but may also be adjacent to
other tents).
</ul>
<p>
Click in a square to place or remove a tent. Right-click to mark a
square as empty (not a tent). Right-click and drag along a row or
column to mark many squares at once as empty.
<p>
Warning '!' marks appear to indicate adjacent tents. Numbers round
the edge of the grid light up red to indicate they do not match the
number of tents in the row. Groups of tents light up red to indicate
that they have too few trees between them, and vice versa.

View file

@ -0,0 +1,22 @@
Towers
<p>
Fill in the grid with towers whose heights range from 1 to the grid
size, so that every possible height appears exactly once in each row
and column, and so that each clue around the edge counts the number
of towers that are visible when looking into the grid from that
direction. (Taller towers hide shorter ones behind them. So the
sequence 2,1,4,3,5 would match a clue of 3 on the left, because the
1 is hidden behind the 2 and the 3 is hidden behind the 4. On the
right, it would match a clue of 1 because the 5 hides everything
else.)
<p>
To place a tower, click in a square to select it, then type the
desired height on the keyboard. To erase a tower, click to select a
square and then press Backspace.
<p>
Right-click in a square and then type a number to add or remove the
number as a pencil mark, indicating tower heights that you think
<em>might</em> go in that square.
<p>
Left-click on a clue to mark it as done (grey it out). To unmark a
clue as done, left-click on it again.

View file

@ -0,0 +1,19 @@
Tracks
<p>
Complete the track from A to B so that the rows and
columns contain the same number of track segments as are indicated in the
clues to the top and right of the grid. There are only straight and
90-degree curved rail sections, and the track may not cross itself.
<p>
Left-click on an edge between two squares to add a track segment between
the two squares. Right-click on an edge to add a cross on the edge,
indicating no track is possible there.
<p>
Left-click in a square to add a colour indicator showing that you know the
square must contain a track, even if you don't know which edges it crosses
yet. Right-click in a square to add a cross indicating it contains no
track segment.
<p>
Left- or right-drag between squares to lay a straight line of is-track or
is-not-track indicators, useful for filling in rows or columns to match the
clue.

View file

@ -0,0 +1,15 @@
Twiddle
<p>
Rotate square sections of the grid to arrange the squares into
numerical order starting from the top left.
<p>
In the basic game, you rotate a 2&times;2 square section. Left-click
in the centre of that section (i.e. on a corner point between four
squares) to rotate the whole section anticlockwise. Right-click to
rotate the section clockwise.
<p>
When you master the basic game, go to the Type menu to try it with
larger rotating groups (for a 3&times;3 group you must click in the
centre of a square to rotate the block around it). Or select the
'orientable' mode in which every square must end up the right way
round as well as in the right place. Or both!

View file

@ -0,0 +1,22 @@
Undead
<p>
Fill in every grid square which doesn't contain a mirror with either a
ghost, a vampire, or a zombie. The numbers round the grid edges show
how many monsters must be visible along your line of sight if you look
directly into the grid from that position, along a row or column.
Zombies are always visible; ghosts are only visible when reflected in
at least one mirror; vampires are only visible when not reflected in
any mirror.
<p>
To place a monster, click in a square to select it, then type the
monster's letter on the keyboard: G for a ghost, V for a vampire or Z
for a zombie. To erase a monster, click to select a square and then
press Backspace.
<p>
Right-click in a square and then type a letter to add or remove the
monster as a pencil mark, indicating monsters that you think
<em>might</em> go in that square.
<p>
Left-click on a clue to mark it as done (grey it out). To unmark a
clue as done, left-click on it again.

View file

@ -0,0 +1,14 @@
Unequal
<p>
Fill in the grid with numbers from 1 to the grid size, so that every
number appears exactly once in each row and column, and so that all
the <code>&lt;</code> signs represent true inequalities (i.e. the
number at the pointed end is smaller than the number at the open end).
<p>
To place a number, click in a square to select it, then type the
number on the keyboard. To erase a number, click to select a square
and then press Backspace.
<p>
Right-click in a square and then type a number to add or remove the
number as a pencil mark, indicating numbers that you think
<em>might</em> go in that square.

View file

@ -0,0 +1,11 @@
Unruly
<p>
Colour every square either black or white, in such a way that:
<ul><li>no three consecutive squares, horizontally or vertically, are
the same colour
<li>each row and column contains the same number of black and white
squares.</ul>
<p>
Left-click in an empty square to turn it black, or right-click to turn
it white. Click again in an already-filled square to cycle it between
black and white and empty; middle-click to reset any square to empty.

View file

@ -0,0 +1,5 @@
Untangle
<p>
Move the points around so that none of the lines cross.
<p>
Click on a point and drag it to move it.

View file

@ -0,0 +1,153 @@
# Makefile for Puzzles icons.
PUZZLES = blackbox bridges cube dominosa fifteen filling flip flood \
galaxies guess inertia keen lightup loopy magnets map mines \
net netslide palisade pattern pearl pegs range rect \
samegame signpost singles sixteen slant solo tents towers \
twiddle tracks undead unequal unruly untangle
BASE = $(patsubst %,%-base.png,$(PUZZLES))
WEB = $(patsubst %,%-web.png,$(PUZZLES))
IBASE = $(patsubst %,%-ibase.png,$(PUZZLES))
IBASE4 = $(patsubst %,%-ibase4.png,$(PUZZLES))
P48D24 = $(patsubst %,%-48d24.png,$(PUZZLES))
P48D8 = $(patsubst %,%-48d8.png,$(PUZZLES))
P48D4 = $(patsubst %,%-48d4.png,$(PUZZLES))
P32D24 = $(patsubst %,%-32d24.png,$(PUZZLES))
P32D8 = $(patsubst %,%-32d8.png,$(PUZZLES))
P32D4 = $(patsubst %,%-32d4.png,$(PUZZLES))
P16D24 = $(patsubst %,%-16d24.png,$(PUZZLES))
P16D8 = $(patsubst %,%-16d8.png,$(PUZZLES))
P16D4 = $(patsubst %,%-16d4.png,$(PUZZLES))
ICONS = $(patsubst %,%.ico,$(PUZZLES))
CICONS = $(patsubst %,%-icon.c,$(PUZZLES))
RC = $(patsubst %,%.rc,$(PUZZLES))
BIN = ../
PIC = ./
# Work around newer ImageMagick unilaterally distorting colours when
# converting to PNG.
CSP = -set colorspace RGB
base: $(BASE)
web: $(WEB)
pngicons: $(P48D24) $(P32D24) $(P16D24)
winicons: $(ICONS) $(RC)
gtkicons: $(CICONS)
all: base web pngicons winicons gtkicons
# Build the base puzzle screenshots from which all the other images
# are derived. Some of them involve showing a move animation
# part-way through.
fifteen-base.png : override REDO=0.3
flip-base.png : override REDO=0.3
netslide-base.png : override REDO=0.3
sixteen-base.png : override REDO=0.3
twiddle-base.png : override REDO=0.3
$(BASE): %-base.png: $(BIN)% $(PIC)%.sav
$(PIC)screenshot.sh $(BIN)$* $(PIC)$*.sav $@ $(REDO)
# Build the screenshots for the web, by scaling the original base
# images to a uniform size.
$(WEB): %-web.png: %-base.png
$(PIC)square.pl 150 5 $^ $@
# Build the base _icon_ images, by careful cropping of the base
# images: icons are very small so it's often necessary to zoom in
# on a smaller portion of the screenshot.
blackbox-ibase.png : override CROP=352x352 144x144+0+208
bridges-ibase.png : override CROP=264x264 107x107+157+157
dominosa-ibase.png : override CROP=304x272 152x152+152+0
fifteen-ibase.png : override CROP=240x240 120x120+0+120
filling-ibase.png : override CROP=256x256 133x133+14+78
flip-ibase.png : override CROP=288x288 145x145+120+72
galaxies-ibase.png : override CROP=288x288 165x165+0+0
guess-ibase.png : override CROP=263x420 178x178+75+17
inertia-ibase.png : override CROP=321x321 128x128+193+0
keen-ibase.png : override CROP=288x288 96x96+24+120
lightup-ibase.png : override CROP=256x256 112x112+144+0
loopy-ibase.png : override CROP=257x257 113x113+0+0
magnets-ibase.png : override CROP=264x232 96x96+36+100
mines-ibase.png : override CROP=240x240 110x110+130+130
net-ibase.png : override CROP=193x193 113x113+0+80
netslide-ibase.png : override CROP=289x289 144x144+0+0
palisade-ibase.png : override CROP=288x288 192x192+0+0
pattern-ibase.png : override CROP=384x384 223x223+0+0
pearl-ibase.png : override CROP=216x216 94x94+108+15
pegs-ibase.png : override CROP=263x263 147x147+116+0
range-ibase.png : override CROP=256x256 98x98+111+15
rect-ibase.png : override CROP=205x205 115x115+90+0
signpost-ibase.png : override CROP=240x240 98x98+23+23
singles-ibase.png : override CROP=224x224 98x98+15+15
sixteen-ibase.png : override CROP=288x288 144x144+144+144
slant-ibase.png : override CROP=321x321 160x160+160+160
solo-ibase.png : override CROP=481x481 145x145+24+24
tents-ibase.png : override CROP=320x320 165x165+142+0
towers-ibase.png : override CROP=300x300 102x102+151+6
tracks-ibase.png : override CROP=246x246 118x118+6+6
twiddle-ibase.png : override CROP=192x192 102x102+69+21
undead-ibase.png : override CROP=416x480 192x192+16+80
unequal-ibase.png : override CROP=208x208 104x104+104+104
untangle-ibase.png : override CROP=320x320 164x164+3+116
$(IBASE): %-ibase.png: %-base.png
$(PIC)crop.sh $^ $@ $(CROP)
# Convert the full-size icon images to 4-bit colour, because that
# seems to work better than reducing it in 24 bits and then
# dithering.
$(IBASE4): %-ibase4.png: %-ibase.png
convert -colors 16 +dither $(CSP) -map $(PIC)win16pal.xpm $^ $@
# Build the 24-bit PNGs for the icons, at three sizes.
$(P48D24): %-48d24.png: %-ibase.png
$(PIC)square.pl 48 4 $^ $@
$(P32D24): %-32d24.png: %-ibase.png
$(PIC)square.pl 32 2 $^ $@
$(P16D24): %-16d24.png: %-ibase.png
$(PIC)square.pl 16 1 $^ $@
# The 8-bit icon PNGs are just custom-paletted quantisations of the
# 24-bit ones.
$(P48D8) $(P32D8) $(P16D8): %d8.png: %d24.png
convert -colors 256 $^ $@
# But the depth-4 images work better if we re-shrink from the
# ibase4 versions of the images, and then normalise the colours
# again afterwards. (They're still not very good, but my hope is
# that on most modern Windows machines this won't matter too
# much...)
$(P48D4): %-48d4.png: %-ibase4.png
$(PIC)square.pl 48 1 $^ $@-tmp2.png
convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@
rm -f $@-tmp2.png
$(P32D4): %-32d4.png: %-ibase.png
$(PIC)square.pl 32 1 $^ $@-tmp2.png
convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@
rm -f $@-tmp2.png
$(P16D4): %-16d4.png: %-ibase.png
$(PIC)square.pl 16 1 $^ $@-tmp2.png
convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@
rm -f $@-tmp2.png
# Build the actual Windows icons themselves, by feeding all those
# PNGs to my icon builder script.
$(ICONS): %.ico: %-48d24.png %-48d8.png %-48d4.png \
%-32d24.png %-32d8.png %-32d4.png \
%-16d24.png %-16d8.png %-16d4.png
$(PIC)icon.pl -24 $*-48d24.png $*-32d24.png $*-16d24.png \
-8 $*-48d8.png $*-32d8.png $*-16d8.png \
-4 $*-48d4.png $*-32d4.png $*-16d4.png > $@
# Build the .RC files which bind the icons into the applications.
$(RC): %.rc:
echo '#include "puzzles.rc2"' > $@
echo '200 ICON "$*.ico"' >> $@
# Build the GTK icon source files.
$(CICONS): %-icon.c: %-16d24.png %-32d24.png %-48d24.png
$(PIC)cicon.pl $^ > $@
clean:
rm -f *.png *.ico *.rc *-icon.c

View file

@ -0,0 +1,27 @@
SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
VERSION :1:1
GAME :9:Black Box
PARAMS :8:w8h8m5M5
CPARAMS :8:w8h8m5M5
SEED :15:999785320716678
DESC :24:c8b9f8528193756b9a2fd24d
UI :2:E0
NSTATES :2:18
STATEPOS:2:13
MOVE :2:F2
MOVE :2:F4
MOVE :2:F5
MOVE :3:F25
MOVE :3:F26
MOVE :3:F11
MOVE :3:F12
MOVE :3:F15
MOVE :4:T7,7
MOVE :4:T7,4
MOVE :4:T1,7
MOVE :2:F3
MOVE :2:F9
MOVE :3:F27
MOVE :4:T5,7
MOVE :4:T1,8
MOVE :1:R

View file

@ -0,0 +1,72 @@
SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
VERSION :1:1
GAME :7:Bridges
PARAMS :15:10x10i30e10m2d2
CPARAMS :15:10x10i30e10m2d2
SEED :15:944199396008454
DESC :49:a1a4a4b4k4b6b2a4b2b2b1e2e1a4b4c3j25d2a1f2c3a4d4c3
AUXINFO :838:bd75eb5f7b129109b5cdcff0925c77ca5c0a135365002b93b44c5013c7a307b9504affcfb8ad934263196fc3e6d0b023abe48d254d46d29520e50a5e423c0fb1bc01ccc51cad61045c439e7c2bb8e5788bc7f3622aaa3a8125ebde11c9cd69b6f2393246fd094ad91e81ae58cd557b73bd1c9839cfad5835c8519e44298204eaca58dfd79289546959bfbabdc5f3cb7a27b8d3fb2d0b062bd5c2e469493c19f8c89989df73d8a3ab02d9afcbfedf245306d15881a01d153122f8374c7526abecc90919f99ff62e9789cabc402249af095ceb14c8c59c0d9ffbcdd731d50114e7c30c31ef0638f4d352abbfd04b4315d368d65bbfe005b6586245bc5244e5050098cf4c1b6986120f40d5ce038c10a3f309286f950cdc287e495aa13c70ab0c1f113a135556d7ce895fd8244afcbad43fe51f275837f223a1cb95151de8a158cb0add7fa8c9f1fa0e09a1ce842136c1679144cead56b164c4ef1a09ed36fd9704ba191b5957bc3d5bb97d8a1f7451d357a6638ac320b0beb0cd35aa404c8f1621c6d400960aa97bf6ce3a944339d7e401c4d98c31773b2a8881352d5653fdb5e8f7c04b
NSTATES :2:63
STATEPOS:2:41
MOVE :10:L8,0,5,0,1
MOVE :10:L8,0,5,0,2
MOVE :10:L8,0,8,2,1
MOVE :10:L8,0,8,2,2
MOVE :4:M8,0
MOVE :10:L0,2,3,2,1
MOVE :10:L0,2,3,2,2
MOVE :10:L0,2,0,7,1
MOVE :10:L0,2,0,7,2
MOVE :4:M0,2
MOVE :10:L1,0,3,0,1
MOVE :4:M1,0
MOVE :10:L3,0,5,0,1
MOVE :10:L3,0,3,2,1
MOVE :10:L1,3,1,5,1
MOVE :10:L0,7,5,7,1
MOVE :10:L0,7,0,9,1
MOVE :10:L0,9,5,9,1
MOVE :10:L0,9,5,9,2
MOVE :10:L0,9,0,7,2
MOVE :4:M0,9
MOVE :4:M0,7
MOVE :10:L4,8,8,8,1
MOVE :10:L4,8,8,8,2
MOVE :4:M4,8
MOVE :10:L5,9,9,9,1
MOVE :10:L5,9,9,9,2
MOVE :4:M5,9
MOVE :10:L9,9,9,6,1
MOVE :4:M9,9
MOVE :10:L8,8,8,5,1
MOVE :4:M8,8
MOVE :10:L9,6,9,4,1
MOVE :4:M9,6
MOVE :4:M9,4
MOVE :10:L1,5,4,5,1
MOVE :10:L1,5,4,5,2
MOVE :10:L1,5,1,3,2
MOVE :4:M1,3
MOVE :4:M1,5
MOVE :10:L3,4,3,2,1
MOVE :10:L3,4,3,2,2
MOVE :4:M3,4
MOVE :10:L4,5,8,5,1
MOVE :10:L7,7,5,7,1
MOVE :4:M5,7
MOVE :4:M7,7
MOVE :10:L7,3,4,3,1
MOVE :4:M7,3
MOVE :10:L5,0,3,0,2
MOVE :4:M5,0
MOVE :4:M3,0
MOVE :10:L3,2,6,2,1
MOVE :4:M3,2
MOVE :10:L6,2,8,2,1
MOVE :4:M6,2
MOVE :10:L8,2,8,5,1
MOVE :4:M8,2
MOVE :4:M8,5
MOVE :10:L4,5,4,3,1
MOVE :4:M4,3
MOVE :4:M4,5

View file

@ -0,0 +1,27 @@
#!/usr/bin/perl
# Given a list of input PNGs, create a C source file file
# containing a const array of XPMs, under the name `xpm_icon'.
$k = 0;
@xpms = ();
foreach $f (@ARGV) {
# XPM format is generated directly by ImageMagick, so that's easy
# enough. We just have to adjust the declaration line so that it
# has the right name, linkage and storage class.
@lines = ();
open XPM, "convert $f xpm:- |";
push @lines, $_ while <XPM>;
close XPM;
die "XPM from $f in unexpected format\n" unless $lines[1] =~ /^static.*\{$/;
$lines[1] = "static const char *const xpm_icon_$k"."[] = {\n";
$k++;
push @xpms, @lines, "\n";
}
# Now output.
foreach $line (@xpms) { print $line; }
print "const char *const *const xpm_icons[] = {\n";
for ($i = 0; $i < $k; $i++) { print " xpm_icon_$i,\n"; }
print "};\n";
print "const int n_xpm_icons = $k;\n";

View file

@ -0,0 +1,37 @@
#!/bin/sh
# Crop one image into another, after first checking that the source
# image has the expected size in pixels.
#
# This is used in the Puzzles icon build scripts to construct icons
# which are zoomed in on a particular sub-area of the puzzle's
# basic screenshot. This way I can define crop areas in pixels,
# while not having to worry too much that if I adjust the source
# puzzle so as to alter the layout the crop area might start
# hitting the wrong bit of picture. Most layout changes I can
# conveniently imagine will also alter the overall image size, so
# this script will give a build error and alert me to the fact that
# I need to fiddle with the icon makefile.
infile="$1"
outfile="$2"
insize="$3"
crop="$4"
# Special case: if no input size or crop parameter was specified at
# all, we just copy the input to the output file.
if test $# -lt 3; then
cp "$infile" "$outfile"
exit 0
fi
# Check the input image size.
realsize=`identify -format %wx%h "$infile"`
if test "x$insize" != "x$realsize"; then
echo "crop.sh: '$infile' has wrong initial size: $realsize != $insize" >&2
exit 1
fi
# And crop.
convert -crop "$crop" "$infile" "$outfile"

View file

@ -0,0 +1,22 @@
SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
VERSION :1:1
GAME :4:Cube
PARAMS :4:c4x4
CPARAMS :4:c4x4
SEED :15:125146248070163
DESC :6:C461,3
NSTATES :2:14
STATEPOS:1:5
MOVE :1:D
MOVE :1:D
MOVE :1:D
MOVE :1:U
MOVE :1:L
MOVE :1:L
MOVE :1:D
MOVE :1:U
MOVE :1:D
MOVE :1:U
MOVE :1:U
MOVE :1:U
MOVE :1:L

View file

@ -0,0 +1,53 @@
SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
VERSION :1:1
GAME :8:Dominosa
PARAMS :1:6
CPARAMS :1:6
DESC :56:55521461210004364611033535444421636022603153156422620503
NSTATES :2:46
STATEPOS:2:33
MOVE :6:D18,19
MOVE :6:E22,23
MOVE :6:E22,30
MOVE :5:E9,17
MOVE :6:D38,46
MOVE :6:E14,15
MOVE :5:E6,14
MOVE :6:E33,34
MOVE :6:E34,42
MOVE :6:E26,34
MOVE :6:D34,35
MOVE :6:E42,50
MOVE :6:E16,24
MOVE :4:D4,5
MOVE :4:D6,7
MOVE :6:D15,23
MOVE :6:E17,25
MOVE :6:D16,17
MOVE :6:E11,12
MOVE :6:D51,52
MOVE :5:E3,11
MOVE :6:D10,11
MOVE :4:D2,3
MOVE :6:E37,45
MOVE :6:D49,50
MOVE :6:D40,48
MOVE :6:D25,26
MOVE :6:D24,32
MOVE :6:D33,41
MOVE :6:D42,43
MOVE :6:D27,28
MOVE :6:E21,29
MOVE :6:D31,39
MOVE :6:D47,55
MOVE :6:E13,21
MOVE :6:E13,14
MOVE :6:D12,13
MOVE :6:D20,21
MOVE :6:D14,22
MOVE :6:D29,30
MOVE :6:D36,37
MOVE :6:D44,45
MOVE :6:D53,54
MOVE :4:D0,1
MOVE :4:D8,9

View file

@ -0,0 +1,74 @@
SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
VERSION :1:1
GAME :7:Fifteen
PARAMS :3:4x4
CPARAMS :3:4x4
SEED :15:307905346810973
DESC :37:8,11,3,6,14,13,4,2,0,9,12,10,5,1,7,15
NSTATES :2:66
STATEPOS:2:47
MOVE :4:M1,2
MOVE :4:M1,3
MOVE :4:M0,3
MOVE :4:M0,1
MOVE :4:M1,1
MOVE :4:M1,2
MOVE :4:M0,2
MOVE :4:M0,0
MOVE :4:M1,0
MOVE :4:M1,1
MOVE :4:M0,1
MOVE :4:M0,0
MOVE :4:M1,0
MOVE :4:M3,0
MOVE :4:M3,1
MOVE :4:M1,1
MOVE :4:M1,0
MOVE :4:M3,0
MOVE :4:M3,1
MOVE :4:M1,1
MOVE :4:M1,0
MOVE :4:M2,0
MOVE :4:M2,1
MOVE :4:M1,1
MOVE :4:M1,3
MOVE :4:M0,3
MOVE :4:M0,1
MOVE :4:M1,1
MOVE :4:M1,2
MOVE :4:M0,2
MOVE :4:M0,1
MOVE :4:M2,1
MOVE :4:M3,1
MOVE :4:M3,2
MOVE :4:M2,2
MOVE :4:M2,3
MOVE :4:M3,3
MOVE :4:M3,1
MOVE :4:M2,1
MOVE :4:M2,2
MOVE :4:M1,2
MOVE :4:M1,3
MOVE :4:M2,3
MOVE :4:M2,2
MOVE :4:M1,2
MOVE :4:M0,2
MOVE :4:M0,3
MOVE :4:M1,3
MOVE :4:M1,2
MOVE :4:M2,2
MOVE :4:M2,3
MOVE :4:M0,3
MOVE :4:M0,2
MOVE :4:M1,2
MOVE :4:M2,2
MOVE :4:M2,3
MOVE :4:M1,3
MOVE :4:M1,2
MOVE :4:M3,2
MOVE :4:M3,3
MOVE :4:M1,3
MOVE :4:M1,2
MOVE :4:M2,2
MOVE :4:M2,3
MOVE :4:M3,3

View file

@ -0,0 +1,38 @@
SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
VERSION :1:1
GAME :7:Filling
PARAMS :3:7x7
CPARAMS :3:7x7
SEED :15:279172739852696
DESC :49:0000000031051240010004000001106171000400001013105
NSTATES :2:30
STATEPOS:2:13
MOVE :4:38_3
MOVE :4:39_3
MOVE :4:36_4
MOVE :4:43_4
MOVE :4:35_4
MOVE :4:47_5
MOVE :4:40_5
MOVE :4:34_5
MOVE :4:41_5
MOVE :4:25_7
MOVE :4:23_6
MOVE :4:16_6
MOVE :4:18_7
MOVE :4:19_7
MOVE :4:20_7
MOVE :4:26_7
MOVE :4:24_7
MOVE :4:29_6
MOVE :4:22_6
MOVE :4:15_6
MOVE :3:7_4
MOVE :3:0_4
MOVE :3:1_3
MOVE :3:2_3
MOVE :3:6_2
MOVE :3:5_5
MOVE :3:4_5
MOVE :3:3_5
MOVE :4:10_5

Some files were not shown because too many files have changed in this diff Show more