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:
parent
ae59995553
commit
84cd812ccd
169 changed files with 85 additions and 19915 deletions
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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! */
|
||||
|
||||
|
|
|
@ -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 $@
|
|
@ -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
|
|
@ -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]
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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/&/&/g;
|
||||
$text =~ s/</</g;
|
||||
$text =~ s/>/>/g;
|
||||
return $text;
|
||||
}
|
|
@ -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
|
|
@ -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%; }
|
|
@ -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
|
|
@ -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";
|
||||
}
|
|
@ -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()}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
|
@ -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";
|
||||
}
|
|
@ -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'
|
||||
]
|
|
@ -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.
|
|
@ -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.
|
|
@ -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!
|
|
@ -1,10 +0,0 @@
|
|||
Dominosa
|
||||
<p>
|
||||
Tile the rectangle with dominoes (1×2 rectangles) so that
|
||||
every possible domino appears exactly once (that is, every possible
|
||||
pair of numbers, including doubles).
|
||||
<p>
|
||||
Click between two adjacent numbers to place or remove a domino.
|
||||
Right-click to place a line between numbers if you think a domino
|
||||
definitely cannot go there. Dominoes light up red if two identical
|
||||
ones appear on the grid.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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).
|
|
@ -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.
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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.
|
|
@ -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.
|
|
@ -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!
|
|
@ -1,17 +0,0 @@
|
|||
Magnets
|
||||
<p>
|
||||
Fill each domino shape with either a magnet (consisting of a + and
|
||||
− pole) or a neutral domino (green).
|
||||
<p>
|
||||
The number of + poles that in each row and column must match the
|
||||
numbers along the top and left; the number of − poles must
|
||||
match the numbers along the bottom and right. Two + poles may not be
|
||||
orthogonally adjacent to each other, and similarly two − poles.
|
||||
<p>
|
||||
Left-click a domino to toggle it between being empty and being a
|
||||
magnet (the + is placed in the end you click). Right-click to toggle
|
||||
between empty, neutral, and a ?? mark indicating that you're sure
|
||||
it's a magnet but don't yet know which way round it goes.
|
||||
<p>
|
||||
Left-click a clue to mark it as done (grey it out). To unmark a clue
|
||||
as done, left-click it again.
|
|
@ -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.
|
|
@ -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.)
|
|
@ -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!
|
|
@ -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!
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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!
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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×2 square section. Left-click
|
||||
in the centre of that section (i.e. on a corner point between four
|
||||
squares) to rotate the whole section anticlockwise. Right-click to
|
||||
rotate the section clockwise.
|
||||
<p>
|
||||
When you master the basic game, go to the Type menu to try it with
|
||||
larger rotating groups (for a 3×3 group you must click in the
|
||||
centre of a square to rotate the block around it). Or select the
|
||||
'orientable' mode in which every square must end up the right way
|
||||
round as well as in the right place. Or both!
|
|
@ -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.
|
|
@ -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><</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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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";
|
|
@ -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
Loading…
Reference in a new issue