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:
parent
3ee79724f6
commit
1a6a8b52f7
289 changed files with 147273 additions and 0 deletions
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 */ \
|
||||
|
|
194
apps/plugins/puzzles/Buildscr
Normal file
194
apps/plugins/puzzles/Buildscr
Normal 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 $@
|
70
apps/plugins/puzzles/CHECKLST.txt
Normal file
70
apps/plugins/puzzles/CHECKLST.txt
Normal 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
|
25
apps/plugins/puzzles/LICENCE
Normal file
25
apps/plugins/puzzles/LICENCE
Normal 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.
|
617
apps/plugins/puzzles/PuzzleApplet.java
Normal file
617
apps/plugins/puzzles/PuzzleApplet.java
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
54
apps/plugins/puzzles/README
Normal file
54
apps/plugins/puzzles/README
Normal 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
157
apps/plugins/puzzles/Recipe
Normal 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
|
26
apps/plugins/puzzles/SOURCES
Normal file
26
apps/plugins/puzzles/SOURCES
Normal 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
197
apps/plugins/puzzles/benchmark.pl
Executable 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/&/&/g;
|
||||
$text =~ s/</</g;
|
||||
$text =~ s/>/>/g;
|
||||
return $text;
|
||||
}
|
27
apps/plugins/puzzles/benchmark.sh
Executable file
27
apps/plugins/puzzles/benchmark.sh
Executable 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
|
19
apps/plugins/puzzles/blackbox.R
Normal file
19
apps/plugins/puzzles/blackbox.R
Normal 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
|
1543
apps/plugins/puzzles/blackbox.c
Normal file
1543
apps/plugins/puzzles/blackbox.c
Normal file
File diff suppressed because it is too large
Load diff
21
apps/plugins/puzzles/bridges.R
Normal file
21
apps/plugins/puzzles/bridges.R
Normal 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
|
3262
apps/plugins/puzzles/bridges.c
Normal file
3262
apps/plugins/puzzles/bridges.c
Normal file
File diff suppressed because it is too large
Load diff
21
apps/plugins/puzzles/chm.but
Normal file
21
apps/plugins/puzzles/chm.but
Normal 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">}
|
7
apps/plugins/puzzles/chm.css
Normal file
7
apps/plugins/puzzles/chm.css
Normal 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%; }
|
110
apps/plugins/puzzles/combi.c
Normal file
110
apps/plugins/puzzles/combi.c
Normal 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
|
85
apps/plugins/puzzles/configure.ac
Normal file
85
apps/plugins/puzzles/configure.ac
Normal 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
|
19
apps/plugins/puzzles/cube.R
Normal file
19
apps/plugins/puzzles/cube.R
Normal 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
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
52
apps/plugins/puzzles/desktop.pl
Executable 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";
|
||||
}
|
4777
apps/plugins/puzzles/devel.but
Normal file
4777
apps/plugins/puzzles/devel.but
Normal file
File diff suppressed because it is too large
Load diff
781
apps/plugins/puzzles/divvy.c
Normal file
781
apps/plugins/puzzles/divvy.c
Normal 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
|
21
apps/plugins/puzzles/dominosa.R
Normal file
21
apps/plugins/puzzles/dominosa.R
Normal 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
|
1748
apps/plugins/puzzles/dominosa.c
Normal file
1748
apps/plugins/puzzles/dominosa.c
Normal file
File diff suppressed because it is too large
Load diff
351
apps/plugins/puzzles/drawing.c
Normal file
351
apps/plugins/puzzles/drawing.c
Normal 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
192
apps/plugins/puzzles/dsf.c
Normal 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
867
apps/plugins/puzzles/emcc.c
Normal 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;
|
||||
}
|
757
apps/plugins/puzzles/emcclib.js
Normal file
757
apps/plugins/puzzles/emcclib.js
Normal 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();
|
||||
}
|
||||
});
|
364
apps/plugins/puzzles/emccpre.js
Normal file
364
apps/plugins/puzzles/emccpre.js
Normal 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";
|
||||
}
|
29
apps/plugins/puzzles/emccx.json
Normal file
29
apps/plugins/puzzles/emccx.json
Normal 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'
|
||||
]
|
22
apps/plugins/puzzles/fifteen.R
Normal file
22
apps/plugins/puzzles/fifteen.R
Normal 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
|
1215
apps/plugins/puzzles/fifteen.c
Normal file
1215
apps/plugins/puzzles/fifteen.c
Normal file
File diff suppressed because it is too large
Load diff
24
apps/plugins/puzzles/filling.R
Normal file
24
apps/plugins/puzzles/filling.R
Normal 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
|
2179
apps/plugins/puzzles/filling.c
Normal file
2179
apps/plugins/puzzles/filling.c
Normal file
File diff suppressed because it is too large
Load diff
500
apps/plugins/puzzles/findloop.c
Normal file
500
apps/plugins/puzzles/findloop.c
Normal 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...
|
||||
*/
|
21
apps/plugins/puzzles/flip.R
Normal file
21
apps/plugins/puzzles/flip.R
Normal 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
1349
apps/plugins/puzzles/flip.c
Normal file
File diff suppressed because it is too large
Load diff
19
apps/plugins/puzzles/flood.R
Normal file
19
apps/plugins/puzzles/flood.R
Normal 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
1372
apps/plugins/puzzles/flood.c
Normal file
File diff suppressed because it is too large
Load diff
28
apps/plugins/puzzles/galaxies.R
Normal file
28
apps/plugins/puzzles/galaxies.R
Normal 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
|
3995
apps/plugins/puzzles/galaxies.c
Normal file
3995
apps/plugins/puzzles/galaxies.c
Normal file
File diff suppressed because it is too large
Load diff
2896
apps/plugins/puzzles/grid.c
Normal file
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
132
apps/plugins/puzzles/grid.h
Normal 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
3215
apps/plugins/puzzles/gtk.c
Normal file
File diff suppressed because it is too large
Load diff
19
apps/plugins/puzzles/guess.R
Normal file
19
apps/plugins/puzzles/guess.R
Normal 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
1518
apps/plugins/puzzles/guess.c
Normal file
File diff suppressed because it is too large
Load diff
16
apps/plugins/puzzles/html/blackbox.html
Normal file
16
apps/plugins/puzzles/html/blackbox.html
Normal 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.
|
13
apps/plugins/puzzles/html/bridges.html
Normal file
13
apps/plugins/puzzles/html/bridges.html
Normal 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.
|
14
apps/plugins/puzzles/html/cube.html
Normal file
14
apps/plugins/puzzles/html/cube.html
Normal 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!
|
10
apps/plugins/puzzles/html/dominosa.html
Normal file
10
apps/plugins/puzzles/html/dominosa.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
Dominosa
|
||||
<p>
|
||||
Tile the rectangle with dominoes (1×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.
|
6
apps/plugins/puzzles/html/fifteen.html
Normal file
6
apps/plugins/puzzles/html/fifteen.html
Normal 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.
|
12
apps/plugins/puzzles/html/filling.html
Normal file
12
apps/plugins/puzzles/html/filling.html
Normal 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.
|
10
apps/plugins/puzzles/html/flip.html
Normal file
10
apps/plugins/puzzles/html/flip.html
Normal 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.
|
8
apps/plugins/puzzles/html/flood.html
Normal file
8
apps/plugins/puzzles/html/flood.html
Normal 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.
|
11
apps/plugins/puzzles/html/galaxies.html
Normal file
11
apps/plugins/puzzles/html/galaxies.html
Normal 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.
|
52
apps/plugins/puzzles/html/group.html
Normal file
52
apps/plugins/puzzles/html/group.html
Normal 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).
|
12
apps/plugins/puzzles/html/guess.html
Normal file
12
apps/plugins/puzzles/html/guess.html
Normal 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.
|
14
apps/plugins/puzzles/html/inertia.html
Normal file
14
apps/plugins/puzzles/html/inertia.html
Normal 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.
|
104
apps/plugins/puzzles/html/javapage.pl
Executable file
104
apps/plugins/puzzles/html/javapage.pl
Executable 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;
|
||||
}
|
120
apps/plugins/puzzles/html/jspage.pl
Executable file
120
apps/plugins/puzzles/html/jspage.pl
Executable 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;
|
||||
}
|
15
apps/plugins/puzzles/html/keen.html
Normal file
15
apps/plugins/puzzles/html/keen.html
Normal 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.
|
10
apps/plugins/puzzles/html/lightup.html
Normal file
10
apps/plugins/puzzles/html/lightup.html
Normal 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.
|
13
apps/plugins/puzzles/html/loopy.html
Normal file
13
apps/plugins/puzzles/html/loopy.html
Normal 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!
|
17
apps/plugins/puzzles/html/magnets.html
Normal file
17
apps/plugins/puzzles/html/magnets.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
Magnets
|
||||
<p>
|
||||
Fill each domino shape with either a magnet (consisting of a + and
|
||||
− 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 − poles must
|
||||
match the numbers along the bottom and right. Two + poles may not be
|
||||
orthogonally adjacent to each other, and similarly two − 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.
|
15
apps/plugins/puzzles/html/map.html
Normal file
15
apps/plugins/puzzles/html/map.html
Normal 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.
|
18
apps/plugins/puzzles/html/mines.html
Normal file
18
apps/plugins/puzzles/html/mines.html
Normal 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.)
|
17
apps/plugins/puzzles/html/net.html
Normal file
17
apps/plugins/puzzles/html/net.html
Normal 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!
|
14
apps/plugins/puzzles/html/netslide.html
Normal file
14
apps/plugins/puzzles/html/netslide.html
Normal 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!
|
11
apps/plugins/puzzles/html/palisade.html
Normal file
11
apps/plugins/puzzles/html/palisade.html
Normal 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.
|
12
apps/plugins/puzzles/html/pattern.html
Normal file
12
apps/plugins/puzzles/html/pattern.html
Normal 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.
|
13
apps/plugins/puzzles/html/pearl.html
Normal file
13
apps/plugins/puzzles/html/pearl.html
Normal 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.
|
8
apps/plugins/puzzles/html/pegs.html
Normal file
8
apps/plugins/puzzles/html/pegs.html
Normal 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.
|
21
apps/plugins/puzzles/html/range.html
Normal file
21
apps/plugins/puzzles/html/range.html
Normal 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.
|
10
apps/plugins/puzzles/html/rect.html
Normal file
10
apps/plugins/puzzles/html/rect.html
Normal 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.
|
14
apps/plugins/puzzles/html/samegame.html
Normal file
14
apps/plugins/puzzles/html/samegame.html
Normal 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.
|
14
apps/plugins/puzzles/html/signpost.html
Normal file
14
apps/plugins/puzzles/html/signpost.html
Normal 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.
|
11
apps/plugins/puzzles/html/singles.html
Normal file
11
apps/plugins/puzzles/html/singles.html
Normal 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.
|
8
apps/plugins/puzzles/html/sixteen.html
Normal file
8
apps/plugins/puzzles/html/sixteen.html
Normal 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.
|
9
apps/plugins/puzzles/html/slant.html
Normal file
9
apps/plugins/puzzles/html/slant.html
Normal 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.
|
20
apps/plugins/puzzles/html/solo.html
Normal file
20
apps/plugins/puzzles/html/solo.html
Normal 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!
|
20
apps/plugins/puzzles/html/tents.html
Normal file
20
apps/plugins/puzzles/html/tents.html
Normal 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.
|
22
apps/plugins/puzzles/html/towers.html
Normal file
22
apps/plugins/puzzles/html/towers.html
Normal 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.
|
19
apps/plugins/puzzles/html/tracks.html
Normal file
19
apps/plugins/puzzles/html/tracks.html
Normal 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.
|
15
apps/plugins/puzzles/html/twiddle.html
Normal file
15
apps/plugins/puzzles/html/twiddle.html
Normal 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×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×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!
|
22
apps/plugins/puzzles/html/undead.html
Normal file
22
apps/plugins/puzzles/html/undead.html
Normal 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.
|
14
apps/plugins/puzzles/html/unequal.html
Normal file
14
apps/plugins/puzzles/html/unequal.html
Normal 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><</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.
|
11
apps/plugins/puzzles/html/unruly.html
Normal file
11
apps/plugins/puzzles/html/unruly.html
Normal 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.
|
5
apps/plugins/puzzles/html/untangle.html
Normal file
5
apps/plugins/puzzles/html/untangle.html
Normal 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.
|
153
apps/plugins/puzzles/icons/Makefile
Normal file
153
apps/plugins/puzzles/icons/Makefile
Normal 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
|
27
apps/plugins/puzzles/icons/blackbox.sav
Normal file
27
apps/plugins/puzzles/icons/blackbox.sav
Normal 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
|
72
apps/plugins/puzzles/icons/bridges.sav
Normal file
72
apps/plugins/puzzles/icons/bridges.sav
Normal 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
|
27
apps/plugins/puzzles/icons/cicon.pl
Executable file
27
apps/plugins/puzzles/icons/cicon.pl
Executable 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";
|
37
apps/plugins/puzzles/icons/crop.sh
Executable file
37
apps/plugins/puzzles/icons/crop.sh
Executable 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"
|
22
apps/plugins/puzzles/icons/cube.sav
Normal file
22
apps/plugins/puzzles/icons/cube.sav
Normal 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
|
53
apps/plugins/puzzles/icons/dominosa.sav
Normal file
53
apps/plugins/puzzles/icons/dominosa.sav
Normal 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
|
74
apps/plugins/puzzles/icons/fifteen.sav
Normal file
74
apps/plugins/puzzles/icons/fifteen.sav
Normal 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
|
38
apps/plugins/puzzles/icons/filling.sav
Normal file
38
apps/plugins/puzzles/icons/filling.sav
Normal 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
Loading…
Reference in a new issue