Created: 15-07-2021
Last update: 15-07-2021
Back Previous Next

Intermediate howto 1 - tracking progress

How to track progress is also explained in the very long jigsaw puzzle how-i, but it's worth looking at in detail. What does it mean to be "tracking progress"? Think of it this way: there is a KiSS set that is actually a game or puzzle to be solved. When the player wins the game or solves the puzzle, the KiSS set maps a cel saying something like "you win!!!" and possibly resets everything to start over. But a game is rarely won by doing something in one step. If it takes five actions to win, there must be a way to keep count of how many actions are completed. In the jigsaw puzzle set, where the player wins when all puzzle pieces are in the correct place, something has to keep count of how many pieces are in place yet; each piece is a step towards solving the puzzle. Tracking progress is counting steps.

There are four ways to track progress, requiring FKiSS2 (movement and collision), FKiSS2.1 (mapping and conditional timers), FKiSS3 (augmenting a variable) and FKiSS4 (advancing frames). Note that, because the movement is done through FKiSS code and not by the player dragging cels, if the viewer only checks for collision after dragging, the FKiSS2 method DOES NOT WORK. (On all viewers that I usually use, it does work.)

There is also a simple way to track progress in FKiSS that relies on a pile of copies of the same cel, and isn't very useful.

The FKiSS2 method requires two small bar-shaped cels, fixed in place, only movable through FKiSS commands, and preferably hidden under a background cel so the player won't try to drag them. Due to viewer quirks, they should both be larger than one pixel, and if transparent, their transparency value should be at most 254, never the full 255.

The two bars are positioned a fixed space apart. The idea is that, for every step taken, the first bar moves a set amount towards the second bar, until finally it lands on top of the other bar and triggers in(). The space apart should be a multiple of the first bar's width plus one pixel, so that at the one-but-last step, the first bar is still one pixel removed from the other bar, to prevent collision due to their sides touching. The multiple should be the number of steps to count. If a win requires five steps, and the first bar is two pixels wide, then the distance between the top left corner of the first bar to the top left corner of the second bar should be 5 * (2 + 1) = 15 pixels. And the first bar must move 3 pixels towards the second bar for every step. When the first bar finally hits the second, make sure to reposition it back at the start so you can reset the tracker and replay the game.

