Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
Tags

Internet Janitor

239
Posts
18
Topics
329
Followers
3
Following
A member registered Aug 02, 2018 · View creator page →

Creator of

Recent community posts

A brief followup: v1.20 slightly modified the behavior of "range", requiring an adjustment to the above script:

When looking up a card by index, instead of

deck.cards[(range deck.cards)[index]]

The "range" operator can now be used more directly:

(range deck.cards)[index]

The most comprehensive resource is The Lil Reference Manual. You might also find The Lil Reference Card handy.

The Decker Guided Tour includes some simple examples with explanations, as does the Decker Reference Manual. The Decker Reference manual includes descriptions of all the "interfaces" Decker provides for interacting with decks, widgets, and other goodies.

There are also a number of examples and walkthroughs in threads on this forum, like this thread where I walk through several approaches for implementing an adventure game inventory system.

If there's something in particular you'd like help with, feel free to post more specific questions!

Unfortunately, this is a bit difficult. Decker and Lil do not include Unicode support, as it would add considerable complexity throughout the application, especially in the text layout routines and the fonts shipped with with every standalone deck. This was a tradeoff I made to keep the scope of the project within my own limited means to develop it.

It is possible to create custom fonts that remap ASCII characters to a different set of glyphs- this deck, for example, uses a customized font to display some text using the Cyrillic alphabet, and I know of at least one person tinkering with a Hiragana font. Rich text fields support inline images, which offers a (somewhat clumsy) solution to needing a specific additional character or two. The biggest downside to the customized font approach is text entry: Decker has no way of knowing how to map non-ASCII characters into the glyph set of a given font. Depending on how different your language's character set and/or keyboard layout is from US-ASCII, it might be very unpleasant or confusing to type.

I apologize for the accessibility problems posed by these constraints. I think most improvements would be highly language-specific and possibly quite daunting in scope.

I don't have a mac running MacOS 13 to test with, but looks like it may be the zsh issue in Cosmopolitan. You can try executing it under "sh" or "bash":

% sh ./lilt-1.19.com
 range 10
(0,1,2,3,4,5,6,7,8,9)
%

Alternatively, it's reasonably straightforward to build Lilt from source on MacOS. Assuming you have a C compiler installed, after cloning the repository:

% make lilt

I think I'm going to bundle future releases of APE-Lilt with a readme.

(1 edit)

That's a fun idea- thank you for sharing!

A few suggestions:

  • Instead of using "," to join together long lists of string literals, you could use "split" on a single string. This is more concise, but also a bit more efficient at runtime, since Lil does not (currently) pre-evaluate constant expressions. The following lines are equivalent:
("A","B","CD","EFG","H","I")
"|" split "A|B|CD|EFG|H|I"
  • If the random[] function is given a single list argument, it will select a single value from that list, whereas specifying a "1" as the second argument will produce a list of length one. The former case appears to be closer to what you want. The following lines are equivalent:
sum random[(1,2,2,3,4) 1]
first random[(1,2,2,3,4) 1]
random[(1,2,2,3,4)]
random[1,2,2,3,4]
  • We don't strictly need to "fuse" in both syl[] and word[]; the string-cast of a list of strings is their concatenation, so a single fuse at the top level will collapse nested lists-of-lists-of-strings:
   (list "A","B"),(list "C","DEE","EFF") 
(("A","B"),("C","DEE","EFF"))
   "" fuse (list "A","B"),(list "C","DEE","EFF")
"ABCDEEEFF"
  • We can simplify slightly by referencing cons/vow in syl[] as globals, instead of threading them through word[]/syl[] as arguments. Once syl[] doesn't take any arguments, we can observe that the "@" operator is a shorthand for the loop in word[]. The following lines are equivalent:
each x in i syl[] end
syl @ i

All together, I think I might factor this program as follows:


A followup: v1.19 includes a soft-keyboard mode and several other small UI adaptations on devices which produce touch input events, which makes all of Decker's functionality available on keyboard-less devices like tablets.

Currently the outlook seems poor.

As I understand it, the current solutions are either Chromium-specific or Firefox-specific, with no Safari support whatsoever. I'm not interested in adding features that can only work in bleeding-edge browsers, especially if that browser is Chrome.

Worse, most new web APIs are gated behind the requirement for a "secure context", which demands that the page itself be served via HTTPS and not accessed from a local filesystem. This severely limits what I can use in a standalone single-document HTML build of Decker.

If, in the future, a portable API becomes broadly available for saving in place, I'd be delighted to add support.

That's really slick!

I have a  few thoughts on handling subtitles. In your script, you use an if-elseif chain to assign the values to your subtitle field:


This totally works, and is perhaps the most straightforward approach possible! One alternative Lil offers is to take advantage of the fact that conditionals are expressions to cut down on repetition, like so:


If none of the elseif clauses match, the implicit "else" will evaluate to the number 0. The "unless" operator returns its left hand side "unless" its right hand side is not 0, which lets us keep the field value the same unless there's a new caption available.

Another alternative would be to build a dictionary of captions keyed by their trigger frame and index into it:


Of course, what might be even better than defining a dictionary in a bunch of code could be to store it in a grid! By converting the caption dictionary into a CSV representation (including a header row naming our columns "f" and "c" respectively):

f,c
0,
46,"Hi, trying another"
72,"Decker video experiment."
117,"So, if all goes well,"
140,"this should be much better"
167,"video quality than the last time"
221,"and we should have subtitles!"
278,

