Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Internet Janitor

682
Posts
46
Topics
1,633
Followers
17
Following
A member registered Aug 02, 2018 · View creator page →

Creator of

Recent community posts

 ((count a1) take a2) join a1
 
# (("x","aa"),("x","bb"),("x","cc"),("x","dd"))

From the lil reference manual:

If join is applied to non-table arguments, it produces a list by pairing adjacent elements from x and y. If either argument is a number, it is considered "range" of that argument, and otherwise it is interpreted as a list. For example, "ABC" join 3 gives (("A",0),("B",1),("C",2)).

As you can see, it has a preference for preserving the length of the left argument if they don't match. Some functional languages call this operation on lists "zip()". Applying "join" to tables performs a natural join.

If you simply want to concatenate lists and atoms, use the comma operator (,):

 a1,a2
# ("aa","bb","cc","dd","x")

(Lil doesn't have list literals per se, just atom literals and the comma operator.)

Does that make sense?

raze of a table (like the .value of a grid widget) makes a dictionary mapping the first column to the second column. For example,

 insert k v with "Apple" 11 "Banana" 22 end
+----------+----+
| k        | v  |
+----------+----+
| "Apple"  | 11 |
| "Banana" | 22 |
+----------+----+
 raze insert k v with "Apple" 11 "Banana" 22 end
{"Apple":11,"Banana":22}

the dd.chat[] function will exit if the value corresponding to a key in that map is a number (instead of a string or rtext), so we just need to add another entry to that dictionary.

If you have a dictionary in a variable, you can modify it in-place:

 d:raze insert k v with "Apple" 11 "Banana" 22 end
{"Apple":11,"Banana":22}
 d["Cursed Fruit"]:33
{"Apple":11,"Banana":22,"Cursed Fruit":33}

You can also perform the equivalent amendment if the dictionary was yielded by a subexpression, as long as you wrap it in parentheses:

 (raze insert k v with "Apple" 11 "Banana" 22 end)["Cursed Fruit"]:33
{"Apple":11,"Banana":22,"Cursed Fruit":33}

You can also construct a dictionary functionally using "dict" and then take the union of some other dictionary and the new dictionary with ",":

 (list "Cursed Fruit") dict 33
{"Cursed Fruit":33}
 (()["Cursed Fruit"]:33) # (yet another way of saying the above)
{"Cursed Fruit":33}
 (raze insert k v with "Apple" 11 "Banana" 22 end),((list "Cursed Fruit") dict 33)
{"Apple":11,"Banana":22,"Cursed Fruit":33}

Or you could make a second table and join its rows to the original (also with ",") before razing:

 raze insert k v with "Apple" 11 "Banana" 22 end,insert k v with "Cursed Fruit" 33 end
{"Apple":11,"Banana":22,"Cursed Fruit":33}

Many ways to peel this particular apple. I strongly recommend using the Listener to experiment with examples like these whenever you find yourself puzzling over a tricky expression; building things up in little pieces helps you verify an idea as you go.

Do those examples make sense?

Glad you and your kids had fun with it!

The valentine's day theme does limit its applicability, but it should be reasonably easy to repurpose for other holidays in the future; everything is data-driven, and adding cover art or border images is simply a matter of creating new cards that follow an appropriate naming convention.

Decker cards are 512x342 pixels by default, just like the display on a Macintosh Plus. Most of the cards in the wizard expose a "render[]" event handler that composites together their preview image (locally) and is invoked by subsequent cards to assemble more and more of the final greeting card image. The script for the "Border" card can be seen here:

on view do
 on tr x do (count "back_") drop x end
 back.value:select "Border Style":tr@key where key like "back_*" from deck.cards
 preview.clear[]
 preview.paste[render[]]
end
on render do
 i:deck.cards["back_%s" format first back.rowvalue].image.copy[]
 i.transform["right"]
 i
end

If the images look somewhat horizontally compressed on the final printout it probably relates to how much of the page border your printer is filling and how the PDF aspect ratio gets squeezed. I dialed things in fairly close for my home laser printer, but this behavior can vary quite a bit.

I guess the only thing left to note is a dash of convenience!

If you have a bunch of game flags stored in checkboxes on a card named, say, "inventory", you might have buttons with game logic that look something like

on click do
 if inventory.widgets.frog.value
  play["munch"]
  alert["delicious!"]
  inventory.widgets.frog.value:0
 else
  alert["i wish i had a frog..."]
 end
end

That "inventory.widgets.frog.value" phrase could get a little verbose if you have to write it frequently. If you wanted, you could define some "helper" functions in the Deck-level script (see: File -> Properties... -> Script in the menu):

on setflag   name do inventory.widgets[name].value:1 end
on clearflag name do inventory.widgets[name].value:0 end
on getflag   name do inventory.widgets[name].value   end

Which will make those functions available in any script elsewhere in the deck.

We could then simplify the earlier example:

on click do
 if getflag["frog"]
  play["munch"]
  alert["delicious!"]
  clearflag["frog"]
 else
  alert["i wish i had a frog..."]
 end
end

In Lil,

thing["name"]

is equivalent to

thing.name

so long as 'name' is a Lil identifier: it must consist of letters, digits, and underscores (_) and may not begin with a digit or contain any spaces.

Thus, provided we follow appropriate naming conventions for our flags, we could go another step further:

on click do
 if getflag.frog
  play.munch
  alert["delicious!"]
  clearflag.frog
 else
  alert["i wish i had a frog..."]
 end
end

Does any of this help?

Welcome to the Decker community!

There isn't currently any mechanism for visually grouping sets of cards within a deck while editing.

If you're comfortable with programming, you might be able to achieve the project structure you describe by writing pieces of a large game as separate decks and then using a Lilt script to glue the pieces together; Lilt has most of the same scripting interfaces as Decker itself and can also copy and paste cards, widgets, and other resources between decks. I can provide more detailed examples if needed.

From a scripting perspective it's fairly easy to write queries that find cards based on a naming convention. For example, in Valentine-er all the cards containing border designs use a "back_" prefix in their names, and the script which populates the list of options does essentially:

select where key like "back_*" from deck.cards

If you happen to remember the names of cards within a project, you can also use the Listener to quickly navigate to them via the keyboard like so:


Does any of that help?

Sure!

The PDF module can make any kind of printable output, and is especially handy if the thing you want to print needs to be customizable or include computation and randomization, like decoder wheels, bingo cards, or procedurally generated maps, scenarios, and character sheets for a tabletop game.

Decker community · Created a new topic Valentine-er

Just what it says on the tin. You can try it out right here!

and the result:

This tool uses the PDF Module to produce printable cards; I think there's great potential for other "Print-Shop"-esque tools made with Decker. Thoughts, questions?

I think I follow.

Let's say we have a canvas named "char" and some buttons that are meant to animate that canvas to their position.

We can give each button an identical script calling a helper function we will write called "seek" which takes three parameters: a thing to move (them), a widget giving a destination (there), and the number of frames the animation should take (steps):

on click do
 seek[char me 30]
end

(Remember: "me" in this case will be each button itself!)

On the card (or possibly the Deck-level script), we'll define seek[] thusly:

on seek them there steps do
 a:them.pos
 b:(there.pos+.5*there.size)-.5*them.size
 each t in (range steps)/(steps-1)
  them.pos:a+t*b-a
  sleep[1]
 end
end


The variables "a" and "b" store the initial position and the final position of "them", respectively. The expression:

there.pos+.5*there.size

Is the position of the destination plus half its size, which is to say its centerpoint. By subtracting half the size of "them" from the centerpoint of "there", we'll get the position which will center "them" on "there".

To tween the object smoothly from "a" to "b", we need to compute a series of uniform steps from 0 to 1. Consider the following examples:

 (range 3)/2
# (0,0.5,1)
 (range 5)/4
# (0,0.25,0.5,0.75,1)
 (range 8)/7
# (0,0.142857,0.285714,0.428571,0.571429,0.714286,0.857143,1)

Given a time "t" between 0 and 1, we can linearly interpolate between "a" and "b" with:

a+(t*(b-a))

Which in Lil can be written more concisely as

a+t*b-a

Since arithmetic expressions have uniform precedence.

For extra fun, we could replace that linear interpolation with an easing function from Ease like "ease.inOutBack[]"

on seek them there steps do
 a:them.pos
 b:(there.pos+.5*there.size)-.5*them.size
 each t in (range steps)/(steps-1)
  them.pos:ease.inOutBack[t a b]
  sleep[1]
 end
end


How's that?

All the parameters of every zazz function are documented. zazz.scroll[] only takes two parameters: a target and a scroll direction.

It is not possible to scroll less than one pixel per frame, but we could use a lower "duty cycle" by scrolling only every N-th frame. The "sys.frame" field automatically increments 60 times per second, so we could for example call zazz.scroll[] only when sys.frame modulo 4 equals zero to scroll every fourth frame, or at 15FPS:


on view do
 if !2%sys.frame zazz.scroll[A 1,0] end
 if !3%sys.frame zazz.scroll[B 1,0] end
 if !4%sys.frame zazz.scroll[C 1,0] end
 if !5%sys.frame zazz.scroll[C 0,1] end
 go[card] 
end

Let's start with a few basics.

In most cases, scripts on widgets, cards, and the deck should consist of a series of event handlers. Event handlers are written as an "on ... do ... end" block, like so:

on click do
 # more code in here
end

Decker will automatically create some "stub" event handlers like this when you initially edit the script of a widget or card. If you write code OUTSIDE of an event handler, it will be executed EVERY time ANY event occurs, which can cause odd and surprising misbehavior. None of the scripts you have included here have a well-formed "on ... do ... end" block around them.

---

In the "if ... else ... end" conditional structure, the "else" portion is optional. If there's no alternative:

if someCondition
 # some code
else
 # nothing
end

You can leave off the "else" half:

if someCondition
 # some code
end

---

The colon (:) symbol is Decker's assignment operator. In simple cases, it assigns values to variables:

myVariable:42

And in more complex cases it can also be used to assign to attributes of interface values, like the "text" attribute of a field widget:

myField.text:"Some Text"

In several of your scripts you seem to be using colons as if they were a equality operator. If you want to check whether an attribute of a widget matches a number, you may want the "~" operator:

if blueFrame.value~37
 ...
end

As I explained in the previous thread, a conditional which checks multiple conditions should wrap each "clause" in parentheses so as to apply an intuitive order of operations:

if (blueFrame.value~1) & (pottedPlant.value~1) & (bookStack.value~1)
 # some code
end

But when you're specifically checking whether a flag is "truthy" you don't need to compare anything to 1; the above can be simplified to

if blueFrame.value & pottedPlant.value & bookStack.value
 # some code
end

---

Let's say your title card's "view" event handler resets the value of the three buttons on the card cardOffice:

on view do
 cardOffice.widgets.blueFrame  .value:0
 cardOffice.widgets.pottedPlant.value:0
 cardOffice.widgets.bookStack  .value:0
end

Each button should have a "click" event handler which sets the button's value to record the click. We can then also have those event handlers call an event handler defined on the card which acts as a centralized place to check whether every button has been clicked. Let's call our "synthetic" event "checkObjects". As a convenience, within a widget's event handlers you can refer to the widget itself as "me". The whole script could look something like:

on click do
 dd.open[deck]
 dd.say["(Something's tiny femur is displayed in this frame.)"]
 dd.say["(I should ask the professor what creature this belonged to.)"]
 dd.close[]
 me.value:1
 checkObjects[]
end

Then we'll need to define that "checkObjects" handler within the card script:

on checkObjects do
 if blueFrame.value & pottedPlant.value & bookStack.value
  dd.open[]
  dd.say["The code worked!"]
  dd.close[]
 end
end

Does that help point you in the right direction?

Could you post the scripts you're trying that work and those which do not work? It's much easier to diagnose problems with scripts when I can read them and clearly understand what you have tried.

The two ways of concatenating strings in Lil are fuse, which takes a simple string to intercalate between the elements of a list:

 " : " fuse "Alpha","Beta"             # -> "Alpha : Beta"

And format, which offers a richer printf-like string formatting language:

 "%s : %s" format "Alpha","Beta"       # -> "Alpha : Beta"  

The format primitive is useful in many situations, including gluing a fixed prefix and/or suffix onto strings:

"Prefix %s Suffix" format "MyString"   # -> "Prefix MyString Suffix"

In situations where a function, operator, or interface attribute expects a string, you can often get away with simply providing a list of strings (perhaps joined together into a list with the comma (,) operator) which will be implicitly fused together. Per The Lil Reference Manual for type coercions:

When a string is required, numbers are formatted, and lists are recursively converted to strings and joined. Otherwise, the empty string is used.

This is why you're able to elide the fuse in your example; "field.text" always treats a written value as a string

As noted in the previous post Ahm linked, Lil has uniform operator precedence; expressions are evaluated right-to-left unless parentheses are used.

The expression

x=1&y=1&z=1

In your first example is only working by coincidence; It is not equivalent to

(x=1)&(y=1)&(z=1)

But rather

x=(1&(y=(1&(z=1))))

A fellow containment breach? Wonder where they'll turn up next...

I haven't changed my mind about this design choice, but I did add an escape-hatch to Web-Decker which offers another possible workaround:  The Forbidden Library includes a module called "kb" which pumps raw key events to the active card. This module will not function in Native-Decker, but that may be acceptable if you otherwise plan to offer graceful degradation to alternative input methods.

(1 edit)

There is not presently a way to inspect the remaining per-frame quota (or other limits) from within executing scripts.

As a developer you can get a rough idea of how close your scripts are to quota by enabling the "Script Profiler" feature from within the Decker menu. This will display a live chart of the percentage of quota scripts have consumed over the past few seconds.

If you want to provide the user with visual feedback during long-running scripts, you need only draw visible updates from time to time. When a script runs out of quota it is simply paused momentarily to allow Decker to service input and then automatically resumed, unless the user explicitly halts it.

In C, expressions are explicitly terminated with a semicolon (;). In Lil, expressions are implicitly terminated when they are no longer "incomplete"; that is, their rightmost token is not a primitive or other syntactic form which "expects" an additional subexpression to the right. Outside comments and string literals, all whitespace in Lil is equivalent, so we can sequence "statements" with newlines between them:

ax:2+3
bx:ax*5

...or we could run them together on a single line:

ax:2+3 bx:ax*5

In this particular example we could even remove the space separating the two statements, because Lil identifiers may not begin with a number; splitting the "3" from the "bx" is unambiguous. I do not recommend using such a style in practice:

ax:2+3bx:ax*5

Lil interprets all three variations as the sequence of tokens (shown separated by whitespace):

ax : 2 + 3 bx : ax * 5

Whitespace is only required when Lil would otherwise "see" a single token but multiple tokens are intended:

onfoo   # reference to a variable named "onfoo"
on foo  # the keyword "on" followed by the identifier "foo"

The bracketed argument list for a function call is not a special case; just like the "do...end" of a function body, it accepts any sequence of expressions, each implicitly terminated. If the argument-expressions are complex, it may be stylistically clearer to parenthesize them and/or use whitespace to produce a visually apparent grouping, but this does not change how Lil interprets the expression. Consider:

x:11
y:first range 10
z:3*5
foo[x y z]

Versus the following equivalent alternatives:

foo[11 first range 10 3 * 5]
foo[(11) (first range 10) (3 * 5)]
foo[
 11
 first range 10
 3 * 5
]

Or, my preference for this particular situation,

foo[11 (first range 10) 3*5]

The comma operator (,) should always be understood as a primitive operator, rather than special syntax; it's of the same nature as the operators plus (+) or times (*).

Does that help clarify?

It is possible to directly manipulate the card background (card.image) instead of using a canvas widget, but it's limited in some ways; see the Image Interface documentation. If you have a hand-drawn card background this can also make it easy to accidentally destroy your doodles, so use caution! For a quite elaborate example of such an animation, try clicking the first "e" in "Decker" on the title screen of the guided tour deck.

When I'm frequently making changes to a script I do often defer logic to the card-level script for editing convenience, possibly by giving the widget a stub script like:

on click do
 reset_game[]
end

With "reset_game" defined on the card or deck script. It is possible to quickly jump to a widget's script by enabling "File -> X-Ray Specs" from within the script editor and then ctrl+clicking in the widget's bounding box to view its script; ctrl+clicking outside any widgets switches to the card script. This is a very useful tool for getting a "bird's eye view" of what's happening on a card. Alternatively you could use the F-keys on your keyboard to switch between Interact and Widgets mode (F1 and F2, respectively), click a widget, and press cmd/ctrl+R to edit its script.

I think in your example you may be tripping over Lil's order of operations (or lack thereof). Without parentheses, expressions are carried out strictly right-to-left. Parens may be useful for visually grouping arguments, but do not inherently denote lists. The following expressions are equivalent:

(me.size/2, me.size)
me.size/(2, me.size)
me.size/2,me.size

I think you intended:

(me.size/2),me.size

When the position for canvas.text[] is specified as an (x,y) pair, anchors control the positioning of the text relative to that point: "center" means the string will be centered upon that point.

When the position for canvas.text[] is instead specified as a (x,y,w,h) rectangle, anchors control the alignment and justification of text within that rectangle. Thus, to wrap and center text within the bounds of the canvas you'd want something like 

canvas.text[somestring 0,0,canvas.size "center"]

Or, incorporating a margin,

local margin:15
canvas.text[somestring margin,margin,me.size-margin*2 "center"]


How's that?

(2 edits)

A card itself cannot be marked animated; only widgets.

With the "Widgets" tool active, you can set this property from the "Widgets -> Animated" menu item.

If you want an "animated card" you can either use a self-refreshing approach with go[card] like in your original script, or add an animated widget to the card that acts as an "event pump" by allowing the view[] event to bubble up to the card. The Decker Sokoban example uses something similar to the latter approach.

(2 edits)

If you use "Card -> Copy Card" from the menu, Decker will store a text-based serialization of the card, its widgets, and any fonts or contraption prototypes those widgets reference in the clipboard. This also works with "Edit -> Copy Widgets" for a group of widgets. As in the examples in The Contraption Bazaar, this can be a handy way of sharing a chunk of a project that Decker users can immediately paste into their own deck and try out. Here's how that might look for your example:

%%CRD0{"c":{"name":"card1","script":"local s: \"abcdef\"\non view do\n  if ! sys.frame < next_frame.text\n   canvas.clear[]\n   local t: flip (list \"\" split s),(list random[2 count s])\n   local y: \"\" fuse each u in t if u[1] \"%u\" else \"%s\" end format u[0] end\n   canvas.text[y (50,50, 200,100)]\n   next_frame.text: sys.frame + 5\n  end\n  go[card]\nend","widgets":{"canvas":{"type":"canvas","size":[100,100],"pos":[206,121],"volatile":1,"scale":1},"next_frame":{"type":"field","size":[100,20],"pos":[63,128],"volatile":1}}},"d":{}}

(Note that sometimes the itch.io WYSIWYG editor will scramble code that it confuses for embedded HTML; this can be quite annoying, but is not a bug in Decker.)

I have marked the canvas as "volatile" to keep the snippet smaller, since the script clears and repaints it constantly.

There are a few ways we can simplify and improve the script:

  • Instead of using a "next_frame" field to keep track of the most recent refresh, we could repaint when sys.frame modulo 5 is 0; then there's no extra state to worry about. This also avoids the latent problem that saving a deck (and thus serializing the field) may mean that the number in that field is much larger than sys.frame on the next work session, causing the animation to appear to "freeze" for an arbitrary amount of time before playing. This can be avoided (as in the snippet above) by also making the next_frame field volatile.
  • By marking the canvas as "animated", Decker will automatically send "view" events to the canvas, allowing us to move the script within the canvas and make it self-contained, referring to itself as "me". Using a card-level event pump may be easier in some cases; use whatever approach you prefer.
  • If you want to "zipper" a pair of lists into a list of pairs, you can use the Lil "join" operator instead of transposing a pair of lists:

 (1,2,3) join "ABC"
((1,"A"),(2,"B"),(3,"C"))
 local s: "abcdef" s join random[2 count s]
(("a",0),("b",1),("c",0),("d",0),("e",0),("f",0))
  • In this situation, however, we don't actually need to build that list of pairs; we could instead randomly choose a case-formatting string while iterating over the letters. The random[] function can be called with any list, producing a random choice from that list:
 random["%u","%l"]
"%u"
 random["%u","%l"]
"%l"
  • canvas.text[], like many functions in Lil which expect a string argument, will implicitly fuse lists of strings together, so you don't need to do so explicitly.
  • Assuming the goal was to center the string within the canvas, you can do this generically by using the third "anchor" parameter for canvas.text[].

With these tweaks, the script would be:

local s: "abcdef"
on view do
 if !5%sys.frame
  local y:each c in s random["%u","%l"] format c end
  me.clear[]
  me.text[y me.size/2 "center"]
 end
end

If we wanted to "code golf" there are a few more ways we could shorten this script, but whether you'd consider them improvements is subjective:

  • The "local" keyword for variable declarations is optional; we only need it explicitly as documentation of intent to human readers and to force Lil to shadow, rather than inherit, variable declarations of the same name in a higher scope. In this situation it doesn't change the semantics of the program.
  • The variable "s" is now used only once, so we can inline its value.
  • Likewise the variable "y" is now used only once, so we can inline the entire loop into its use site.
on view do
 if !5%sys.frame
  me.clear[]
  me.text[
   each c in "abcdef" random["%u","%l"] format c end
   me.size/2 "center"
  ]
 end
end

And as a widget snippet attached to a volatile canvas:

%%WGT0{"w":[{"name":"canvas","type":"canvas","size":[100,100],"pos":[206,121],"animated":1,"volatile":1,"script":"on view do\n if !5%sys.frame\n  me.clear[]\n  me.text[\n   each c in \"abcdef\" random[\"%u\",\"%l\"] format c end\n   me.size/2 \"center\"\n  ]\n end\nend","scale":1}],"d":{}}

Does that help?

Decker's touch mode is primarily designed with tablets in mind. Some users have reported a good experience on those phones which support a stylus for more precise input.

Decker uses a custom soft keyboard in order to provide a consistent experience on all devices and operating systems in both web-decker and native-decker, with the ability to easily type any of the characters used in Lil scripts. Having fine control over the behavior of this soft keyboard is also important for supporting some functionality.

Various mobile browsers often report misleading or inaccurate information about the screen and its DPI to webapps, including invisibly reserving space for their own UI elements or introducing their own scaling factors. On iPhones specifically, Decker's "fullscreen mode" functionality does not work, because iOS Safari intentionally does not implement the standard fullscreen APIs available on other devices. I try to avoid adding device-specific or browser-specific code to web-decker whenever I can, because that kind of work is inherently brittle.

There's no need at all to apologize for asking questions, especially when you've clearly done some experimenting and reading on your own before reaching out!

In a script like so:

on click do
 sleep[5*60]
 play["sosumi"]
end

Decker will wait five seconds before playing the sound. If you reverse the order of those operations:

on click do
 play["sosumi"]
 sleep[5*60]
end

The sound will begin to play immediately, with its playback overlapping the 5-second sleep.

You can also use sleep["play"] to ask Decker to sleep until all sound clips have finished play[]ing:

on click do
 play["sosumi"]
 sleep["play"]
end

See All About Sound for more detail and examples.

There's a lot of material in this thread that might be helpful.

There are a number of possible approaches for what you're describing. I'll assume that when you say "play a GIF" you intend to use a gif or colorgif contraption?

If you have set up a button to take you to another card, its script might look something like the following:

on click do
 go["otherCard"]
end

If you simply wanted to wait for time to elapse before changing cards, you could use the sleep[] function, which accepts a number of frames to wait as an argument. Decker runs at 60 frames per second, so a 5 second delay would look like this:

on click do
 sleep[5 * 60]
 go["otherCard"]
end

While Decker is sleeping, the user can't interact with other widgets, and contraptions that normally animate or otherwise update themselves on every frame will appear to be "frozen". Some contraptions are designed to allow an external script to explicitly tell them to update themselves; by convention this will often take the form of exposing an .animate[] function which can be called from the outside. I have updated the gif and colorgif contraptions (see above) to support this convention. (If you already have a gif or colorgif contraption in your deck, re-pasting the updated definition from the bazaar will "upgrade" any existing instances of the contraption.)

If the card contained a gif widget named "mygif", we could rewrite the above script to give it a chance to keep running while Decker waits for 5 seconds by using a loop and only sleeping one frame at a time:

on click do
 each in range 5 * 60
  mygif.animate[]
  sleep[1]
 end
 go["otherCard"]
end

You alluded to wanting the GIF to start playing only when the button was clicked in the first place. Perhaps you meant you want the contraption to appear during that interval? This sort of thing can be done by manipulating the .show attribute of the widget. Supposing the contraption was initially set to "Show None",

on click do
 mygif.show:"solid"
 each in range 5 * 60
  mygif.animate[]
  sleep[1]
 end
 mygif.show:"none"
 go["otherCard"]
end

(Note that I reset the GIF to be invisible again at the end; this is not essential, but makes testing easier!)

I'd also like to point out that it's very straightforward to script simple "slideshow" animations with sleep[] by putting each frame on its own card:

on click do
 go["firstCard"]
 sleep[30]
 go["secondCard"]
 sleep[30]
 go["thirdCard"]
 sleep[30]
 # and so on
end

Or more concisely, for many frames of the same delay,

on click do
 each cardName in ("firstCard","secondCard","thirdCard","fourthCard","fifthCard")
  go[cardName]
  sleep[30]
 end
end

Of course, having a very large number of frames in such an animation can also make your deck quite large!

Does any of this point you in the right direction?

I found both the story and its presentation riveting and highly atmospheric; well done!

Dec(k)-Month The Second is now complete. We've had another bumper crop of toys, games, and zines: please check out the submissions, leave some nice comments and constructive criticism for your fellow deckbuilders, and share their creations with your friends. Don't forget to give a rating to submissions to help increase their visibility here on itch, and if you're a participant please remember to tag your creations with #decker!

If anyone didn't quite make the deadline and needs an extension, feel free to contact me within the next few days for help with late submissions.

Millie's correct; I'm not intentionally doing anything special beyond lowering the bit depth and sampling rate. Converting 10-second-or-less audio clips in this way and saving them from Decker is pretty easy; something like the following in the Listener or a buttons script:

write[read["sound"]]

If you use Native-Decker this will only support reading .WAV files, but for Web-Decker it ought to work for any audio format your browser can handle.

Dealing with arbitrarily long audio is possible, but would be more complicated from within Decker.

This should be fixed in Ply v1.0.1.

(1 edit)

The Sims 2 is real???

Amazing; 6 blinking on my cell phone out of 5 stars.

This is fantastic; an immersive point-and-click collage stuffed with puzzles. I did find ringing the bell a bit treacherous: clicking it quickly seems to make the game unwinnable. You might want to use >2 instead of =3 somewhere in your scripts. ;)

