puzzles: resync with upstream

This updates the upstream sources to include a modified get_cursor_location
patch that I anticipate Simon will merge shortly. Also, I've streamlined
the resync process to only copy the exact files we need to reduce clutter.

Change-Id: I6a5ac60821fce346c500b101c363ae0c63c2ee95
This commit is contained in:
Franklin Wei 2020-12-07 02:08:18 -05:00
parent ae59995553
commit 84cd812ccd
169 changed files with 85 additions and 19915 deletions

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* auto-generated on Dec 7 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,236 +0,0 @@
# -*- sh -*-
# Build script to build Puzzles.
#
# You can cut out large components of the build by defining a subset
# of these options on the bob command line:
# -DNOSIGN -DNOWINDOWS -DNOMACOS -DNOICONS -DNOJAVA -DNOJS
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
ifneq "$(NOICONS)" yes then
# First build some local binaries, to run the icon build.
in puzzles do perl mkfiles.pl -U CFLAGS='-Wwrite-strings -Werror'
in puzzles do make -j$(nproc)
# Now build the screenshots and icons.
in puzzles/icons do make web winicons gtkicons -j$(nproc)
# 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
endif
# Re-run mkfiles.pl now that it knows the icons are there. (Or for the
# first time, if we didn't bother building the icons.)
in puzzles do perl mkfiles.pl
# Rebuild the configure script.
in puzzles do ./mkauto.sh
ifneq "$(NOMACOS)" yes then
# 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) XFLAGS='-Wwrite-strings -Werror' -j$(nproc)
return puzzles/Puzzles.dmg
enddelegate
endif
ifneq "$(NOWINDOWS)" yes then
# 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 -j$(nproc) # build help files for installer
in puzzles do mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs
in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss
ifneq "$(VISUAL_STUDIO)" "yes" then
in puzzles with clangcl64 do mkdir win64 && Platform=x64 make -f Makefile.clangcl BUILDDIR=win64/ VER=-DVER=$(Version) XFLAGS='-Wwrite-strings -Werror' -j$(nproc)
in puzzles with clangcl32 do mkdir win32 && Platform=x86 make -f Makefile.clangcl BUILDDIR=win32/ SUBSYSVER=,5.01 VER=-DVER=$(Version) XFLAGS='-Wwrite-strings -Werror' -j$(nproc)
# Code-sign the binaries, if the local bob config provides a script
# to do so. We assume here that the script accepts an -i option to
# provide a 'more info' URL, and an optional -n option to provide a
# program name, and that it can take multiple .exe filename
# arguments and sign them all in place.
ifneq "$(cross_winsigncode)" "" in puzzles do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ win64/*.exe win32/*.exe
# Build installers.
in puzzles with wixonlinux do candle -arch x64 puzzles.wxs -dWin64=yes -dBindir=win64/ && light -ext WixUIExtension -sval puzzles.wixobj
in puzzles with wixonlinux do candle -arch x86 puzzles.wxs -dWin64=no -dBindir=win32/ && light -ext WixUIExtension -sval puzzles.wixobj -o puzzles32.msi
ifneq "$(cross_winsigncode)" "" in puzzles do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ -n "Simon Tatham's Portable Puzzle Collection Installer" puzzles.msi puzzles32.msi
else
delegate windows
in puzzles with visualstudio do/win nmake -f Makefile.vc clean
in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version)
ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ win64/*.exe
# Build installers.
in puzzles with wix do/win candle puzzles.wxs -dWin64=yes -dBindir=win64/ && light -ext WixUIExtension -sval puzzles.wixobj
in puzzles with innosetup do/win iscc puzzles.iss
return puzzles/win64/*.exe
return puzzles/puzzles.msi
enddelegate
endif
in puzzles do chmod +x win32/*.exe win64/*.exe
endif
# 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 HTML docs.
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
ifneq "$(NOWINDOWS)" yes then
# 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 winbin64 winbin32
in puzzles/win64 do mv `cut -f2 -d: ../gamedesc.txt` ../winbin64
in puzzles/win32 do mv `cut -f2 -d: ../gamedesc.txt` ../winbin32
# Make a zip file of the Windows binaries and help files.
in puzzles do zip -j puzzles.zip winbin64/*.exe puzzles.chm puzzles.hlp puzzles.cnt
in puzzles do zip -j puzzles32.zip winbin32/*.exe puzzles.chm puzzles.hlp puzzles.cnt
endif
# 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
ifneq "$(NOJAVA)" yes then
# Build the Java applets.
delegate nestedvm
in puzzles do make -f Makefile.nestedvm NESTEDVM="$$NESTEDVM" VER=-DVER=$(Version) XFLAGS="-Wwrite-strings -Werror" -j$(nproc)
return puzzles/*.jar
enddelegate
endif
# Build the Javascript applets. Since my master build machine doesn't
# have the right dependencies installed for Emscripten, I do this by a
# delegation.
ifneq "$(NOJS)" yes then
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/ XFLAGS="-Wwrite-strings -Werror" -j$(nproc)
return puzzles/js/*.js
enddelegate
# Build a set of wrapping HTML pages for easy testing of the
# Javascript puzzles. These aren't quite the same as the versions that
# will go on my live website, because those ones will substitute in a
# different footer, and not have to link to the .js files with the
# ../js/ prefix. But these ones should be good enough to just open
# using a file:// URL in a browser after running a build, and make
# sure the main functionality works.
in puzzles do mkdir jstest
in puzzles/jstest do ../html/jspage.pl --jspath=../js/ /dev/null ../html/*.html
endif
# 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
# Phew, we're done. Deliver everything!
ifneq "$(NOICONS)" yes then
deliver puzzles/icons/*-web.png $@
endif
ifneq "$(NOWINDOWS)" yes then
deliver puzzles/winbin64/*.exe $@
deliver puzzles/winbin32/*.exe w32/$@
deliver puzzles/puzzles.zip $@
deliver puzzles/puzzles32.zip w32/$@
deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi
deliver puzzles/puzzles32.msi w32/puzzles-$(Version)-32bit-installer.msi
deliver puzzles/puzzles.chm $@
deliver puzzles/puzzles.hlp $@
deliver puzzles/puzzles.cnt $@
endif
deliver puzzles/.htaccess $@
deliver puzzles/doc/*.html doc/$@
deliver puzzles/devel/*.html devel/$@
ifneq "$(NOMACOS)" yes then
deliver puzzles/Puzzles.dmg $@
endif
ifneq "$(NOJAVA)" yes then
deliver puzzles/*.jar java/$@
endif
ifneq "$(NOJS)" yes then
deliver puzzles/js/*.js js/$@
deliver puzzles/jstest/*.html jstest/$@
deliver puzzles/html/*.html html/$@
deliver puzzles/html/*.pl html/$@
endif
deliver puzzles/wwwspans.html $@
deliver puzzles/wwwlinks.html $@
# deliver puzzles/puzzles.armv4.cab $@ # (not built at the moment)
# This one isn't in the puzzles subdir, because makedist.sh left it
# one level up.
deliver puzzles*.tar.gz $@

View file

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

View file

@ -1,17 +0,0 @@
all: puzzles.chm puzzles.hlp puzzles.txt HACKING
preprocessed.but: puzzles.but
sed 's/PREFIX-/$(BINPREFIX)/g' puzzles.but > preprocessed.but
puzzles.chm: preprocessed.but
halibut --chm=puzzles.chm preprocessed.but
puzzles.hlp: preprocessed.but
halibut --winhelp=puzzles.hlp preprocessed.but
puzzles.txt: preprocessed.but
halibut --text=puzzles.txt preprocessed.but
HACKING: devel.but
halibut --text=HACKING devel.but
clean:
rm -f puzzles.hlp puzzles.txt preprocessed.but HACKING *.html *.hh[pck]

View file

@ -1,651 +0,0 @@
/*
* 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[] typeMenuItems;
private int customMenuItemIndex;
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"));
addMenuItemCallback(jm, "New", "jcallback_newgame_event");
addMenuItemCallback(jm, "Restart", "jcallback_restart_event");
addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC);
addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED);
jm.addSeparator();
addMenuItemCallback(jm, "Undo", "jcallback_undo_event");
addMenuItemCallback(jm, "Redo", "jcallback_redo_event");
jm.addSeparator();
solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
solveCommand.setEnabled(false);
if (mainWindow != null) {
jm.addSeparator();
addMenuItemCallback(jm, "Exit", "jcallback_quit_event");
}
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) {
int key = e.getKeyChar();
if (key == 26 && e.isShiftDown() && e.isControlDown()) {
runtimeCall("jcallback_redo_event", new int[0]);
return;
}
runtimeCall("jcallback_key_event", new int[] {0, 0, key});
}
});
pp.addMouseListener(new MouseAdapter() {
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 JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) {
return addMenuItemCallback(jm, name, callback, new int[] {arg}, false);
}
private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback) {
return addMenuItemCallback(jm, name, callback, new int[0], false);
}
private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int[] args, boolean checkbox) {
JMenuItem jmi;
if (checkbox)
jm.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);
}
typeMenuItems[customMenuItemIndex] =
addMenuItemCallback(typeMenu, "Custom...",
"jcallback_config_event",
new int[] {CFG_SETTINGS}, true);
}
private void addTypeItem
(JMenu targetMenu, String name, int newId, final int ptrGameParams) {
typeMenu.setVisible(true);
typeMenuItems[newId] =
addMenuItemCallback(targetMenu, name,
"jcallback_preset_event",
new int[] {ptrGameParams}, true);
}
private void addTypeSubmenu
(JMenu targetMenu, String name, int newId) {
JMenu newMenu = new JMenu(name);
newMenu.setVisible(true);
typeMenuItems[newId] = newMenu;
targetMenu.add(newMenu);
}
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: // configure Type menu
if (arg1 == 0) {
// preliminary setup
typeMenuItems = new JMenuItem[arg2 + 2];
typeMenuItems[arg2] = typeMenu;
customMenuItemIndex = arg2 + 1;
return arg2;
} else if (xarg1 != 0) {
addTypeItem((JMenu)typeMenuItems[arg2],
runtime.cstring(arg1), arg3, xarg1);
} else {
addTypeSubmenu((JMenu)typeMenuItems[arg2],
runtime.cstring(arg1), arg3);
}
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 = customMenuItemIndex;
for (int i = 0; i < typeMenuItems.length; i++) {
if (typeMenuItems[i] instanceof JCheckBoxMenuItem) {
((JCheckBoxMenuItem)typeMenuItems[i]).setSelected
(arg1 == i);
}
}
return 0;
default:
if (cmd >= 1024 && cmd < 2048) { // palette
colors[cmd-1024] = new Color(arg1, arg2, arg3);
}
if (cmd == 1024) {
pp.setBackground(colors[0]);
if (statusBar != null) statusBar.setBackground(colors[0]);
this.setBackground(colors[0]);
}
return 0;
}
} catch (Throwable ex) {
ex.printStackTrace();
System.exit(-1);
return 0;
}
}
private void addStatusBar() {
statusBar = new JLabel("test");
statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));
getContentPane().add(BorderLayout.SOUTH,statusBar);
}
// Standalone runner
public static void main(String[] args) {
final PuzzleApplet a = new PuzzleApplet();
JFrame jf = new JFrame("Loading...");
jf.getContentPane().setLayout(new BorderLayout());
jf.getContentPane().add(a, BorderLayout.CENTER);
a.mainWindow=jf;
a.init();
a.start();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
a.stop();
a.destroy();
}
});
jf.setVisible(true);
}
public static class PuzzlePanel extends JPanel {
private static final long serialVersionUID = 1L;
protected BufferedImage backBuffer;
public PuzzlePanel() {
setPreferredSize(new Dimension(100,100));
createBackBuffer(100,100, Color.black);
}
public void createBackBuffer(int w, int h, Color bg) {
if (w > 0 && h > 0) {
backBuffer = new BufferedImage(w,h, BufferedImage.TYPE_3BYTE_BGR);
Graphics g = backBuffer.createGraphics();
g.setColor(bg);
g.fillRect(0, 0, w, h);
g.dispose();
}
}
protected void paintComponent(Graphics g) {
g.drawImage(backBuffer, 0, 0, this);
}
}
public static class ConfigComponent {
public int type;
public int configItemPointer;
public JComponent component;
public ConfigComponent(int type, int configItemPointer, JComponent component) {
this.type = type;
this.configItemPointer = configItemPointer;
this.component = component;
}
}
public class ConfigDialog extends JDialog {
private GridBagConstraints gbcLeft = new GridBagConstraints(
GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1,
0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(0, 0, 0, 0), 0, 0);
private GridBagConstraints gbcRight = new GridBagConstraints(
GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
GridBagConstraints.REMAINDER, 1, 1.0, 0,
GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5), 0, 0);
private GridBagConstraints gbcBottom = new GridBagConstraints(
GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
GridBagConstraints.REMAINDER, GridBagConstraints.REMAINDER,
1.0, 1.0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
private static final long serialVersionUID = 1L;
private List components = new ArrayList();
public ConfigDialog(JApplet parent, String title) {
super(JOptionPane.getFrameForComponent(parent), title, true);
getContentPane().setLayout(new GridBagLayout());
}
public void addTextBox(int ptr, String name, String value) {
getContentPane().add(new JLabel(name), gbcLeft);
JComponent c = new JTextField(value, 25);
getContentPane().add(c, gbcRight);
components.add(new ConfigComponent(C_STRING, ptr, c));
}
public void addCheckBox(int ptr, String name, boolean selected) {
JComponent c = new JCheckBox(name, selected);
getContentPane().add(c, gbcRight);
components.add(new ConfigComponent(C_BOOLEAN, ptr, c));
}
public void addComboBox(int ptr, String name, String values, int selected) {
getContentPane().add(new JLabel(name), gbcLeft);
StringTokenizer st = new StringTokenizer(values.substring(1), values.substring(0,1));
JComboBox c = new JComboBox();
c.setEditable(false);
while(st.hasMoreTokens())
c.addItem(st.nextToken());
c.setSelectedIndex(selected);
getContentPane().add(c, gbcRight);
components.add(new ConfigComponent(C_CHOICES, ptr, c));
}
public void finish() {
JPanel buttons = new JPanel(new GridLayout(1, 2, 5, 5));
getContentPane().add(buttons, gbcBottom);
JButton b;
buttons.add(b=new JButton("OK"));
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
save();
dispose();
}
});
getRootPane().setDefaultButton(b);
buttons.add(b=new JButton("Cancel"));
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private void save() {
for (int i = 0; i < components.size(); i++) {
ConfigComponent cc = (ConfigComponent) components.get(i);
switch(cc.type) {
case C_STRING:
JTextField jtf = (JTextField)cc.component;
runtimeCall("jcallback_config_set_string", new int[] {cc.configItemPointer, runtime.strdup(jtf.getText())});
break;
case C_BOOLEAN:
JCheckBox jcb = (JCheckBox)cc.component;
runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcb.isSelected()?1:0});
break;
case C_CHOICES:
JComboBox jcm = (JComboBox)cc.component;
runtimeCall("jcallback_config_set_choice", new int[] {cc.configItemPointer, jcm.getSelectedIndex()});
break;
}
}
runtimeCall("jcallback_config_ok", new int[0]);
}
}
}

View file

@ -1,171 +0,0 @@
# -*- 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
!makefile clangcl Makefile.clangcl
!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
LATIN_DEPS = matching tree234
LATIN = latin LATIN_DEPS
LATIN_SOLVER = latin[STANDALONE_SOLVER] LATIN_DEPS
# 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
# Test program built from latin.c.
latincheck : [U] latin[STANDALONE_LATIN_TEST] LATIN_DEPS STANDALONE
latincheck : [C] latin[STANDALONE_LATIN_TEST] LATIN_DEPS STANDALONE
# Test program built from matching.c.
matching : [U] matching[STANDALONE_MATCHING_TEST] tree234 STANDALONE
matching : [C] matching[STANDALONE_MATCHING_TEST] tree234 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
%.tmpdir/PuzzleEngine.class: %.mips
mkdir -p $(patsubst %.mips,%,$<).tmpdir
cd $(patsubst %.mips,%,$<).tmpdir && \
java -cp $(NESTEDVM)/build:$(NESTEDVM)/upstream/build/classgen/build \
org.ibex.nestedvm.Compiler -outformat class -d . \
PuzzleEngine ../$<
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
applet.manifest:
echo "Main-Class: PuzzleApplet" >applet.manifest
PuzzleApplet.class: PuzzleApplet.java org
javac -source 1.7 -target 1.7 PuzzleApplet.java
%.jar: %.tmpdir/PuzzleEngine.class PuzzleApplet.class applet.manifest org
cd $(patsubst %.jar,%,$@).tmpdir && ln -s ../applet.manifest ../org ../PuzzleApplet*.class .
cd $(patsubst %.jar,%,$@).tmpdir && jar cfm ../$@ applet.manifest PuzzleEngine.class PuzzleApplet*.class org
echo '<applet archive="'$@'" code="PuzzleApplet" width="700" height="500"></applet>' >$*.html
!end
# A benchmarking and testing target for the GTK puzzles.
!begin gtk
test: benchmark.html benchmark.txt
benchmark.html: benchmark.txt benchmark.pl
./benchmark.pl benchmark.txt > $@
benchmark.txt: benchmark.sh $(GAMES)
./benchmark.sh > $@
!end
!begin am
test: benchmark.html benchmark.txt
benchmark.html: benchmark.txt benchmark.pl
./benchmark.pl benchmark.txt > $@
benchmark.txt: benchmark.sh $(GAMES)
./benchmark.sh > $@
!end

View file

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

View file

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

View file

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

View file

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

View file

@ -1,52 +0,0 @@
#!/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";
}

View file

@ -1295,7 +1295,8 @@ flag in the \c{game_ui} to indicate which flash type is required.)
\c const game_drawstate *ds,
\c const game_state *state,
\c const game_params *params,
\c int *x, int *y, int *w, int *h);
\c int *x, int *y,
\c int *w, int *h);
This function queries the backend for the rectangular region
containing the cursor (in games that have one), or other region of
@ -1303,34 +1304,34 @@ interest.
This function is called by only
\cw{midend_get_cursor_location()}(\k{midend-get-cursor-location}). Its
purpose is to allow frontends to query the location of the backend's
cursor. With knowledge of this location, a frontend can, for example,
purpose is to allow front ends to query the location of the backend's
cursor. With knowledge of this location, a front end can, for example,
ensure that the region of interest remains visible if the puzzle is
too big to fit on the screen at once.
On returning, \cw{*x}, \cw{*y} should be set to the X and Y
coordinates of the upper-left corner of the rectangular region of
interest, and \cw{*w} and \cw{*h} should be the width and height of
that region, respectively. All return values are in units of pixels in
screenspace coordinates. In the event that a cursor is not visible on
screen, this function should return and leave the return parameters
untouched \dash the mid-end will notice this. The backend need not
that region, respectively. In the event that a cursor is not visible
on screen, this function should return and leave the return parameters
untouched \dash the midend will notice this. The backend need not
bother checking that \cw{x}, \cw{y}, \cw{w} and \cw{h} are
non-\cw{NULL} \dash the mid-end guarantees that they will not be.
non-\cw{NULL} \dash the midend guarantees that they will not be.
Defining what constitutes a \q{region of interest} is left up to the
backend. If a game provides a conventional cursor \dash such as Mines,
Solo, or any of the other grid-based games \dash the most logical choice
is of course the cursor location. However, in other cases such as Cube
or Inertia, there is no \q{cursor} in the conventional sense \dash the
player controls an object moving around the screen. In these cases, it
makes sense to define the region of interest as the bounding box of
the player or another sensible region \dash such as the grid square the
player is sitting on in Cube.
Solo, or any of the other grid-based games \dash the most logical
choice is of course the location of the cursor itself. However, in
other cases such as Cube or Inertia, there is no \q{cursor} in the
conventional sense \dash the player instead controls an object moving
around the screen. In these cases, it makes sense to define the region
of interest as the bounding box of the player object or another
sensible region \dash such as the grid square the player is sitting on
in Cube.
If a backend does not provide a cursor mechanism at all, the backend
is free to provide an empty implementation of this function, or a
\cw{NULL} pointer in the \cw{game} structure \dash the mid-end will
\cw{NULL} pointer in the \cw{game} structure \dash the midend will
notice either of these cases and behave appropriately.
\S{backend-status} \cw{status()}
@ -3353,27 +3354,31 @@ function. Some back ends require that \cw{midend_size()}
\H{midend-get-cursor-location} \cw{midend_get_cursor_location()}
\c void midend_get_cursor_location(midend *me,
\c int *x, int *y, int *w, int *h);
\c bool midend_get_cursor_location(midend *me,
\c int *x, int *y,
\c int *w, int *h);
This function returns the location of the backend's on-screen cursor
or other region of interest in the parameters \cw{*x}, \cw{*y},
\cw{*w} and \cw{*h}, which describe a rectangle with an upper-left
corner at \cw{(*x,*y)} and a size of \cw{*w} pixels wide by \cw{*h}
pixels tall. The mid-end will ignore any return parameters that may be
equal to \cw{NULL}.
This function requests the location of the back end's on-screen cursor
or other region of interest.
What exactly this region contains is up to the backend, but in general
the region will be an area that the player is controlling with the
cursor keys \dash such as the player location in Cube and Inertia, or
the cursor in any of the conventional grid-based games. With knowledge
of this location, a frontend can, for example, ensure that the region
of this location, a front end can, for example, ensure that the region
of interest remains visible even if the entire puzzle is too big to
fit on the screen.
If there is no such region (if either the cursor is not visible, or if
the game does not have cursor support), both \cw{*x} and \cw{*y} will
be set to \cw{-1}.
On success, this function returns \cw{true}, and the locations pointed
to by \cw{x}, \cw{y}, \cw{w} and \cw{h} are updated to describe the
cursor region, which has an upper-left corner located at \cw{(*x,*y)}
and a size of \cw{*w} pixels wide by \cw{*h} pixels tall. The caller
may pass \cw{NULL} for any number of these pointers, which will be
ignored.
On failure, this function returns \cw{false}. Failure can occur if
there is currently no active cursor region, or if the back end lacks
cursor support.
\H{midend-status} \cw{midend_status()}

View file

@ -1,701 +0,0 @@
/*
* 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() {
gametypelist.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(int menuid, const char *name, int value);
*
* Add a preset to the drop-down types menu, or to a submenu of
* it. 'menuid' specifies an index into our array of submenus
* where the item might be placed; 'value' specifies the number
* that js_get_selected_preset() will return when this item is
* clicked.
*/
js_add_preset: function(menuid, ptr, value) {
var name = Pointer_stringify(ptr);
var item = document.createElement("li");
item.setAttribute("data-index", value);
var tick = document.createElement("span");
tick.appendChild(document.createTextNode("\u2713"));
tick.style.color = "transparent";
tick.style.paddingRight = "0.5em";
item.appendChild(tick);
item.appendChild(document.createTextNode(name));
gametypesubmenus[menuid].appendChild(item);
gametypeitems.push(item);
item.onclick = function(event) {
if (dlg_dimmer === null) {
gametypeselectedindex = value;
command(2);
}
}
},
/*
* int js_add_preset_submenu(int menuid, const char *name);
*
* Add a submenu in the presets menu hierarchy. Returns its index,
* for passing as the 'menuid' argument in further calls to
* js_add_preset or this function.
*/
js_add_preset_submenu: function(menuid, ptr, value) {
var name = Pointer_stringify(ptr);
var item = document.createElement("li");
// We still create a transparent tick element, even though it
// won't ever be selected, to make submenu titles line up
// nicely with their neighbours.
var tick = document.createElement("span");
tick.appendChild(document.createTextNode("\u2713"));
tick.style.color = "transparent";
tick.style.paddingRight = "0.5em";
item.appendChild(tick);
item.appendChild(document.createTextNode(name));
var submenu = document.createElement("ul");
item.appendChild(submenu);
gametypesubmenus[menuid].appendChild(item);
var toret = gametypesubmenus.length;
gametypesubmenus.push(submenu);
return toret;
},
/*
* int js_get_selected_preset(void);
*
* Return the index of the currently selected value in the type
* dropdown.
*/
js_get_selected_preset: function() {
return gametypeselectedindex;
},
/*
* 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) {
gametypeselectedindex = n;
for (var i in gametypeitems) {
var item = gametypeitems[i];
var tick = item.firstChild;
if (item.getAttribute("data-index") == n) {
tick.style.color = "inherit";
} else {
tick.style.color = "transparent";
}
}
},
/*
* 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) {
disable_menu_item(undo_button, (undo == 0));
disable_menu_item(redo_button, (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) {
dialog_init(Pointer_stringify(titletext));
},
/*
* 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() {
dialog_launch(function(event) {
for (var i in dlg_return_funcs)
dlg_return_funcs[i]();
command(3); // OK
}, function(event) {
command(4); // Cancel
});
},
/*
* void js_dialog_cleanup(void);
*
* Stop displaying a dialog, and clean up the internal state
* associated with it.
*/
js_dialog_cleanup: function() {
dialog_cleanup();
},
/*
* void js_focus_canvas(void);
*
* Return keyboard focus to the puzzle canvas. Called after a
* puzzle-control button is pressed, which tends to have the side
* effect of taking focus away from the canvas.
*/
js_focus_canvas: function() {
onscreen_canvas.focus();
}
});