We can make a grid widget called "subs" with column formats "is" to indicate we have an Integer column (i) followed by a String column (s):

And then build our dictionary by gluing together the "f" and "c" columns of that table using the "dict" operator:


Or, being slightly more verbose, we could even phrase that as a query if we wanted:


The grid widget storing this metadata can be made invisible or tucked away on a different card if desired.

All of these approaches have their merits, of course. The best script is the one that makes you happiest!

(1 edit)

This is unhinged, in the best way!

You might be able to get better image quality by using ffmpeg to explode the video into individual frames and then performing the dither on that batch of images using imagemagick, like I described elsewhere, before fusing them back together into a GIF.

I'm really looking forward to seeing where you go with this idea.

Glad you're enjoying Decker!

If you save a deck (File -> Save), it preserves the contents of every widget and card. In some cases, this is all you need. This may be a bit inconvenient on the web, because web-decker cannot re-save decks in-place.

In a Lil script, you can use the "write[]" function to prompt the user to save images, sounds, text files, or arbitrary binary files, and later use the "read[]" function to request that the user choose a file to import. You could certainly use this functionality to add persistence to your decks, as well as interacting with an interesting range of existing file formats. See Built In Functions in the Decker reference manual for details.

Both of the above require some human intervention for saving/restoring data. I'm a bit hesitant to expose browser localStorage to web-decker, as it can be a very brittle place for storing information and it's unclear how to give native-decker functional parity. However, you could go rogue and tweak an .html build of your own decks to insert such functionality!

Starting from an .html deck, open it in your favorite text editor, search for a line that reads "primitives=(env,deck)=>{", and add the following to the function body:

 env.local('localstorage',lmnat(([k,v])=>{
  if(v){try{localStorage.setItem(ls(k),ls(v))                     }catch(e){};return v}
  else {try{const v=localStorage.getItem(ls(k));if(v)return lms(v)}catch(e){};return NONE}
 }))

This introduces a new built-in function to Lil, "localstorage[]", that can be called with one or two arguments, to read a localstorage key or write one, respectively:


(Naturally, this functionality won't work if your deck is loaded into native-decker, or some other instance of web-decker that does not contain the above modifications.)

Does any of that help?

Thank you for explaining your issues in more detail!

In Decker, the random seed is pre-initialized to a random value. In Lilt, it is consistently seeded out of the box. From the Lilt Documentation:

Choose y random elements from x. In Lilt, sys.seed is always pre-initialized to a constant.

The idea behind this is that Lilt behaves deterministically until you explicitly want randomness, which simplifies testing. This is also how random numbers have always worked in the K interpreter. I admit that this might be a footgun for the unwary, so I'll give changing the default some thought. For now, you can initialize the RNG with something like 

sys.seed:sys.ms

I gave your argument-parsing example a spin, and it works as expected for me. The "elseif" construct was introduced in Decker/Lilt version 1.16. In older versions of Lilt, "elseif" was not a keyword, so it would have just been a reference to an undefined variable with a value of 0, and then the predicate would similarly have been evaluated and then discarded. This explains why you're seeing the code behave as if the 'elseif args[2] ~ "-m"' line doesn't exist.

If you're using 1.7, please rebuild from source or download a fresh executable- there have been many improvements, bugfixes and additions in the last 9 releases. :)

(1 edit)

If you can recall any specific hairy edges, please share! Without user feedback, the only use-cases I've had to guide improvements are the things I've done myself, like using Lilt to re-slice images for gamedev projects, writing test fixtures, doing bulk image-import, and the occasional code-golf puzzle.

A nice, simple one this time-

The "enum" Contraption:


Works just like a "compact" slider widget, but instead of a numeric value within a range, it chooses a string from a list of newline-delimited "options". This widget respects the "font", "show", and "locked" properties, and produces a "change[]" event just like a slider.

%%WGT0{"w":[{"name":"enum1","type":"contraption","size":[100,25],"pos":[289,76],"script":"on change val do\n \nend","def":"enum","widgets":{"s":{"interval":[0,2],"format":"one"},"o":{"value":"one\ntwo\nthree"}}}],"d":{"enum":{"name":"enum","size":[100,25],"resizable":1,"margin":[5,5,5,5],"description":"select from among enumerated string values.","script":"on get_options   do o.text end\non get_value     do (\"\\n\" split o.text)[s.value] end\non set_options x do o.text:x s.value:0 view[] end\n\non set_value x do\n v:\"\\n\" split o.text\n d:v dict range count v\n s.interval:0,(count v)-1\n s.value:v[0] unless d[x]\n s.format:v[s.value]\n s.font:card.font\n s.show:card.show\n s.locked:card.locked\nend\n\non change do\n view[]\n card.event[\"change\" get_value[]]\nend\n\non view do\n v:\"\\n\" split o.text\n s.interval:0,(count v)-1\n s.format:v[s.value]\n s.font:card.font\n s.show:card.show\n s.locked:card.locked\nend\n","template":"on change val do\n \nend","attributes":{"name":["options"],"label":["Options"],"type":["code"]},"widgets":{"s":{"type":"slider","size":[100,25],"pos":[0,0],"interval":[0,100],"style":"compact"},"o":{"type":"field","size":[100,20],"pos":[0,-30],"locked":1,"style":"plain"}}}}}

Could you elaborate upon what you mean by "Decker-like"?

Lilt is a command-line interpreter for Lil, and it is like Decker in the sense that it's Decker's scripting language, it's highly portable, and it has a small API of useful bits and pieces that fits comfortably inside your head at once.

Do you instead mean something more visual and drag-and-drop, from the perspective of editing CLI apps, using them, or both?

Decker community · Created a new topic How do you use Lilt?

Since v1.9, I have provided APE builds of Lilt, the standalone command-line Lil interpreter, as a companion application to Decker.

It seems to get a moderate number of downloads for each release, and I'm curious to hear how it's being used by you folks. Are you using it to learn Lil? To automate the creation of decks? Did you download it by accident? Are there features you find particularly useful, or features you wish it had?

Your thoughts and experiences could be very useful for shaping my future work on this tool.

Looks like you have a typo of ':' instead of '.' in your grayscale import script. Try this:

on click do
 # make 1-bit dithered image
 i:read["image" "gray"]
 r:image[c.size]
 r.paste[i 0,0,r.size]
 r.transform["dither"]
 c.paste[r]
end

Sometimes it's helpful to try things step-by-step in the Listener when they don't seem to work properly:

As for image export, you're only copying from the card's background image, which does not include the contents of any canvases stacked on top:

on click do
 write[card.image.copy[c.pos c.size]]
end

If you want to export the image, you need to "copy[]" from the canvas:

on click do
 write[c.copy[]]
end

And as in the examples for The Ornamented Ovum, you may want to map pattern 0 (transparent) to pattern 32 (opaque white) before exporting:

on click do
 write[c.copy[].map[0 dict 32]]
end

(1 edit)

Yes. Just as it is possible to export images with the "write[]" function, you can import images with "read[]", which takes a few arguments to specify what type of file you wish to open, and how you wish to interpret it. The documentation for this is near the bottom of  the Built-In Functions section of the manual.

As an example, let's say we have a card with a canvas named "c" and a button with a script like so:


The default behavior for importing an image is to posterize it to Decker's 16-color palette. If we want a dithered image, we need to read it in grayscale and resize it (in this case using another image as a scratchpad) before we perform the dithering for best quality:


In these examples I'm resizing the image to fit the dimensions of the canvas, which slightly vertically squishes the image of Pippi the hen. Depending on your specific needs you might also choose to resize the image and/or the canvas proportionally, letterbox or crop the image.

It's also perhaps worth noting that Decker is capable of decoding the individual frames of animated GIF images, so it's possible to make a variety of animation player/viewer applications. The Bazaar has some examples. Likewise, it's also possible to export animated GIFs with "write[]".

Does that give you a useful starting point?

No trouble at all. Glad I could help!

If you want to manually put an image on a canvas widget:

  1. draw the image somewhere.
  2. use the selection or lasso tool to select the image, and copy it to the clipboard.
  3. switch to widget mode and select the canvas widget.
  4. choose Edit -> Paste Into Canvas.


You may want to lock the canvas once you've pasted an image into it. (Widgets -> Locked). The default behavior of a non-draggable canvas widget is to allow the user to scribble on it in interact mode; locking the canvas disables this.

If the destination canvas doesn't already exist, and you have an image in the clipboard, you can use Edit -> Paste as new Canvas to make a new canvas containing the image, which will be locked by default.

If you want to put an image into a canvas using a script (or interactively at the Listener), you can use "canvas.paste[]"- see the Canvas Interface documentation.

Does that clear things up?

@eoin2: v1.1 has a "Save Photo!" button, along with some goofy new parts. I did a little writeup in the Decker forum if you wanted some details on the scripting side of things. :)

Eoin2 asked how we might add a button to take a screenshot of a decorated egg. Let's work through it step-by-step!

I've added an invisible canvas named "outline" to the card. This will both allow us to define a rectangular portion of the card to export (rather than the entire card) and give us a scratchpad for compositing:


As a starting point, let's look at writing out images in general, with the following script on a button:


Running this script will prompt the user to save an image, which will contain the entire background of the current card. Let's instead "copy[]" from the card background, based on the dimensions of the "outline" canvas:


Pattern 0 (which normally looks white in a card background) is transparent in the output image. If we want it to be opaque, we can map[] this pattern to 32 ("opaque white") before writing the image out:


OK, we can save parts of the card background. What about our egg, and everything else on the card?

One approach is to draw all the widgets of the card on our canvas in sequence, back-to-front, and offset to reflect the position of "outline". Drawing fields is a bit trickier than canvases, but we don't need to handle every possible option here:


Another approach entirely is to (ab)use a custom transition function. Transition functions are given a rendered image of the entire card (and the destination card) to mix together, so we can perform a "fake" transition that stashes our card image for safekeeping and then, as a flourish, draws a little shutter animation. When the transition completes, we save the piece of the card image we're interested in.

One complication here is that editable fields will be drawn in a "disabled" state while transition animations are played. As long as we lock the "name" field before the transition and restore it afterward, no one will be the wiser.


I went with the second approach, mainly for the visual pizzaz.

And there you have it, easy egg-exports! If anyone has further questions I'm happy to clarify.


Sure! The "write[]" function can programmatically export images (or even animations), and there are also ways to composite together and crop images with scripts. I'll see about working up an example and maybe a few more goodies this evening when I have time.

Decker community · Created a new topic The Ornamented Ovum

