Created: 04-07-2021
Last update: 04-07-2021
AniMisc


How I made jigsaw puzzles to play on the computer



This how-I assumes passing familiarity with Linux (especially the notion of desktop environments and user rights), KiSS, and layered images.


Screenshots with a magnifying glass beside them are shrunk: click on the magnifying glass to see the full size version.

Intro

It is perfectly simple to cut up an image into puzzle pieces to drag around and fit together on a computer screen. Image processing programs may even come with a jigsaw overlay to do just that. Two jigsaw puzzle games for Linux simply let you import the image, slice it up and present it as a puzzle to be loaded. And KiSS viewers can also display jigsaw puzzle pieces. Or if you don't want to make your own, but are happy to play puzzles online, there are sites that cater to your jigsaw pleasures for free, bearing in mind that if you're getting a product for free, then you are the product. See the links at the bottom of the page for the big sites that will turn up in any Google search.

Getting pictures

To make puzzles, I collected a number of images from the internet, and made one myself. A word of caution: if you plan to distribute the puzzles, you must ask for permission to use other people's images, which especially abovementioned puzzle sites are not likely to grant, because they want their puzzles played online so they can show ads, harvest your data or whatever their business model is. But as long as it stays private and on your computer, anything is game: photos posted on Twitter, catalogue pictures, desktop images, cartoons, hilarious or poignant panels from webcomics, or your own snapshots. Most of my image collection are photos posted on forums, then there's a few wiki images (including an ancient, decorated terracotta bathtub), some elegant interiors from property sites, and choice photos from a gardening newsletter.

Making a puzzle in Palapeli

Palapeli is a Linux puzzle game written for KDE. To those not acquainted with Linux, there are two main desktop environments (graphical user interfaces comparable to the Windows desktop, and extras that integrate seamlessly into those interfaces) called Gnome and KDE. In the past, Gnome stood for simplicity, and KDE was the bloated blingy one. Gnome has since branched into Gnome 3, Unity, Mate and Cinnamon, while KDE has kept its name, but changed dramatically after version 3, which has been forked to Trinity. There are a number of other desktop environments, but, Gnome and KDE being the main players, Linux software can be written with one of these two in mind. The issue being that these desktops use different software libraries, and installing a KDE program on Gnome - or, in my case, Cinnamon - meant installing a good part of the KDE core, too. It was worth it, though.

Palapeli presents a background with puzzle pieces and, if you click on the Preview button at the top right, a preview window with a small version of the image (enlarged on mouseover), usually to the left of the puzzle screen; I dragged the preview window over the puzzle screen for a more compact screenshot. Palapeli can be configured to pick a different background from /usr/share/palapeli/backgrounds (where you can copy a 512x512 pixel background of your own, using sudo; restart Palapeli to have it show up, but this can cause problems, see final note) and to show the puzzle pieces flat (for older processors) or with a bevel, for that authentic 3D puzzle piece look. A selected piece will be outlined by a blue glow; this colour can be altered in Palapeli's settings (Menu, Settings, Configure Palapeli). When two fitting pieces are close enough - this distance can also be adjusted in the settings - they snap together. The pieces are shrunk in size, because the window needs to have enough room for both the growing assembled puzzle, and the unplaced pieces around it; once the last piece is in place, the image swells to its full size.


Palapeli's playfield.

Adding a new puzzle to Palapeli is simple. If you're in the middle of a puzzle, click "Back to collection" (top right) to go to the main screen. (Your puzzle progress will be saved.) In the main screen, click on "Create puzzle" (top left, right net to "Delete puzzle").


Palapeli's top-of-screen buttons.

A dialog window shows up where you can fill in (or browse for) the image file name (it will be copied, the original will stay, unaltered, where it is), give the puzzle a title, add a comment to be displayed in italics under the title, and attribute the image to an author. This last field must be filled, or the Next button remains greyed out. I don't know the author for most pics, so I added either a site name or "from internet", which appeared in the puzzle selection screen as a clunky "by from internet". Be careful: I haven't found how to correct any typos in these fields. If a puzzle's title ends up misspelled, I have to delete the puzzle and import the image again.


First puzzle-creating dialog.

Next, the chosen image can be sliced into pieces in a number of styles. For the most normal appearance, in keeping with Palapeli's default puzzle collection, choose "Rectangular grid". Choosing "Classic jigsaw pieces" makes the pieces come out with shallow ear-like plugs, rather than the properly interlocking tear-shaped ones.


Second puzzle-creating dialog.

Finally, set the details for the chosen style, and then click on the Finish button. I leave these at their defaults, except for the number of pieces (default 30), which I increase to anything between 50 and 100, because when it comes to the real thing, I prefer puzzles of anything between 400 and 2000 pieces, the kind that take weeks to finish. Sadly, a computer screen doesn't offer that kind of playing space, and 100 pieces makes for a cramped playfield with teensy pieces all over the place, hard to keep track of even when the window is maximized. Obviously, smaller image files should have less pieces while bigger images can have more. Setting 90 pieces is good for a large scene; the total may be rounded down to 88 or 84 pieces depending one how the image is sliced. Feel free to experiment; if the puzzle turns out awful, it can be deleted from the collection with the "Delete Puzzle" button (top left), an action which, as the game reminds, can't be undone, but all you have to do to get the puzzle back is create a new puzzle using the same image file and settings.

The toggle box "Dump grid image" is usually left unchecked, but I was curious where this file is dumped and whether it could be used as a template. It was dumped in my home directory as the black and white jigsaw puzzle line file "goldberg-slicer-dump.png", and I kept it for later use.


Third puzzle-creating dialog.

As said, the Rectangular grid option makes "normal" jigsaw puzzles, the Classic option makes puzzle pieces with ear-like plugs, the Rectangular pieces option makes squares (for the masochists; really, this is the hardest kind) and the Cairo, Hexagonal and Rotrex make for some crazyweird puzzle pieces! I chose one of them to see how it looked, but can't get over the disproportionate sizes, some pieces being huge, and others mere slivers.


A weirdly cut puzzle.

Having made a number of puzzles this way, I found that they had not been added to /usr/share/palapeli/collection, where the default puzzles are. That's understandable, as only a user with root privileges can write to /usr and its subdirectories. I expected the files to be added to my home directory under the catch-all hidden directory ~/.local. Here I did find a share/palapeli/collection subdirectory, only the created puzzles didn't have their titles for a name, but alphanumerical jumbles.


Only the default puzzles have proper names.

I thought I could use "Export to file", which exports a self-created puzzle to a file with the puzzle title as name and ".puzzle" as extension (eg. "Ancient Bathtub.puzzle"), to the home directory. (The file manager Nemo offers to open files with this extension in Palapeli, but that only works if the file is in ~/.local/share/palapeli/collection, and even then it just opens the Palapeli collection screen, not the specific puzzle.) Then I would delete the messily named puzzle, and import the exported and properly named file, or just copy it directly into ~/.local/share/palapeli/collection. Importing it gave it a jumbled name again, while copying it to ~/.local/share/palapeli/collection meant that it didn't show up in the Palapeli collection screen. Clearly, exporting the files serves to have a backup and to exchange puzzles with others, and I'll just have to live with the local funny filenames.

Why not move them straight to /usr/share/palapeli/collection? Because, apart from the fact that I can't directly write into that directory, it doesn't contain puzzle files. It contains five images and five configuraton files which make puzzles out of these images when Palapeli is installed, and put these puzzles in ~/.local/share/palapeli/collection. The configuration files contain translations: a configuration for a horse image might come with the titles Horse, Pferd, Cheval, Caballo, Kon etc. so that, on being installed, Palapeli can use the system's language setting for the puzzle title and filename. It's an elegant way to deal with the fact that not every Linux user speaks the same language.

Final note: Palapeli apparently has an internal database, and version 2.1, at least, quits with a segmentation fault if anything is added or removed from any of its directories, by, for instance, creating a new puzzle. Maybe it's not happy running in Cinnamon? To fix this, just keep restarting Palapeli until it stops crashing. On adding non-puzzle files: I added an extra background (experimental and ugly) to /usr/share/palapeli/backgrounds using sudo, but the .save files that are created for the puzzles include, and export, settings like what background the puzzle has, and if I export a puzzle with this extra background and import it on another computer where that background is missing, the game might crash. So I deleted that extra background, and would advise against adding any.

Making a puzzle in xjig

xjig is a puzzle game like Palapeli, and likewise installed from Linux Mint's repositories. Its puzzle pieces are bevelled by default, its window title is "xpuzzle", and a timer goes "boop" when the puzzle is completed. Where Palapeli's puzzle pieces are static, xjig's pieces revolve as I drag them (because I hold down the left mouse button; if I drag with the right mouse button, the piece stays still), and may only fit after being flipped. The bevelling is not very smooth, and when two pieces connect, they blend into a seamless whole. Rather than slicing up an image, xjig calculates puzzle piece shape, so pieces can have inverted, negative parts that become visible again when they fit onto other pieces. It's hard to explain, you have to see it for yourself.

The difference between xjig and Palapeli is that while both leave the source image file untouched, Palapeli makes an actual puzzle file that can be stored, while xjig, using a filename and other options, creates a puzzle on the fly that exists only in memory. The only way to "make" a puzzle that looks the same every time is to build a "menu", like the old batch menus that could be built under MS-DOS, where each entry runs a (line from a) script that reads "xjig -file filename -[options]". An interesting project, but outside the scope of this how-i.

As the x at the start of the name implies, xjig goes back to a time before Gnome and KDE, and though under Linux Mint Debian Edition 4 it can be started from the Games menu (where it always comes up with he same graphic of Tina Turner on a motorbike), it should be called from the command line, from where it will present any picture as a puzzle, no preparation needed. Typing "xjig" in the console does the same. Typing "xjig -file doggy.jpg" should, if doggy.jpg exists, slice it up for me in a jigsaw puzzle of, by default, 24 pieces.

Typing "xjig --help" directly into a terminal displays the following:

usage  : xjig []

options: -file : use image in 
         -w       : number of tiles in x direction
         -h       : number of tiles in y direction
         -ts      : set average tile size
         -ww      : width of image
         -wh      : height of image
         -side    : lock side  only
         -no_flip    : no automatic flip of landscape images
         -no_crop    : do not crop images
         -shm        : use MIT-SHM (default)
         -no_shm     : don't use MIT-SHM extension
         -shapes     : use shape extension to draw on desktop
         -no_anim    : don't animate rotation and flipping of tiles

controls:

click left:              rotate 90 degrees left
click right:             rotate 90 degrees right
click middle:            flip tile to backside on double sided puzzle
drag left:               drag with automatic rotation
drag middle:             straight drag
drag left+middle:        pause rotator drag for a straight drag
drag middle+left:        pause straight drag for a static rotation
drag middle+click left:  rotate 90 degrees left during straight drag
drag middle+click right: rotate 90 degrees right during straight drag
CTRL + click left:       (same as click middle)

author : Helmut Hoenig, July-24-96 (V2.4)   (Helmut.Hoenig@hub.de)

Typing

xjig -file wonkrain.jpg -w 3 -h 2 -ww 100 -wh 55
gives me the following six-piece mini puzzle with flippable pieces:


This screenshot needs no shrinking.

Being so old, xjig has its own man page (type: "man xjig"). This page states:

       Any image-file in GIF, JPEG, or PPM format can be used  as  the  source
       for  the puzzle, which is then randomly created regarding the sizes se‐
       lected by the options.
So PNG is out.

And:

       Puzzles can be doubled sided so you might have to flip the tiles to the
       correct side to let them snap together.

Here "double-sided" means that a puzzle piece may be mirrored, not that I can combine two images into one puzzle. An option prevents this mirroring:

OPTIONS
   Tile Selection
       -file name  use the specified file as the source image for the puzzle

       -side p     select the side of the image to be on  top,  if  you  don't
                   like the mess with the double sided tiles.

So a six-piece non-flippable puzzle requires typing the following at the prompt:

xjig -file [(path/)filename] -w 3 -h 2 -ww 100 -wh 55 -side 0

That was fun, but I'd like to play a puzzle game without first having to look up where the picture file is, then what size I should display it at, then how many pieces it should have both vertically and horizontally, and then type all this into a terminal. There is another menu option "xjig (Random Picture)", but that option doesn't work. The underlying executable is xjig-random, and its very short manpage reads:

NAME
       xjig-random - a random jigsaw puzzle

SYNOPSIS
       xjig-random 

DESCRIPTION
       xjig-random  selects  a random image from the /usr/share/games/xjig di‐
       rectory and loads it into xjig.

       Command-line options may be specified and will  be  passed  through  to
       xjig.

Typing "xjig-random --help" in a terminal causes the following to be spat out (formatted a bit for legibility):

Can't locate File/HomeDir.pm in @INC (you may need to install the File::HomeDir module)
(@INC contains:
/etc/perl
/usr/local/lib/x86_64-linux-gnu/perl/5.28.1
/usr/local/share/perl/5.28.1
/usr/lib/x86_64-linux-gnu/perl5/5.28
/usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.28
/usr/share/perl/5.28 /usr/local/lib/site_perl
/usr/lib/x86_64-linux-gnu/perl-base) at /usr/games/xjig-random line 11.
BEGIN failed--compilation aborted at /usr/games/xjig-random line 11.

So I Google "File::HomeDir module". (Well... I DuckDuckGo it.) It is, as expected, a perl module to find a user's home directory on any platform. Searching for the same term in Synaptic Package Manager, I find and install "libfile-homedir-perl". Now the menu entry for xjig-random works, and serves me up "tina.gif", which is the only image file in /usr/share/games/xjig. Which is of course owned by user "root", but my file manager, Nemo version 4.8.6, has the option "Open as Root", with which I can open this directory and drag any file into it.


How ordinary users add new puzzle images to root-owned directories.

Making a screen capture of the current wallpaper, and using GIMP to carve a nice swirly rectangle out of that ("Select", "Crop to selection", "Export as", "swirlywallpapersnippet.jpg") gives me an image to drag into the xjig directory. It is not moved but only copied into the xjig directory, and the copy is still owned by my username, instead of "root". In this directory window, opened as root, I right-click on the copy and set its permissions to match those of "tina.gif"; then I close the window. From now on, random-xjig presents me with a puzzle of either one picture or the other, although I can't set any options (like number of puzzle pieces) for it.


Don't forget to change file ownership.

I'm not satisfied; there has to be an easier way to make a puzzle. There are two GUI programs for xjig - see links - xJigsaw which now works with xpuz, a newer fork of xjig, and xjiggui for xjig itself, both bundled with the puzzle program they support. As xpuz can be installed alongside xjig without conflicts, I download xjigsaw_MI19_64.tar.gzv, a 64-bit binary compiled on Linux Mint 19, from the xJigsaw Help page. The archive is a directory "xjigsaw" in which I find a readme file, a picture of balloons and a binary called "xjigsaw". As the xJigsaw Help page says you can just put the binary anywhere and run it from there, I extract the directory to my home directory, open a terminal, and type:

cd ~/xjigsaw
xjigsaw

Nothing happens. I consult the readme file and check for missing dependencies; there are none. Then I remember that sometimes an executable has to be told to look for itself in the current directory, represented by a dot:

./xjigsaw