This is looking amazing. The subtle animation on the Christmas lights adds a lot, and everything has so much texture!

dd.say[] and dd.ask[] accept rich-text, so one alternative might be to represent your input options as hidden rich-text fields. You could also use rtext.cat[] to convert an image stored in a canvas into a rich-text table, or use any other method of constructing it on the fly.

Suppose you have a pair of fields named "op1" and "op2". Their "value" attribute is their rich-text, which is represented in Lil as a table of text runs and attributes. It is important to wrap each table in a list with the "list" primitive like so; otherwise the comma operator will join the rows of those tables and make all the options in the prompt "stick together":

dd.ask[
 "Some Question"
 (list op1.value),(list op2.value)
] 

(2 edits)

To de-mystify a little, when Dialogizer is displaying a dd.say[] modal, it's doing something similar to the following:

while !pointer.down
 blink_cursor[]
 deck.card.event["animate"]
 sleep[1]
end

The "animate" event which is being sent to the current card provides the opportunity to make other things happen while the dialog box is idling and waiting for user input. Puppeteer uses this hook to animate puppets, and it can be used to give other modules and contraptions a chance to do their thing as well.

Any "blocking" scripts you write yourself can use a similar approach; a synchronous animation script becomes responsible for explicitly routing the events to everything that needs to do work on every frame, whereas an asynchronous script allows Decker to handle some of that plumbing automatically.