I felt inspired this evening to make a silly little egg-decorating sandbox, The Ornamented Ovum:

Show me your best eggs!

If anyone is interested in making parts for their own paper-doll kits with Decker, all you need to do is:

  1. Draw something.
  2. If necessary, switch to "Transparency Mask" mode and fill/paint any areas that need to be opaque white.
  3. Select your drawing and copy it to the clipboard.
  4. Switch to the widget tool and choose "Edit -> Paste as new Canvas".
  5. Make your new canvas widget borderless, draggable, and shown transparent.

All together:


Lather, rinse, and repeat for each part. If you want to borrow pieces from The Ornamented Ovum, don't forget you can copy and paste widgets between decks!

When you save a deck as a ".html" file, it's just a document sitting on your computer. If you want other people to be able to access it from their own computers, you'll probably want to upload it somewhere.

One option is to create a page for your deck here on Itch.io. They have documentation describing how to embed an HTML5 game (in this case, your .html deck) on an Itch project page.

Another option is to use a web host like neocities. You can create a whole website of your very own, for free!

And of course, you don't necessarily have to put a deck on the web at all to share it. You could email the .html file to your friends, or even write it to a floppy disk and physically mail it to somebody as a surprise. Anyone with a reasonably modern web browser can open a decker .html file on their own computer.

Does that help clear things up?

Update: this fix is included in v1.16. Thanks again for the report, micpp!

Well, shoot.

It's a very simple bounds-checking error; a careless oversight on my part. I have pushed a fix to the GitHub repository.

Thanks for bringing this to my attention, and apologies to anyone inconvenienced by this issue.

in v1.15, you can place a deck named "start.deck" in the same directory as the executable of native-decker and it will take precedence over the built-in "guided tour" deck. For details: https://beyondloom.com/decker/decker.html#startup

I've been slowly improving the performance of both web- and native-Decker since v1.0, and I'm pleased to announce that I've passed an interesting milestone: native-Decker is now entirely usable on my favorite Bim*, the OLPC XO-4:


The XO-4 is a rugged and repairable 1ghz ArmV7 machine that ships with an obsolete version of Fedora 18. I love it dearly.

The latest head revision of the Decker Repository can be built from source with the GCC and Libc that are available in standard OLPC OS images. If anyone else has one of these machines I'd be happy to provide more detailed build instructions.

I suspect that many other inexpensive Linux-based SoCs can now handle Decker as well. Happy hacking!


(* a small computer which is my friend)

If you take her out and she orders the lobster, remember that you can deduct dinner as a business expense. You'll need to retain a copy of the receipt for your records.

I greatly enjoyed exploring this zine and tracking down the secrets. The whole project radiates creativity and feels like going on an adventure. Well done!

read[] of GIF frames was introduced today in v1.14, along with some new example contraptions using it to play animation clips on cards:


(1 edit)

The v1.14 release introduced a generalization for read[], allowing scripts to break an animated GIF image down into frames. There are many interesting ways to take advantage of this functionality. For example,

The "gif" Contraption:

At last, an easy way to import animated GIFs into decks! This contraption prompts the user to select a GIF file, unpacks and dithers it to 1-bit, automatically resizes itself to match the size of the image, and then loops the frames at 30fps:

Note that using GIF widgets can quickly expand the size of your decks- use them sparingly, and avoid importing huge or overly-long animations! Decker's GIF loader is brand-new and may have some quirks to hammer out, so remember to save often while playing with this feature.

%%WGT0{"w":[{"name":"gif1","type":"contraption","size":[100,100],"pos":[206,121],"def":"gif","widgets":{"c":{},"f":{},"b":{}}}],"d":{"gif":{"name":"gif","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"Import and play animated gifs. Careful: huge gifs can quickly bloat the size of your deck!","script":"on view do\n fr:extract arg where arg..type=\"image\" from f.value\n c.show:card.show\n c.clear[]\n if count fr\n  b.show:\"none\"\n  i:fr[(count fr)%sys.ms/2*60]\n  card.size:i.size\n  c.paste[i]\n else\n  b.show:\"solid\"\n end\nend","widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"border":0,"scale":1},"f":{"type":"field","size":[73,35],"pos":[8,-50],"locked":1},"b":{"type":"button","size":[69,20],"pos":[16,40],"script":"on click do\n g:\"[255,0,255,246,148,108,132,51,61,147,144,86,66,111,185,134,69,0]\"\n grays:(0,1,32+range 16) dict \"%j\" parse g\n f.value:raze each i in read[\"image\" \"frames\"].frames\n  rtext.make[\"\" \"\" i.map[grays].transform[\"dither\"]]\n end\n view[]\nend","text":"Open Gif..."}}}}}

The "scrubber" Contraption:

This contraption doesn't animate automatically; instead, it allows a user to manually scrub back and forth through the frames of the animation. It also demonstrates importing color images (resampled to the Decker 16-color palette) instead of 1-bit dithering.