; (event that means another step is finished)
; move progress tracking bar 3 pixels to the right
;@  move(#trackingbar,3,0)

; map cel saying "you did it!" or something
; put trackingbar back at original distance from finishbar
;@  movebyx(#trackingbar,#finishbar,-15)

With the above setup, you get a reaction only for the final step. To follow your progress, you can stake out a line of finishbar objects - since you only need their object numbers, you can reuse the same cel - and each time the trackingbar object advances, it hits another object, triggering another in() that can be used to map part of a progress bar. By progress bar, I mean a cosmetic addition that displays progress, but doesn't track it. The demo set at the bottom of this howto has a big, bold, visible progress bar, and tiny progress tracking bars hidden under a lid that can be dragged aside to see them in action.

; (event that means another step is finished)
; move progress tracking bar 3 pixels to the right
;@  move(#trackingbar,3,0)

; map first part of progress bar

; map second part of progress bar

; map third part of progress bar, which is now full
; use alarm for a delay...
; unmap all parts of progress bar
; put trackingbar back at original distance from finishbar
;@  movebyx(#trackingbar,#finishbar,-15)

I could go deeply into how I made the demo set, which is more complicated than the example above. But this is an intermediate tutorial; it just explains the basis of progress tracking through collision, which is to nudge a little bar along until it hits something. If you want to experiment with this technique, feel free to adapt the demo set's FKiSS2 cnf to your purpose.

Under FKiSS2.1, with its absolute positioning and conditional timers, progress will not be tracked through collision, but through what cels have already been mapped. The conditional timers "ifmapped()" and "ifnotmapped()" take a cel name, not an object number, so I can't use ambiguous cels. Cels that stay mapped as the count goes up, like the bits of progress bar, don't quite work either, so I want a progress tracking object where every cel represents one step. For instance, a number that goes up. Here is the very thing:

#8.999  number1.cel      *0 :0                   ;
#8.999  number2.cel      *0 :0                   ;
#8.999  number3.cel      *0 :0                   ;
#8.999  number4.cel      *0 :0                   ;
#8.999  number5.cel      *0 :0                   ;
#8.999  number6.cel      *0 :0                   ;
#8.999  number7.cel      *0 :0                   ;
#8.999  number8.cel      *0 :0                   ;
#8.999  number9.cel      *0 :0                   ;

The syntax of the conditional timers is "if(not)mapped("celname.cel",timer,duration)", so whatever happens if the condition is true, goes under a separate alarm. When an event happens that constitutes a step, it uses ifmapped() for every number cel except the last, and an extra ifnotmapped() for the first. That's a lot of alarms to ode, and if I only have to click one object or cell to advance, I can put all timers under a press() event. If the advance can come from any number of things to click, I will let their press() events set a timer, with the shortest possible duration, for one central alarm which contains all conditional timers. Like this:

;@  timer(0,1)

; let's see what's mapped

This is how alarm(0) would look, starting at the high cels and working its way down, so the "win" code will be at the top. Despite starting at number 8, I still wind up with nine alarms, because I need to transition from displaying no numbers to mapping number 1.

Here, I run into a problem. At the start of the game, number 1 is unmapped. But after the first step, number 1 is also unmapped.

; Start at the one-but-last of 9 steps
;@  ifmapped("number8.cel",1,1)
;@  ifmapped("number7.cel",2,1)
;@  ifmapped("number2.cel",7,1)
;@  ifmapped("number1.cel",8,1)
;@  ifnotmapped("number1.cel",9,1)

;@  unmap("number8.cel") map("number9.cel")
; YOU WIN!!! map a happy cel!
; Unmap all cels if you want to start over.

;@  unmap("number7.cel") map("number8.cel")
;@  unmap("number6.cel") map("number7.cel")
;@  unmap("number2.cel") map("number3.cel")
;@  unmap("number1.cel") map("number2.cel")
;@alarm(9) ; but what if this is already step 2?
;@  map("number1.cel")

I need a different condition for that last alarm; a condition that is only true when the step count is zero. The progress tracker bar will save me; I no longer need to detect its collisions, but I can use the fact that it moves. Because a second pair of conditional timers, taking an object number instead of a celname, is called "ifmoved()" and "ifnotmoved()", depending on whether the object is still at the coordinates specified in the object coordinates block. (Note: these coordinates may vary per page.)

When the step count is zero, the progress tracker bar is at its initial position. The code under alarm(0) becomes:

; Start at the one-but-last of 9 steps
;@  ifmapped("number8.cel",1,1)
;@  ifmapped("number7.cel",2,1)
;@  ifmapped("number2.cel",7,1)
;@  ifmapped("number1.cel",8,1)
;@  ifnotmoved(#progresstrackerbar,9,1)

Well, it's one of those things that works in theory, but not in practice. The FKiSS reference page says that ifnotmoved() is decided by whether or not the object is at different coordinates from its starting position on that page, but in practice, depending on the viewer, it is more often decided by whether the object has been moved AT ALL, through FKiSS commands or by dragging. As this object has been.

But there is a third and last pair of conditional timers: "iffixed()" and "ifnotfixed()". They too need an object number, and their condition is a simple one: the object's fix value is zero, or it is not. I just have to define some throwaway object hidden under a background cel, so it can't be unfixed through dragging, set its fix value to 0 with the FKiSS2.1 command "setfix(#object,0)", wait until the object's zero fix value causes ifnotfixed() to set a timer, and put "setfix(#object,10)" under the resulting alarm to make sure it's set off only once. The second value in this example is 10, but it can be any value that isn't 0. The code involved is as follows:

; press start or reset button
;@  setfix(#hiddenobject,0) ; UNFIX!!
; Start at the one-but-last of 9 steps
;@  ifmapped("number8.cel",1,1)
;@  ifmapped("number7.cel",2,1)
;@  ifmapped("number2.cel",7,1)
;@  ifmapped("number1.cel",8,1)
;@  ifnotfixed(#hiddenobject,9,1) ; ONLY IF UNFIXED!!

[code for alarms 1 to 8]
;@  map("number1.cel") setfix(#hiddenobject,10) ; REFIX!!

And that dots the i's and crosses the t's of the FKiSS2.1 way of tracking progress.

The code in the FKiSS3 cnf, while being more complicated than that of the earlier versions because it uses labels, has the easiest way of tracking progress: in a variable! FKiSS3 variables have a name of one letter or one letter and one number, and contain integer values, that is to say, whole numbers. So I start out with a variable, let's say "F" for "finished", which is initially 0, and for each step, I add 1 to F, until F is 5, or whatever the number of the final step is. I don't have to move or map anything, but if I want to, I can still map bits of a progress bar; I can even show the progress tracker advancing, although it's just for show now.

It seems simple: under every event that constitutes a step - generally, press() or release(), because it's likely to be a player action - add 1 to F. But then, you'd also have to check under every event if F equals 5 yet, and add what needs to happen when it does. That's a lot of repeated code, and FKiSS3 has a solution for repeated code: labels. If you're putting actions under an event, and a bunch of actions happen frequently, put that bunch under, say, label(4) and slip "gosub(4)" in between the event's actions. Or, if calling the label is the last or only action, use "goto(4)". Below is a very rough example:

; (event that means another step is finished)
; let label(0) take care of it
;@  goto(0)

; (another event that means another step is finished)
;@  goto(0)

[all events get "goto(0)"]

;@  add(F,F,1) ; progress tracked!
; optional progressbar code
;@  ifequal(F,1)
;@    map("progress1.cel")
; Just step out now, don't process what follows.
;@    exitevent()
;@  endif()
;@  ifequal(F,2)
;@    map("progress2.cel")
; Just step out now, don't process what follows.
;@    exitevent()
;@  endif()
;@  ifequal(F,5)
; that's it, you've won!!
; fireworks!!! map happy screen!!
; unmap progress cels, put stuff back in place, reset variable
;@    let(F,0)
;@  endif()

FKiSS4 still uses a variable to track progress, like FKiSS3, but now the variable is stored as a frame number. The progress trackers that are no longer used for collision detection, and the big progress bar that is just for show, will be recast as cel groups with frames, because to have a frame number, I need a cel group. Storing progress as a frame number isn't as invisible as storing it in a variable, but that's great when you want to see the progress happening, and don't want to have to tiresomely ask the variable's value and use that to map cels, as in the example above. FKiSS4 unites incrementing value and providing a visual in one step.

First, you need a cel group with the right number of frames that will function as tracking mechanism. You can define several cel groups with the same number of frames to display progress in more ways than one, but the other cel groups should follow the lead of the main group. I choose the tracking display that shows numbers, one after the other. That means each frame should only contain one number cel.

Note, there is NO cel assigned to frame 0. That means that if the cel group is set to frame 0, it is effectively completely unmapped.

#8.999  number1.cel      *0 :0                   ;!ProgressNo :  1                 ;
#8.999  number2.cel      *0 :0                   ;!ProgressNo :    2               ;
#8.999  number3.cel      *0 :0                   ;!ProgressNo :      3             ;
#8.999  number4.cel      *0 :0                   ;!ProgressNo :        4           ;
#8.999  number5.cel      *0 :0                   ;!ProgressNo :          5         ;
#8.999  number6.cel      *0 :0                   ;!ProgressNo :            6       ;
#8.999  number7.cel      *0 :0                   ;!ProgressNo :              7     ;
#8.999  number8.cel      *0 :0                   ;!ProgressNo :                8   ;
#8.999  number9.cel      *0 :0                   ;!ProgressNo :                  9 ;

If I did that for the progress bar, the bar would not fill up, but a small section of it would move around. To make the bar grow, make sure that every frame contains its final cel and all the cels that come before it:

#5.999  psbar1.cel       *0 :0                   ;!SmallBar :  1 2 3 4 5 6 7 8 9 ;
#5.999  psbar2.cel       *0 :0                   ;!SmallBar :    2 3 4 5 6 7 8 9 ;
#5.999  psbar3.cel       *0 :0                   ;!SmallBar :      3 4 5 6 7 8 9 ;
#5.999  psbar4.cel       *0 :0                   ;!SmallBar :        4 5 6 7 8 9 ;
#5.999  psbar5.cel       *0 :0                   ;!SmallBar :          5 6 7 8 9 ;
#5.999  psbar6.cel       *0 :0                   ;!SmallBar :            6 7 8 9 ;
#5.999  psbar7.cel       *0 :0                   ;!SmallBar :              7 8 9 ;
#5.999  psbar8.cel       *0 :0                   ;!SmallBar :                8 9 ;
#5.999  psbar9.cel       *0 :0                   ;!SmallBar :                  9 ;

The little progress tracker bar that bumps into one little finish bar after another, has been retired, along with its repositioning code. Instead, the finish bars themselves are mapped one by one, and also packed into one object using an X offset tag.

#28.999 trckdot.cel      *0 :0                   ;%x5  !Trackdots :  1 2 3 4 5 6 7 8 9 ;
#28.999 trckdot.cel      *0 :0                   ;%x10 !Trackdots :    2 3 4 5 6 7 8 9 ;
#28.999 trckdot.cel      *0 :0                   ;%x15 !Trackdots :      3 4 5 6 7 8 9 ;
#28.999 trckdot.cel      *0 :0                   ;%x20 !Trackdots :        4 5 6 7 8 9 ;
#28.999 trckdot.cel      *0 :0                   ;%x25 !Trackdots :          5 6 7 8 9 ;
#28.999 trckdot.cel      *0 :0                   ;%x30 !Trackdots :            6 7 8 9 ;
#28.999 trckdot.cel      *0 :0                   ;%x35 !Trackdots :              7 8 9 ;
#28.999 trckdot.cel      *0 :0                   ;%x40 !Trackdots :                8 9 ;
#28.999 trckend.cel      *0 :0                   ;%x45 !Trackdots :                  9 ;

To make sure progress bars etc. are blank at the start of the game, I set all these cel groups to frame 0 before the set loads:

;@  setframe(!ProgressNo,0) ; This is the progress-tracking cel group.
;@  setframe(!SmallBar,0) setframe(!Trackdots,0)

Next, you need a label that handles the progress tracking code, so that, as in FKiSS3, every event that constitutes a step will simply call the label. But instead of adding 1 to a variable, the FKiSS4 label uses that variable to ask what the progress-tracking cel group's active frame is, and make the next frame active, which maps the right cels on the progress displays at the same time. Each event that constitutes a step again has "goto(0)" under it, and label(0) is much shorter:

; What is the current frame of guiding lodestar !ProgressNo? Add 1.
;@  letframe(F,!ProgressNo) add(F,F,1)
; Set !ProgressNo to the next frame, and all the other progress displays, too.
;@  setframe(!ProgressNo,F) setframe(!SmallBar,F) setframe(!Trackdots,F)
; So, is the game finished?
;@  ifequal(F,9)
; that's it, you've won!!
; fireworks!!! map happy screen!!
; Reset variable and all frames (setting them to 0 unmaps them):
;@    setframe(!ProgressNo,0) setframe(!SmallBar,0) setframe(!Trackdots,0)
;@  endif()

Finally, here's the inadequate solution for FKiSS-only viewers: if the object you must click to win has several layers, and each click unmaps a layer, clicking the bottom layer scores the win. Unless the object is fixed, the layers must all have the same object number, so they move together when dragged; and unless you want to hint at progress, the cels must all look the same so as not to give the game away, yet they can't have the same celname, or they would be ambiguous. When cels are ambiguous (defined more than once in the object declaration block), any action using that celname usually affects only the topmost cel, which frustrates the solution of clicking cels away to get at the underlying ones. So, the cel that informally tracks progress must be copied to as many cels as there are steps to count, and then called "cel1.cel", "cel2.cel" etc. It would look like this:

;@  unmap("cel1.cel") map(#progressbar1) move(#progresstracker,10,0)
;@  unmap("cel2.cel") map(#progressbar2) move(#progresstracker,10,0)
;@  map(#progressbar3) move(#progresstracker,10,0)
; YOU WIN!!!
;@  timer(10,1) ; reset timer

; Reset alarm (optional)
; Remap cels, unmap progress bars, move progress tracker back
;@  map("cel1.cel") map("cel2.cel") map("cel3.cel")
;@  unmap(#progressbar1) unmap(#progressbar2) unmap(#progressbar3)
;@  move(#progresstracker,-30,0)

It might be possible, using colossal numbers of duplicate cels made transparent or some other complicated construction, to simulate more complex progress tracking in FKiSS. My advice: don't do it. Viewers that are too old for FKiSS2 and upward, are also too old for long cnfs and large numbers of cels.

Click here to download the demo set.

Back Previous Next