A window opens. Two big buttons on the left, "Choose Image" (which looks for files in, by default, /home/username/Pictures) and "Make Jigsaw" let me quickly pick an image (I choose my swirly snippet) to generate a puzzle out of, while to the right, the first tab, "Options", lets me set size and number of puzzle pieces, and choose from a range of puzzle piece styles. (If the puzzle width is deemed too narrow, the fill-in field turns pinkish, and I can't choose a puzzle piece style until I enter a bigger width.) Under the second tab, "Extra Options", I tick "Fix to 90 degree" so each piece rotates per 90 degrees instead of spinning round madly, or, to get the Palapeli experience of simple dragging, "No rotation"; "Two sided", which causes pieces to be possibly flipped as well as rotated, is unticked by default. "Help" explains all the options, while "Tools" lets me create a standalone executable puzzle (how, is explained under the Help tab), change the interface language, and add an xJigsaw menu item to the menu under Games. If I want more control, xJigsaw has created a hidden directory ".xjigsaw" in my home directory, where I can find and edit the file "xjigsaw.ini".


The puzzle creation screen in all its glory.

Unlike xjig, xpuz does accept PNG files. After choosing an image and options, I click the big button "Make Jigsaw", and the puzzle creation screen disappears to make way for a big puzzle board, showing the total number of pieces followed by the same timer and keyboard shortcuts in its title bat as xjig-random, and a preview window with a button "Start New Jigsaw", which destroys the current puzzle board and brings the puzzle creation screen back up. By default, all edge pieces are sorted in a column on the puzzle board, to the right of the other puzzle pieces. The only thing missing is the "boop" sound when a puzzle is finished, but instead I get a "tingg!" when I start a new jigsaw.

If I want to store puzzles as files, or manage without xJigsaw altogether, I set the image and options in the puzzle creation screen, but instead of clicking on the big "Make Jigsaw" button, I go to the tab "Tools" and click on "Create an executable puzzle", after ticking "no preview" if I don't want a preview window for the puzzle. This opens a Save File dialog, default filename "puzzle.xpuz", default directory "/home/username", to save a file of, in my case, 1.5MB - comparable to a Palapeli puzzle file of the same dimensions - that starts the puzzle when I click on it. This is perfect - I don't need xjig any more.


The puzzle board.

But curiosity killed the cat, and I want to try out xjiggui, even if the new xjig it comes with might conflict with the old version already installed. I want to download it from SourceForge, but as it is a deb file that actually requires installing, FireFox helpfully suggests I open it with the appropriate installer.


Instead of downloading, why not just install?

The installation screen tells me what's in the package, and a quick tap installs it.


Extra info while installing.

I can't find xjiggui in the Games submenu, because it's called itself "Jigsaw Puzzle" under a new submenu: "Other". Typing "xjiggui" in a terminal also works. The installation screen didn't lie: the game screen really is adorably simple, but I go for the Advanced Settings to change most of the settings I found so annoying about xjig version 2.4. Overall, xjiggui offers the same settings as xJigsaw, but instead of an .ini file to edit, the Advanced Options screen has a button "Show xjig options" which opens a text editor that shows what would be the output of typing "xjig --help" for this new version in a terminal. Its options are a bit wider than those of the older version. Interestingly, pressing "S" would "Save a puzzle (for future use)".


Options to prevent flipped pieces and regulate rotation.

After choosing an image, it is displayed with a grid overlay to give an idea of how the image will be sliced up. The choice of puzzle piece styles equals that of xJigsaw. The big Go button opens the familiar xjig puzzle screen, while the small Stop button on the preview window closes it again. The initially empty rectangle to the left of the Go button displays an image collection that you can add to or delete from; if any image is still in the collection, but deleted from disk, the image after it will be loaded with a false warning that its format is not supported. There is a button to add player names, to make the game "multiplayer", ie. let people play under different names so they can keep their scores separate. Again, the "boop" is missing, but otherwise, it's an enormous improvement on the old xjig.


Ready to make that puzzle.

What happened to the old xjig, though? Its menu item no longer works, although the xjig-random menu item does. If I type "xjig" in the terminal, it no longer grabs the first file from its own directory, but produces the same output as if I'd typed "xjig --help". It is version 2.6, so the old version is well and truly overwritten. This means that if I supply it with a filename, I can test how it saves a puzzle. (The S key does nothing on the xjiggui puzzle board. I tried.) For a start, I discover xjig wants absolute pathnames, not "~/".

xjig -file "/home/username/Desktop/swirlywallpapersnippet.jpg"

The puzzle board appears, and I press Shift-s. The puzzle board title bar briefly displays "Game Saved (Debug Only)" but I can't find any saved puzzle file. Oh well. I assume saving was planned but never implemented.

I should edit the Games submenu to get rid of the xjig entry or give it a filename as parameter, but don't know how; that will be for a different how-i. For now, I will move to the next step: making a puzzle out of an image from scratch.

Making a layered image file puzzle

Layered image files are a feature of all the big graphics editors, who typically save them in their own format. Paint Shop Pro, before it was acquired by Corel (and possibly after, I only used it up to version 7) has the PSP format. That staple of graphical designers, Adobe Photoshop, has PSD. GIMP, multiplatform but the pride of Linux users, has XCF. The principle is the same in all cases: an image file has layers, each layer contains part of the image, and the layers can be moved around in the graphics editor, which functions as viewer. It's a bit clunky to have to load up behemoths like Photoshop or GIMP for one small game, with no features like puzzle pieces snapping together or a main screen presenting a choice of puzzle titles, but you can import from any file format and use any colour depth you like. Paint Shop Pro 7 even came with a filter that superimposed jigsaw lines on an image, which could then, using these lines, be manually chopped up into pieces. That filter is now on an old laptop in a box somewhere, so I'll use the "goldberg-slicer-dump.png" that Palapeli provided.

As an aside, the highest possible colour depth of layered images may be why, in a time when KiSS viewers supported palettes of 16 or rarely 256 colours, artists made "pseudo-KiSS" sets that were essentially PSD files where each "cel" was a layer. Of course, nowadays all monitors and graphics cards are truecolour, the dithering days of limited colour depth forgotten.

Using GIMP (version 2.8, but any version that supports layers should work) I will make a pseudo-puzzle game. First, I open "goldberg-slicer-dump.png" and instantly save this as "PseudoPuzzle.XCF", after which the PNG file can be deleted or left for later, if I'm making more puzzles. Next, I'll make a new layer, name it "Example" and, on that layer, lazily create "modern art" (a doodle run through multiple filters). That layer will be the base image. It has the same size as the puzzle grid, so I won't have to mess up that grid by resizing it.


A zoomed-out example of the image and its jigsaw puzzle grid.

I then drag the puzzle grid layer on top. The puzzle grid layer is white and black, but I can make the white parts transparent by adding a mask to the layer. I duplicate the layer, which can be done by choosing "Layer" from the menu (making sure the right layer is active!) and then "Duplicate layer"; this copied layer, I drag below the base image to get it out of sight; it will be used later to help slice the image into pieces. Going back to the puzzle grid layer, I choose "Layer" from the menu, then "Add mask to layer".


Making a mask for this layer.

In the dialog that follows, I choose "Greyscale copy of layer" as a basis for the mask, and "Invert mask" because normally the black parts of a mask are see-through, but it's the white parts of the puzzle grid that should disappear, so the mask, in being made, also becomes a negative version of itself.


Mask settings: use greyscale, invert mask.

Now I can see the grid overlying the base image. This is a good moment to save the XCF file, so I can reload it if anything goes wrong.


The base image and its jigsaw puzzle grid.

The duplicated puzzle grid layer I made earlier will now be dragged to the top of the other layers, obscuring the base image, so I can flood-fill every puzzle piece with a colour. It can be alternating colours or a different colour for each piece, so long as no two same-coloured pieces touch. And this should be done in a way that will remove the need for later editing, because the puzzle grid is anti-aliased (has partly transparent edge pixels to make edges look smoother) with black lines in the middle, and I don't want that, I just want solid interlocking blocks of colour. So, for each puzzle piece, I choose the Magic Wand, with NO "Antialiasing" or "Feather edges", BUT with "Select transparent areas" and Threshold at 15.


Settings for the Magic Wand, or Fuzzy Select as GIMP calls it.

I then click on a puzzle piece, and in the menu, choose "Select", then "Grow", then set enlargement at 2 pixels. This takes a bite out of the grey and black areas of the grid that I would otherwise have to painstakingly paint out by hand. I delete the selection, then flood-fill it with a chosen colour. In fact, if that chosen colour is already selected as background colour, and the layer doesn't have transparency, the deleted area will automatically take on that colour, no floodfill required. A layer's transparency can be set somewhere, but, to keep it simple, if the deleted part becomes transparent, it must be flood-filled, else it becomes the background colour.

Next piece, same procedure. Enlarging the selection will now steal a bit of the edge of the previous piece, but that is not a problem. If you drag the transparent grid on top of the pieces, they'll still look fine.


Growing the selection to chip away at the black lines.

I've done these two pieces separately, but for the rest, I'll simply select all pieces I want in that colour - for the second and subsequent selections, I hold down the Shift key when clicking - grow all these selections in one step, and delete the selections, which should colour the selected pieces. If it doesn't, set Flood Fill (or Bucket Fill as GIMP calls it) to "Fill whole selection" instead of "Fill similar colours", and one flood fill should take care of the lot of them. The main thing is that no two pieces of the same colour should touch, so four colours, and therefore four flood-fill actions, should be enough. There are various ways to tackle this, but the point is to end up with something like this:


One corner of the puzzle.

When this layer is finished, I'll call it "Slicer", and make a duplicate of Example, named "Slice 1". This duplicate will be sliced into pieces. The masked puzzle grid layer can be deleted, unless you want to keep it as a background.

When singling out puzzle pieces, it really matters which layer is active. If you've selected part of the image, and click on "Delete", and the selected part doesn't disappear, you can bet on it that the layer you see is not the active one, and you're deleting part of a different layer. GIMP has an Undo function, but I'd just as soon not risk making that mistake in the first place. So, first I make Slicer active by clicking on its name in the layer list. Since Slicer has four colours, I'll select all pieces in the same colour. Then, with that selection still in place, I make "Slice 1" active, and choose "Cut"; all selected pieces are punched out, as it were. Then I go to "Edit" and "Paste as", where I pick "New layer", and get a layer of one-fourth of the puzzle's pieces, all separate from each other, which I name "Slice 2". I repeat this for two other colours to create the layers "Slice 3" and "Slice 4"; the last colour can be skipped, as it corresponds to the pieces remaining on "Slice 1".

Due to the automatic cropping that happens when pasting a selection as a layer, you may notice a yellow dotted line that indicates layer size as opposed to image size. If this bothers you, get rid of this line with "Layer", "Layer to Image Size". (The pieces will be individually cropped later to save space, so that dotted line will become unavoidable.)


Your new layer should look like this.

Next, the pieces in these four layers each need to be on their own layer. Why make these four layers of puzzle pieces anyway? Because on these layers, the pieces are separate from each other, and can be easily deleted or have a filter applied without affecting the other pieces. It makes editing so much simpler.

If you're as paranoid as me, save the file at this point. Then consider two things: layer names, and puzzle piece embellishment. As every piece is a layer, it would be confusing to have them all named "Pasted Layer" followed by a number. I will be calling them only by numbers; to be exact, by their column number (horizontal position) and row number (vertical position). So the top left piece will be "1-1" and the bottom right piece will be "9-6".

Next, the pieces can be kept as they are, or given a black outline, or a more threedimensional appearance using the "Add bevel filter" under "Filters", "Decor". Now that the pieces are still collected on four layers instead of on 54 separate ones, whatever editing you want to do on them only has to be applied four times.

To add a black outline, I duplicate each Slice, and call the copy "Slice [n] outline". I go to that new layer (ie. make that layer active), and, with the Magic Wand, but NO antialiasing or feathering, select the space between the pieces. I then grow this selection by 1 pixel, and fill the selection with black (or any colour not found in the pieces themselves). This bathes the pieces in the outline colour, which covers not only the space between them, but also their outer edge to a depth of 1 pixel. Then, I go to the original Slice layer, select the empty space between the pieces again, go back to the Slice outline layer, and delete the selection, leaving only the pieces with one-pixel-thick outlines. To be repeated once for each Slice.

To add a bevel, I duplicate each Slice and call the copy "Slice [n] Bevel", go to the Slice Bevel layer, select the empty space between the pieces (again, with NO antialiasing or feathering), invert the selection so that now all pieces are selected, and use the "Bevel" filter on them. The filter's default setting works very well, although bevelling the pieces does bring out their non-antialiased and therefore jagged edges. Again, to be repeated once for each Slice.

In GIMP 2.8, the filter dialogue has "Work on copy" ticked by default; this takes the result and puts it in a new file, suddenly switching the view away from the file you're working on. This option was new to me, and the first time it happened I panicked, thinking I'd somehow lost all my work. Now I untick that box, or, if I forget, use Ctrl-X on that new file and paste its content back into the main file as a new layer.


Warning: may create new file.

There are different ways to slice up the Slice levels into separate pieces. Two that I can think of: either duplicate each Slice fourteen times and from each of the sub-Slices, delete all but one puzzle piece; or - a safer way - select each puzzle piece and make it a new layer. As every Slice contains 12 to 15 pieces, it's a lot of dull work either way, and care must be taken to make sure the right layer is active when cutting, pasting or deleting, and whatever the selection method, feathering and antialiasing must be off.

Here's how I did it: first I went to Slice 4, because it has the puzzle piece in the top left corner, in row 1 and column 1 of the puzzle, so the puzzle piece's layer name will be 1-1. With the rectangle selection tool, I drew a rectangle around this piece, that doesn't touch the other pieces. For neatness' sake, although it's not necessary, I then clicked on the empty space within the selection with the Magic Wand while holding down the Ctrl key, so that the selection marquee now hugged the piece's outline. Next, I cut the piece (Ctrl-X) and chose "Edit", "Paste as", "New Layer In Place". (In either an older GIMP version or Paint Shop Pro, I remember using "Promote Selection to Layer".) I renamed the new layer "1-1". Done! Next piece is in first row, third column, and its layer will be "1-3", else all steps are the same. Also, I am now still on layer 1-1, and must return to Slice 4 to select the next piece. In the course of cutting and pasting, I forgot this several times, wondering why my selection was empty.

Note: pasting the puzzle piece automatically crops the layer size to a box enclosing that puzzle piece, showing a yellow dotted line around the piece when its layer is active. To get rid of that dotted line, I chose "Layer", "Layer to Image Size", but doing that for every puzzle piece swelled the filesize to over 100Mb, as all that empty layer space still padded the file. Resizing the layers back to piece size shrunk the file to around 15Mb.

For the last piece left on the Slice layer, of course, don't cut and paste: just rename the Slice layer to the right numbers.

If you're a completist like me, you may have each piece in flat, outlined, and bevelled form. You can separate these into three puzzle files by copying the file twice and, from each file, deleting all but the flat, outlined or bevelled pieces. Or, if you're using the shiny new GIMP that has the layer group feature, sort the layers into three groups: turning off a group makes all layers in that group invisible. I dragged all layers into their numerical order per group, which allowed me to check if I'd accidentally maimed any pieces with all this cutting and pasting, which of course I had, so I had to re-copy and paste them out of the Example layer. Then, I named all puzzle piece layers after their piece position and style, so "1-1o" for the first outlined piece and "1-1b" for the first bevelled piece.

The puzzle is now almost done, but needs some refinements. Firstly, at 1000x750, it's a bit big. Fortunately, GIMP can display it at a reduced size. Secondly, the playfield is only as large as the puzzle itself. No problem: I can enlarge the image by choosing "Image", "Canvas size", and setting the new height and width to 200 pixels larger than the puzzle size. The Center button puts the picture where I want it, the "Resize layers" dropdown is set to "None" so I don't have to crop all layers manually later, and the "Fill with" dropdown is set to "Transparency" to prevent any layers having an unwanted border added, although any imported image layer, having no transparency, would get a border in the background colour, which in this case is white.


Giving the puzzle a border.

I would like to create an actual playfield, though. A textured green background with a lighter rectangle in the middle, on which is projected a ghostly version of the puzzle image. I create a new layer, flood-fill it with a pattern, duplicate the Example layer, pull the duplicate layer up above the flood-filled pattern layer, reduce the duplicate's layer opacity to make it look like the ghost of the original image, and merge down this duplicate layer to the playfield layer to combine them into a playfield with an example in the middle. This done, I go through all layers, delete any I don't want (like the original puzzle grid layer, if it's still there), and crop any that have extra transparent space with "Image", "Crop to Content" to bring filesize down to a minimum. Finally, I drag the pieces around the edges of the playfield. This is what it looks like:


The puzzle is complete.

The puzzle pieces are dragged with the Move Tool. If you want to "save your progress" with this puzzle, just save the file. If you want a fresh start every time you open the puzzle, just don't save. Want to try it? The pseudo-puzzle (15.3MB, XCF) can be downloaded here.

This format has definite drawbacks. The filesize is big, due to the many layers. Pieces have a yellow dotted box appear around them when selected (when their layer is made active) and disappear under each other because they are layered. Every dragging motion is added to the Undo History, gobbling memory. If you're not careful, you'll pick up and drag the background. Also the playfield is too small and cramped, because I underestimated how much size the scattered pieces would take up.

Making a jigsaw puzzle KiSS set

KiSS is short for Kisekae Set System, where Kisekae stands for "kisekae ningyou", which is Japanese for the kind of paper dolls that can have cutout "clothes" affixed to them. As with jigsaw puzzles, this placing of bits of paper or cardboard is easily recreated in software. Simple KiSS viewers load a bunch of images and a configuration file, which ties the images together into something like the layered image file format above. The more advanced FKiSS ("French KiSS", a pun that stands for added functionality) viewers interpret commands in the configuration file, which can be used to make pieces snap together. Note that although it's possible to make simple games using FKiSS, this is not what it was designed for; early FKiSS versions do not, for instance, recognize variables. But dragging images around is what both jigsaw puzzles and paper dolls are all about.

KiSS sets are like layered images, where every layer is a "cel" and all layers are tied together by the "cnf" or configuration file. (See the KiSS page for more information.) A cel file is converted from a regular image file: CKiSS (short for Cherry KiSS) cels contain their own colour information and can be of any colour depth, while normal cels are linked to a palette, the "kcf" (KiSS Colour File), which has 16 or 256 colours. To keep it easy, the first example of a KiSS puzzle will use the CKiSS format, and its cels will be the layers from the pseudo-puzzle that I just laboriously constructed.

Since the image is really too big - I would like the resulting puzzle to be playable under smaller resolutions, too - I'll first scale the XCF to 40% of its size. This will make all the puzzle pieces anti-aliased, which I don't want. So I separately scale back the Slicer layer, whose puzzle pieces are a solid colour, with Interpolation: none. The result is uglier, but still of solid colour, with no half-transparent pixels. And now I will have to slice the base image again! And re-outline the pieces! And re-bevel them! Aaagh!

(I didn't completely reslice them, as the puzzle pieces were fine, only their edges needed work, and the bevelling and outlining would not have worked as well at this size. So I made both the Example layer and the pieces visible, and, for each piece, I selected its template from the non-antialiased Slicer layer, went to the piece layer, chose "Edit", "Copy Visible" (copying both the puzzle piece and its part of the Example layer, to get rid of see-through edge pixels), and then "Edit", "Paste into Selection in Place", followed by "Layer", "Anchor Layer" to merge the floating selection with the piece layer; then I inverted the selection and pressed Delete to trim off any pixels smudged beyond the piece's outline. I did this 54 times per set of pieces.)

A long time ago, there was a Photoshop plugin for making cels from images, that GIMP could apparently use. Googling "KiSS cel plugin" turned up an actual GIMP plugin for making KiSS cels by Nick Lamb, coincidentally also the main author of GnomeKiss, a viewer that I'll be using, as it's in the Debian repository and handles everything from plain KiSS to FKiSS3. I was poring over the GIMP Manual's instructions to find how to import this plugin when it occurred to me to check if GIMP already supports saving to cel format - which it does, since the plugin is already incorporated! Silly me.

If editing the puzzle piece edges was tiresome, exporting is even worse. Here's how it went: make the layer of a piece active. Select all (or a wide rectangle around the piece) and copy ("Edit", "Copy"). Paste to new image ("Edit", "Paste as", "New Image"). This switches the view to the new image, a puzzle piece floating in an empty rectangle. The rectangle should be hugging the puzzle piece's outline; if not, crop it ("Image", "Crop to Content") to trim away the excess space. Save to a cel ("File", "Export as") with the right name, like "1-1.cel". Don't forget to close the new image, choosing "Discard Changes" on the closing dialog.


A makeshift KiSS configuration to see if the pieces came out OK.

After editing and exporting the puzzle pieces, I increased the canvas again for a bigger playfield and created three backdrops - green, blue and red - by adding new layers and filling them with the patterns found in GIMP. I made a black/white version of the Slicer layer, so I would have two "example" cells: the base image, and a layer of shapes. To complete the cels needed for a simple KiSS set, I made a backdrop-sized intro screen of the base image with a milky green border and some lettering.

By old KiSS standards, CKiSS cel files are huge. The opening screen is more than one Mb. In the time when KiSS sets preferably had to fit on a diskette, that's roughly three-quarters of the maximum total filesize. KiSS sets are compressed files, but CKiSS cels don't compress very well. So, as it appears I can directly edit cels in GIMP, I first tried to reduce the colours, and thereby the size, of the big screen-filling cels by making them indexed. (Trimming off the excess transparent pixels around the image helps too, but I already took care of that by cropping all layers to their actual content.)

"Indexed" means that a pixel can't be just any colour, but needs to have a colour in a palette of, typically, maximally 256 colours. So instead of having its own colour information, the pixel refers to a colour slot in the palette. This saves space enormously. Of course, for that to be possible, the number of colours in an image must be reduced to 256. The Example image can't be colour-reduced without serious quality loss, but the red, green and blue backgrounds should contain not much more than 256 colours each. And the black-white slicer cel can be reduced to 2 colours. To make an image indexed, choose "Image", "Mode", "Indexed". This opens a dialog where the default number of colours to convert to is 255, but I'd like the full 256, please. And I'm so confident this will be enough colours, that the "Colour dithering" style is "None".


Menu choice and dialog squashed into one screenshot.

Sadly, it was not to be, as any indexed cel needs a kcf (KiSS palette file), and the automated way in which GIMP saves cels doesn't allow me to request that palette files be saved, too. Once I've saved indexed cels, I can't reopen them in GIMP for lack of a kcf. Plus, if I did find a way to save the indexed image palettes as kcf, I'd end up with several palettes of 256 colours each, and there are viewers that happily mix normal and CKiSS cels, but balk at more than one 256-colour palette. Modern viewers don't have a problem with several 256-colour palettes, but they also don't have a problem with cels of over 1 Mb. So I deleted this experiment, and hit on another way to reduce filesize, at least for the screenwide cels.

Tiling! Since said cels were the result of flood-filling with a pattern, why not "flood-fill" the set with the same cell placed in rows and columns like so many piled-up blocks? There should not be too many of those blocks, since every block means another object with coordinates to add; but the filesize of CKiSS cells depends solely on their height and width, so if I could tile the whole background with one square, it would dramatically decrease the size of the KiSS archive.

By looking closely at the green background, I can see that its pattern repeats every 50 pixels in both directions. So I could make a cel of 50x50 pixels and display that across the screen, erm, how many times? The screen size is 580x480, which is not a multiple of 50. What if I make horizontal bands, so the extra space at the end of a row of tiles is not an issue? Then I've still got a vertical problem. What if I make one band narrower than the rest? A plan is born. The top band will be 80 pixels high, while the 'tiling' band will be 100 pixels high and placed 4 times, requiring as many extra object numbers. The red pattern repeats at 64 pixels, so that would mean a band of 96 pixels high followed by three bands of 128 pixels high. The blue background repeats at 64 pixels, too. Because of their fixed sizes, it's easy to calculate the coordinates for these cels. The introduction screen has been chopped up into a title bar above the example file, a subscript bar below it, and an identical block on either side: three cells that will become four objects.

Making a jigsaw puzzle KiSS set - KiSS version

Which leads to the coding part of making a KiSS set. Objects have numbers, cels have filenames, and the numbers and filenames are linked to each other and to a "set", which is like a page. A KiSS configuration file (cnf) consists of a header for palette filename, screen size and border colour, a block for declaring cels and objects, an optional block for FKiSS code (which I'll get to later), and a block for object positioning per set. A KiSS set (collection of files, typically archived in LZH format) has ten sets (scenes, numbered 0 to 9), each numbered set displaying the objects assigned to it in the first block, at the coordinates specified in the last block. Here is a highly simplified one-cel, two-object cnf as an example, where everything after a semicolon is a comment, and ignored by the KiSS parser:

; comment: optionally name of set and author, extra notes
%palette.kcf ; palette filename for indexed cels, 16 palettes allowed, none for CKiSS
(580,480) ; board size
[0
; This colour index number in the set's palette is the colour of the border
; around the set, if the viewer window is bigger than the set size.
; So if the set's palette file's first colour (colour 0) is purple, the area
; around the set screen will be purple. CKiSS cels don't need kcf files, so
; the border colour will be the viewer's default.
;   (Blank line inserted for legibility)
; Below are the object declarations: object, cel, palette, set(s), comment.
#0.999 bird.cel         *0 :0                   ; a bird cel locked in place
; Object 0 (all counting starts at 0) is locked in place, as shown by the
; period followed by a number, which means it won't budge until it's been
; clicked 999 times. Maximum lock number 32767. The "*0" refers to which kcf
; the cel gets its colours from, CKiSS uses no kcfs, so 0. After the colon come
; the set numbers, this immobile bird is only displayed in the first set.
#1     bird.cel         *0 :  1 2 3 4 5 6 7 8 9 ;%t200 a draggable bird cel
; Same cel, different object, in all sets except the first.
; Note the way all columns are aligned, also for better legibility.
; The "%t200" changes cel transparency (0=opaque, 255=invisible) and, since it
; follows a semicolon (comment) is actually FKiSS! Plain KiSS viewers may not
; respect this transparency tag.
;   (Blank line inserted again, below follows object coordinates per set.)
; set #0 You don't have to add the set name as comment, it's for clarity.
$0 20,20 *
; Above, the first object is at 20 pixels down and right from the top left
; corner while the second is an asterisk (not placed). For set 1 to set 9, it's
; the other way round.
; The coordinate block for a set begins with "$", but the number after the $ is
; not the set number but the default internal palette, and thereby background
; colour, for that set. (For CKiSS, just leave it at 0.)
; If there are 128 objects in a set (the maximum for older viewers) you would
; have 128 coordinates or asterisks after each $. If you edited by hand, imagine
; how confusing it would be without those set numbers in the comments!
; set #1
$0 * 20,20
; set #2
$0 * 20,20
; set #3
$0 * 20,20
; set #4
$0 * 20,20
; set #5
$0 * 20,20
; set #6
$0 * 20,20
; set #7
$0 * 20,20
; set #8
$0 * 20,20
; set #9
$0 * 20,20

That was a lot of comments jam-packed into a tiny cnf, wasn't it? It's a quick way to explain things - look at this section of the KiSS page for a more in-depth explanation, notably about how one cel can be two objects, and what objects are for anyway - but for clarity, below is the same file with all the fat trimmed out:

%palette.kcf
(580,480)
[0
#0.999 bird.cel         *0 :0                   ;
#1     bird.cel         *0 :  1 2 3 4 5 6 7 8 9 ;%t200
$0 20,20 *
$0 * 20,20
$0 * 20,20
$0 * 20,20
$0 * 20,20
$0 * 20,20
$0 * 20,20
$0 * 20,20
$0 * 20,20
$0 * 20,20

And now, ta-daah! I am going to write a KiSS puzzle cnf. The header is simple.

; A proof-of-concept puzzle KiSS set by Cal, 2021
(580,480)
[0

The next step is a list of all the cels and what their object numbers are, and which sets they appear in, and if maybe they should be a bit transparent. (For legacy reasons - the earliest KiSS viewer was for MS-DOS - all files in a KiSS set have names of 8 characters.) Snipped for space:

#0.999 example.cel      *0 :0                   ; inside intro screen
#55.999  intro.cel      *0 :0                   ; intro screen title
#56.999  introb.cel     *0 :0                   ; intro screen left
#57.999  introb.cel     *0 :0                   ; intro screen right
#58.999  intros.cel     *0 :0                   ; intro screen subscript
; Puzzle pieces
#1  1-1.cel             *0 :  1 2 3             ; flat puzzle pieces, sets 1-3
#2  1-2.cel             *0 :  1 2 3             ;
   [and so on until]
#53 6-8.cel             *0 :  1 2 3             ;
#54 6-9.cel             *0 :  1 2 3             ;
#1  1-1o.cel            *0 :        4 5 6       ; outlined puzzle pieces, sets 4-6
   [and so on until]
#54 6-9o.cel            *0 :        4 5 6       ;
#1  1-1b.cel            *0 :              7 8 9 ; bevelled puzzle pieces, sets 7-9
   [and so on until]
#54 6-9b.cel            *0 :              7 8 9 ;
; backgrounds
#0.999 example.cel      *0 :      3     6     9 ;%t128 the base image
#0.999 slicerbw.cel     *0 :    2     5     8   ;%t128 puzzle shapes
#55.999 green.cel       *0 :  1 2 3             ; green maple screen
#56.999 greentl.cel     *0 :  1 2 3             ; green tiling 4x
#57.999 greentl.cel     *0 :  1 2 3             ;
#58.999 greentl.cel     *0 :  1 2 3             ;
#59.999 greentl.cel     *0 :  1 2 3             ;
#55.999 blue.cel        *0 :        4 5 6       ; blue gritty screen
#56.999 bluetl.cel      *0 :        4 5 6       ; blue tiling 3x
#57.999 bluetl.cel      *0 :        4 5 6       ;
#58.999 bluetl.cel      *0 :        4 5 6       ;
#55.999 red.cel         *0 :              7 8 9 ; red leather screen
#56.999 redtl.cel       *0 :              7 8 9 ; red tiling 3x
#57.999 redtl.cel       *0 :              7 8 9 ;
#58.999 redtl.cel       *0 :              7 8 9 ;

Some comments on this file: the same cel can be in two objects, as was shown before. The same cel can be fully opaque (base image as part of intro screen) and transparent to some degree (base image used as example of what the finished puzzle looks like). The same object number can (and should) be shared by more than one cel; one object number can refer to different cels on different pages, like the objects 55 to 58 or 59 for background cels. It is best to limit object numbers by recycling them, as each object number equals a pair of coordinates that I'll have to work out and manually add to the next part of the cnf. When to recycle an object number? When different cels fill the same gap on different pages; the slicer and example cel are the same size and centered in the same way, while the backgrounds are all similarly tiled. Last but not least, object numbers have nothing to do with layering. The line order is the layer order. That's why "example.cel" superimposed on the intro screen is the topmost line in the block, while "example.cel" as a ghostly image of the puzzle is right at the bottom, just above the backgrounds, even though they are both object #0.

As can be inferred from the numbers between colon and semicolon, set 0 shows only the intro screen, which refers the user to sets 1 to 9. The first three of these sets show flat pieces, the second three show pieces with a hint of outline, and the third three show pieces with a bevelled edge, like real puzzle pieces. The first of every three has a green background, the second of the three has a blue background, and the third, a red background. This is because pure KiSS is entirely static, and if you want a different playfield, you have to put it in a different scene.

These three triplets follow the same pattern: on the first, the puzzle is assembled on an empty background, and the player can scatter and reassemble the pieces. On the second, the puzzle pieces are scattered around a rectangle of puzzle piece silhouettes to show where the pieces should go. On the third, they are scattered around a faded example. There is no way to "save progress" in a traditional KiSS viewer, so on loading the set, the pieces will always be in the same initial position.

That code so far should be enough to open the cnf in GnomeKiss, which will show all cels clustered in the top left corner, since there's no coordinate block yet. Coordinates count down and right from the top left corner. Which is why all objects, having no coordinates and therefore defaulting to 0,0, are stuck there.

In the coordinates block, object numbers determine order. There are ten lines, one for each set, all starting with "$" and a number. That number refers to an internal palette in a kcf file, of which there are none in this set, so it's just the default, 0. After that comes a list of coordinates or asterisks, as many as the highest object number in the cnf. So if I defined only a single object and gave it the number 43, all lines in the coordinate block would need 44 coordinates, because the viewer counts from 0. Specifically, they would need 43 asterisks, signifying that no object of that number is in that set, and one set of coordinates for that one forty-fourth object. That's why objects should be numbered contiguously, with no gaps between numbers.

What if there are only three sets showing objects, and the rest are empty? Then three lines starting with "$" would contain coordinates, while the remaining seven would be a line of asterisks to signify for every object number that it is not defined for that set. It depends on the viewer whether the undefined sets show all objects in the top left corner, or are greyed out.

Given that the highest object number I have is 59 (less than the maximum of 63 for the earliest KiSS viewers!), I need ten lines with, as placeholders, 60 asterixes each:

; set #0
$0 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * * * *
; set #1
$0 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * * * *
; set #2
$0 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * * * *
[etc. to set 9]

The first set, 0, is simple. Object #0 in that set is the centered example cel, which is 180 pixels shorter and narrower than the screen size, and therefore 90 pixels down and to the right. There are no puzzle pieces in set 0, so that's 54 asterixes. There is a title bar which starts at the top, so its coordinates are 0,0. Then two bars (one cel, two objects) flanking example.cel. They are both 90 pixels down, but one hugs the left side (that's 0) while the other, 90 pixels wide, is to the far side of the 580x480 screen (580 - 90 makes 490). The coordinates are x,y where x is to the side and y is down. In short, here is the first line:

; set #0
$0 90,90 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * 0,0 0,90 490,90 0,390 *

That was easy. For subsequent sets, the coordinates for the first puzzle piece are 90,90 but the rest of the pieces have to be dragged in place and their position noted; GnomeKiSS displays the last clicked-on object's coordinates in the status bar under the playfield. In PlayFKiSS for Windows, I could drag every object to where I wanted it, then save the cnf, which would write the coordinates into it, but also flush any comments. GnomeKiSS doesn't save anything, but shows the name and coordinates of the last clicked cel, which I then enter into the cnf using any editor that saves to plain text. It took ages, but I got it done.

KiSS viewers are like browsers: they all have their quirks, and some are more forgiving than others. Each time I loaded the cnf into GnomeKiSS, every line in the coordinates block produced the message: "line is too long, should be 256 or less characters". Possibly related to this, GnomeKiSS displayed objects at coordinates that belonged to a different cell that the one I was looking at, and didn't seem to want to display the same cel under two object numbers in the same set. If I broke up the set coordinate line, all coordinates after the line break were ignored. Until I found you can continue the set coordinates on a new line, so long as that new line begins with a space. The odd positioning of some screen elements continued, although the same set showed up just fine in the latest version of UltraKiSS that I'd ferreted out, just to reassure myself that I wasn't going insane.


Riddle me this.

It took me a while to work out the blindingly obvious: that combining cels into an object makes the object as large as the shapes of all its cels combined, including cels not appearing in every set. And when a viewer won't let a cel stick out beyond its border, if the same object is a block-shaped screen-filling cel in one set and a tiny dot-shaped cel in another, the tiny dot will be pushed to the middle of the screen even if its object coordinates place it at the edge, because of its invisible huge block outline straining against the sides. In this case, the problem was using object numbers #57 and #58 for both background tiles (which, being as wide as the playfield, must always have x coordinate 0), and pieces of the intro screen, placed at the right and bottom of the screen. In short, I needed to renumber the bottom and right side of the intro screen to objects #60 and #61, and add two sets of coordinates to the end of the cnf.


Stage 1 of the end product.

As a traditionalist, I also create an archive of type "lzh" containing the cnf, a readme-file, and all cels, to which I will add any further cels and cnfs I make until the set is complete. I don't have the non-free Unix archiver "lha.exe" installed under Linux Mint, and barely use other operating systems any more, so I use the archiver built into UltraKiSS.

It's not the most challenging of puzzles, as the object numbers of the pieces appear in the status bar, making it easy to guess which piece comes after which. Due to the layered nature of KiSS, pieces can slip under each other, and under the finished part of the puzzle. And to show off its piece styles and backgrounds, one puzzle needs nine different sets! There is an answer to this in the form of the so-called FKiSS extensions: additional scripting commands in incremental versions, starting with just FKiSS and going up to FKiSS4 (and FKiSS5/UltraKiSS, but that's beyond this how-I's scope). A KiSS set can have more than one cnf, and this one will get a cnf for every version.

Making a jigsaw puzzle KiSS set - FKiSS version

To summarize what each FKiSS version adds: FKiSS allows the mapping and unmapping (appearing and disappearing) of cels, moves them, and detects if these cels are being dragged with the mouse. It also introduces timers, playing sounds and some other basic stuff. FKiSS2 adds collision detection (is one puzzle piece touching another?) and both relative and absolute movement; FKiSS2.1 refines collision detection, and adds conditions (is this cel is mapped, is this object moved or fixed) and the ability to set a fix value. FKiSS3, beginning to resemble a programming language, has variables, labels (like alarms, but with no delay), extra syntax to test for situations like "is the mouse cursor hovering over this cell?" and a new concept, "ghost", when cels are mapped and visible, but ignored by mouse events (notably, they can't be dragged). FKiSS4 has extra object definition tags, improved variable names, keyboard support, "attachment" which makes two objects move together, cel groups to apply one action to a list of cels, and "frames", a faster way of (un)mapping groups of cels. FKiSS5, which I won't be using for this set, is only supported by UltraKiSS, displays a number of image file formats, adds a lot of Java-based syntax, and allows cels to move to a different layer on the fly.

Since I don't want to redo all the object coordinates, I'll copy the KiSS-only cnf, "puzzkiss.cnf", to "puzzfks.cnf", and add a FKiSS code block. FKiSS code has its own block that either comes between the object declaration block and the coordinates block, or goes below the coordinates block, at the very bottom of the file. Every line of a FKiSS block starts with a semicolon, to let KiSS-only viewers think this is a comment, immediately followed by an at sign, to let the FKiSS parser know it isn't. Normal comment lines are still allowed, of course. A FKiSS block starts with a single word, EventHandler, followed by three commands that are optional, but should be at the top, in this order:

;@EventHandler
;@initialize()
;@begin()
;@version()

What do these three commands do? Nothing. Because they're events (hence the header, EventHandler). More accurately, they refer to events. The basic syntax of FKiSS is: if event happens, then do actions. The actions are the commands that do something, but they always fall under an event. The names of both actions and events are followed by brackets, that may contain information for the event or action to work with.

The event initialize() always comes directly under EventHandler, and happens when the set loads, before the cels are shown. This is the event at which to unmap any cels you don't want to be seen at the start. The second event, begin(), happens when the KiSS set has been fully loaded, ready to play. In viewers that support it, the version() event happens if the viewer knows the FKiSS version number in the brackets; its standard use is to unmap a screen-filling cel that reads "THIS VIEWER CAN'T HANDLE THIS FKISS VERSION". The order in which the last two are processed isn't the same in every viewer; in UltraKiSS, version(), no matter where its position in the cnf, is processed before begin(), and if I put any other event than version() above begin() in the cnf and then load the set, I get the following error message:

Line 468] Unknown configuration command: @begin()

(Why would I even want to put version() or any other event above begin()? To use a dummy alarm() event to avoid confusing FKiSS-only viewers, which don't understand version(); see further down.)

With its ability to unmap any unwanted cels, FKiSS can fit the intro screen, all backgrounds and all puzzle pieces in one single set. This means that the subscript cel in the intro scene, which tells the player to change to a different set, must be replaced. The intro scene can be clicked away to get at the playfield. The player can cycle through the three background colours of the playfield by clicking on each background, which unmaps it and reveals the background underneath, until reaching the lowest background, which remaps all backgrounds. Same goes for the puzzle underlay, the dimmed image that serves as example. Don't want it? Click it to make it disappear. But you can't cycle through the three puzzle piece styles that way, because you click the puzzle pieces to drag them, so they would change their appearance every time they were moved. So the puzzle piece style has to be chosen in a little Settings box. Ditto for the puzzle underlay, actually, because unlike the background sequence of green-blue-red, the example sequence is picture-pieces-nothing, and you can't click on nothing to make something reappear.

FKiSS (or FKiSS1 to distinguish it from later versions) does not support version(). That it recognizes "EventHandler" and "initialize()" is proof enough that the viewer is FKiSS-capable. There is also a version FKiSS1b that adds transparency. Strictly speaking, a plain KiSS viewer should not be able to render cels transparently at all (although the viewer programmer might have decided to incorporate the "%t" transparency tag while ignoring actions and events) which is why old FKiSS viewers may show cels with a %t setting as fully opaque. That would not apply to CKiSS cels, which can be of any colour depth and transparency, but the oldest viewers also tend not to support CKiSS. Anyway, for legacy's sake, I'll display two warning cels: one that the viewer does not support FKiSS, and one that it does not support transparency. The first will be unmapped under initialize(), the second will have a %t value of 255 (invisible) so that it's only visible if transparency is not supported, and will be unmapped along with the intro screen, because transparency is not such a big deal. So if the viewer doesn't know FKiSS commands or the transparency tag, both will show up when the set is loaded. It will look like this:


Despite the subscript, that puzzle will never appear.

Below is the FKiSS code for making both warning and intro screen disappear. If the viewer supports FKiSS, the version warning is unmapped before the set is loaded; the intro screen and transparency warning, after 3 seconds. Note, in addition to the new objects with cels "warning.cel" and "warningt.cel", the reassigning of the top three intro screen cels, which were originally part of objects #0, #55 and #56, to new object numbers, so they can be unmapped separately; and the new intro screen subscript cel that replaces the cel of object #60, telling the user to wait for the puzzle instead of referring to sets 1-9. Also, since the lower instance of example.cel, which covers slicerbw.cel and would normally block it from sight, is see-through, and therefore doesn't, slicerbw.cel has to be unmapped along with the warning.

(580,480)
[0
;
; Object declaration block
;
; warning
#66.999  warning.cel    *0 :0                   ;
#65.999  warningt.cel   *0 :0                   ;%t255
; intro screen
#62.999  example.cel    *0 :0                   ; new object!
#63.999  intro.cel      *0 :0                   ; new object! intro screen title
#64.999  introb.cel     *0 :0                   ; new object! intro screen left
#61.999  introb.cel     *0 :0                   ; intro screen right
#60.999  intros1.cel    *0 :0                   ; DIFFERENT intro screen subscript
[all the other cel declarations, now only on set 0]
;
;@EventHandler
; If the viewer handles FKiSS, it makes the warning disappear:
;@initialize()
;@  unmap("warning.cel") ; or unmap(#66)
; example.cel covers slicerbw.cel but is transparent, so...
;@  unmap("slicerbw.cel")
;@begin()
; A three-second timer gets rid of the intro screen and transparency warning.
;@  timer(0,3000) ; timers are in milliseconds
; The alarm event doesn't have to be put immediately under the timer action
;@alarm(0) ; when timer 0 is up
;@  unmap(#60) unmap(#61) unmap(#62) unmap(#63) unmap(#64) unmap(#65)
;
;  ** Object coordinates **
;
; set #0 - we have a bunch of extra objects added to the end!
$0 90,90 90,90 119,90 177,90 221,90 267,90 295,90 354,90 385,90 444,90 90,138
 130,124 164,139 222,124 251,136 311,124 346,139 397,126 430,138 90,177 120,190
 177,174 208,190 267,175 295,190 356,175 387,185 445,174 90,240 134,225 164,239
 222,240 254,239 311,226 356,239 400,228 432,236 90,275 120,290 178,274 209,289
 267,276 294,288 356,274 385,289 444,275 90,339 134,326 166,338 221,325 253,339
 311,325 342,338 400,325 432,339 0,0 0,80 0,180 0,280 0,380 0,390 490,90
 90,90 0,0 0,90 180,295 180,180

Under initialize(), either the warning cel or its object number is unmapped, it doesn't matter which. Under the event begin(), there is an action, timer(), which sets the event alarm(0) (the first defined alarm; everything starts counting at 0) to 3000 milliseconds, which is 3 seconds. After those 3 seconds, alarm(0) causes all objects AND NOT CELS of the intro screen to be unmapped, as well as the transparency warning. The intro screen contains ambiguous cels, so here it does matter whether object or cel is unmapped.

An ambiguous cel is one that occurs twice or more in the object declaration block. For instance, example.cel occurs twice as part of object #0 in the KiSS-only cnf, although in the current FKiSS cnf it has been split into object #0 (transparent puzzle underlay) and object #62 (new object that is part of intro screen on top of puzzle) so that the intro screen can be unmapped without unmapping a part of the background. The two sidebars using introb.cel have different object numbers, in order to place them at different coordinates in the same set. The unmap() command can be used with cels to unmap part of an object (as part of a blinking animation, for instance) but should never be used with ambiguous cels; those should only be unmapped via their object number. GnomeKiSS will warn about ambiguous cels when loading a set.

Speaking of which, I see two new problems, now that I've put all backgrounds on the same set and am planning to map and unmap them. Each of the three backgrounds consists of a top bar cel and a tiled bar cel, the second repeated three or four times under different object numbers so they could be positioned at different coordinates. In other words, the tiled bars are ambiguous cels. The object numbers were recycled by repeating them across the sets, but green tile #57 is not at the same position as blue tile #57. Since they're on the same page now, the green and other tiled bars will need separate object numbers. The blue and red tiled bars are at the same coordinates, but they still need separate object numbers, because I want to be able to change from a green to a red to a blue background. If I add "unmap(#57)", that will make a tile disappear from all three backgrounds, and if I add "unmap("bluetl.cel"), that applies to three cels, only one of which may end up unmapped.

How did I get into this mess? Oh yes, tiling. And why did I want to tile cels? To keep the filesize down. And why did I want to do that? So I could use CKiSS cels, which are huge. And why CKiSS cels? Because I can't make the kcf palette files for indexed cels right now, and besides, the kcf format is a bit complicated to explain. Sigh.

All right then, what do I have. The top bars of the backgrounds (green.cel, blue.cel, red.cel) are all object #55 at coordinates 0,0. The cels only occur once. Those can stay. Of the tiled cels (greentl.cel, bluetl.cel, redtl.cel) the green tile bar occurs four times under object numbers #56 to #59; it can keep them. The red and blue tiles each occur three times and need three new objects each. As the highest object number so far is #66 for the warning, the blue tiles will number #67 to #69, and the red ones #70 to #72. Their coordinates will be added at the end of the set coordinates line.

[header and object declarations above]
; backgrounds
#0.999 example.cel      *0 :0                   ;%t128 the base image
#0.999 slicerbw.cel     *0 :0                   ;%t128 puzzle shapes
#55.999 green.cel       *0 :0                   ; green maple screen
#56.999 greentl.cel     *0 :0                   ; green tiling 4x
#57.999 greentl.cel     *0 :0                   ;
#58.999 greentl.cel     *0 :0                   ;
#59.999 greentl.cel     *0 :0                   ;
#55.999 blue.cel        *0 :0                   ; blue gritty screen
#67.999 bluetl.cel      *0 :0                   ; blue tiling 3x
#68.999 bluetl.cel      *0 :0                   ; with new object numbers!
#69.999 bluetl.cel      *0 :0                   ;
#55.999 red.cel         *0 :0                   ; red leather screen
#70.999 redtl.cel       *0 :0                   ; red tiling 3x
#71.999 redtl.cel       *0 :0                   ; with new object numbers!
#72.999 redtl.cel       *0 :0                   ;
;
[FKiSS code]
;
; set #0 - we have six more objects to tack onto the end!
$0 90,90 90,90 119,90 177,90 221,90 267,90 295,90 354,90 385,90 444,90 90,138
 130,124 164,139 222,124 251,136 311,124 346,139 397,126 430,138 90,177 120,190
 177,174 208,190 267,175 295,190 356,175 387,185 445,174 90,240 134,225 164,239
 222,240 254,239 311,226 356,239 400,228 432,236 90,275 120,290 178,274 209,289
 267,276 294,288 356,274 385,289 444,275 90,339 134,326 166,338 221,325 253,339
 311,325 342,338 400,325 432,339 0,0 0,80 0,180 0,280 0,380 0,390 490,90
 90,90 0,0 0,90 180,295 180,180 0,96 0,224 0,352 0,96 0,224 0,352

If the user clicks on the green background, all its mapped cels disappear, showing the blue background underneath. Clicking the blue background will unmap its cels to show the red one underneath. Clicking the red one remaps all backgrounds, so the green one, being above the other two, will become the visible background. For simplicity's sake, since each background consists of two cels, of which the second is ambiguous (so the code would have to refer to the object numbers of that cel), the backgrounds will only change colour if I click on the bar at the top. I could repeat the code below for every background object, but I ain't gonna. (The FKiSS2 cnf will have a button to deal with this.)

; Colour-cycle backgrounds by clicking on top bar:
;@press("green.cel")
;@  unmap("green.cel") unmap(#56) unmap(#57) unmap(#58) unmap(#59)
;@press("blue.cel")
;@  unmap("blue.cel") unmap(#67) unmap(#68) unmap(#69)
;@press("red.cel")
;@  map(#55) map(#56) map(#57) map(#58) map(#59) map(#67) map(#68) map(#69)

Two comments. First, the cels I unmap all belong to object #55, which is the top tile of all three backgrounds, so I must never unmap the whole object, but only its cels. However, I can map the whole object as a quick way of remapping all its cels. Second, the quotes around the cel names, just like the hashes before the object numbers, are not optional! (Higher FKiSS versions introduce variables, which have neither quotes nor hashes.)

The other cels that cycle through different looks, need a Settings panel. The puzzle piece objects are, for now, showing all their cels, which, since the flat puzzle pieces are on top of the outlined and bevelled ones, means that the puzzle presents as a flat image. The settings panel needs to cover all puzzle pieces, so the user can't start dragging them until the settings have been taken care of. So, I'm going to make a screencap of all flat pieces fitting together, a "dummy puzzle" image. On that image, I'll draw a Settings box background with some text. This will be one cel. I'll assign it to the new object #73 and add extra coordinates 90,90 to the set coordinates line. Next, I'll make seven separate button cels that start in the same top left corner as the settings cel, so I can make them part of object #73. This is how the object declaration for object #73 looks (nb. its cels are above the puzzle pieces, but hidden under the intro cels):

; Settings panel
#73.999  button1a.cel   *0 :0                   ; puzzle piece button flat
#73.999  button1b.cel   *0 :0                   ; puzzle piece button outline
#73.999  button1c.cel   *0 :0                   ; puzzle piece button bevel
#73.999  button2a.cel   *0 :0                   ; underlay button picture
#73.999  button2b.cel   *0 :0                   ; underlay button pieces
#73.999  button2c.cel   *0 :0                   ; underlay button none
#73.999  button3.cel    *0 :0                   ; start button
#73.999  settings.cel   *0 :0                   ; panel

Note how, in the definition above, the shorter buttons cover up the part of the longer buttons that should not be clickable.


The panel parts laid out side by side.

This is what the user sees:


To play, you have to get past the settings panel.

The topmost button will cycle through puzzle piece type (both button image and actual pieces) in the same way as the background does: unmap topmost layer to uncover second layer, unmap second layer to reveal bottom layer, completely remap all layers. (The bottom layer always stays mapped.) There are 54 pieces, if I didn't mention that before, so I've got my work cut out for me. Note here that to remap the button I can't use "map(#73)", as that would affect the other settings button.

; Press setting:puzzle piece button
;@press("button1a.cel")
;@  unmap("button1a.cel") unmap("1-1.cel") unmap("1-2.cel") unmap("1-3.cel")
;@  unmap("1-4.cel") unmap("1-5.cel") unmap("1-6.cel") unmap("1-7.cel")
   [etc. until all flat puzzle cels are unmapped]
;@press("button1b.cel")
;@  unmap("button1b.cel") unmap("1-1o.cel") unmap("1-2o.cel") unmap("1-3o.cel")
;@  unmap("1-4o.cel") unmap("1-5o.cel") unmap("1-6o.cel") unmap("1-7o.cel")
   [etc. until all outlined puzzle cels are unmapped]
;@press("button1c.cel")
;@  map("button1a.cel") map("button1b.cel") map(#1) map(#2) map(#3) map(#4)
;@  map(#5) map(#6) map(#7) map(#8) map(#9) map(#10) map(#11) map(#12) map(#13)
   [etc. until #54]

How many unmaps, or other actions, will fit on a line? I've no idea, but 256 characters is a safe maximum. For legibility you could break it up into lines of maximally 80 characters each, just so long as every line begins with semicolon-at. Indenting action lines (adding tab or extra spaces) is done to make the code legible, the parser doesn't need it.

The second button has a complication: example.cel is ambiguous (used in two objects) so I can't directly refer to it. Instead, I either unmap #0 and then instantly remap slicerbw.cel (the part of object #0 that I want to make visible), unmap #0 completely, or remap #0 completely and then instantly unmap slicerbw.cel so that example.cel remains. This way of coding depends on the actions being carried out one after the other, as is standard for FKiSS viewers; a viewer that is a multi-threaded java application and carries out all actions simultaneously, may show a different result.

; Press setting:underlay button
;@press("button2a.cel")
;@  unmap("button2a.cel") unmap(#0) map("slicerbw.cel")
;@press("button2b.cel")
;@  unmap("button2b.cel") unmap(#0)
;@press("button2c.cel")
;@  map("button2a.cel") map("button2b.cel") map(#0) unmap("slicerbw.cel")

The Start button unmaps the whole Settings object, leaving the user to get on with the puzzle; the only way to change the settings again is to reload the set. But! The puzzle is already assembled (so all the pieces would fit under the settings dialog). What to do? Fortunately, the KiSS-only cnf has the coordinates for the scattered puzzle pieces in the coordinates line under set 2. Unfortunately, moveto(), which moves an object to absolute coordinates, is not supported in the first FKiSS version. I can use move(), which moves an object the given number of pixels on the x and y axis. So, for each of the 54 puzzle pieces, I would have to compare the "assembled" coordinates to the "scattered" coordinates and "move" the difference. For instance, puzzle piece object #1 belongs at 90,90, and is scattered to 505,37. I subtract 90 and 90 from 505 and 37 to get 415 and -53, leading to "move(#1,415,-53)". There, 53 more calculations to go.

I'm going to have to do it, aren't I.

With gritted teeth, I present the first few lines of what happens when the start button is pressed, and refer anyone interested to "puzzfks.cnf" in archive "puzzconc.lzh" (download link at the bottom of this how-I) for the rest.

;@press("button3.cel")
;@  unmap(#73)
;@  move(#1,415,-53) move(#2,383,66) move(#3,-72,-83) move(#4,197,319) move(#5,-254,329)
;@  move(#6,-295,-11) move(#7,-3,315) move(#8,120,220) move(#9,-314,322) move(#10,-60,257)
[AND! SO! ON!]

Okay, is the puzzle done now?? No, there are empty sets to take care of. To avoid preventable error messages on loading the set, I fill out the set coordinate lines for sets 1 to 9. There are 74 objects in total and they're all in set 0, so the coordinate lines for the other sets are all "$0" followed by 74 asterisks. I type 15 asterisks with spaces between them, copy and paste four times, and delete the last asterisk; then I copy the whole line and paste for each empty set. Now, some viewers grey out empty sets and their go-to buttons, while others show them as "default", containing all objects, every object at coordinates 0,0. Because no one has to see that, I will use set(), the event that triggers if that set (as in, scene) is made active, and changeset(), the action to, you guessed it, move to another set:

; Always go back to set 0
;@set(1)
;@  changeset(0)
;@set(2)
;@  changeset(0)
;@set(3)
;@  changeset(0)
[etc. to set 9]

This cnf could be finished now, but because I hate myself, there is another improvement I want to add. Since the puzzle pieces are layers, some will slip under others when dragged. Wouldn't it be great if they could be lifted above the other levels for the duration of dragging? Standard FKiSS does not allow that. To simulate that, I could take all the puzzle pieces and make new versions of them with a drop shadow (to the bottom right, so that the top left, which determines the object coordinates, stays unchanged) and then add them to the object declaration block, ABOVE the old pieces.

First, the time-consuming labour of making these pieces. Having layer groups in GIMP means that I can take one of the three layer groups containing all puzzle pieces done in one style - I pick the bevelled pieces layer group - and simply duplicate it, dragging the duplicate layer group under the other puzzle piece group layers. I make the first layer in that group active, make sure that only this layer's piece is visible, choose "Filters", "Light and Shadow", and then "Drop shadow (legacy)" because it follows the shape of the piece. In the dialog that pops up, I leave the x and y offsets at 4, but change the radius from 15 to 5, as the shadow area shouldn't be too large. This creates a layer called "Drop Shadow" under the active layer - the layer containing the puzzle piece - so I merge the active layer down and rename it to something like "6-8bds" (piece in row 6 column 8, bevelled, drop shadow). It appears that even with reduced size, the drop shadow extends one pixel above and to the left of the piece, so I select the rectangle of apparently empty space to the left of the piece, right up to its outline (taking care not to shear off any of its pixels) and press Delete, then do the same for the space above, to trim off this invisible and undesirable bit of drop shadow. I then copy the layer and paste it as a new image, use "Crop to Content" on this image (checking again if the piece has extra space to the top and left, which means I haven't trimmed off the drop shadow well enough) and export it to "6-8bds.cel", after which I close the image without saving. (After adding all these layers, I do save the puzzle XCF, of course.)


Adding a (legacy) drop shadow.

Of course, I don't bother giving the flat and outlined pieces a drop shadow too; for each drop-shadowed piece, I make the layer of the equivalent flat or outlined piece active and visible. This layer will perfectly overlay layer "6-8bds" so that the drop shadow now seems to belong to the flat or outlined piece (this is why I dragged the duplicate layer group under the other ones). To make the cel, I choose "Edit", "Copy Visible" and "Edit", "Paste as New Image" (keyboard shortcuts: Ctrl-Shift-C, Ctrl-Shift-V), crop, and export the resulting image to "6-8ds.cel" or "6-8ods.cel" before closing it without saving. Obviously, I have to do this for every. Single. Piece.

(After doing the flat and outlined pieces, I don't save the puzzle XCF, since the only changes are which layer is active. I either close and reload the puzzle XCF, or empty the Undo buffer.)

This is where CKiSS really shines. Regular cels don't support varying levels of transparency in the same cell; I would have to make the drop shadow a separate cel, or several separate cels, each of decreasing transparency.


What you will see when you click on this piece.

Click and drag a piece, and its dropshadow version is mapped, making it seem to jump up above the other pieces. Release the mouse button, and the dropshadow version disappears. It doesn't matter that the dropshadow versions are also in layers, because only one of them is visible at a time. Here's how to code that with a dropshadowed version of a puzzle piece (the one with "ds" added to its name), using catch(), which is like press(), but for items that are either draggable, or, if fixed, not fixed to the maximum value; and drop(), which is what happens when you release the mouse button on such items.

;@catch("1-1b.cel") ; Lift piece when grabbing it with the mouse
;@  map("1-1bds.cel")
;@drop("1-1b.cel") ; Unmap lifted piece and drop shadow when releasing it
;@  unmap("1-1bds.cel")
;@catch("1-2b.cel")
;@  map("1-2bds.cel")
;@drop("1-2b.cel")
;@  unmap("1-2bds.cel")
[repeated ad nauseam for 3x54 objects]

The drop-shadow cels will have to be added to the object declaration block under the settings panel, but above the regular pieces. They will be assigned to objects #1 to #54, just like the regular puzzle pieces, so they will always be in the same place as the cel I just clicked. This means that (i) I have to unmap 162 dropshadow cells under initialize(), and (ii) in the settings panel, clicking button1c.cel shouldn't remap the whole puzzle piece objects, but just all flat and outlined puzzle piece cels. Ye gods and things on toast, there has to be an easier way to code this. (In FKiSS4, this is what groups and frames are used for.)

I'm not going to quote even a snippet of that huge slab of code, it should be obvious how to declare objects and use map() and unmap() by now. Suffice it to say that the cnf for the first KiSS version is now done, so onto FKiSS2, which does support version().

Making a jigsaw puzzle KiSS set - FKiSS2 version

The version levels for version() are:

For FKiSS1 and FKiSS1b the level numbers are supposedly 0 and 1, but that's a moot point as they don't support version(). Which is why, after making a new warning cel for FKiSS2 and copying "puzzfks.cnf" to "puzzfk2.cnf", I'm changing the object declaration and FKiSS block as follows:

; warning
#66.999  warning2.cel   *0 :0                   ; this cel replaces warning.cel
[etc]

;@EventHandler
;@initialize()
; don't unmap warning cel here, that happens under version()
; example.cel covers slicerbw.cel but is transparent, so...
;@  unmap("slicerbw.cel")
[also, unmap ALL 162 puzzle pieces with dropshadow]
;
;@begin()
; the timer for alarm(0) is now under version()
;@  timer(2,2000) ; switch set off in 2 seconds unless version() prevents it
;
;@alarm(1) ; dummy wrapper for FKiSS-only viewers
;@version(2)
;@  timer(2,0) ; turn timer for alarm(2) off
;@  unmap("warning2.cel") ; now for FKiSS2
;@  timer(0,3000) ; unmap intro cel in 3 seconds
;
;@alarm(2)
;@  quit() ; may not close set but hopefully stops anything set does

The event alarm() happens when the duration for the corresponding action timer() runs out, so when there is no timer of the same number, the alarm is never triggered, and any code under it is never carried out. The alarm above version() is a dummy (does nothing) wrapper (hides syntax from the viewer). Normally, "alarm(1)" presumes a "timer(1,[duration])" somewhere, but since there is no "timer(1)", a FKiSS-only viewer will think that version() is an action under alarm(), rather than an event, and because alarm(1) is not triggered, version() is not processed and found to be an unsupported command that may or may not crash the viewer. This matters because if the viewer is FKiSS2-capable, there is no problem; the warning in this cnf is for viewers that recognize at most the simplest FKiSS syntax. In this cnf, version() assumes the tasks that had, in the previous cnf, been divided between initialize() and begin().

The alarm-wrapper trick stops a FKiSS-only viewer from choking on version(), but there is other unsupported syntax further down. So begin() sets a 2-second timer for alarm(2) which issues a quit() that should shut down the set, close the viewer or even do absolutely nothing, but if the set acts funny from trying to carry out code it doesn't understand, hopefully quit() will cut that short. Under version(2), this timer is cancelled with "timer(2,0)".

(There should never be a timer under initialize(), as, when the set takes time to load, the timer may have run out and triggered the alarm before the set appears.)

But now I run into a viewer-specific problem: the other viewers I used will, if I put begin() before version(), process them in that order, but UltraKiSS insists on first processing version(), which turns off the timer for alarm(2), and then begin(), which sets the timer for alarm(2) again, so the set shuts down after two seconds. The formerly dummy wrapper alarm finds a use after all: its timer is set under version() - which means that in a viewer that doesn't recognize version(), it will still never be triggered - and it also, after begin() is done, turns off the timer for alarm(2).

;@begin()
;@  timer(2,2000) ; quitting alarm
;
; A three-second timer gets rid of the intro screen.
;@alarm(1) ; dummy wrapper for FKiSS-only viewers
;@  timer(2,0) ; in case begin() started timer(2) after version() stopped it
;@version(2)
;@  timer(2,0) ; no need to quit
;@  timer(1,1000) ; forestall alarm2 (if version() is processed before begin())
;@  unmap("warning2.cel")
;@  timer(0,3000) ; unmap intro cel in 3 seconds
;
;@alarm(2) ; if FKiSS2 not supported
;@  quit()

FKiSS2 has two huge advantages over FKiSS. The first is collision detection: knowing when an object touches, or stops touching, another object, which triggers the events in(), stillin(), out() and stillout(). The second is being able to move an object to relative or absolute coordinates using the action moveto(). These two features are used for "snap-to", which is when you pull a clothing cel over a doll cel and it jumps to the right position. Or you drag a puzzle piece cel to... you get the idea. Snap-to code has met with some resistance, the idea being that one should be able to put a shoe on a doll's head if one so desires; but there is no reason, aesthetic or otherwise, to deliberately put pieces of a puzzle in the wrong place. I can now scatter the puzzle pieces using moveto() instead of move(), and because moveto() always puts them in the same place no matter where they were before, I can do it more than once. I can also use collision for a mechanism to detect that the puzzle has been completed.

Replacing move() by moveto() is easy, because I made the move() block by copying "move(#,0,0)" 54 times, putting in the object numbers and "scattered" coordinates, then changing the coordinates to their calculated new values. But before I started calculating, I copied this block to an empty text document and replaced move() with moveto(). After copying the FKiSS cnf to what will become the FKiSS2 cnf, I just have to paste the moveto() block over the move() block.

A new cel is needed for snap-to: a tiny square of 3 by 3 pixels (PlayFKiSS may not detect collision for one single pixel), positioned in the middle of each piece in the puzzle grid under object numbers #74 to #127, with a fix value of 999. In GIMP, I'll make this square, duplicate the Slicer layer, and then copy and paste the square in the middle of each piece in the duplicate layer. Hovering over a pixel with the cursor tells me its coordinates in the status bar, so I can note the coordinates of each object-to-be from the XCF, to be added to the set coordinates, without having to open the KiSS viewer. The square is exported as "snapto.cel".


The snap-to objects are the tiny neon pink dots.

The snap-to objects can be put under the backgrounds in the object declaration block to hide them from sight; collision detection will happen nevertheless. I put their object declaraton lines above those of the backgrounds for testing, then later cut and paste them to their final place.

; 54 snap-to objects
#74.999 snapto.cel      *0 :0                   ;
#75.999 snapto.cel      *0 :0                   ;
   [continue until]
#127.999 snapto.cel     *0 :0                   ;

Beginning with first piece #1 and first snap-to object #74, when a piece object "hits" a snap-to object, a sound (of snapping my fingers, that I recorded) is played using the FKiSS action "sound()" - which, because it depends on a combination of viewer, soundfile support, operating system and hardware, does not work on all viewers! - and the piece object is moved into its initial position (copied and pasted from the set coordinates line). The in() event is only triggered if the two objects were absolutely not overlapping before. There is another event, stillin(), to see if two objects are still overlapping, but at what interval do you test that, or is it tested only if the object moves? And I've found that stillin() is spotty with some viewers. I'll put it in to realign any slightly displaced pieces, but if you nudge the piece out of place, and want it to snap back to its position, you may have to drag it a distance away, drop it, then drag it over its snap-to object again.

; Snap-to code
;@in(#1,#74)
;@  sound("snapto.wav") moveto(#1,90,90)
;@stillin(#1,#74)
;@  sound("snapto.wav") moveto(#1,90,90)
;@in(#2,#75)
;@  sound("snapto.wav") moveto(#2,119,90)
;@stillin(#2,#75)
;@  sound("snapto.wav") moveto(#2,119,90)
[etc.]

I must repeat: stillin() is not reliable! In GnomeKiSS, if a piece snaps in place and I tug it a few pixels, it returns to its place. If I yank it away, it returns to its place but then snaps back to where I dragged it. This will not work well with what I've planned below, so the stillin()s have been removed.

The mechanism to detect puzzle completion also relies on collision. Two, optionally three cels are needed: a small shape like a rectangle, moved to track progress, the same shape, but stationary, and, optionally, a button to make them visible (to let the user follow the progress) or not. The first two shapes have to be small enough to fit in the set window 55 times in a row, either vertically or horizontally, without touching. The set dimensions are 580x480, so assuming I'm using a strip along the top of the screen, I have 580 pixels. Let's say I reserve 30 pixels for an on/off button, that leaves 550 pixels which is conveniently 10 pixels per position. There must be room on either side, so the small shape must be 8 or ideally 6 pixels wide.

Also, instead of just being an on/off button, the button will bring up the settings panel with an on/off setting added.

The bars will both be rectangles in different colours, 6 pixels wide, 10 pixels high; I made a chequered background of 10x10 squares to draw them on. The button, 30x30 pixels, will be a bit more fancy. I select a soft round brush and change its settings, if necessary, to make it the size of the button; make a new layer, and click once with the brush to make a hazy round dot on it; make a new layer under it, and make that layer active; use the elliptic selection tool to draw a circle around the dot, and flood-fill that circle with a darker shade of that colour. If I like the how the resulting shape turns out, I merge the two layers.


A collage of zoomed-in stages and final button.

The code added to show, unmap and remap the button, and open the settings:

[object definition block, under intro screen cels]
#130.999 setbtn.cel     *0 :0                   ; settings button
#129.999 bar2.cel       *0 :0                   ; end bar
#128.999 bar1.cel       *0 :0                   ; progress bar

[added under initialize()]
;@  unmap(#128) unmap(#129) unmap(#130)

[added below alarm(0) and its unmapping actions]
;@press(#130) ; open settings
;@  unmap(#130) map(#73)

[add #128, #129, #130 to start button code]
;@press("button3.cel")
;@  unmap(#73) map(#128) map(#129) map(#130)
[tacked onto the end of the moveto()s, which may move the progress bar by causing out():]
;@  moveto(#128,33,0) ; reposition progress bar

; set #0
$0 [add after existing coordinates] 33,0 573,0 0,0
[and add three asterisks to all the other sets]

Doesn't that mean I can press the button and open the settings in mid-game, thereby forcing the game to restart? It does, and I'll solve that when overhauling the settings panel, after I finish the progress bar code.

To see how the puzzle is coming along, each time a piece goes in its right place, the progress bar moves a fixed distance to the right, until, at the 54th movement, it hits the stationary bar. Of course, pieces being layered, sometimes you have to drag the placed piece away to rescue a piece that got lost under it. So in() moves the bar to the right, but out() moves the bar back to the left. I alter the puzzle piece collision code as follows:

;@in(#1,#74)
;@  sound("snapto.wav") moveto(#1,90,90)
;@  move(#128,10,0) ; this moves the progress bar horizontally
;@out(#1,#74)
;@  move(#128,-10,0) ; this sets the progress bar back until next in()
;@in(#2,#75)
;@  sound("snapto.wav")  moveto(#2,119,90)
;@  move(#128,10,0) ; this moves the progress bar horizontally
;@out(#2,#75)
;@  move(#128,-10,0) ; this sets the progress bar back until next in()
[etc. to objects #54 and #127]

That's a lot of repetition, but having to repeat code is the bane of early FKiSS versions, and the reason why FKiSS isn't really suited to making games. It got better with each version, though. Ironically, fks.cnf is very long because sticking to legacy code makes a lot of repetition necessary, yet precisely legacy viewers may choke on such a huge cnf.

What happens when the progress bar hits the end bar? The standard reaction is to show a "you did it!" message, which the user can click on to restart the game. The message, like the settings screen, must cover all pieces, which could shift from their spot otherwise, but should, apart from its message, be transparent enough to show the finished puzzle underneath. Clicking the message unmaps it and opens the settings panel with its start button. The code to add:

[object definition block, above #130, under intro screen cels]
#131.999 finished.cel   *0 :0                   ;

[add another unmap under initialize()]
;@  unmap(#128) unmap(#129) unmap(#130) unmap (#131)

[add at bottom of FKiSS block]
; progress bar hits finish
;@in(#128,#129)
;@  map(#131) unmap(#128) unmap(#129) ; map end screen, unmap progress
;@  unmap(#130) ; don't open settings while "finished" cel is shown
; click ending away, which opens settings
;@press(#131)
;@  unmap(#131) map(#73)

; set #0
$0 [add after existing coordinates] 90,90

To allow the user to change the settings mid-game without restarting, the settings panel, which had a puzzle piece button, an underlay button and a start button, will have a background button, a progress bar button and a Return button added to it. The background button takes over the background's colour cycling, the progress bar button makes the progress and finish bars visible or invisible, and the Return button makes the settings screen disappear.

Space being cramped on the left side of the settings panel, these buttons will be added in a column on the right. This column will need a separate object number, #132, and "423,90" is added to the first line in the object coordinates block. (For any object coordinates added for set 0, don't forget to add an asterisk for sets 1 to 9.) This should be the last object needed.

The settings panel will grow a bit and get new lettering - I love trying out new fonts - so setting2.cel will replace settings.cel. Unlike settings.cel which is superimposed on a picture of the finished puzzle, setting2.cel will be partly transparent so you can see the puzzle pieces change while cycling through the styles, but can't touch them. The top right button (button4.cel) reads "Return", but as you can't return to a game you haven't started yet, will initially be unmapped to show the blank dummy (button4a.cel) underneath. The middle button (button5a/b/c.cel) cycles the background, while the button6a/b.cel at the bottom makes the progress cels transparent or opaque rather than mapping and unmapping them, as unmapped cels can't trigger collision detection; to indicate this, the Yes and No buttons have a picture of opaque and transparent progress bars on them. To keep the font consistent between the left and the right column, button3.cel gets two replacements reading "start" and "restart", while button2c.cel is replaced by button2d.cel.

; Settings panel
; new stuff
#132.999  button4.cel    *0 :0                   ; Return button
#132.999  button4a.cel   *0 :0                   ; blank button
#132.999  button5a.cel   *0 :0                   ; green bg button
#132.999  button5b.cel   *0 :0                   ; blue bg button
#132.999  button5c.cel   *0 :0                   ; red bg button
#132.999  button6a.cel   *0 :0                   ; progbar on button
#132.999  button6b.cel   *0 :0                   ; progbar off button
;
; old stuff
#73.999  button1a.cel   *0 :0                   ; puzzle piece button flat
#73.999  button1b.cel   *0 :0                   ; puzzle piece button outline
#73.999  button1c.cel   *0 :0                   ; puzzle piece button bevel
#73.999  button2a.cel   *0 :0                   ; underlay button picture
#73.999  button2b.cel   *0 :0                   ; underlay button pieces
#73.999  button2d.cel   *0 :0                   ; underlay button none - NEW CEL!
#73.999  button3b.cel   *0 :0                   ; REstart button on top - NEW CEL!
#73.999  button3a.cel   *0 :0                   ; start button - NEW CEL!
#73.999  setting2.cel   *0 :0                   ; panel - NEW CEL!

; set #0
$0 [add after existing coordinates] 423,90

The cels "button4.cel" ("Return") and "button3b.cel" ("Restart") must be unmapped under initialize(). Because the restart button overlies the start button and is only unmapped when opening the set, the user only sees "Start" the first time, and "Restart" for every replay. Pressing the pink settings button (object #130) now maps both #73 and #132.

;@initialize()
; example.cel covers slicerbw.cel but is transparent, so...
;@  unmap("slicerbw.cel")
;@  unmap (#128) unmap (#129) unmap (#130) unmap (#131) unmap("button3b.cel") unmap("button4.cel")
[...]
[almost at bottom of FKiSS block]
;@press(#130) ; open settings
;@  unmap(#130) map(#73) map (#132)


The beautiful new settings panel.

The Return button simply unmaps both objects of the setting panel, and remaps object #130, the pink settings button. It is blank when starting a new game, by unmapping button4.cel under initialize(), but also when presenting the settings for a new game after clicking on object #131, the "finished" message.

; Press setting:return
;@press("button4.cel")
;@  map(#130) unmap (#65) unmap(#132)
[...]
[all the way at bottom of FKiSS block]
; click ending away, which opens settings WITHOUT RETURN BUTTON
;@press(#131)
;@  unmap(#131) map(#73) map(#132) unmap("button4.cel")

The code for colour-cycling backgrounds is easily transplanted to the settings by replacing "press background cel" with "press button 5", and adding an unmap() for that button, or remapping buttons as the case may be:

; Press setting:backgrounds button
;@press("button5a.cel")
;@  unmap("button5a.cel")
;@  unmap("green.cel") unmap(#56) unmap(#57) unmap(#58) unmap(#59)
;@press("button5b.cel")
;@  unmap("button5b.cel")
;@  unmap("blue.cel") unmap(#67) unmap(#68) unmap(#69)
;@press("button5c.cel")
;@  map("button5a.cel") map("button5b.cel")
;@  map(#55) map(#56) map(#57) map(#58) map(#59) map(#67) map(#68) map(#69)

As said earlier, the progress bars can't be unmapped, but only made transparent, as unmapped objects don't trigger collision detection. They are set by the action "transparent()" (supported from FKiSS1b onwards) to a value of 254 rather than the maximum 255, because, depending on the viewer, a completely transparent object may not trigger collision detection either. As transparent(), like move(), doesn't set an absolute value but changes a current value, going back from near-invisible to fully visible requires a negative value.

; Press setting:progress bar visible
;@press("button6a.cel")
;@  unmap("button6a.cel") transparent(#128,254) transparent(#129,254)
;@press("button6b.cel")
;@  map("button6a.cel") transparent(#128,-254) transparent(#129,-254)

Concerning the existing buttons: I replaced "button2c.cel" with "button2d.cel" and "button3.cel" with "button3a.cel". I then copied the code for button3a.cel and replaced its name by "button3b.cel", because the Start and Restart button do exactly the same! Then it occurred to me that I could both point them to a single block of code with a very short timer:

; Press start to unmap panel and scatter pieces
;@press("button3a.cel")
;@  timer(3,1)
;@press("button3b.cel")
;@  timer(3,1)
;@alarm(3)
;@  unmap(#73) unmap(#132) map(#128) map(#129) map(#130)
;@  moveto(#1,505,37) moveto(#2,502,156) moveto(#3,105,7) moveto(#4,418,409) moveto(#5,13,419)
[etc.]

That concludes the cnf for FKiSS2.

Making a jigsaw puzzle KiSS set - FKiSS2.1 version

The FKiSS2 cnf has a serious fault that can only be addressed by version 2.1: the settings buttons don't keep up with the current situation. This is a problem when restarting, or changing the settings halfway through. Because opening the settings always remaps all settings cels, if the background is already blue and the background button is green, I have to click away the green button and the blue button before I can change the background to red. Ditto the underlay and puzzle style, and, worst of all, the progress bar button, which doesn't work with absolute values, so I can't just keep clicking the button until it aligns with what I'm seeing. I want the buttons to reflect the current settings, and while I'm at it, I'd like a way to recover puzzle pieces that get lost under other pieces. FKiSS2.1 has what I need, in the form of conditional actions.

The first step, after copying "puzzfk2.cnf" to "puzzfk21.cnf", is to make a new warning cel to say the viewer doesn't support FKiSS2.1 rather than FKiSS2, and to have it unmapped under "version(3)".

; warning
#66.999  warning3.cel   *0 :0                   ; this cel replaces warning2.cel

; version(3) and upwards still need the wrapper alarm!
;@alarm(1) ; dummy wrapper for FKiSS-only viewers
;@  timer(2,0) ; in case begin() started timer(2) after version() stopped it
;@version(3) ; replaces version(2), rest of code stays the same
;@  timer(2,0) ; no need to quit
;@  timer(1,1000) ; forestall alarm2 (if version() is processed before begin())
;@  unmap("warning3.cel")
;@  timer(0,3000) ; unmap intro cel in 3 seconds

FKiSS2.1, which despite its name adds as much new functionality as FKiSS2, has the following three pairs of conditional timer actions: if(not)mapped(), if(not)fixed, and if(not)moved(). It also has setfix(), to change an object's fix value, and the collision detection duo collide() and apart(), which I won't use, as they need cel names, and snapto.cel, being used in 54 objects, is a very ambiguous cel. The timers ifmapped() and ifnotmapped() also need a cel name (the other four need object numbers), but the cels I have in mind are mostly unique.

The syntax is simple: if(not)mapped("name.cel",timer,delay). So when for instance the green background might be unmapped - which should change what button is shown when the settings panel opens - I'll set a conditional alarm to also unmap the green background button. For the puzzle pieces, I can check if the first of the flat and the outlined puzzle pieces are mapped. The underlays are not so simple; one of them, "example.cel", is an ambiguous cel. And the progress bars are not unmapped at all. I'll need two sneaky extra cells to deal with these two problem cases. It doesn't matter what they look like, as they'll be hidden under the red background, and just so I don't have to add anything to the coordinates block, they'll share an object number with one of the red tiles, since those are never unmapped. This should do it:

[added two differently named cels for ifnotmapped()]
#72.999 redtl.cel       *0 :0                   ; hey buddy, care to share your number?
#72.999 snapto1.cel     *0 :0                   ; just a copy of snapto.cel, is mapped when example.cel is
#72.999 snapto2.cel     *0 :0                   ; ditto, is mapped when progress bars are visible

These two copies will have to be mapped and unmapped by the settings buttons. Note that, now that slicerbw.cel being unmapped may cause button2b.cel to be unmapped also, clicking on button2a.cel explicitly has to map the underlying button2b.cel.

[adding snapto1.cel and snapto2.cel to buttonpress code]
;@press("button6a.cel")
;@  unmap("button6a.cel") unmap("snapto2.cel") transparent(#128,254) transparent(#129,254)
;@press("button6b.cel")
;@  map("button6a.cel") map("snapto2.cel") transparent(#128,-254) transparent(#129,-254)
   [...]
;@press("button2a.cel")
;@  unmap("button2a.cel") unmap(#0) map("slicerbw.cel") map("button2b.cel")
;@  unmap("snapto1.cel")
   [...]
;@press("button2d.cel")
;@  map("button2a.cel") map("button2b.cel") map(#0) unmap("slicerbw.cel") map("snapto1.cel")

Now everything is ready to set the conditional timers when the settings panel is mapped after clicking the pink button or finishing the puzzle, both of which open the settings panel. Under the existing code for "press(#130)" and "press(#131)", I add the following:

;@  ifnotmapped("1-1.cel",4,1) ifnotmapped("1-1o.cel",5,1) ; puzzle pieces
;@  ifnotmapped("snapto1.cel",6,1) ifnotmapped("slicerbw.cel",7,1) ; underlays
;@  ifnotmapped("green.cel",8,1) ifnotmapped("blue.cel",9,1) ; backgrounds
;@  ifnotmapped("snapto2.cel",10,1) ; if progress bar not visible

At the bottom of the FKiSS block, I add the following alarms:

; Alarms to unmap settings buttons
;@alarm(4)
;@  unmap("button1a.cel")
;@alarm(5)
;@  unmap("button1b.cel")
;@alarm(6)
;@  unmap("button2a.cel")
;@alarm(7)
;@  unmap("button2b.cel")
;@alarm(8)
;@  unmap("button5a.cel")
;@alarm(9)
;@  unmap("button5b.cel")
;@alarm(10)
;@  unmap("button6a.cel")

That concludes the settings, but I'm not done yet! Instead of hoping that fitted puzzle pieces aren't nudged out of place, I intend to fix them. The code will change so that when puzzle pieces collide with their snapto object, they are moved into place and get a fix value of 32767, which is considered "maximally fixed", ie. no amount of clicking and dragging will budge them (although not all viewers treat this fix value that way), and the progress bar moves forward; there will be no moving back.

Since the pieces, once in place, can't be displaced, the "finished" message neither has to cover them nor be transparent, so I replace it with a smaller message positioned above the puzzle. As the FKiSS code only refers to this message by its object number and never by its cel name, it doesn't need any changes. I do need to change the one-but-last set of coordinates under set #0 from "90,90" to "90,0".

#131.999 finishsm.cel   *0 :0                   ; replaces finished.cel

I mentioned earlier that catch() and drop(), used to "lift" puzzle pieces, only work on objects that are not maximally fixed. When the pieces that snap in place become maximally fixed before letting go of the mouse button, there may be no drop() event, in which case the "lifted" piece remains visible and overshadows neighbouring pieces. So every drop() must be replaced with release(), which happens whenever the mouse lets go of the cel, no matter what the fix value.

But what if other pieces "fall" under the immovably fixed ones? To retrieve them, the pink button will get a companion blue button, by copying it, resizing the canvas to make room under the pink button, moving the button down into that new space and using "Colours", "Colorize". And the blue button will, using ifnotfixed(), move all loose pieces off the puzzle area and back to the sides. The blue button will belong to the same object, #130, as the pink button, so its mapping and unmapping is already taken care of, but for clicking purposes, both buttons must now be addressed individually, instead of by their object number.

Code-wise, I must replace all "drop(" with "release(", remove all out() actions, add "setfix(#object,32767)" to all in() actions, and replace any instance of "press(#130)" by "press("setbtn.cel")". Since the pieces are fixed while playing, they must be unfixed with "setfix(#object,0)" on restarting. In the object declaration block, I copy and paste the line for the pink button and change the celname to "movebtn.cel". Then I can add the new code: when the blue button is pressed, ifnotfixed(#object,timer,1) is repeated for every object 1 to 54, followed by 54 alarms, each of which moves its object to a position around the puzzle's perimeter by copying the coordinates from the moveto() block. Since I've already used ten alarms, the alarm numbers added will range from 11 to 64.

#130.999 setbtn.cel     *0 :0                   ; settings button
#130.999 movebtn.cel    *0 :0                   ; button to move loose pieces off field
;
[...]
;
;@press("button3b.cel") ; restart: remove fix, then reposition via timer
;@  setfix(#1,0) setfix(#2,0) setfix(#3,0) setfix(#4,0) setfix(#5,0) setfix(#6,0)
[etc. until object #54]
;@  timer(3,1)
;
[...]
;
;@release("1-1.cel") ; release() replaces drop()
;@  unmap("1-1ds.cel")
[etc. until cel "6-9b.cel"]
;
[...]
;
;@press("setbtn.cel") ; open settings
;@  unmap(#130) map(#73) map(#65)
;@  ifnotmapped("1-1.cel",4,1) ifnotmapped("1-1o.cel",5,1) ; puzzle pieces
[etc. for other settings buttons]
;
[...]
;
; Snap-to code and progress
;@in(#1,#74)
;@  sound("snapto.wav") moveto(#1,90,90) setfix(#1,32767) ; setfix() added
;@  move(#128,10,0) ; move progress bar forward
[out() event removed, etc. up to #54]
;
[...]
;
; Rescatter loose pieces by moving them to initial positions
;@press("movebtn.cel")
;@  ifnotfixed(#1,11,1) ifnotfixed(#2,12,1) ifnotfixed(#3,13,1) ifnotfixed(#4,14,1)
[etc. to ifnotfixed(#54,64,1)
;
;@alarm(11)
;@  moveto(#1,505,37)
;@alarm(12)
;@  moveto(#2,502,156)
[etc. to alarm(64)]

The most playable cnf so far is done. I love version 2.1!

Making a jigsaw puzzle KiSS set - FKiSS3 version

Up to FKiSS2.1, syntax was developed by Japanese fans, which is why KiSS viewers written by Japanese programmers, such as KiSSLD, tend to support at most that version. FKiSS3 came out of discussions on a mailing list followed by mostly Americans, and was largely developed by Chad Randall, author of PlayFKiSS and C++ programmer. As such, it's less paperdoll scripting commands, and more programming language. It has if-endif blocks, simple variables, goto-constructions using labels, and let-actions to query every aspect of the situation. Labels are like alarms, but without the delay. In programming language terms, labels are like includes, and they save a lot of repetition.

FKiSS3 introduces an action for cels and objects that may be on top of, but should not get in the way of, other cels and objects: ghost(). A "ghosted" cel or object may be mapped, but mouseclicks pass through it as if it were unmapped. The syntax is "ghost(object number/celname,1)" to ghost something, and "ghost(object number or celname,0)" to make it clickable again. Unfortunately the earliest FKiSS3-capable viewer, PlayFKiSS, does this the other way round. What's needed to make code compatible with both approaches is a "ghost test", using an almost wholly transparent cel overlaying the whole playfield. This cel is ghosted in the regular way under initialize(), and a "ghosting" variable is set to 1. The user will click on the playfield at least once, so if this cel is not in fact ghosted by the viewer, this will cause a press() event, upon which the cel will be ghosted the "wrong" way, and the "ghosting" variable is set to 0. This variable will be used by anything else that needs ghosting. Variables in FKiSS3 have fixed names consisting of just a (case-insensitive) letter, or a letter and a single number, for instance, "P" or "B5", and can be set through the let() action, for instance: "let(B5,1)". For simplicity's sake, the name chosen for the ghosting variable is "G".

Creating a ghost testing cel is easy; it's a solid white rectangle of 580x480 pixels that I create in GIMP by choosing to make a new file, filling in the height and width, and exporting the result as "ghostest.cel". This cel is a whopping 1Mb, so I decide to use the tiling principle and divide the total size by 4, remaking ghostest.cel as a rectangle of 145x120 pixels instead, to cover the playfield as sixteen additional objects. I'd hoped not to add any more object numbers, but in the FKiSS4 cnf I expect to slim down the cnf again by throwing a lot of object numbers out. I already plan to throw out all snapto.cel objects in this cnf, but the objects after that would have to be renumbered, so I'll save that for last. As with all new objects, I add the ghost test object coordinates to the coordinates block by hand; since the ghost-testing objects are all the same size, their coordinates are easy to calculate.

I also make a new warning cel for FKiSS3, copy "puzzfk21.cnf" to "puzzfk3.cnf", add the new cels to the object definition block and raise "version(n)" by one. Under version(4), which is only processed if the viewer understands FKiSS3 commands, I add code to ghost the testing objects and set the ghosting variable to its default value. Below alarm(0) comes the code to reverse-ghost if the testing cel is still clickable. Since the tiled testing cel is ambiguous, I ghost the sixteen object numbers rather than the cel; generally, an event happens for all instances of the cel, so the press() event can use the cel name, but actions are applied only to the first instance. (Under FKiSS4, I hope to combine all objects into one with offset tags.) Once the start button is clicked, the testing cel has served its purpose; its instances are unmapped, ghosting variable G is used to set the value of unghosting variable U, and non-interactive parts of the set, like the progress bars, are ghosted using the variable. The unghosting variable U will prove its worth later on.

; warning
#66.999  warning4.cel   *0 :0                   ;
#65.999  warningt.cel   *0 :0                   ;%t255
; ghost test tiles, almost transparent
#133.999 ghostest.cel   *0 :0                   ;%t254
#134.999 ghostest.cel   *0 :0                   ;%t254
#135.999 ghostest.cel   *0 :0                   ;%t254
#136.999 ghostest.cel   *0 :0                   ;%t254
#137.999 ghostest.cel   *0 :0                   ;%t254
#138.999 ghostest.cel   *0 :0                   ;%t254
#139.999 ghostest.cel   *0 :0                   ;%t254
#140.999 ghostest.cel   *0 :0                   ;%t254
#141.999 ghostest.cel   *0 :0                   ;%t254
#142.999 ghostest.cel   *0 :0                   ;%t254
#143.999 ghostest.cel   *0 :0                   ;%t254
#144.999 ghostest.cel   *0 :0                   ;%t254
#145.999 ghostest.cel   *0 :0                   ;%t254
#146.999 ghostest.cel   *0 :0                   ;%t254
#147.999 ghostest.cel   *0 :0                   ;%t254
#148.999 ghostest.cel   *0 :0                   ;%t254
[rest of object declaration block]

[initialize(), begin(), wrapper alarm]
;@version(4)
;@  timer(2,0) ; no need to quit
;@  timer(1,1000) ; forestall alarm2 (if version() is processed before begin())
;@  unmap("warning4.cel")
;@  timer(0,3000) ; unmap intro cel in 3 seconds
;@  let(G,1)  ; the ghosting variable
;@  ghost(#79,1) ghost(#80,1) ghost(#81,1) ghost(#82,1) ghost(#83,1) ghost(#84,1) ghost(#85,1) ghost(#86,1)
;@  ghost(#87,1) ghost(#88,1) ghost(#89,1) ghost(#90,1) ghost(#91,1) ghost(#92,1) ghost(#93,1) ghost(#94,1)

; ghost press() event after alarm(0)
;@press("ghostest.cel")  ; this proves that ghost(x,1) didn't work
;@  let(G,0)
;@  ghost(#133,0) ghost(#134,0) ghost(#135,0) ghost(#136,0) ghost(#137,0) ghost(#138,0) ghost(#139,0) ghost(#140,0)
;@  ghost(#141,0) ghost(#142,0) ghost(#143,0) ghost(#144,0) ghost(#145,0) ghost(#146,0) ghost(#147,0) ghost(#148,0)

; Press setting: (re)start
;@press("button3a.cel") ; start button
; Calculate unghosting variable, now that ghosting variable has the right value.
;@  ifequal(G,1)
;@    let(U,0)
;@  else
;@    let(U,1)
;@  endif
; unmap ghost testing objects
;@  unmap(#133) unmap(#134) unmap(#135) unmap(#136) unmap(#137) unmap(#138) unmap(#139) unmap(#140)
;@  unmap(#141) unmap(#142) unmap(#143) unmap(#144) unmap(#145) unmap(#146) unmap(#147) unmap(#148)
; ghost non-interactive elements with variable (backgrounds, underlays, progress bars)
;@  ghost(#0,G) ghost(#55,G) ghost(#56,G) ghost(#57,G) ghost(#58,G) ghost(#59,G)
;@  ghost(#67,G) ghost(#68,G) ghost(#69,G) ghost(#70,G) ghost(#71,G) ghost(#72,G)
;@  ghost(#128,G) ghost(#129,G)
[rest of startbutton code, and code for other buttons]

; set #0
$0 [add after existing coordinates] 0,0 145,0 290,0 435,0 0,120 145,120 290,120 435,120 0,240 145,240 290,240 435,240 0,360 145,360 290,360 435,360
[and add 16 asterisks to other sets]

While I'm at it, to clean up the code, I move all button code blocks directly below the ghost press() event, in the right order, from button 1 to button 6, followed by the two round buttons and the finishing screen. The FKiSS block is now structured as follows: initialize(), begin() and version(), plus three alarms that are needed for initialization; the ghost check; all button press events; all set() events; code to raise pieces and show a drop shadow when clicked on; code for when the pieces have been placed correctly; and the alarms from alarm(3) onward, which will be replaced by labels, that will be added to the bottom of the FKiSS code block. Labels are events written as "label(number)", and called from another event with the actions "gosub(number)" (carry out label code, and when finished, return to process rest of calling event code) or "goto(number)" (stop processing calling event code, just go to label code). There are other commands to call labels; this set won't need them.

Any block of code that has to be copied and pasted from one event to another, belongs under a label. Here is a list:

My plan for the labels:

Revisiting the start button, and the ghosting code I just added to it: the existing code under that is replaced by "let(S,1) goto(0)", where the action "let(x,y)" is used to set variable x to the value of y, which is either a number or another variable. The restart button sets S to 2 instead of 1, then also calls the label. The return button only calls the label, as S should normally have a value of 0.

; Press setting: (re)start
;@press("button3a.cel")
[all the ghosting code I just added to the start button!! plus:]
;@  let(S,1) goto(0)
;@press("button3b.cel")
;@  let(S,2) goto(0)
;
; Press setting: return
;@press("button4.cel")
;@  goto(0)

The if-else-end construction introduced in FKiSS3 is common in programming languages, although each language may have a subtly different syntax, like having to add "then" after "if". The "if" part is a condition, which in FKiSS3 can only compare two numerical values, a comparison which is baked into all of its possible syntax:

ifequal()
ifnotequal()
ifgreaterthan()
iflessthan()
else()
endif()

Like an opening and a closing bracket, every "if" word has to be followed by an endif(); any code has to go between the two, the result being an if-end block. Such code may include the catch-all condition "else()", making the existing block an if-else-end block, and nested if-end blocks, which are blocks within blocks. For clarity's sake, the if, endif and the code between them each start on a new line, and the code between if and endif is indented; if the code contains a block, that block is indented even further. A purely theoretical example:

; B must be the same as A if A is 1, else A is set to 2 or 0:
;@  ifequal(A,1) ; A is 1
; a nested block:
;@    ifnotequal(B,A)
;@      let(B,A)
;@    endif()
;@  else() ; A is not 1
;@    ifgreaterthan(A,1) ; A is more than 1
;@      let(A,2)
;@    else() ; A is 0 or negative
;@      let(A,0)
;@    endif()
;@  endif()

This should work, but UltraKiSS, for one, does not like nested if-blocks, and will pop up error messages about them. So, for the sake of compatibility, I'll avoid them.

Writing the first label is a combination of cut and paste, and the use of if-end constructions. First, the settings screen is unmapped and the buttons are mapped, because that always happens. Then, if S is 0, that's it, we're done: goto(5) goes to the (un)ghosting label, ignoring any instructions below it. If label(0) does not jump to label(5) but continues processing its own code, that means S is 1 or 2, and the progress objects need to be (re)mapped. If 2, the puzzle was restarted, so D must be set to 0, the puzzle pieces must be unfixed, and the progress bar repositioned. (In the previous cnfs, repositioning the progress bar had to wait until after the pieces were re-scattered, in case it was moved by a collision event during the scattering, but these collision events will be removed from the code.) Finally, S is set to 0, gosub(5) takes care of unghosting and returns to the last instruction in label(0), a goto() that calls label(2), which scatters the pieces with help from label(3). Woohoo! We're doing some real programming here!

; **LABELS**
;
;@label(0)
; Unmap settings, remap buttons
;@  unmap(#73) unmap(#132) map(#130)
;@  ifequal(S,0) ; return button
;@    goto(5) ; we're done here! (note extra indentation)
;@  endif() ; every if-block needs a closing endif
; (re)start code
; Map progress objects on (re)start
;@  map(#128) map(#129)
;@  ifequal(S,2) ; REstart button
; Unfix objects, reposition progress bar, reset D
;@    setfix(#1,0) setfix(#2,0) setfix(#3,0) setfix(#4,0) setfix(#5,0) setfix(#6,0) setfix(#7,0) setfix(#8,0) setfix(#9,0)
;@    setfix(#10,0) setfix(#11,0) setfix(#12,0) setfix(#13,0) setfix(#14,0) setfix(#15,0) setfix(#16,0) setfix(#17,0) setfix(#18,0)
;@    setfix(#19,0) setfix(#20,0) setfix(#21,0) setfix(#22,0) setfix(#23,0) setfix(#24,0) setfix(#25,0) setfix(#26,0) setfix(#27,0)
;@    setfix(#28,0) setfix(#29,0) setfix(#30,0) setfix(#31,0) setfix(#32,0) setfix(#33,0) setfix(#34,0) setfix(#35,0) setfix(#36,0)
;@    setfix(#37,0) setfix(#38,0) setfix(#39,0) setfix(#40,0) setfix(#41,0) setfix(#42,0) setfix(#43,0) setfix(#44,0) setfix(#45,0)
;@    setfix(#46,0) setfix(#47,0) setfix(#48,0) setfix(#49,0) setfix(#50,0) setfix(#51,0) setfix(#52,0) setfix(#53,0) setfix(#54,0)
;@    moveto(#128,33,0) let(D,0)
;@  endif()
; Reset S
;@  let(S,0)
; Label 5 will unghost the pieces
;@  gosub(5)
; Label 2 will scatter the pieces
;@  goto(2)

Next label: bringing up a correctly mapped settings screen. First, the events that call the label, or set variables that the label needs. The code under the pink button and the finish message, both of which call label(1), is now considerably simpler. Note, again, the rationale for using gosub() versus goto(); the button press() event uses goto() because that's the last command in this event block anyway, but the press() event for the finish message uses gosub() so it can follow up the label by unmapping the return button, which should not be visible at the start of the game.

; Press button: settings
;@press("setbtn.cel")
;@  unmap(#130) goto(1)

; Press finish screen: open settings WITHOUT RETURN BUTTON
;@press(#131)
;@  unmap(#131) gosub(1) unmap("button4.cel")

FKiSS2.1 depended on the mapped state of a cel to show the right buttons on the settings panel, but FKiSS3 has variables to keep track of various states, so snapto1.cel and snapto2.cel can be deleted from the object definition and FKiSS code blocks. The variables used for the puzzle piece type, underlay, background and visibility of the progress bar will be C, C1, C2 and C3 respectively. Their initial value, when the set loads, is 0, which means everything is mapped and the topmost cel is visible. For each layer unmapped, the value goes up by 1. For instance, C=0 means flat pieces, C=1 means outlined pieces and C=2 means bevelled pieces. The progress bar variable, C3, only has values 0 (opaque) and 1 (transparent). The values are set by clicking the first, second, fifth and sixth settings buttons:

; Press setting: puzzle piece button
;@press("button1a.cel")
;@  let(C,1) ; ADDED
;@  unmap("button1a.cel")
;@  unmap("1-1.cel")
[etc]
;@press("button1b.cel")
;@  let(C,2) ; ADDED
;@  unmap("button1b.cel")
;@  unmap("1-1o.cel")
[etc]
;@press("button1c.cel")
;@  let(C,0) ; ADDED
;@  map("button1a.cel") map("button1b.cel")
;@  map("1-1.cel")
[etc]

; Press setting: underlay button
;@press("button2a.cel")
;@  let(C1,1) ; ADDED, and delete "unmap("snapto1.cel")"
;@  unmap("button2a.cel") unmap(#0) map("slicerbw.cel") map("button2b.cel")
;@press("button2b.cel")
;@  let(C1,2) ; ADDED
;@  unmap("button2b.cel") unmap(#0)
;@press("button2d.cel")
;@  let(C1,0) ; ADDED, and delete "map("snapto1.cel")"
;@  map("button2a.cel") map("button2b.cel") map(#0) unmap("slicerbw.cel")

[code for buttons 3 and 4]

; Press setting: backgrounds button
; ADDED code to set C2
;@press("button5a.cel")
;@  let(C2,1) unmap("button5a.cel")
;@  unmap("green.cel") unmap(#56) unmap(#57) unmap(#58) unmap(#59)
;@press("button5b.cel")
;@  let(C2,2) unmap("button5b.cel")
;@  unmap("blue.cel") unmap(#67) unmap(#68) unmap(#69)
;@press("button5c.cel")
;@  let(C2,0) map("button5a.cel") map("button5b.cel")
;@  map(#55) map(#56) map(#57) map(#58) map(#59) map(#67) map(#68) map(#69)
;
; Press setting: progress bar visible
; ADDED code to set C3, REMOVED code to map and unmap snapto2.cel
;@press("button6a.cel")
;@  let(C3,1) unmap("button6a.cel") transparent(#128,254) transparent(#129,254)
;@press("button6b.cel")
;@  let(C3,0) map("button6a.cel") transparent(#128,-254) transparent(#129,-254)

Using these variables, label(1) will now do the work of both the code deleted from under the pink button and the finish message, and the alarms 4 to 10, which can also be deleted. For the first three buttons, if the variable's value is higher than 0, the top button should always be unmapped, but the button under that must only be unmapped if the value is exactly 2. The label's last action is to call label(5) which ghosts the puzzle pieces.

;@label(1)
; First, map whole settings screen
;@  map(#73) map(#132)
; Show the right puzzle piece button
;@  ifgreaterthan(C,0)
;@    unmap("button1a.cel")
;@  endif()
;@  ifequal(C,2)
;@    unmap("button1b.cel")
;@  endif()
; Show the right underlay button
;@  ifgreaterthan(C1,0)
;@    unmap("button2a.cel")
;@  endif()
;@  ifequal(C1,2)
;@    unmap("button2b.cel")
;@  endif()
; Show the right background
;@  ifgreaterthan(C2,0)
;@    unmap("button5a.cel")
;@  endif()
;@  ifequal(C2,2)
;@    unmap("button5b.cel")
;@  endif()
; Show the right progress bar transparency
;@  ifequal(C3,1)
;@    unmap("button6a.cel")
;@  endif()
; Finally, ghost puzzle pieces
;@  goto(5)

The next label will have a huge amount of code, because it has to test if a puzzle piece object is unfixed and if so, move it somewhere near one of the playground's edges - and it must do so for every object individually, using an if-end block. Fortunately, the code to decide where to move the piece will not have to be repeated for every object, as it's under a separate label. Label(2) is called from the (re)startbutton via label(0), or directly via the blue button, which has all its code, as well as alarms 11 to 74, deleted and replaced by:

; Press button: rescatter loose pieces
;@press("movebtn.cel")
;@  goto(2)

This label uses the FKiSS3 action "letfix(variable,object)", which, if that was not obvious, takes the fix value of the object and stores it in the variable. (FKiSS3 has many actions of this type, such as letobjectx(), letmousex(), lettransparent() etc.)

;@label(2)
; Scatter pieces, if unfixed, uses label(3) to set X, Y
;@  letfix(F,#1)
;@  ifequal(F,0)
;@    gosub(3) moveto(#1,X,Y)
;@  endif()
;@  letfix(F,#2)
;@  ifequal(F,0)
;@    gosub(3) moveto(#2,X,Y)
;@  endif()
[repeat right up to object #54]

Variables X and Y (names chosen for an obvious reason) are helpfully filled by label(3). This is why labels are a thing of beauty: the same code can be reused instead of having to be copied and pasted under every single one one of 54 objects. The new command "random(variable,starting value, maximum value)" would also be a thing of beauty, if it worked better. According to the documentation I have, the range of possible outcomes is in fact one less than the second number, so "random(V,1,2)", which should, like a coinflip, give V a value of 1 or 2, will only make it 1; counterintuitively, I must write "random(V,1,3)". And while this coinflip construction, when repeated 54 times, should come out as half ones and half twos, it is, in GnomeKiSS, all twos, and UltraKiSS, while spreading the results better, likewise favours the higher outcome. I remember something about random() needing a larger range of values to be properly random, so I'll try again, picking a high maximum value and reduce that to a number between 1 and 4, each value representing an edge of the puzzle's playfield:

;@label(3)
; Pick one of four edges: 1 and 2 vertical, 3 and 4 horizontal
;@  random(R,1,101)  ; pick a number between 1 and 100
;@  ifgreaterthan(R,50)  ; upper half?
;@    ifgreaterthan(R,75)  ; upper quarter?
;@      let(R,4)
;@    else()  ; nope, lower quarter
;@      let(R,3)
;@    endif()
;@  else()
;@    ifgreaterthan(R,25)  ; same principle for lower half
;@      let(R,2)
;@    else()
;@      let(R,1)
;@    endif()
;@  endif()

Of course, instead of reducing R to a smaller number to determine one of four ways to fill X and Y, I could fill X and Y directly. If on a vertical edge, coordinate x would be 0 (on the left) or 490 (on the right), while coordinate y would be a random number between 0 (top) and 390 (bottom), putting the piece anywhere along that edge. For a horizontal edge, this would be roughly the other way round. You'd think that in a 580x480 square, the maximum values would be 580 and 480, but from those maximums, I have to subtract respectively the width and the height of the biggest puzzle piece, including the drop shadow. Also, the pieces should not be exactly lined up, so the coordinate that sticks a piece to one of four edges will be randomized a bit as well. Here's the third attempt, with an even wider range:

;@label(3)
; Pick one of four edges
;@  random(R,1,401) ; for a value between 1 and 400
;@  ifgreaterthan(R,200) ; horizontal edge
;@    random(X,1,490) ; pick a horizontal position along top/bottom edge
;@    ifgreaterthan(R,300) ; placed at bottom edge
;@      random(Y,390,396)
;@    else() ; placed at top edge
;@      random(Y,1,6)
;@    endif()
;@  else() ; vertical edge
;@    random(Y,1,390) ; pick a vertical position along left or right edge
;@    ifgreaterthan(R,100) ; placed on right side
;@      random(X,490,496)
;@    else() ; placed on left side
;@      random(X,1,6)
;@    endif()
;@  endif()

This does not work well in GnomeKiSS, which does not seem to like nested blocks in combination with random(), or an else() between two nested if-else-end blocks; it appears to ignore the second else(), stacking all pieces in every corner but the bottom left one. Nor will GnomeKiSS let me change the value of a variable inside an if-end block based on that variable, at least not within the same event:

if x>2
  do stuff
  sadly, "elseif X>1" is not possible in FKISS3 and nested ifs act funny, so...
  set x to 0 <-this does not happen!!
endif
if x>1 <-can also be x>2 since setting to 0 did not work
  do stuff that should not happen if x>2, but does happen
endif
x will probably have value 0 after quitting this code block, but that's too late

The solution is to replace "set x to 0", which in this example is meant to prevent further processing, with exitevent(), which achieves the same by leaving the event block, skipping any code that comes after it. Written out without nested blocks, label(3) becomes:

;@label(3)
; Pick one of four edges
;@  random(R,1,401) ; for a value between 1 and 400
;@  ifgreaterthan(R,200)
;@    random(X,1,490) ; pick a spot along horizontal edge
;@  else()
;@    random(Y,1,390) ; pick a spot, along vertical edge
;@  endif
;@  ifgreaterthan(R,300) ; stick to bottom
;@    random(Y,390,396)
; We're done here, let's LEAVE!
;@    exitevent()
;@  endif
;@  ifgreaterthan(R,200) ; stick to top
;@    random(Y,1,6)
; LEAVING!
;@    exitevent()
;@  endif()
;@  ifgreaterthan(R,100) ; hug right side
;@    random(X,490,496)
;@  else() ; hug left side
;@    random(X,1,6)
;@  endif()

Brilliant! Works like a charm! The fourth label requires the deleting of all in() events, including those of the progress objects, because rather than waiting for a puzzle piece to collide with a hidden dot, FKiSS3 can check, the moment you release an object, what its position is, and can compare that position to what it should be, using variables. There is already a release() event for each individual puzzle piece cel - to unmap its dropshadow equivalent - but now there is going to be a separate release() event for the object number, because this concerns the position on the screen, which is a property of objects. The release() event will use the FKiSS3 commands letobjectx() and letobjecty(), while the label uses two of its mathematical functions: add() and sub().

;@release(#1)
; set P to object number
;@  let(P,1)
; set P0 and P1 to the correct values (the initial values from the coordinates block) for this object
;@  let(P0,90) let(P1,90)
; store current coordinates of this object in P2 and P3
;@  letobjectx(P2,#1) letobjecty(P3,#1)
; label checks if piece close enough to right position
;@  gosub(4)

The gosub() implies there's more code below, but I want to show how label(4) decides whether or not the piece "fits". For both the x and y axis, I want the difference between what the coordinate is, and what it should be, and to add those two differences; if the total difference is below a certain threshhold (and that is much more accurate than having a puzzle piece collide with a dot) it counts as a fit. The difference has to be a positive number, so I subtract the smaller value from the larger one.

;@label(4)
; Check if puzzle piece placed: find positive difference on x and y axis
;@  ifgreaterthan(P0,P2) ; desired and actual x coordinate
;@    sub(P4,P0,P2)
;@  else()
;@    sub(P4,P2,P0)
;@  endif()
;@  ifgreaterthan(P1,P3) ; ditto for y coordinate
;@    sub(P5,P1,P3)
;@  else()
;@    sub(P5,P3,P1)
;@  endif()
; Find total difference, reset P and quit if too large
;@  add(P6,P5,P4)
;@  ifgreaterthan(P6,10)
;@    let(P,0) ; to let piece know it doesn't fit
;@    exitevent()

There's that exitevent() again: if the piece doesn't fit, variable P, which contained the released piece's object number, is made empty, and the label is told to quit (because P does not immediately become empty) and, having been called through gosub(), gives back control to the release() event. But we're in the middle of an if-else-end block here, so let's continue:

;@  else()
; Piece is placed: play sound, move bar, augment total in variable D
;@    sound("snapto.wav") add(D,D,1) move(#128,10,0)
;@  endif()
; Is the puzzle finished? Then unmap buttons and progress bars, map finish screen.
;@  ifequal(D,54)
;@    unmap(#128) unmap(#129) unmap(#130) map(#131)
;@  endif()

Instead of having to write "play a sound" under each collision event, I now need to do it only once, under label(4); that makes it much easier to write code for, say, a Mute button. I also don't have to wait for two progress objects to collide, because in this same label, variable D (for "done") keeps count of the number of pieces placed, and when it reaches the maximum of 54, does the stuff that needs to happen when the puzzle is completed. But the object release() event still has stuff to do; if P still has the same value as its object number after calling label(4), the object has to snap to the right coordinates (stored in P0 and P1), and become fixed and ghosted (using the G variable set in the ghost-testing stage).

[continued code under "release(#1)"]
; is variable still equal to object#? then snap-to, fix and ghost
;@  ifequal(P,1)
;@    moveto(#1,P0,P1) setfix(#1,32767) ghost(#1,G)
;@  endif()
[repeat this whole block for objects #2 to #54, setting P to object number and P0/P1 to object's initial coordinates]

Why ghost the piece once it is placed? Well, now that the puzzle's progress is counted through clicking and releasing rather than via collision, clicking repeatedly on a piece that is already in the right place can raise the value of variable D, so the puzzle may act as if finished when it is only half done. Since the piece is fixed, I could replace release() by drop() which doesn't work for maximally fixed objects, but firstly, not every viewer agrees on what is the right value for "maximally fixed"; secondly, not even all viewers agree on this use of drop(); and thirdly, ghosting has another advantage: I can now fish lost pieces out from under the ghosted ones. Even if I can't see the pieces that have slipped under others, clicking on where I think they are will raise their visible, drop-shadow version if I guessed correctly, and allow me to drag them to the side.

I put label(5) off to the last moment in order to have a good think about it. If the settings screen is visible, all pieces must be ghosted, whether they are already fixed in place or not. That is a relatively short block of code, so I should first check whether the settings screen is mapped. But if I test for that first, the "else" block - what to do if the settings screen is not mapped - will be huge, because if the settings are unmapped after clicking "Return", I must only unghost the unfixed and therefore unplaced pieces, which means testing the fix value of 54 objects, just like in label(2). But I can avoid using else() at all by putting exitevent() inside the if-end block, so that anything under the block is only carried out if anything inside the block is not.

In this case, I use letmapped() instead of setting a variable whenever something maps or unmaps the setting screen, because I don't care what mapped or unmapped it, I just want a simple yes/no answer. If it is unmapped, I also don't care whether the player clicked "Return" or "Restart", because if the puzzle was restarted, the puzzle pieces have already been unfixed by label(0) before it issued "gosub(5)", so the rule that only unfixed pieces must be unghosted, still applies. At this point I am glad to have put the "right" (ie. "works in the current viewer") values in variables G and U just after the set was loaded, so I can now use ghost() with these variables.

;@label(5)
; Ghost all pieces if settings screen is showing.
;@  letmapped(G0,"setting2.cel") ; put a value in variable G0
;@  ifequal(G0,1) ; yes, it is mapped
;@    ghost(#1,G) ghost(#2,G) ghost(#3,G) ghost(#4,G) ghost(#5,G) ghost(#6,G) ghost(#7,G) ghost(#8,G) ghost(#9,G)
;@    ghost(#10,G) ghost(#11,G) ghost(#12,G) ghost(#13,G) ghost(#14,G) ghost(#15,G) ghost(#16,G) ghost(#17,G) ghost(#18,G)
;@    ghost(#19,G) ghost(#20,G) ghost(#21,G) ghost(#22,G) ghost(#23,G) ghost(#24,G) ghost(#25,G) ghost(#26,G) ghost(#27,G)
;@    ghost(#28,G) ghost(#29,G) ghost(#30,G) ghost(#31,G) ghost(#32,G) ghost(#33,G) ghost(#34,G) ghost(#35,G) ghost(#36,G)
;@    ghost(#37,G) ghost(#38,G) ghost(#39,G) ghost(#40,G) ghost(#41,G) ghost(#42,G) ghost(#43,G) ghost(#44,G) ghost(#45,G)
;@    ghost(#46,G) ghost(#47,G) ghost(#48,G) ghost(#49,G) ghost(#50,G) ghost(#51,G) ghost(#52,G) ghost(#53,G) ghost(#54,G)
;@    exitevent() ; no else() needed
;@  endif
; Else unghost unfixed pieces
;@  letfix(F0,#1) ; put fix value in variable F0
;@  ifequal(F0,0) ; if fix value is 0
;@    ghost(#1,U) ; use ghost() with the unghosting value
;@  endif()
[repeat the unghosting code for every object up to #54.]

If you made it this far through the tutorial, congratulations, you may be a programmer. If you want to see the full code, it's in the finished set at the bottom of this tutorial. The last adjustment I had to make was to delete objects #74 to #127 from the object definition block, remove their coordinates from under set 0 in the coordinates block (I had to count carefully to delete the right ones) and under the other sets (I just deleted the last 54 asterisks under set 1, and copied its total block to sets 2 to 9), renumber objects #128-#148 to #74-#94 in the trimmed-down object definition block, and change those object numbers to their new numbers wherever they occur in the FKiSS code block.

Making a jigsaw puzzle KiSS set - FKiSS4 version

FKiSS4 is a continuation of the FKiSS3 development that started with Chad Randall and the later version of PlayFKiSS, and then continued for a while after I left the mailing list. In short, there are two phases to FKiSS4, and the last PlayFKiSS supports only the first phase (and not always well, as it was a beta). The two players that support FKiSS4 all the way are the java viewer UltraKiSS by William Miles, and !PlayKiSS for RiscOS by Nick Roberts, who wrote up a comprehensive FKiSS reference page that I'm very grateful for right now.

When it comes to reducing the amount of code, FKiSS4 is a godsend, starting in the object declaration block, with new tags comparable to "%t", the old transparency tag. The most useful of these is the %u tag that has cels start out unmapped, so I don't have to unmap the more than 150 drop-shadow puzzle pieces under initialize(). The second most useful is the %g tag, ghosting cels at load time with no worries over whether it should be "ghost(x,1)" or "ghost(x,0)". (I would still need the ghost-testing cels for determining how to ghost and unghost on the fly, of course. FKiSS4-capable viewers should get it right, but given that ghost() was used in sets written for PlayFKiSS, UltraKiSS has a setting to reverse the way ghost() is parsed, for the sake of compatibility.) Finally, there are the incidentally very useful %x and %y tags, giving cels an offset within an object, which deserve an illustrated explanation.

Suppose there is a KiSS set of an anime character, skipping with hands pointed outwards in that typical sassy anime way, and the anime character has a winter outfit that includes a pair of fluffy red mittens. Because the base doll is not facing the front but half turned away, to keep up the illusion of three dimensions, the right mitten has to pass under the winter coat, which has to pass under the left mitten. To this end, the mittens must be two different cels. They should be also be one object, always stay at the same distance from each other, and move together when dragged.

Cels are normally cropped; there is no more empty space around them than strictly necessary. Their image height and width is like an invisible box, the upper left corner of which determines their position. But if two or more cels need to maintain a certain position relative to each other, the space around each of them needs to be a big box that they all fit in. Any space downwards or to the right can be trimmed, but the upper left points of the boxes surrounding them must be at the same coordinates. That is why the left mitten has a lot of empty space at the top, while the right mitten has a large stretch of empty space to the left. This space acts as an offset point, pushing the left mitten down and the right mitten to the side.


Mitten cels showing their image size.

The offset tags in FKiSS4 would allow both mitten cels to be cropped right to their outline, because the job of that empty space - keeping the mittens in a certain position relative to each other - would be done by the tags, which would tell the cels to always stay the specified distance away from their shared upper left corner.

Why would I need separate offset tags, when the cel's image size itself acts like an offset point? Because a big image size, even if most of it is empty space, can mean a big filesize, especially if the image is a CKiSS cel. And offset tags are great for, wait for it, tiling! Consider this: to cut down on filesize, I have a background cel that is smaller than the background, and must be projected on that background several times, one projection under the other, to completely cover it. Because the projections have different positions, I have to assign them different object numbers. But with these offset tags, I can define the same cel multiple times in the same object, giving the cel instances different offset points to fit them under each other, and still just use one object number, with one set of coordinates, and manipulate the object with one single command. And if the command I want to use requires the cel's name, I can also assign all instances of this very ambiguous cel to the same cel group, and use the cel group name instead. (More on cel groups below.)

There is no better example for this than the ghost test object, which in the previous "puzzfk3.cnf" was sixteen objects for the purpose of tiling, but which, in the newly started "puzzfk4.cnf", will be just object #0, an object number it will share with any other large, static cels, like the backgrounds, and whose coordinates in the coordinates block of set 0 will be 0,0. As the object number is not unique to this collection of cel instances, these cel instances will be assigned the cel group name "!GhostTest", consisting of sixteen times the same cel, the first one in the top left corner, the rest stacked beside and below it at the distances set by %x and/or %y. (Where %x or %y are not specified, they are the default value, ie. 0.)

; warning
#0.999 warning5.cel     *0 :0                   ;%x180 %y180 !Warning
#0.999 warningt.cel     *0 :0                   ;%t255 %x180 %y295 !Intro
;
; ghost test tiles, almost transparent
#0.999 ghostest.cel     *0 :0                   ;%t254 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x145 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x290 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x435 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %y120 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x145 %y120 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x290 %y120 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x435 %y120 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %y240 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x145 %y240 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x290 %y240 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x435 %y240 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %y360 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x145 %y360 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x290 %y360 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x435 %y360 !GhostTest

Cel group names always begin with an exclamation mark, don't need quotes, and ideally (in practice, this can vary) can be used by any command that accepts a cel name. Lumping a lot of cels together under a name and applying commands to that name really cuts down on the volume of code. Instances of an ambiguous cel can also be referred to separately by giving them each their own cel group name, where the "group" is just one cel instance. Both uses of cel groups are not mutually exclusive, as a cel instance can be assigned to more than one group. Finally, cel groups are needed for frames, a quick method to cycle through mapped/unmapped states; this will be used for the settings panel and the puzzle pieces. In all, cel groups are a versatile tool for writing compact code.

Cel groups can even supplement version(). I did mention that the later version of PlayFKiSS doesn't support all of FKiSS4? To my knowledge, it does not support frames and cel groups. That is why the above example includes the two warning cels: the top cel has its own cel group, "!Warning". In this later PlayFKiSS version, version(5) will trigger, but as this cnf uses cel groups, the warning cel should stay up, and not be unmapped. That is why, under version(), the unmap() event will not use "warning5.cel", but "!Warning". (The transparency warning is invisible anyway, unless the viewer doesn't support transparency, and will be unmapped along with the rest of cel group "!Intro.") The FKiSS2.1 conditional timer ifmapped() will, if warning5.cel is still mapped (ie. the viewer doesn't understand cel groups), trigger alarm(2) and close the set.

The ghost test looks much like in the FKiSS3 cnf, but I want to use the cel group name instead of the original sixteen object numbers. It should be possible, according to the FKiSS reference page, to use "ghost(GhostTest,1)", but UltraKiSS refuses to accept a cel group name with ghost(), so I must use its object number, #0. Not a big deal, as the other parts of object #0 are either ghosted via the %g tag or unmapped at this point. The important thing is that !GhostTest can be used with press() and unmap().

;@initialize()
[all the cels that were unmapped here have the %u tag, so initialize() is empty now]
[begin, alarm(1)]
;@version(5)
;@  timer(2,0) ; no need to quit
;@  timer(1,1000) ; forestall alarm2 (if version() is processed before begin())
;@  unmap(!Warning) ; only if viewer supports cel groups
;@  ifmapped("warning5.cel",2,2000) ; else, quitting alarm
;@  timer(0,3000) ; unmap intro cel in 3 seconds
;@  let(G,1)  ; the ghosting variable
;@  ghost(#0,1) ; #0 should have been !GhostTest, but alas
;
;@press(!GhostTest)  ; this proves that ghost(x,1) didn't work
;@  let(G,0)
;@  ghost(#0,0) ; again, #0 should have been !GhostTest

; Press setting: (re)start
;@press("button3a.cel") ; start button
; Now that G is known, set U
;@  ifequal(G,1)
;@    let(U,0)
;@  else
;@    let(U,1)
;@  endif
;@  unmap(!GhostTest)
[non-interactive cels that were ghosted here now have the %g tag]
[but two buttons, being in object #0, are unintentionally also ghosted]
; Unghost two buttons which are part of object #0
;@  ghost("setbtn.cel",U) ghost("movebtn.cel",U)
;@  let(S,2) goto(0)

Due to offset tags and cel group names, all object numbers that have coordinates 0,0 have been changed to object #0. All object numbers that don't have coordinates 0,0, but that are connected with objects that do, such as the lower tiles of tiled backgrounds, have been changed to object #0 with offsets. All object numbers at coordinates 90,90, or connected to the settings screen, will become object #55. The puzzle pieces retain object numbers #1 to #54, of course, the finishing screen becomes object #56, and there will be a new progress bar, object #57. The extra object coordinates, or their asterisks in the empty sets, can be deleted. (No example code for the coordinates, it should be clear how to do this by now.) Like the ghost test cels in cel group "!GhostTest", all intro parts of object #0 are assigned to cel group "!Intro" for quick unmapping.

In the example below, note the %u tag for cels that would otherwise be unmapped under initialize(). Also, note that anything after the semicolon that doesn't start with "!" or "%" is treated as a comment, so no need to put a second semicolon before the actual comments. (Except when using frames, which I'll get to when declaring the settings screen cels.)

; intro screen
#0.999 example.cel      *0 :0                   ;%x90 %y90 !Intro (inside intro screen)
#0.999 intro.cel        *0 :0                   ;!Intro (intro screen title)
#0.999 introb.cel       *0 :0                   ;%y90 !Intro (intro screen left)
#0.999 introb.cel       *0 :0                   ;%x490 %y90 !Intro (intro screen right)
#0.999 intros1.cel      *0 :0                   ;%y390 !Intro (intro screen subscript)
; finished screen
#56.999 finishsm.cel    *0 :0                   ;%u
; Settings and scatter buttons
#0.999 setbtn.cel       *0 :0                   ;%u !Buttons (settings button)
#0.999 movebtn.cel      *0 :0                   ;%u !Buttons (button to move loose pieces off field)

The settings screen gets two extra buttons, "Mute" and "Edges" (unmaps all except the edge pieces) and becomes a bit bigger; I use the same settings screen cel, but add a bar below it for the new stuff.


Two new settings.

And now for frames! The settings screen buttons, backgrounds, underlays and puzzle pieces also get cel group names, but the buttons and any cels they affect will additionally get frames to replace the "ifmapped()" tests in FKiSS2.1, and cooperate with the button variables in FKiSS3. The syntax for frames is "!CelgroupName : n n n etc" where each "n" is a frame number. After the last frame number, if I have comments to add, I do put them after a new semicolon.

Cels in a cel group with no frame number are always mapped; otherwise, they are mapped only when their "frame" (think of a frame in a film reel) is "active" (like the film frame currently in front of the projector). Frames start at 0, and I assume frame 0 is active (the cels assigned to that frame are mapped) by default. (At least for UltraKiSS, I later discover this assumption is incorrect.) In the example below, all settings cels are in the frameless cel group "!Settings", but the button cels are also in the first, second or third frame of cel groups like "!BackgroundBtn", "!PieceBtn" etc. In the object declaration block, the lines for the new cels are added above that of the existing settings cel, the lower part of which is almost transparent (a way to cover pieces and stop them from being clicked in pre-ghost() versions), but would discolour any cels below it.

; added to bottom of settings panel
#55.999  button7b.cel   *0 :0                   ;%y222 !Settings !MuteBtn : 0 ;sound on
#55.999  button7a.cel   *0 :0                   ;%y222 !Settings !MuteBtn : 1 ;sound off
#55.999  button8a.cel   *0 :0                   ;%x333 %y222 !Settings !EdgesBtn : 0 ;show all pieces
#55.999  button8b.cel   *0 :0                   ;%x333 %y222 !Settings !EdgesBtn : 3 ;unmap non-edge pieces
; NOTE EdgesBtn has frame 0 and 3, instead of 0 and 1!
#55.999  setting3.cel   *0 :0                   ;%y222 !Settings
; Settings panel
#55.999  button4.cel    *0 :0                   ;%u %x333 !Settings Return button
#55.999  button4a.cel   *0 :0                   ;%x333 !Settings blank button
#55.999  button5a.cel   *0 :0                   ;%x333 !Settings !BackgroundBtn : 0 ;green bg button
#55.999  button5b.cel   *0 :0                   ;%x333 !Settings !BackgroundBtn : 1 ;blue bg button
#55.999  button5c.cel   *0 :0                   ;%x333 !Settings !BackgroundBtn : 2 ;red bg button
#55.999  button6c.cel   *0 :0                   ;%x333 !Settings !ProgressBtn : 0 ;progbar on button
#55.999  button6d.cel   *0 :0                   ;%x333 !Settings !ProgressBtn : 1 ;progbar off button
#55.999  button1a.cel   *0 :0                   ;!Settings !PieceBtn : 0 ;puzzle piece button flat
#55.999  button1b.cel   *0 :0                   ;!Settings !PieceBtn : 1 ;puzzle piece button outline
#55.999  button1c.cel   *0 :0                   ;!Settings !PieceBtn : 2 ;puzzle piece button bevel
#55.999  button2a.cel   *0 :0                   ;!Settings !UnderlayBtn : 0 ;underlay button picture
#55.999  button2b.cel   *0 :0                   ;!Settings !UnderlayBtn : 1 ;underlay button pieces
#55.999  button2d.cel   *0 :0                   ;!Settings !UnderlayBtn : 2 ;underlay button none
#55.999  button3b.cel   *0 :0                   ;%u !Settings REstart button on top
#55.999  button3a.cel   *0 :0                   ;!Settings start button
#55.999  setting2.cel   *0 :0                   ;!Settings

Skipping the puzzle pieces for now, to go straight to the bottom of the object declaration block: the underlays and backgrounds are now object #55 and object #0, with offsets as needed. Meant to be unclickable, they all have ghost tags. The frame number each background is assigned to, is the same as the frame number of the corresponding button in !BackgroundBtn. The underlays are in cel group !Underlay, with two frames; the corresponding button cel group, !UnderlayBtn, has three frames, but setting !Underlay to a frame that it has no cels in, makes it unmap all its cels, which in this case is exactly what should happen.

#55.999 example.cel     *0 :0                   ;%t128 %g !Underlay : 0 ;the base image
#55.999 slicerbw.cel    *0 :0                   ;%t128 %g %u !Underlay : 1 ;puzzle shapes
#0.999 green.cel        *0 :0                   ;%g !Background : 0 ;green maple screen
#0.999 greentl.cel      *0 :0                   ;%g %y80 !Background : 0 ;green tiling 4x
#0.999 greentl.cel      *0 :0                   ;%g %y180 !Background : 0
#0.999 greentl.cel      *0 :0                   ;%g %y280 !Background : 0
#0.999 greentl.cel      *0 :0                   ;%g %y380 !Background : 0
#0.999 blue.cel         *0 :0                   ;%g !Background : 1 ;blue gritty screen
#0.999 bluetl.cel       *0 :0                   ;%g %y96 !Background : 1 ;blue tiling 3x
#0.999 bluetl.cel       *0 :0                   ;%g %y224 !Background : 1
#0.999 bluetl.cel       *0 :0                   ;%g %y352 !Background : 1
#0.999 red.cel          *0 :0                   ;%g !Background : 2 ;red leather screen
#0.999 redtl.cel        *0 :0                   ;%g %y96 !Background : 2 ;red tiling 3x
#0.999 redtl.cel        *0 :0                   ;%g %y224 !Background : 2
#0.999 redtl.cel        *0 :0                   ;%g %y352 !Background : 2

Under version(5), alarm(0) is set to unmap the intro screen and bring up the settings. Since the settings screen is mapped but hidden under the intro screen, this can be done with the beautifully simple "unmap(!Intro)". But I notice that some settings buttons are gone when the settings screen first opens, and when I click the settings buttons (see code in next paragraph) they don't react at first. I use letframe(), and assume that because these buttons are in a frameless cel group followed by a framed cel group, their frame is not 0 by default, and needs to be set to 0 before the buttons are used, with setframe():

;@alarm(0)
; Set frame to 0 so buttons respond at the first click
;@  setframe(!PieceBtn,0) setframe(!UnderlayBtn,0) setframe(!BackgroundBtn,0) setframe(!ProgressBtn,0)
;@  setframe (!MuteBtn,0) setframe (!EdgesBtn,0)
; Unmap intro screen
;@  unmap(!Intro)

The code for pressing the buttons now works with frames, so, although I still use the C[number] variables, I don't have to spell things out for every individual button cel any more. Note that for backgrounds and puzzle pieces, the number of frames in the group and its button group is the same; for underlays, it's one less; and the progress bar, which no longer has to be transparent and has been redesigned, doesn't use frames to (un)map itself, only its button does. (It does use frames, and variable D, to display its progress, on which more later.)

; Press setting: puzzle piece button
;@press(!PieceBtn) ; works for all three buttons in this group
;@  letframe(C,!PieceBtn) ; but which button/frame did I click on?
;@  ifequal(C,2)
;@    let(C,0) ; back to zero
;@  else()
;@    add(C,C,1) ; raise frame number by one
;@  endif()
;@  setframe(!PieceBtn,C) ; show next button
;@  setframe(!Pieces,C) ; show corresponding puzzle piece style
;
; Press setting: underlay button
;@press(!UnderlayBtn)
;@  letframe(C1,!UnderlayBtn)
;@  ifequal(C1,2)
;@    let(C1,0) ; back to zero
;@  else()
;@    add(C1,C1,1) ; raise frame number by one
;@  endif()
;@  setframe(!UnderlayBtn,C1)
;@  setframe(!Underlay,C1) ; !Underlay has only frames 0/1, so frame 2 will unmap it

[(re)start and return button code...]

; Press setting: backgrounds button
;@press(!BackgroundBtn)
;@  letframe(C2,!BackgroundBtn)
; Go to next (or back to first) button
;@  ifequal(C2,2)
;@    let(C2,0) ; back to zero
;@  else()
;@    add(C2,C2,1) ; raise frame number by one
;@  endif()
; Use frames to change to next button, and to next background.
;@  setframe(!BackgroundBtn,C2)
;@  setframe(!Background,C2)
;
; Press setting: progress bar visible
; Button has frames, bar is (un)mapped
;@press(!ProgressBtn)
;@  letframe(C3,!ProgressBtn)
;@  ifequal(C3,0)
;@    let(C3,1)
;@    setframe(!ProgressBar,55) ; unmap by setting to undefined frame
;@  else()
;@    let(C3,0)
;@    setframe(!ProgressBar,D) ; map to current progress
;@  endif()
;@  setframe(!ProgressBtn,C3)

The new Mute and Edge buttons, like the progress bar button, only have frames to quickly display the right button when the settings screen is opened; their frame numbers are stored in variables that, since FKiSS4 allows longer filenames, are called SoundOff and EdgesOnly.

; Press setting: mute
;@press(!MuteBtn)
;@  letframe(SoundOff,!MuteBtn)
;@  ifequal(SoundOff,0)
;@    let(SoundOff,1)
;@  else()
;@    let(SoundOff,0)
;@  endif()
;@  setframe(!MuteBtn,Soundoff)

Unlike the Mute button which simply stores a variable to later decide whether or not to play the snap-to sound, the Edge button is meant to unmap or remap all puzzle pieces that are not edge pieces (as it can be easier to lay out the edge of a puzzle first). But all puzzle pieces already display one of three frames, and, if unmapped and then remapped, might reappear showing the wrong frame, or something. To avoid that mess and the reams of code for checking and setting frames, I will simply add three frames for edge pieces. To take for example the object declarations of two puzzle pieces, one an edge piece, the other not:

; Puzzle pieces
#1  1-1.cel             *0 :0                   ;!Pieces : 0 3 ; flat puzzle pieces
#11 2-2.cel             *0 :0                   ;!Pieces : 0

#1  1-1o.cel            *0 :0                   ;!Pieces : 1 4 ; outlined puzzle pieces
#11 2-2o.cel            *0 :0                   ;!Pieces : 1

#1  1-1b.cel            *0 :0                   ;!Pieces : 2 5 ; bevelled puzzle pieces
#11 2-2b.cel            *0 :0                   ;!Pieces : 2

The difference between the two pieces is that the edge piece has two frames, while the other has one. At frames 0 to 2, both pieces will be mapped; at 3 to 5, the non-edge piece will vanish. Because the button for puzzle piece styles is meant to have the same frame number as cel group !Pieces, in the higher frame range, the button will also disappear - unless the frame number of !Pieces is adjusted using the separate variable EdgesOnly. The code for the Edge button - which demonstrates why, in the object declaration block, the button's frames are 0 and 3 - adds its value to C, the current piece style button frame, in TempVar, a new variable used anyplace where a value has to be calculated on the spot, and TempVar sets the frame number for !Pieces.

; Press setting: show edges only
;@press(!EdgesBtn)
;@  letframe(EdgesOnly,!EdgesBtn)
;@  ifequal(EdgesOnly,0)
;@    let(EdgesOnly,3) ; Note: 3!!
;@  else()
;@    let(EdgesOnly,0)
;@  endif()
;@  setframe(!EdgesBtn,EdgesOnly)
;@  add(TempVar,C,EdgesOnly) ; EdgesOnly will add either 3 or 0
;@  setframe(!Pieces,TempVar)

The last two lines of the edge button code replace the last line of the piece style button code, so that it doesn't map any pieces that should stay unmapped:

; Press setting: puzzle piece button
;@press(!PieceBtn)
;@  letframe(C,!PieceBtn)
;@  ifequal(C,2)
;@    let(C,0) ; back to zero
;@  else()
;@    add(C,C,1) ; raise frame number by one
;@  endif()
;@  setframe(!PieceBtn,C)
;@  add(TempVar,C,EdgesOnly) ; EdgesOnly will add either 3 or 0
;@  setframe(!Pieces,TempVar)

Giving the buttons frames makes it easier to map them and the cels they affect, but also helps to show the right buttons when the settings screen appears after pressing the "finished" message or the pink button in the top left corner. In the FKiSS3 cnf, the button states were already stored in variables; now, in label(1), rather than using if-blocks to match a value to a cel-mapping action, the variables can be directly used to set the buttons' frames.

; Press button: settings
; NOTE: these buttons are now part of object #0, so use their cel group !Buttons to unmap them.
;@press("setbtn.cel")
;@  unmap(!Buttons) goto(1)

[...]

; Press finish screen: unmap filled progress bar, open settings WITHOUT RETURN BUTTON
; NOTE: it's not worth fiddling with frames just for the return button, unmap it directly.
;@press(#56)
;@  unmap(#56) setframe(!ProgressBar,55) gosub(1) unmap("button4.cel")

[...]

;@label(1)
; First, map whole settings screen
;@  map(!Settings)
; Show the right puzzle piece button
;@  setframe(!PieceBtn,C)
; Show the right underlay button
;@  setframe(!UnderlayBtn,C1)
; Show the right background button
;@  setframe(!BackgroundBtn,C2)
; Show the right progress bar button
;@  setframe(!ProgressBtn,C3)
; Show the right mute button
;@  setframe(!MuteBtn,SoundOff)
; Show the right edge button
;@  setframe(!EdgesBtn,EdgesOnly)
; Finally, ghost puzzle pieces
;@  goto(5)

Before I move on to label 5 and FKiSS4's version of the do-while loop, I'll finish the object declaration block with the dropshadow pieces and the new progress bar, the latter of which is both brilliant and a piece of work. The dropshadow pieces are always unmapped, except when puzzle pieces are clicked on. Having three styles, they are also given three frames, and three extra if they are edge pieces, but instead of being one large cel group, they have an individual cel group per object number. I've given a short example of two puzzle pieces in the object declaration block earlier, below is the same for their dropshadow equivalent:

; Drop shadow pieces
#1  1-1ds.cel           *0 :0                   ;!Ds01 : 1 4 ; flat puzzle pieces
#11 2-2ds.cel           *0 :0                   ;!Ds11 : 1

#1  1-1ods.cel          *0 :0                   ;!Ds01 : 2 3 ; outlined puzzle pieces
#11 2-2ods.cel          *0 :0                   ;!Ds11 : 2

#1  1-1bds.cel          *0 :0                   ;!Ds01 : 3 6 ; bevelled puzzle pieces
#11 2-2bds.cel          *0 :0                   ;!Ds11 : 3

The main difference between the puzzle pieces and their dropshadow twins is that the latter have no frame 0. Instead of having to code catch() and release() events for every puzzle piece cel, I now have one catch() and one release() event for "!Pieces", and while either event has to cover 54 possibilities, at least I can use setframe() instead of typing out every possible celname.

To map the right dropshadow piece, the frame to set the dropshadow cel group to is put in TempVar; beware, setframe() uses the cel group name as the first parameter, but in letframe() the cel group name comes second. The FKiSS4 action letcatch() assigns the number of an object being dragged to a variable, in this case ObjNo. Having filled these two variables, I count down the values of ObjNo from 1 to 54, to set the right frame for the right cel group. Luckily for me, FKiSS4 has something that FKiSS3 sorely lacked: the condition elseifequal(), allowing me to work my way down the line without nested if-blocks and exitevent() solutions. The example below is for objects 1 and 11, rather than the full 54.

; Lift pieces when selecting them
;@catch(!Pieces)
;@  letframe(TempVar,!Pieces) ; get style from cel group
;@  add(TempVar,TempVar,1) ; add one for dropshadow style group
;@  letcatch(ObjNo) ; put dragged object number in variable ObjNo
;@  ifequal(ObjNo,1)
;@    setframe(!Ds01,TempVar)
;@  elseifequal(ObjNo,11) ; long live elseifequal()
;@    setframe(!Ds11,TempVar)
;@  endif()

When using UltraKiSS in PlayFKiSS compatibility mode, an error pops up at load time saying that catch() needs an object or cel name, presumably due to the compatibility option "Enable Catch events on Fixed or Sticky objects", which means that catch() may now have to deal with non-zero fix values, and fix value is attached to a cel-object combination, but can't be deduced from the cel group name. Despite the error, the set works anyway, presumably because every part of !Pieces is unfixed until placed, at which point it becomes ghosted, and catch() can't affect it anyway. Since ghosting the pieces has made the difference between catch() and press() irrelevant, I change "catch(!Pieces)" to "press(!Pieces)".

Unmapping the dropshadow piece is similar, except now I only need ObjNo, as the drop shadow cel group will always be set to frame 0, to unmap all cels in it. This is why I made the drop shadow frames one higher than the corresponding puzzle piece frames; for the drop shadow pieces, I didn't want the frames to start at 0. ObjNo is still filled with the same object number I just clicked on, so I'm not using letcatch() here; since release() means no more dragging, letcatch() might not even work, anyway. I do, however, set ObjNo to 0 when I'm done.

;@release(!Pieces)
;@  ifequal(ObjNo,1)
;@    setframe(!Ds01,0) ; frame 0 unmaps all, this is why I moved the frames up
;@  elseifequal(ObjNo,11)
;@    setframe(!Ds11,0)
;@  endif()
;@  let(ObjNo,0) ; empty letcatch var after release

While I'm on the subject of release(), in the FKiSS3 cnf I use this with celnames to unmap drop shadow pieces, and with object numbers to 1. check if a puzzle piece is within 10 pixels of being placed correctly, 2. make it snap to its position and ghost it, incrementing the progress variable D, and 3. play a sound and adjust the progress bar. For the object release(), I fill five variables and call label(4) for the calculation, so I don't have to repeat it for every object. The five variables are: P for object number (voided under label 4 if the piece is not well-placed), P0 and P1 (filled in by hand) with the object's correct coordinates, and P2 and P3 (filled by letobjectx() and letobjecty()) with the object's current coordinates.

But in FKiSS4, I can use a variable, instead of an object number, in commands that take an object. And instead of supplying the correct coordinates, I can use letinitx() and letinity() that put an object's initial coordinates in a variable, these initial coordinates also being the correct ones! So, immediately under "release(!Pieces)", above all the setframe() checks for drop shadow pieces, I use the already filled ObjNo instead of an object number in the following block of code:

; FIRST check placement
; correct values
;@  letinitx(P0,ObjNo) letinity(P1,ObjNo)
; store current coordinates
;@  letobjectx(P2,ObjNo) letobjecty(P3,ObjNo)
; Check if puzzle piece placed: find positive difference on x and y axis
;@  ifgreaterthan(P0,P2)
;@    sub(P4,P0,P2)
;@  else()
;@    sub(P4,P2,P0)
;@  endif()
;@  ifgreaterthan(P1,P3)
;@    sub(P5,P1,P3)
;@  else()
;@    sub(P5,P3,P1)
;@  endif()
; Find total difference, place and ghost piece if small enough
;@  add(P6,P5,P4)
;@  iflessthan(P6,11)
;@    moveto(ObjNo,P0,P1) setfix(ObjNo,32767) ghost(ObjNo,G) gosub(4)
;@  endif()

(Why, if fix value becomes moot because the puzzle piece is ghosted, do I still fix it? Because I need to be able to check if a piece is already in its right place for the settings and scatter buttons, and while there is a letfix() command to check fix value, there is no letghost() equivalent.)

The code above contains the calculation formerly under label(4), and could take all code from label(4), but I've decided to keep settings-related code under that separate label, for clarity. A sound is now only played, and progress only shown, if the settings allow it:

;@label(4)
; Piece is placed: play sound, show progress on bar, augment total in variable D
;@  ifequal(SoundOff,0)
;@    sound("snapto.wav")
;@  endif()
;@  add(D,D,1)
;@  ifequal(C3,0)
;@     setframe(!ProgressBar,D)
;@  endif()
; Is the puzzle finished? Then unmap buttons (but leave filled progress bar), and map finish screen.
;@  ifequal(D,54)
;@    unmap(!Buttons) map(#56)
;@  endif()

But, how could I forget, in FKiSS4 gosub() and goto() can take a variable instead of a number. Since press() and release() essentially set a cel group to a frame number, I can, instead of the endless procession of elseifequal() commands under both these events, define 54 labels, one for each piece; set variable ToFrame for these labels to use; and call these labels using a combination of ObjNo and TempVar (I can't just use ObjNo, as the labels can't start at 1, labels 1-5 already being in use). Clicking on pieces now executes the following:

; Lift pieces when selecting them
;@press(!Pieces)
;@  letframe(ToFrame,!Pieces) ; get style from cel group
;@  add(ToFrame,ToFrame,1) ; add one for dropshadow style group
;@  letcatch(ObjNo) ; put dragged object number in variable ObjNo
;@  add(TempVar,ObjNo,5) ; use label(6) to label(59) for objects 1-54
;@  goto(TempVar)
; Aaaaand we're done!

While releasing them does this:

; Unmap dropshadows and check placement
;@release(!Pieces)
; FIRST check placement
   [etc]
;@  iflessthan(P6,11)
;@    moveto(ObjNo,P0,P1) setfix(ObjNo,32767) ghost(ObjNo,G) gosub(4)
;@  endif()
; Call label to unmap dropshadow piece
;@  add(TempVar,ObjNo,5) let(ToFrame,0) let(ObjNo,0)
;@  goto(TempVar)

Below, a shortened example of the labels being called, each with just one action. (According to the FKiSS reference page, since labels and alarms can have the same names as variables now, I could also define a label with the literal name "TempVar" which would be called instead, ignoring all numbered labels. Which would defeat the purpose in this case.) To keep the cnf organized, the new labels go under label(5).

;@ label(6)
;@  setframe(!Ds01,ToFrame)
  [...]
;@ label(16)
;@  setframe(!Ds11,ToFrame)
  [...]
;@ label(59)
;@  setframe(!Ds54,ToFrame)

Now that I've condensed a lot of code under "press(!Pieces)" and "release(!Pieces)", it's time to explain how my new and improved progress bar works. It is now object #57, yet in the code examples above, it is always referred to by its cel group name, as "setframe(!ProgressBar,55)" or "setframe(!ProgressBar,D)". The first action unmaps the object, as its highest frame number is only 54, while the second sets the frame to the same number as the pieces placed. No longer a rectangle moving towards another rectangle, object #57 is an empty base (frame 0) with a single bar cel repeated across the base 54 times, its offset increasing 6 pixels for every frame. Visually, the bar seems to fill up, as every time the frame number is raised, another bar appears.

The amount of code in the object declaration block is enormous. It starts with a simple 1-frame line at the top:

[this one appears when the 54th piece is put in place]
#57.999  progbar.cel    *0 :0                   ;%g %x326 %y8 !ProgressBar : 54

But the "progbar.cel" line occurs 54 times, each time adding previous frames to its own, as otherwise the progress bar would appear to be a line moving forward instead of a bar filling up. A few lines down, it looks like this:

#57.999  progbar.cel    *0 :0                   ;%g %x296 %y8 !ProgressBar : 49 50 51 52 53 54
and still lower, like this:
#57.999  progbar.cel    *0 :0                   ;%g %x188 %y8
;!ProgressBar : 31 32 33 34 35 36 37 38 39 40 41 43 42 44 45 46 47 48 49 50 51 52 53 54
and the base at the bottom, which has to be mapped in every frame, looks like this:
#57.999  progback.cel   *0 :0                   ;%g !ProgressBar : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 43 42 44 45 46 47 48 49 50 51 52 53 54

I'm learning something about frames: at first I thought that frame 0 is the default, and a cel group with frames would initially only show the cels in frame 0. It shows them all. I don't know if this is unique to UltraKiSS or true of FKiSS4 viewers generally, but just after loading, all frames show and none of them is properly active until after the first setframe(). That would explain why letframe() only seems to work after a setframe() for that same cel group. It certainly means that I have to initialize the progress bar by adding the following code to alarm(0):

; Make progress bar empty
;@  setframe(!ProgressBar,0)

Before dealing with drop shadows and piece placement, I revised the code that makes the settings screen appear and show all the right buttons, in label(1), the last line of which was a call to label(5), where the puzzle pieces are ghosted while the settings screen is open, and, if unfixed (and therefore not yet placed), unghosted after the settings screen closes. In the first part of label(5), instead of ghosting objects #1 to #54, I try to ghost !Pieces, which UltraKiSS doesn't accept, saying that ghost() needs a cel name, object number or variable. In the second part, I test the fix value of every puzzle piece object and, if it is unfixed, unghost it. But because FKiSS4 accepts a variable value instead of an object number for letfix() and setfix(), and due to the new action repeat(), I don't have to repeat the same code for every object in either part. To copy a line from the FKiSS reference age:

repeat(i,d,v) Performs a gosub(i) 'd' times. Before each gosub, 'v' is set to the index number of the call (i.e. 1 the first time, 2 the second time, etc).

The first argument, i, is the number (or, in FKiSS4, possibly the name) of a label. Of course repeat() has to do a gosub(), as it doesn't call the label once, but several times. How often does it call the label? That is the second argument, d. But if it calls the label, say, 12 times, doesn't the label do exactly the same thing every time? Not necessarily, because the third argument, v, is a variable that goes up in value each time the label is called. So for the first call, v=1, the next time, v=2 etc. and the label can use if-blocks to do different things depending on the value of v. Or, the label uses v as an object number. You can see where this is going:

;@label(5)
; Ghost all pieces if settings screen is showing.
;@  letmapped(G0,"setting2.cel")
;@  ifequal(G0,1)
;@    let(ToGhost,G) ; the ghost value
;@    let(F0,0)
; Else unghost unfixed pieces.
;@  else
;@    let(ToGhost,U) ; the unghost value
;@  endif
;@  repeat(SetGhost,54,ObjNo)

Label(SetGhost) is an incredibly elegant solution to processing a contiguous series of objects. First, unless the settings screen is open and F0 is 0, label(SetGhost) sets F0 to the fix value of the object number in ObjNo. Either way, if F0 is 0, the ghost value (0 or 1), which has been set once in label(5), is applied to this object number. Due to the repeat() command, this needs to be written out only once for all 54 objects.

;@label(SetGhost)
;@  ifnotequal(G0,1)
;@    letfix(F0,ObjNo)
;@  endif()
;@  ifequal(F0,0)
;@    ghost(ObjNo,ToGhost)
;@  endif()

Most buttons on the settings screen have been addressed: what remains are the (re)start and return buttons. The return button simply closes the settings screen; the start button also unghosts and scatters the puzzle pieces and sets the progress bar to zero; the restart button does all that, unfixes all pieces and resets the progress variable.

The code under the start button now ghosts all of object #0, so part of that object nees unghosting; apart from that, the button code is unchanged. It sets variable S and calls label(0), which closes the settings screen, optionally restarts the game, and now also unghosts the top left corner buttons. Label(0) always calls label(5), and if (re)starting, also label(2). The FKiSS4 version of label(0) is tightened up a bit by using cel groups and setframe():

;@label(0)
; Unmap settings, remap buttons
;@  unmap(!Settings) map(!Buttons)
;@  ifequal(S,0) ; return button
;@    goto(5)
;@  endif()
; (re)start code
;@  ifequal(S,2) ; REstart button: Unfix objects, reset D
;@    setfix(!Pieces,0) let(D,0)
;@  endif()
; Map progress bar according to settings
;@  ifequal(C3,0)
;@    setframe(!ProgressBar,D)
;@  endif()
; Reset S
;@  let(S,0)
; Label 5 unghosts pieces, label 2 will scatter them
;@  gosub(5) goto(2)

Sadly, UltraKiSS will not let me use setfix() with a cel group name either, so

;@    setfix(!Pieces,0) let(D,0)
becomes
;@    let(D,0) repeat(Unfix,54,ObjNo)

Label(Unfix) is even simpler than label(SetGhost):

;@label(Unfix)
;@  setfix(ObjNo,0)

The piece-scattering code in label(2) uses label(3) to calculate a random X and Y coordinate. Since label(2) just goes down a list of objects, checking whether an object is unfixed and if so, running label(3) to come up with coordinates for it, I thought I could move the fix test and placement to label(3), and restrict label(2) to:

;@label(2)
; Scatter pieces, if unfixed, uses label(3) to set X, Y per piece
;@  repeat(3,54,ObjNo)

Somehow, the code moved from label(2) to label(3) made the latter not work any more, leaving most pieces in place. So I restored the old label(3) and put the old label(2) code in an intermediate label, and once again, things worked.

;@label(2)
; Scatter pieces, if unfixed, uses label(3) to set X, Y per piece
;@  repeat(Scatter,54,ObjNo)
;
;@label(Scatter)
;@  letfix(F,ObjNo)
;@  ifequal(F,0)
;@    gosub(3) moveto(ObjNo,X,Y)
;@  endif()


The FKiSS4 version of the finished set.

That concludes the tutorial. I've you've made it to the end, congratulations! To see the source or play the puzzle, download puzzconc.lzh here.

Online jigsaw puzzle site links



xjig links







Top