%%WGT0{"w":[{"name":"scrubber1","type":"contraption","size":[100,100],"pos":[49,108],"def":"scrubber","widgets":{"c":{},"f":{},"b":{},"s":{}}}],"d":{"scrubber":{"name":"scrubber","size":[100,100],"resizable":1,"margin":[0,0,0,19],"description":"Import animated gifs and allow the user to manually scrub through the frames. Careful: huge gifs can quickly bloat the size of your deck!","script":"on change do\n view[]\nend\n\non view do\n fr:extract arg where arg..type=\"image\" from f.value\n s.interval:0,(count fr)-1\n c.show:card.show\n c.clear[]\n if count fr\n  b.show:\"none\"\n  i:fr[s.value]\n  card.size:i.size+s.size*0,1\n  c.paste[i]\n else\n  b.show:\"solid\"\n end\nend","widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"border":1,"scale":1},"f":{"type":"field","size":[73,35],"pos":[8,-50],"locked":1},"b":{"type":"button","size":[69,20],"pos":[16,40],"script":"on click do\n f.value:raze each i in read[\"image\" \"frames\"].frames\n  rtext.make[\"\" \"\" i]\n end\n view[]\nend","text":"Open Gif..."},"s":{"type":"slider","size":[100,17],"pos":[0,83],"interval":[-1,0]}}}}}

There are many variations possible on the above ideas that you might want, like looping back-and-forth, rescaling imported images to fit the bounding box of the contraption, or firing events to drive other animations. Feel free to share your own takes!

Hey kid, want a chicken? Chickens float. They all float when you're using

The "bob" Contraption:

Works much like the "eye" contraption: copy and paste a (probably transparent) image into the properties panel of the bob widget and your image will hover ominously above a shadow. Adjust the size of the contraption to control how vigorously the object bobs.

%%WGT0{"w":[{"name":"bob1","type":"contraption","size":[100,126],"pos":[305,100],"def":"bob","widgets":{"bg":{"size":[100,126],"image":"%%IMG2AGQAfgD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wDpAQ8ATwEbAEYBIQBBASUAPgEnAD0BJwA+ASUAQQEhAEcBGQBRAQ0A/wD/AOk="},"spr":{"size":[51,91],"pos":[24,20]},"img":{"value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2ADMAWwDvIAQALyADAQIALSADAQEgAwAqIAYBAiABACggAwEBIAQBASACACYgBwEBIAIBASACAQEAJCADAQEgBQECIAIBASABACIgAwEBIAIBASAFAQEgAgEBIAEAIiAFAQEgAwECIAIBAiABAQEgAQAgIAEBASAFAQEgAgEBIAEBAiACAQEgAgAgIAQBAyABAQEgCAEBIAEAHyAEAQEgAwEBIAIBASABAQEgAQECIAIBAQAfIAEBASABAQIgAQECIAEBASABAQIgBAEBIAMAHiADAQIgAgEEIAIBBCAEAB0gAgECIAEBASACAQEgAwEBIAEBAyACACABASADAQEgAQECIAEBASABAQEgAQEBIAIBAgAgIAMBASACAQIgAgEDIAIBAyABAQEAHyABAQEgAgEFIAEBAyABAQEgAgECIAEAHwECIAEBAiABAQEgAgEEIAIBBCABAB8gAgEDIAEBCyABAQIAHgECIAEBBSABAQogAQEBAB4gAgEFIAEBASABAQogAQAeIAEBFAAeIAEBCCABAQYgAQEEAB0BCCABAQogAQECABsBASACAQMgAQEGIAEBCgAZAQEgAgECIAEBCiABAQMgAQEBIAIBAgAXAQEgAwEBIAEBAiABAQ0gAQEEIAEAFiAFAQEgAQETIAIBAQAVIAcBBSABARAgAQAVIAEBASABAQEgAwEBIAEBFgATIAEBASABAQIgAwEBIAEBASABAQggAQELABIgAwECIAIBAiABAQEgAgEDIAEBEQAQAQEgAgEBIAEBASACAQMgAQEKIAEBDAAPAQEgAQECIAEBASABAQIgAgEEIAEBASACAQIgAQEOAA8BASACAQIgAQEBIAEBASABAQMgAQEHIAEBByABAQYgAQAOIAEBAyABAQEgAQEEIAEBAiABAQogAQELIAEADQEBIAEBASADAQEgAgEHIAEBFCABAA0gAQEBIAEBASABAQIgAgEdIAEADAEDIAEBASABAQMgAQEBIAEBGSABAQEADAEDIAIBASABAQcgAQEWIAEBAgALAQEgAgEjIAEBAQALAQwgAQEaAAwBJwAMARkgAQEMAA0BJgANAQEgAQEkAAwBBCABASIACwEDIAEBIwAMIAEBJSABAAwBASACASQACwECIAIBIwAMAScADAECIAEBJAAMIAEBAiABASMADAEVIAEBEAANASYADQEFIAEBIAAOASUADgEUIAEBDwAQASMAEQEhABMgAQEQIAEBDQAVIAMBCyABAQ8AGQEYABsgAQEWAB0gAQETACABDiABAQMAISABAQIAAiABAQUABQECACEgAQECAA0BAwAgIAEBAgAOAQIAIQEDAA0gAQECACAgAQECAA0gAQECACAgAQECAA0gAQEDAB4BBQAMIAEBBAAcIAEBBgAKAQcAGyABAQMgAQEDAAggAQEIABoBAwACIAEBAgAHIAEBAyABAQYAGQEDAAIgAQEDAAYgAQEDAAEBAyABAQMAGAECAAQBAwAGIAEBAgACAQIAAiABAQIAGCABAAUBAgAHIAEBAgACAQIAAwECAB4BAgAIIAEAAiABAQIAAwECAB4BAgAMAQIAAwECAB8BAQALIAEBAgAkAQEACyABAQIAMCABAQEAMSACABA="]}}}}],"d":{"bob":{"name":"bob","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"Animate an object bobbing in midair with a shadow.","script":"on get_object do img.value end\non set_object x do img.value:x view[] end\n\non oval pos size do\n flip pos+size*flip unit 2*pi*(range 20)/20\nend\n\non view do\n i:extract first arg where arg..type=\"image\" from get_object[]\n spr.size:i.size\n spr.clear[]\n spr.paste[i]\n \n bh:.9*bg.size[1]\n sh:spr.size[1]\n sy:.5*bh-sh\n t:(card.index)+sys.ms*0.002\n spr.pos:(.5*bg.size[0]-spr.size[0]),sy+sy*sin t\n\n bg.clear[]\n bg.poly[oval[bg.size*.5,.9 (.4+.2*1+sin t)*(.5*spr.size[0]),.05*bg.size[1]]]\nend","attributes":{"name":["object"],"label":["Object"],"type":["rich"]},"widgets":{"bg":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"show":"transparent","border":0,"scale":1},"spr":{"type":"canvas","size":[23,23],"pos":[38,63],"locked":1,"animated":1,"show":"transparent","border":0,"scale":1},"img":{"type":"field","size":[26,23],"pos":[0,-46],"locked":1,"value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2ABcAFwBIAREABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgERAEg="]}}}}}}