View file

@ -1,500 +0,0 @@
/*
* 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 <ul> object implementing the game-type drop-down, and a list of
// the <li> objects inside it. Used by js_add_preset(),
// js_get_selected_preset() and js_select_preset().
var gametypelist = null, gametypeitems = [];
var gametypeselectedindex = null;
var gametypesubmenus = [];
// 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};
}
// Enable and disable items in the CSS menus.
function disable_menu_item(item, disabledFlag) {
if (disabledFlag)
item.className = "disabled";
else
item.className = "";
}
// Dialog-box functions called from both C and JS.
function dialog_init(titletext) {
// Create an overlay on the page which darkens everything
// beneath it.
dlg_dimmer = document.createElement("div");
dlg_dimmer.style.width = "100%";
dlg_dimmer.style.height = "100%";
dlg_dimmer.style.background = '#000000';
dlg_dimmer.style.position = 'fixed';
dlg_dimmer.style.opacity = 0.3;
dlg_dimmer.style.top = dlg_dimmer.style.left = 0;
dlg_dimmer.style["z-index"] = 99;
// Now create a form which sits on top of that in turn.
dlg_form = document.createElement("form");
dlg_form.style.width = (window.innerWidth * 2 / 3) + "px";
dlg_form.style.opacity = 1;
dlg_form.style.background = '#ffffff';
dlg_form.style.color = '#000000';
dlg_form.style.position = 'absolute';
dlg_form.style.border = "2px solid black";
dlg_form.style.padding = "20px";
dlg_form.style.top = (window.innerHeight / 10) + "px";
dlg_form.style.left = (window.innerWidth / 6) + "px";
dlg_form.style["z-index"] = 100;
var title = document.createElement("p");
title.style.marginTop = "0px";
title.appendChild(document.createTextNode(titletext));
dlg_form.appendChild(title);
dlg_return_funcs = [];
dlg_next_id = 0;
}
function dialog_launch(ok_function, cancel_function) {
// Put in the OK and Cancel buttons at the bottom.
var button;
if (ok_function) {
button = document.createElement("input");
button.type = "button";
button.value = "OK";
button.onclick = ok_function;
dlg_form.appendChild(button);
}
if (cancel_function) {
button = document.createElement("input");
button.type = "button";
button.value = "Cancel";
button.onclick = cancel_function;
dlg_form.appendChild(button);
}
document.body.appendChild(dlg_dimmer);
document.body.appendChild(dlg_form);
}
function dialog_cleanup() {
document.body.removeChild(dlg_dimmer);
document.body.removeChild(dlg_form);
dlg_dimmer = dlg_form = null;
onscreen_canvas.focus();
}
// Init function called from body.onload.
function initPuzzle() {
// Construct the off-screen canvas used for double buffering.
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']);
button_phys2log = [null, null, null];
buttons_down = function() {
var i, toret = 0;
for (i = 0; i < 3; i++)
if (button_phys2log[i] !== null)
toret |= 1 << button_phys2log[i];
return toret;
};
onscreen_canvas.onmousedown = function(event) {
if (event.button >= 3)
return;
var xy = relative_mouse_coords(event, onscreen_canvas);
var logbutton = event.button;
if (event.shiftKey)
logbutton = 1; // Shift-click overrides to middle button
else if (event.ctrlKey)
logbutton = 2; // Ctrl-click overrides to right button
mousedown(xy.x, xy.y, logbutton);
button_phys2log[event.button] = logbutton;
onscreen_canvas.setCapture(true);
};
mousemove = Module.cwrap('mousemove', 'void',
['number', 'number', 'number']);
onscreen_canvas.onmousemove = function(event) {
var down = buttons_down();
if (down) {
var xy = relative_mouse_coords(event, onscreen_canvas);
mousemove(xy.x, xy.y, down);
}
};
mouseup = Module.cwrap('mouseup', 'void',
['number', 'number', 'number']);
onscreen_canvas.onmouseup = function(event) {
if (event.button >= 3)
return;
if (button_phys2log[event.button] !== null) {
var xy = relative_mouse_coords(event, onscreen_canvas);
mouseup(xy.x, xy.y, button_phys2log[event.button]);
button_phys2log[event.button] = null;
}
};
// 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);
};
// 'number' is used for C pointers
get_save_file = Module.cwrap('get_save_file', 'number', []);
free_save_file = Module.cwrap('free_save_file', 'void', ['number']);
load_game = Module.cwrap('load_game', 'void', ['string', 'number']);
document.getElementById("save").onclick = function(event) {
if (dlg_dimmer === null) {
var savefile_ptr = get_save_file();
var savefile_text = Pointer_stringify(savefile_ptr);
free_save_file(savefile_ptr);
dialog_init("Download saved-game file");
dlg_form.appendChild(document.createTextNode(
"Click to download the "));
var a = document.createElement("a");
a.download = "puzzle.sav";
a.href = "data:application/octet-stream," +
encodeURIComponent(savefile_text);
a.appendChild(document.createTextNode("saved-game file"));
dlg_form.appendChild(a);
dlg_form.appendChild(document.createTextNode("."));
dlg_form.appendChild(document.createElement("br"));
dialog_launch(function(event) {
dialog_cleanup();
});
}
};
document.getElementById("load").onclick = function(event) {
if (dlg_dimmer === null) {
dialog_init("Upload saved-game file");
var input = document.createElement("input");
input.type = "file";
input.multiple = false;
dlg_form.appendChild(input);
dlg_form.appendChild(document.createElement("br"));
dialog_launch(function(event) {
if (input.files.length == 1) {
var file = input.files.item(0);
var reader = new FileReader();
reader.addEventListener("loadend", function() {
var string = reader.result;
load_game(string, string.length);
});
reader.readAsBinaryString(file);
}
dialog_cleanup();
}, function(event) {
dialog_cleanup();
});
}
};
gametypelist = document.getElementById("gametype");
gametypesubmenus.push(gametypelist);
// In IE, the canvas doesn't automatically gain focus on a mouse
// click, so make sure it does
onscreen_canvas.addEventListener("mousedown", function(event) {
onscreen_canvas.focus();
});
// In our dialog boxes, Return and Escape should be like pressing
// OK and Cancel respectively
document.addEventListener("keydown", function(event) {
if (dlg_dimmer !== null && event.keyCode == 13) {
for (var i in dlg_return_funcs)
dlg_return_funcs[i]();
command(3);
}
if (dlg_dimmer !== null && event.keyCode == 27)
command(4);
});
// Set up the function pointers we haven't already grabbed.
dlg_return_sval = Module.cwrap('dlg_return_sval', 'void',
['number','string']);
dlg_return_ival = Module.cwrap('dlg_return_ival', 'void',
['number','number']);
timer_callback = Module.cwrap('timer_callback', 'void', ['number']);
// Save references to the two permalinks.
permalink_desc = document.getElementById("permalink-desc");
permalink_seed = document.getElementById("permalink-seed");
// Default to giving keyboard focus to the puzzle.
onscreen_canvas.focus();
// Create the resize handle.
var resize_handle = document.createElement("canvas");
resize_handle.width = 10;
resize_handle.height = 10;
{
var ctx = resize_handle.getContext("2d");
ctx.beginPath();
for (var i = 1; i <= 7; i += 3) {
ctx.moveTo(8.5, i + 0.5);
ctx.lineTo(i + 0.5, 8.5);
}
ctx.lineWidth = '1px';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = '#000000';
ctx.stroke();
}
resizable_div = document.getElementById("resizable");
resizable_div.appendChild(resize_handle);
resize_handle.style.position = 'absolute';
resize_handle.style.zIndex = 98;
resize_handle.style.bottom = "0";
resize_handle.style.right = "0";
resize_handle.style.cursor = "se-resize";
resize_handle.title = "Drag to resize the puzzle. Right-click to restore the default size.";
var resize_xbase = null, resize_ybase = null, restore_pending = false;
var resize_xoffset = null, resize_yoffset = null;
var resize_puzzle = Module.cwrap('resize_puzzle',
'void', ['number', 'number']);
var restore_puzzle_size = Module.cwrap('restore_puzzle_size', 'void', []);
resize_handle.oncontextmenu = function(event) { return false; }
resize_handle.onmousedown = function(event) {
if (event.button == 0) {
var xy = element_coords(onscreen_canvas);
resize_xbase = xy.x + onscreen_canvas.width / 2;
resize_ybase = xy.y;
resize_xoffset = xy.x + onscreen_canvas.width - event.pageX;
resize_yoffset = xy.y + onscreen_canvas.height - event.pageY;
} else {
restore_pending = true;
}
resize_handle.setCapture(true);
event.preventDefault();
};
window.addEventListener("mousemove", function(event) {
if (resize_xbase !== null && resize_ybase !== null) {
resize_puzzle((event.pageX + resize_xoffset - resize_xbase) * 2,
(event.pageY + resize_yoffset - resize_ybase));
event.preventDefault();
// Chrome insists on selecting text during a resize drag
// no matter what I do
if (window.getSelection)
window.getSelection().removeAllRanges();
else
document.selection.empty(); }
});
window.addEventListener("mouseup", function(event) {
if (resize_xbase !== null && resize_ybase !== null) {
resize_xbase = null;
resize_ybase = null;
onscreen_canvas.focus(); // return focus to the puzzle
event.preventDefault();
} else if (restore_pending) {
// If you have the puzzle at larger than normal size and
// then right-click to restore, I haven't found any way to
// stop Chrome and IE popping up a context menu on the
// revealed piece of document when you release the button
// except by putting the actual restore into a setTimeout.
// Gah.
setTimeout(function() {
restore_pending = false;
restore_puzzle_size();
onscreen_canvas.focus();
}, 20);
event.preventDefault();
}
});
// Run the C setup function, passing argv[1] as the fragment
// identifier (so that permalinks of the form puzzle.html#game-id
// can launch the specified id).
Module.callMain([location.hash]);
// And if we get here with everything having gone smoothly, i.e.
// we haven't crashed for one reason or another during setup, then
// it's probably safe to hide the 'sorry, no puzzle here' div and
// show the div containing the actual puzzle.
document.getElementById("apology").style.display = "none";
document.getElementById("puzzle").style.display = "inline";
}

View file

@ -1,33 +0,0 @@
// -*- 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',
// Game-saving and game-loading functions
'_get_save_file',
'_free_save_file',
'_load_game',
// Callbacks to return values from dialog boxes
'_dlg_return_sval',
'_dlg_return_ival',
// Callbacks when the resizing controls are used
'_resize_puzzle',
'_restore_puzzle_size',
// Main program, run at initialisation time
'_main'
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,15 +0,0 @@
Galaxies
<p>
Draw lines along grid edges so as to divide the grid up into connected
regions of squares.
<p>
Every region should have two-way rotational symmetry, should contain
exactly one dot which is in its centre, and should contain no lines
separating two of its own squares from each other. A region satisfying
all of these requirements will be automatically highlighted.
<p>
Click on a grid edge to add or remove a line. Right-click on a dot
and drag the mouse to place an arrow in a grid square pointing to
that dot, to indicate that you think that square must belong in the
same region as that dot. Right-drag an existing arrow to move it, or
drop it off the edge of the grid to remove it.

View file

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

View file

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

View file

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

View file

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

View file

@ -1,267 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
my $jspath = "";
while ($ARGV[0] =~ /^-/) {
my $opt = shift @ARGV;
last if $opt eq "--";
if ($opt =~ /^--jspath=(.+)$/) {
$jspath = $1;
} else {
die "jspage.pl: unrecognised option '$opt'\n";
}
}
open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n";
my $footer = "";
$footer .= $_ while <$footerfile>;
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="${jspath}${filename}.js"></script>
<style class="text/css">
/* Margins and centring on the top-level div for the game menu */
#gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center }
/* Inside that div, the main menu bar and every submenu inside it is a <ul> */
#gamemenu ul {
list-style: none; /* get rid of the normal unordered-list bullets */
display: inline; /* make top-level menu bar items appear side by side */
position: relative; /* allow submenus to position themselves near parent */
margin: 0;
margin-bottom: 0.5em;
padding: 0;
}
/* Individual menu items are <li> elements within such a <ul> */
#gamemenu ul li {
/* Add a little mild text formatting */
font-weight: bold; font-size: 0.8em;
/* Line height and padding appropriate to top-level menu items */
padding-left: 0.75em; padding-right: 0.75em;
padding-top: 0.2em; padding-bottom: 0.2em;
margin: 0;
/* Make top-level menu items appear side by side, not vertically stacked */
display: inline;
/* Suppress the text-selection I-beam pointer */
cursor: default;
/* Surround each menu item with a border. The left border is removed
* because it will abut the right border of the previous item. (A rule
* below will reinstate the left border for the leftmost menu item.) */
border-left: 0;
border-right: 1px solid rgba(0,0,0,0.3);
border-top: 1px solid rgba(0,0,0,0.3);
border-bottom: 1px solid rgba(0,0,0,0.3);
}
#gamemenu ul li.disabled {
/* Grey out menu items with the "disabled" class */
color: rgba(0,0,0,0.5);
}
#gamemenu ul li.separator {
color: transparent;
border: 0;
}
#gamemenu ul li.afterseparator {
border-left: 1px solid rgba(0,0,0,0.3);
}
#gamemenu ul li:first-of-type {
/* Reinstate the left border for the leftmost top-level menu item */
border-left: 1px solid rgba(0,0,0,0.3);
}
#gamemenu ul li:hover {
/* When the mouse is over a menu item, highlight it */
background: rgba(0,0,0,0.3);
/* Set position:relative, so that if this item has a submenu it can
* position itself relative to the parent item. */
position: relative;
}
#gamemenu ul li.disabled:hover {
/* Disabled menu items don't get a highlight on mouse hover */
background: inherit;
}
#gamemenu ul ul {
/* Second-level menus and below are not displayed by default */
display: none;
/* When they are displayed, they are positioned immediately below
* their parent <li>, and with the left edge aligning */
position: absolute;
top: 100%;
left: 0;
/* We must specify an explicit background colour for submenus, because
* they must be opaque (don't want other page contents showing through
* them). */
background: white;
/* And make sure they appear in front. */
z-index: 1;
}
#gamemenu ul ul.left {
/* A second-level menu with class "left" aligns its right edge with
* its parent, rather than its left edge */
left: inherit; right: 0;
}
/* Menu items in second-level menus and below */
#gamemenu ul ul li {
/* Go back to vertical stacking, for drop-down submenus */
display: block;
/* Inhibit wrapping, so the submenu will expand its width as needed. */
white-space: nowrap;
/* Override the text-align:center from above */
text-align: left;
/* Don't make the text any smaller than the previous level of menu */
font-size: 100%;
/* This time it's the top border that we omit on all but the first
* element in the submenu, since now they're vertically stacked */
border-left: 1px solid rgba(0,0,0,0.3);
border-right: 1px solid rgba(0,0,0,0.3);
border-top: 0;
border-bottom: 1px solid rgba(0,0,0,0.3);
}
#gamemenu ul ul li:first-of-type {
/* Reinstate top border for first item in a submenu */
border-top: 1px solid rgba(0,0,0,0.3);
}
#gamemenu ul ul ul {
/* Third-level submenus are drawn to the side of their parent menu
* item, not below it */
top: 0; left: 100%;
}
#gamemenu ul ul ul.left {
/* A submenu with class "left" goes to the left of its parent,
* not the right */
left: inherit; right: 100%;
}
#gamemenu ul li:hover > ul {
/* Last but by no means least, the all-important line that makes
* submenus be displayed! Any <ul> whose parent <li> is being
* hovered over gets display:block overriding the display:none
* from above. */
display: block;
}
</style>
</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">
<div id="gamemenu"><ul><li>Game...<ul
><li id="specific">Enter game ID</li
><li id="random">Enter random seed</li
><li id="save">Download save file</li
><li id="load">Upload save file</li
></ul></li
><li>Type...<ul id="gametype"></ul></li
><li class="separator"></li
><li id="new" class="afterseparator">New game</li
><li id="restart">Restart game</li
><li id="undo">Undo move</li
><li id="redo">Redo move</li
><li id="solve">Solve game</li
></ul></div>
<div align=center>
<div id="resizable" style="position:relative; left:0; top:0">
<canvas style="display: block" id="puzzlecanvas" width="1px" height="1px" tabindex="1">
</canvas>
<div id="statusbarholder" style="display: block">
</div>
</div>
<p>
Link to this puzzle:
<a id="permalink-desc">by game ID</a>
<a id="permalink-seed">by random seed</a>
</p>
</div>
</div>
<div id="apology">
Sorry, this Javascript puzzle doesn't seem to work in your web
browser. Perhaps you have Javascript disabled, or perhaps your browser
doesn't provide a feature that the puzzle code requires (such as
<a href="https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays">typed arrays</a>).
These puzzles have been successfully run in Firefox 19, Chrome 26,
Internet Explorer 10 and Safari 6.
</div>
<hr>
${instructions}
${links}
${footer}
</body>
</html>
EOF
close $outpage;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,162 +0,0 @@
# 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))
P96D24 = $(patsubst %,%-96d24.png,$(PUZZLES))
P96D8 = $(patsubst %,%-96d8.png,$(PUZZLES))
P96D4 = $(patsubst %,%-96d4.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: $(P96D24) $(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 four sizes.
$(P96D24): %-96d24.png: %-ibase.png
$(PIC)square.pl 96 4 $^ $@
$(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.
$(P96D8) $(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...)
$(P96D4): %-96d4.png: %-ibase4.png
$(PIC)square.pl 96 1 $^ $@-tmp2.png
convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@
rm -f $@-tmp2.png
$(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 %-96d24.png
$(PIC)cicon.pl $^ > $@
clean:
rm -f *.png *.ico *.rc *-icon.c

View file

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

View file

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

View file

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

View file

@ -1,37 +0,0 @@
#!/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"

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