Decker community · Posted in Fonts

As Millie says, Decker uses its own font format; adapting e.g. an existing TrueType font for Decker will require some manual work, and perhaps a bit of script-wrangling. Your first stop should probably be fontedit.deck, which includes a very simple font editor. As discussed quite recently, since this editor is a deck, you can feel free to extend and customize it to suit your needs.

I wrote an article some time ago which describes how one could build their own DIY importer for pre-existing bitmapped fonts.

The structure Decker uses to represent fonts is described in The Decker Document Format, but this information is only necessary if you wanted to produce Decker fonts from some external tool. The Font Interface in the Decker Reference Manual describes the APIs for manipulating fonts on the fly from within Decker.

absolutely wild aesthetic; very creative!

Just in case it helps, the Decker reference manual has a section about scripted animation that describes "synchronous" vs. "asynchronous" approaches and the tradeoffs therein. I also recently wrote an article about making action games in Decker that talks about similar issues through a more practical/applied lens.

If you haven't seen it previously, Learn Lil in 10 Minutes is a fast-paced overview of most of Lil's features targeted at folks who already have some exposure to other programming languages.

(1 edit)

A slightly simpler equivalent would be to use an "each" loop:

each i in range 96
 f[i]:f[i].translate[0,1 1]
end

If "f" were a list of Images, it would be possible to use ".." notation to manipulate every element in-place in the same way:

f..translate[0,1 1]

But this notation cannot be applied to an Interface type like a Font to implicitly iterate over every glyph. Alas!

(1 edit)

I just corrected the inconsistency in how the modal fill/stroke palettes draw the first swatch while in transparency mask mode; the fix will be included in the next Decker release; probably this Friday. The behavior of the toolbar is as intended.

Well done. The nonlinear navigation structure is a visceral way of representing how all these issues and thoughts are inter-related and knotted together.

Have you considered also submitting this piece to Dec(k)-Month?