Decker is mainly intended for desktop computers. Since Decker is distributed "unsigned" outside an "App store", it may be necessary to make an exception to security settings to run the native application.

I have plans to make improvements in the future which will provide soft-keyboard support for touch devices, but at present a physical keyboard is necessary for some functionality. Sorry for the inconvenience.

Behold the beauty of

The eye contraption:

A configurable pupil image follows the pointer around the card, constrained by an oval inscribed within the contraption's bounding box. In most cases, you'll want this contraption to be shown in "transparent" mode, with the rest of the eyeball drawn on the card background, but there are also interesting possibilities for placing eyes behind partially-transparent canvases. (Note: make sure you upgrade to Decker v1.13 before using this contraption!)

To specify the pupil image, copy an image using the "select" tool and paste it into the "pupil image" rich-text field in the "Eye Properties" panel as shown above. You may want to use the "View -> Transparency Mask" setting to make parts of the pupil opaque white.

%%WGT0{"w":[{"name":"eye1","type":"contraption","size":[100,100],"pos":[88,220],"show":"transparent","def":"eye","widgets":{"pupil":{},"img":{}}}],"d":{"eye":{"name":"eye","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"An animated eyeball that follows the user's pointer around the card. Best shown \"transparent\" with a blank eye underneath.","script":"on get_pupil do\n img.value\nend\n\non set_pupil x do\n img.value:x\n view[]\nend\n\non view do\n i:extract first arg where arg..type=\"image\" from img.value\n pupil.size:i.size\n pupil.clear[]\n pupil.paste[i 0,0 1]\n \n c:card.size/2\n p:pupil.size/2\n d:pointer.pos-card.offset+c\n m:(c-p)&mag d\n pupil.pos:(c-p)+m*unit heading d\nend","attributes":{"name":["pupil"],"label":["Pupil\nImage"],"type":["rich"]},"widgets":{"pupil":{"type":"canvas","size":[22,23],"pos":[110,44],"locked":1,"animated":1,"show":"transparent","border":0,"image":"%%IMG0ABYAFwAAAAH8AAf/AA//gB//wD//4H//8H//8P//+P//+P//+P//+P//+P//+P//+H//8H//8D//4B//wA//gAf/AAH8AAAAAA==","scale":1},"img":{"type":"field","size":[27,28],"pos":[-47,-56],"locked":1,"border":0,"value":{"text":["","i"],"font":["",""],"arg":["","%%IMG0ABYAFwAAAAH8AAf/AA//gB//wD//4H//8H//8P//+P//+P//+P//+P//+P//+P//+H//8H//8D//4B//wA//gAf/AAH8AAAAAA=="]}}}}}}

Update: in v1.13 I overhauled web-decker's timing loop. Results should be much more consistent with native-decker. I also slipped in a few minor performance enhancements. The downside is that on slow machines, dropped frames will cause some animation to look choppier, and particularly expensive transition animations like the "Heart Wipe" from Public Transit will only render a handful of frames.

Now Serving,

The EggTimer Contraption

A countdown timer for a configurable number of seconds, firing a "finish[]" event and inverting in color when it completes. Clicking a second time resets the countdown.

%%WGT0{"w":[{"name":"eggtimer1","type":"contraption","size":[60,20],"pos":[226,161],"def":"eggtimer","widgets":{"b":{},"s":{},"a":{}}}],"d":{"eggtimer":{"name":"eggtimer","size":[60,20],"resizable":1,"margin":[5,5,5,5],"description":"a configurable countdown timer.","script":"on get_seconds do 0+s.text end\non set_seconds x do s.text:x end\n\non view do\n b.font:card.font\n if b.animated\n  e:(sys.ms-a.text)/1000\n  b.text:r:floor 0|get_seconds[]-e\n  if r=0\n   if b.show=\"solid\" card.event[\"finish\"] end\n   b.show:\"invert\"\n  end\n else\n  b.show:\"solid\"\n  b.text:s.text\n end\nend\n\non click do\n a.text:sys.ms\n b.animated:!b.animated\n view[]\nend","template":"on finish do\n \nend","attributes":{"name":["seconds"],"label":["Seconds"],"type":["number"]},"widgets":{"b":{"type":"button","size":[60,20],"pos":[0,0],"font":"body","text":"10"},"s":{"type":"field","size":[52,18],"pos":[4,-81],"locked":1,"style":"plain","value":"10"},"a":{"type":"field","size":[53,20],"pos":[3,-57],"locked":1,"style":"plain"}}}}}

That's a great question! There are several ways to tackle this, so let's go through it step by step.

To begin with, let's say we have a card with a normal button "chester" and a checkbox "frobnicate":


If we wanted clicking on chester to toggle frobnicate, we'd write a script for him like so:

on click do
 frobnicate.value:!frobnicate.value
end

We're able to write code like this because, from the perspective of a script on a widget, all the widgets on the same card are available in Lil variables corresponding to their name; in this case "frobnicate".

The same is true of cards: every card in the deck is available in a variable corresponding to its name. If the card we're on is named "home", we could also write the above script like this:

on click do
 home.widgets.frobnicate.value:!home.widgets.frobnicate.value
end

Or, to be a bit less repetitive,

on click do
 f:home.widgets.frobnicate
 f.value:!f.value
end

The above script will now work even if "chester" is on a different card from "frobnicate" entirely: what matters is that "frobnicate" is a widget on the card named "home". If we wanted to be really explicit, we could look up the card in "deck" (another "magic variable"), and then look up the widget in that card:

on click do
 f:deck.cards.home.widgets.frobnicate
 f.value:!f.value
end

Side track on naming: All the names of cards and widgets we've seen so far are also valid variable names in Lil: all one word with no spaces, composed of letters, digits, and underscores, and they don't start with a digit. If names aren't valid Lil variable names, like if our card was named "My Fantastic Card", we would have to use bracket-indexing with a string into deck.cards any time we wanted to reference it:

on click do
 f:deck.cards["My Fantastic Card"].widgets.frobnicate
 f.value:!f.value
end

Prefer Lil-compatible names for things you expect to interact with in scripts!

We now know how to reference widgets on a distant card, so we have the building blocks necessary for an inventory system. For a simple game that only has a handful of possible inventory items, it might be easiest to represent each possible item with an individual widget on an "inventory" card. Perhaps they're locked checkbox widgets, toggled with the .value attribute as in our previous examples, or perhaps they're canvas widgets with a default .show property of "none" that appear when you've picked up the item:

inventory.widgets.sworde.show:"solid"
alert["ye have acquired ye sworde!"]
...
if inventory.widgets.sworde.show="solid"
 alert["ye slaye yon wickede beaste withe ye sworde!"]
 alert["forsooth, ye sworde is all gross and sticky, so we shall discarde it."]
 inventory.widgets.sworde.show:"none"
 go["slayed beaste"]
else
 alert["alas, without ye sworde, yon wickede beaste hast devoured thou!"]
 go["game over"]
end

If you have a lot of items, using a grid might be desirable instead. Let's say the grid on our inventory card is named "items" and it initially contains an empty table with a single column, "name":


Now we'll need to manipulate the table inside our grid to pick up items or test for their presence. Our script above might now look something like:

i:inventory.widgets.items
i.value:insert name:"sworde" into i.value
alert["ye have acquired ye sworde!"]
...
i:inventory.widgets.items
if "sworde" in i.value.name
 alert["ye slaye yon wickede beaste withe ye sworde!"]
 alert["forsooth, ye sworde is all gross and sticky, so we shall discarde it."]
 i.value:select where !name="sworde" from i.value
 go["slayed beaste"]
else
 alert["alas, without ye sworde, yon wickede beaste hast devoured thou!"]
 go["game over"]
end

If we're manipulating an inventory frequently throughout our deck, it might be a good idea to factor some of this logic out into "Deck-level" functions. Functions you define in a Deck script can be called from any other script. While we're at it, we can define a function for resetting the game to its initial state. You can set Deck scripts via "File -> Properties... -> Script...":

on get_items do
 inventory.widgets.items
end
on add_item n do
 i:get_items[]
 i.value:insert name:n into i.value
end
on remove_item n do
 i:get_items[]
 i.value:select where !name=n from i.value
end
on has_item n do
 n in get_items[].value.name
end
on reset_game do
 i:get_items[]
 i.value:0 take i.value
end

With these new utility functions we could rewrite our game scripts once again to be more concise and easier to understand:

add_item["sworde"]
alert["ye have acquired ye sworde!"]
...
if has_item["sworde"]
 alert["ye slaye yon wickede beaste withe ye sworde!"]
 alert["forsooth, ye sworde is all gross and sticky, so we shall discarde it."]
 remove_item["sworde"]
 go["slayed beaste"]
else
 alert["alas, without ye sworde, yon wickede beaste hast devoured thou!"]
 go["game over"]
end

And we can also test and manipulate the inventory using these functions in the Listener:


Does that point you in the right direction?

(1 edit)

Copy the code starting at the "%%WGT" part and ending with the last "}" to your clipboard, open Decker, and then choose "Edit -> Paste Widgets" (or just "Edit -> Paste" in web-decker) from the main menu.

The code blocks on this page are what it looks like when you copy and paste one or more widgets in Decker. When you copy a Contraption to the clipboard, it carries along the associated Prototype (contraption definition), which makes it possible to share them like this or easily copy and paste them between decks. The same idea applies to copying and pasting cards.

Contraptions were added in v1.12, so if you downloaded an older version of Decker, please make sure you upgrade!

Does that clear things up?

(1 edit)

Introducing

The "macWindow" contraption:

A resizable window frame that resembles System 6.

If you make the contraption transparent, you can draw underneath the frame. If you leave it "solid", you can overlap it on other windows. The title is configurable, and "close" and "resize" events are fired by clicking on the corner buttons.

While this contraption is not a "true" window, it can be a handy decoration or the beginnings of a more realistic simulacrum.

%%WGT0{"w":[{"name":"win","type":"contraption","size":[59,71],"pos":[211,143],"def":"macWindow","widgets":{"close":{},"resize":{},"title":{}}}],"d":{"macWindow":{"name":"macWindow","size":[59,71],"resizable":1,"margin":[22,34,33,33],"description":"a window frame in the style of MacOS 6.","script":"on get_title do title.text end\non set_title x do title.text:x end\n\non view do\n t:get_title[]\n if count t\n  title.font:\"menu\"\n  s:first title.font.textsize[t]\n  title.size:(s+10),title.size[1]\n  title.pos :(.5*card.size[0]-title.size[0]),title.pos[1]\n  title.show:\"solid\"\n else\n  title.show:\"none\"\n end\nend","template":"on close do\n \nend\n\non resize do\n \nend","image":"%%IMG2ADsARwE8IDgBAyA4AQMgOAEDIAEBBiABAQsgAQEQIAEBCyABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQEgBQEBIAMBASABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQEgBQEBIAMBASABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQcgAwEBIAEBBiABAQMgCAEBIAkBASASAQEgCQEBIAgBAyABAQYgAQEBIAkBASABARAgAQEBIAkBASABAQYgAQEDIAgBASAJAQEgEgEBIAkBASAIAQMgAQEGIAEBCyABARAgAQELIAEBBiABAQMgOAEDIDgBAyA4AT4AKQEBIA4BAwApAQEgBgEBIAcBAwApAQEgBQEBIAEBASAGAQMAKQEBIAQBASADAQEgBQEDACkBASADAQEgBQEBIAQBAwApAQEgAgEBIAcBASADAQMAKQEBIAEBASAJAQEgAgEDACkBBSAFAQQgAQEDACkBASADAQEgBQEBIAQBAwApAQEgAwEBIAUBASAEAQMAKQEBIAMBASAFAQEgBAEDACkBASADAQcgBAEDACkBASAOAQMAKQEBIA4BAwApARIAKQEBIA4BAwApAQEgDgEDACkBASAOAQMAKQEBIA4BAwApAQEgDgEDACkBEgApAQEgDgEDACkBASAOAQMAKQEBIAMBByAEAQMAKQEBIAMBASAFAQEgBAEDACkBASADAQEgBQEBIAQBAwApAQEgAwEBIAUBASAEAQMAKQEFIAUBBCABAQMAKQEBIAEBASAJAQEgAgEDACkBASACAQEgBwEBIAMBAwApAQEgAwEBIAUBASAEAQMAKQEBIAQBASADAQEgBQEDACkBASAFAQEgAQEBIAYBAwApAQEgBgEBIAcBAwApAQEgDgE+IAcBASAGAQEgCwEBIAYBASAHAQEgDgEDIAYBAiAGAQEgCwEBIAYBAiAGAQEgDgEDIAUBASABAQEgBgEBIAsBASAGAQEgAQEBIAUBASACAQcgBQEDIAQBASACAQUgAgEBIAsBASACAQUgAgEBIAQBASACAQEgBQEBIAUBAyADAQEgBwEBIAIBASALAQEgAgEBIAcBASADAQEgAgEBIAUBBSABAQMgAgEBIAgBASACAQEgCwEBIAIBASAIAQEgAgEBIAIBASAFAQEgAwEBIAEBAyABAQEgCQEBIAIBASALAQEgAgEBIAkBASABAQEgAgEBIAUBASADAQEgAQEDIAIBASAIAQEgAgEBIAsBASACAQEgCAEBIAIBASACAQEgBQEBIAMBASABAQMgAwEBIAcBASACAQEgCwEBIAIBASAHAQEgAwEBIAIBByADAQEgAQEDIAQBASACAQUgAgEBIAsBASACAQUgAgEBIAQBASAEAQEgBwEBIAEBAyAFAQEgAQEBIAYBASALAQEgBgEBIAEBASAFAQEgBAEBIAcBASABAQMgBgECIAYBASALAQEgBgECIAYBASAEAQEgBwEBIAEBAyAHAQEgBgEBIAsBASAGAQEgBwEBIAQBCSABAQMgDgEBIAsBASAOAQEgDgE9AAEBOg==","attributes":{"name":["title"],"label":["Title"],"type":["string"]},"widgets":{"close":{"type":"button","size":[11,11],"pos":[9,4],"script":"on click do\n card.event[\"close\"]\nend","style":"invisible"},"resize":{"type":"button","size":[11,11],"pos":[38,4],"script":"on click do\n card.event[\"resize\"]\nend","style":"invisible"},"title":{"type":"field","size":[44,17],"pos":[-50,1],"locked":1,"font":"menu","show":"none","border":0,"style":"plain","align":"center"}}}}}