Skip to main content

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

Lil Programming Questions Sticky

A topic by Internet Janitor created Oct 28, 2022 Views: 17,214 Replies: 346
Viewing posts 48 to 107 of 107 · Previous page · First page
(+1)

Hi! I've been having a fun time messing around in Decker, and am now attempting something more game-y.

Specifically, I'd like to know if these are possible to do:

  • When Button X is clicked, have Button Y on a different card disappear/become hidden
  • When Button Z is clicked, unlock new dialogue on a different card (with the Dialogizer module)
I'm assuming it would be something like, "on click do: Button X = true"—but I'm not sure exactly what terms to use, so I would appreciate any help. Thanks!
Developer(+1)

Widgets have an attribute called "show" which controls their visibility; this can be "solid" (the default), "invert" (an alternate color-scheme), "transparent", or "none".

Suppose the button X is on card C1 and the button Y is on card C2.

You could give X a script for toggling Y's visibility:

on click do
 C2.widgets.Y.toggle["solid"]
end

Or a simpler version that just makes Y visible:

on click do
 C2.widgets.Y.show:"solid"
end

There are several other examples of doing this sort of thing in this thread.

To make clicking a button "unlock" dialogue or behavior elsewhere in the deck, you'll need to remember that the button has been clicked, and consult that record at a later time.

Checkboxes are just a special visual appearance for a button, so every button widget has a "value" attribute that can store a single boolean (0 or 1) value. Thus, we can use the button itself to keep track of whether it's been clicked. Suppose you've given the button Z on card C1 a script like so:

on click do
 me.value:1
end

A script on another card could reset Z's value,

C1.widgets.Z.value:0

Or test its value in a conditional:

if C1.widgets.Z.value
  # do something special
end

If you have lots of "flags" like this, it may be a good idea to centralize them in some kind of "backstage" card so you can keep track of them easily, and perhaps to give yourself a script for resetting the state of the deck.

Does that make sense?

(+1)

Makes sense! Thank you for the detailed walkthrough, that was all very helpful :)

is it possible to have something special unlocked only when two buttons are checked at once?🤔...

Developer(+1)

That would be a logical "and", produced with the & operator:

if C1.widgets.Z.value & C1.widgets.W.value
 # ...
end

hey, I'm completely new to lil and decker and I can't crack the random command. I want to make a widget that sends me to a random card from a selection of say ten cards. is it even possible? I feel like it should be, but I just end up sending myself off to the home card. 

Developer

The "random[]" function can be called in several different ways to produce different kinds of random values. In your case, you want to select a random value from a list of possibilities and then supply that value to the "go[]" function.

The "go[]" function can navigate to cards by index (a number), by name (a string), or by value (a Card interface). We'll go with the latter. Within a script, all the cards of the deck are available as variables of the same name. Commas (,) between values are used to form a list. If we have cards named "cardA", "cardB", and "cardC", and we wish to navigate to one of those cards randomly when a button is clicked, we could give it a script like:

on click do
 go[random[cardA,cardB,cardC]]
end

Does that help?

You might also find some of the examples in this tutorial illustrative: https://itch.io/t/3593043/make-your-own-chicken-generator

(+1)

I'm trying to figure out format strings, specifically to get a slider to show its max value. Is %v what I want, and if so how do I use it? Or is this even possible without additional scripting?

Developer (2 edits) (+1)

You can find documentation for format strings in Lil, The Formatting Language.

The format string of a slider widget is only given its value, which would usually be displayed as an integer (%i) or a floating-point number (%f); the rest of the string could, for example, provide units, like "%f degrees". In this way you *could* hardcode a maximum into the format string, like "%i/20" for an integer between 1 and 20 to be displayed like "7/20".

Extending the current behavior of sliders to make min/max and possibly other attributes of the widget available for formatting could be an interesting feature; I'll mull it over. For now, including that sort of information would require scripting. If you need this functionality in more than one place, perhaps you could use The Enum Contraption as a starting point?

(+1)

Okay, thanks. The description of the "v" pattern type in the Lil docs made me think I could maybe insert any variable using just format strings. It's not too bad to just hardcode it and update it with a script whenever I change the max, though.

Hi there, I noticed an odd behaviour, not sure if it has been flagged before. It relates to having a button widget with a script. If the button already has a script, and you go to the Action area to add a sound or pick a card etc. it overwrites the old script with the new action instead of just adding it into any existing script. Possibly there are good reasons for that, but thought I'd point it out just in case!

Developer

When you open the "Action..." dialog, Decker will attempt to "unpack" any script already attached to the button and reflect it in the settings you see in that dialog, and when you confirm it writes an entirely new script. This dialog exists purely as a convenience for composing simple scripts without writing code, and is not intended to manipulate or append to arbitrary user-generated scripts.

(+1)

Makes sense, thank you!

(1 edit)

I think I've seen somewhere that it is possible to build a personal website using Decker as the "frontend".  So for example, I could have a GitHub Pages repo with the Web Decker HTML file as index.html and a data file (e.g. a CSV at first) to have a certain degree of decoupling between the data and the app. Is it possible and if so, how can I programmatically (in Lil, e.g. when the main deck loads or when a particular button is pressed) import an external CSV file in the Web-based Decker so I can display it as a table (for a start)?

I hope that my problem is clear and thank you.

Developer

It is not possible for an unmodified copy of web-decker to programmatically fetch external files hosted e.g. somewhere on github pages, make http requests, etc. Doing this sort of thing would require adding custom javascript extensions to web-decker. An example of how such an extension might be written can be found in this subthread.

In general, decks should be self-contained objects pre-packed with any data they require to operate. A deck is a database, with grid widgets for storing tables and Lil for querying and manipulating them.

Lilt can be used to import or export data to or from a .deck or .html file in an automated fashion. If you insist on storing some dataset independently from a deck that uses it, it might be possible to use Lilt as part of a continuous integration pipeline or "build script" to bake out an updated web-decker build when an associated csv file is changed.

(+1)

Is it possible to render text and widgets in a color other than black?

Developer(+2)

Many widgets will render in white-on-black instead of black-on-white if you set them to "Show Inverted".

If you set widgets to "Show Transparent" you can draw underneath them and have it show through, and thus provide background colors or patterns. The same trick is also handy for making buttons that appear to have an icon instead of text: draw the icon underneath!

Canvases can be drawn on in any color or pattern; you can use these directly or within Contraptions to make colorful variations on normal widgets. The fancy button contraptions I made recently can be configured with color images.

Finally, you can customize Decker's palette, substituting some different color globally for white and/or black.

(3 edits)

hey ij!

is there a way to write a single line of command to copy and paste more than one widget from one card to another?

i have widgets X, Y and Z stored in card A, and so far i can import each of them separately to card B by writing

card.add[deck.cards.A.widgets.X]
card.add[deck.cards.A.widgets.Y] 
card.add[deck.cards.A.widgets.Z]

but i can't really nail a way to import all three of them with a single line (unless i call all three lines with an .eval[]).

i saw in the decker manual that you can use the .add[] command to import a list or dictionary of widgets from another card, but i don't see how.

thank you!

(+4)

Hi, I'm not IJ but I did some experimentation and I think I've figured it out - it seems the card.copy and card.paste commands let you copy multiple widgets at once.

So you'd do like this:

card.paste[A.copy[A.widgets.X,A.widgets.Y,A.widgets.Z]]

(As a side note, if you're referring to a card in a script you can just use the card's name, you don't need the deck.cards in front of it)

Let me know if this works for you!

(+1)

oooooooooo!!! thank you so much, millie! i'll try as soon as i can!

(+1)

worked like a charm <3 thank you once again!

Developer(+4)

Just to build on this, if the only widgets on card A are X, Y, and Z, you can copy them all with:

A.copy[A.widgets]

And here's one other slightly more concise way to select a bunch of widgets by name:

A.copy[A.widgets @ "X","Y","Z"]
(+1)

i learned so much today!

i think i used "on view" scripts on too many widgets, and now my deck is kinda sluggish. are there lighter ways to make widgets receive information from other widgets without having them wait for signals on every frame?

for example, since i'm building a clicker game, i use on view scripts to keep information flowing between cards. things such as updating numbers and detecting clicks. 

is it lighter to make one widget be the animated one running all the code, or to make many widgets animated running smaller bits of code?

Developer(+1)

There would be somewhat less overhead to having a single animated "pump" which is responsible for updating everything else on a card.

Have you tried enabling the script profiler (Decker -> Script Profiler) to confirm that it's scripts which are slowing things down? Having a large number of widgets shown at any given time (or particularly large canvases) can also start to stack up.

If you're updating large numbers of widgets individually- like lots of separate fields displaying stats- you might find that it's more efficient to replace them with multi-line rich-text fields or grid widgets, both of which can be useful for displaying formatted bulk data. There's no hard-and-fast rule about the best way to approach things; you may need to experiment. If you find any particular operation which seems unusually slow, and you can provide me with a minimized example, I can investigate and possibly improve Decker's performance.

Hey there! I've been redoing The Steppe code, following your suggestions on cohost, but I made a minor modification that seemed to have broken something.

So, there was an invisible field "counter" that would have its numeric text altered according to what card you came from, starting from 1. Then, there was a screen-sized invisible button that would take you to another card based on a giant "if... elseif" code. Your suggestion to simplify it would be to use lists. This was the example given:

cards:(card2,card3,card4,card5,card6,card7,card8)
trans:("n/a","WipeUp","Dissolve","Dissolve","Wink","Dissolve","CircleIn")
go[cards[counter.text-1] trans[counter.text-1]]

It was working well, but then I thought "maybe I could make the counter start from 0, instead of 1, so it would align with the list index on the 'go' and I can remove the -1". So I rewrote all the code for counter to go from 0 to 7 instead 1 to 8, and changed the "[counter.text-1]" to just "[counter.text]".  Except now the button doesn't work properly, it just goes back to the home screen no matter the text in the field counter.

However, if put a +0, it works again. So, right now, it's like this:

cards:(card2,card3,card4,card5,card6,card7,card8) 
trans:("n/a","WipeUp","Dissolve","Dissolve","Wink","Dissolve","CircleIn") 
go[cards[counter.text+0] trans[counter.text+0]]

What I want to know is... am I doing something wrong? Is there another way to use counter.text as an index that I'm missing? I've been messing around with it for quite a while, but can't seem to solve this. 

Developer

The .text attribute of a Field is a string. Performing any sort of arithmetic operation on a string coerces it to a number (as a convenience), and in many places a string like "23" is fully interchangeable with the number 23:

 100+"23"    # 123

Unfortunately, indexing into lists isn't one of those places:

 foo:"Alpha","Beta","Gamma"
 foo[1]      # "Beta"
 foo["1"]    # 0
 foo[0+"1"]  # "Beta"

Part of the reason Lil draws a hard line here is that indexing lists by strings is one way to force them to "promote" to dictionaries when you assign through them; trying to parse strings into numbers could lead to some very nasty ambiguity:

 foo["z"]:"Delta"    # {0:"Alpha",1:"Beta",2:"Gamma","z":"Delta"}

I apologize for the confusion stemming from my suggestions.

One possible alternative would be to make "counter" a Slider widget instead of a Field; The value of a Slider is always a number.

(+1)

Ah, don't worry, you have nothing to apologize for! I thought this could be the case, but that might have had another workaround I wasn't figuring it out.

The code works perfectly now, so I'll keep it as-is with the +0 and write a small note that field.text is always a string, so I don't forget. Thanks for the help!

(1 edit)

hey, ij and fellow deckheads! 

i've been using the rect module to detect the cursor hovering over buttons and having a field widget display information on that button. so far i can only do this by having every button being animated and having a rect.overlaps[pointer me] command. 

what i really wish i could do was having the rect module detect the widget type. like rect.overlaps[pointer <button>] (this doesn't work) and then specify the button by its text. 

is it feasible?

thank you!

Developer (2 edits)

The rect module (which, for those unfamiliar, is included in the "All About Draggable" example deck) includes functions like rect.overlaps[] and rect.inside[] for manipulating "rectangles", which can either be a dictionary containing pairs with the keys "pos" and "size" or any Decker interface value that happens to have those fields- widgets, for example.

The pointer interface has a .pos field, but no .size field. Using an invalid index into an interface type returns 0, so the pointer interface kinda fulfills the contract and sort of works as an argument to these utility functions (as if it were a rectangle of size 0), if only by coincidence/accident.

I whipped up a little test, and sure enough this seems to work fine given an animated button:

on view do
 field1.toggle["solid" rect.inside[pointer button1]]
end


(Note that "tooltips" like this won't work properly on multitouch devices, since such input devices do not update the pointer position while a user's finger happens to be hovering above the display; this is why Decker doesn't have any sort of tooltip functionality built in.)

The same script body ought to work just fine from a centralized event pump rather than having every object with a tooltip be individually animated. I don't really follow what you mean in terms of the rect module "detecting the widget type".

thank you for the answer! this already opens up a lot of possibilities, actually.

what i meant was if there was a way of having, for example, an animated field widget that displayed text whenever the pointer went over a button. and i figured the way to do this was such field having something like:

on view do
 if rect.overlaps[pointer (((any button)))]
  me.text:"pointer over some button"
 else
  me.text:"pointer over any other thing"
 end

(it just occurred to me that i can name a group of widgets that are buttons and do this, but i'm thinking of a less manual approach)

(1 edit)

So, I'm trying to add import options to the image importer/exporter contraption I'm building. So far, this is the interface and everything seems to be working fine. "Extra Options" just makes everything disappear, keeping only itself and the Import/Export buttons.


Only one of the checkbox buttons can be selected at a time (I'm using a basic "if" to check the value of each one and unmark the other 2 if is true).

Here's how the images look when imported in the order of the checkboxes.




So, erm, I think the issue is clear. The Gray Import isn't importing in grays, but rather in a psychedelic way.  Funny enough, the Dither Import relies on the Gray Import to use transformation, otherwise it just completely breaks too. But even so, the Dither result is noisier than just dragging and dropping an image into Decker.

It's worth noting the image is a jpeg, so I'm not sure why it's getting animated with the Gray Import.

Here's the code in the Import button:

on click do
    if colorbutton.value=1
        i:read["image"]
        card.image.paste[i 0,0,card.size]
    end
    if graybutton.value=1
        i:read["image" "gray"]
        card.image.paste[i 0,0,card.size]
    end
    
    if ditherbutton.value=1
        i:read["image" "gray"]
        i.transform["dither"]
        card.image.paste[i 0,0,card.size]
    end
end

I tested it with a canvas, and also did some testing in the Listener, but I can't figure out what's going on, if there should be some other sort of conversion happening before, or something like that.

Developer

This is working as designed. 256-gray images are not directly displayable within Decker, since Decker's color palette does not contain 256 colors and patterns; only 48:

 

A grayscale image must be converted into a proper paletted image before it can be displayed; otherwise the grayscale values are effectively randomly mapped to entries in the above table, and any higher indices appear as white. The image.transform["dither"] function is one way to produce a 1-bit dithered image from a 256-gray image, using Bill Atkinson's algorithm. Rescaling a dithered image will considerably reduce its quality. The correct order of operations to prepare a dithered image therefore must be:

  1. obtain or otherwise create a 256-gray image.
  2. perform any desired palette adjustments to the 256-gray image, like adjusting white/black points or contrast.
  3. scale and crop the 256-gray image to the desired final size.
  4. dither the 256-gray image, resulting in an image consisting exclusively of patterns 0/1.
(4 edits)

I was talking about this on cohost and millie also told me about Decker not being able to display the 256 grays which... makes complete sense tbh, as you yourself explained the palette limitation (which I actually know but didn't connect the dots, so I'm a bit embarrassed). Funny enough people liked the result, so I think I'll rename the option and leave it there for them to use and give life to fever dreams. 

And the order for the dithered image makes sense now, I'll rewrite its import code and see if I can get it right then. Thanks a lot!

Edit: the dithering improved a lot now!

Still on the same contraption as before, I'm having trouble finding a solution for a thing that might be beyond what I understand at the moment: paste the background of a contraption into the card's background without having to use the Listener or create a widget button in the card.

What I mean is, I can copy and paste into the card the image that was imported into the contraption with the following code, for example:

importer.image.paste[importer.widgets.Importer1.image.copy[]]

Where "importer" is the card name and "Importer1" is the name of the contraption. I also use some variations, depending if I'm in the same card as the contraption or not. However, having to rewrite this by changing the card's name every time isn't that practical, so I wanted to make the contraption itself paste its image on the card it's over (kinda like a "stamp"). But, from inside the contraption, I can't seem to get the image to be pasted anywhere else.

I tried messing around with the attributes, but that doesn't seem useful in this situation since no widget is trying to access or modify the contraption's content. Making a script with the generic "card." followed by the rest doesn't work, I guess because contraptions work like cards themselves from what I understood, so the command is ambiguous? I tried using a canvas too, instead of the contraption's background, thinking it would be easier to access, but it also didn't work.

So, am I overlooking something obvious? Or to do what I want, it requires Lil's more complex stuff to deal with prototypes/contraptions also being "cards"?

Developer

From inside a contraption instance, "card" refers to the contraption instance itself; this is handy if you want to send events to the "external" scripts on the contraption or inspect default properties like .locked, .show, or .font.

If you want to refer to the card within which a contraption resides, you can generally use "deck.card". Strictly speaking, "deck.card" is the active card on the deck; the card the user is looking at. This will only be distinct from the contraption's parent card if the contraption is being sent a synthetic event from some external script.

A different angle to consider would be prompting the user for a destination card when importing an image. You could, for example, use alert[] in "choose" mode to select cards from the deck by name, using the current card as a default:

alert["pick a destination card:" "choose" deck.cards deck.card.name]


The downside to the above is that if a user hasn't given cards logical names they're "picking blind", which can be error prone. Every design problem is fractal in nature...

Using "deck.card" worked, hooray! The contraption is now working as intended with all the features I wanted, I'll just do some testing in the next few days and upload it to the jam.

I decided to avoid the "alert" route because, at least for my usage, I need it to just behave like a "stamp", copying the contraption background (which uses Decker full screen) and pasting it exactly like it's shown in the card below. Then one can delete the contraption and work straight on top of the "stamped" image with other widgets, including invisible ones. And I figured being pasted into the background allows image manipulation/editing/painting too.

Now, this whole thing led me to 2 other questions:

 - I'm using this code in a button to paste the contraption background into the card:

deck.card.image.paste[deck.card.widgets.deckstamp1.image.copy[]]

Is there a way to make the "deckstamp1" be a string pulled straight out of the contraption's own name field? This way, the script would always "autocomplete" and the button wouldn't stop working if someone changed the contraption name, for example. I did some testing with various syntaxes, but as always, not sure if I'm doing something wrong or if it's not possible.

- Is there a way to access a contrast adjustment, like the one with "j" and "k" that works only after dragging an image into Decker? I looked around and didn't find anything. I figured that if there is, it would be cool to have a contrast slider and a button to update the image, even if it works just with the dithered import.

(+1)

I'm just starting with decker, and I feel like my question is absolutely basic and stupid, but I swear I've been digging at the documentation and examples and can't find the issue.
I have a button on a card that has the following script:

on click do
 key: 1
 go[bednokey]
end

The idea being that you pick up the key and it leads you to an identical card of the same location with no key.

On a previous card, I have the following script on a button, to avoid going to the card with the key once it's been picked up already:

on click do
 if key=0
  go["fieldbed" "SlideUp"]
 else
  go["fieldbednokey" "SlideUp"]
 end
end

And it won't work at all. It always goes to the card with the key. I tried reversing the conditions, using other symbols, no dice. What's the proper way to do this check?

Developer(+1)

Anything that you want to "remember" across event handlers needs to be stored in a widget. A true/false flag, for example, could be stored in a hidden checkbox. If we had a card named "flags" with a checkbox widget named "hasKey" your scripts might look something like

on click do
 flags.widgets.hasKey.value:1
 go[bednokey]
end
...
on click do
 if flags.widgets.hasKey.value
  go[bednokey "SlideUp"]
 else
  go[bed "SlideUp"]
 end
end

If the widget happens to be on the same card as the script which references it you can shorten paths like "flags.widgets.hasKey.value" to just "hasKey.value".

If you want to show or hide objects on a card, you may find it more flexible to use a transparent Canvas widget containing an image of the key. You can show, hide, or reposition canvases at will, sort of like a sprite object:

myCanvas.show:"solid"
myCanvas.show:"transparent" 
myCanvas.show:"none"
myCanvas.toggle["solid" booleanExpression]
myCanvas.pos:otherWidget.pos

This may also remove the need for a separate "flag" widget; the canvas itself will remember its visibility setting:

if bed.widgets.keyCanvas.show~"none"
 # ...
end

Does that help answer your question? There are lots of other examples of doing this sort of thing in this thread and elsewhere on the forum.

(+1)

This absolutely goes above and beyond answering my question, thanks! I'll try all these out right away.

I might be a bit over my head here, but I'm trying to add the gif export to Deckstamp. However, I'm having trouble adapting the new WigglyKit example with the export code to Deckstamp. In short, I'm... not sure how it works. I understand it grabs the frames and their order, but I don't know how it does that.

The normal canvas and the image interfaces don't have ".value", so it seems it's not that simple to just copy frame order from them, even if the image has "movement" (like the moving patterns). So I tried to use wigglyCanvas, but now I'm having trouble pasting an image in it: I tried using the image interface and it didn't work, so I tried using parts of the code of the import example on WigglyKit, and still nothing.

In any case, I'm guessing the best way would be to grab the card background image and save its different frames together instead of fiddling with wigglyCanvas, but is that possible? What would a code like that look like?

Developer

Let's start with some context. All of the contraptions in WigglyKit share the convention of an "animation value"  consisting of a dictionary with the keys "frames" (a list of images) and "order" (a list of integers; indices into the list "frames"). There's nothing intrinsically special about this dictionary; it's just a convention to bundle together all the relevant information for a wiggly animation such that it can be easily moved around between those contraptions and manipulated with scripts.

WigglyKit offers an example of pulling the animation value out of a WigglyCanvas and then saving it as a GIF:

v:wc.value
# (optionally) make the background opaque:
v.frames:v.frames..copy[].map[0 dict 32]
gif.frames:v.frames @ v.order
gif.delays:10
write[gif]

As noted in the Decker reference manual for the built-in function write[] (see note 12), to save an animated GIF we need to call write[] with a dictionary with the keys "frames" (a list of images) and "delays" (a list of integers; how many 100ths of a second should each frame be displayed?)

So really, what we need to do to save a GIF outside the specific context of WigglyKit is make an appropriately-shaped dictionary and hand it to write[]:

gif.frames: ... # obtain or assemble a list of images, somehow
gif.delays: 10  # one number applies the delay to every frame
write[gif]

Obtaining/assembling that list of images will depend entirely upon your use case. We might grab them from a sequence of canvases:

gif.frames: (c1,c2,c3)..copy[]

We could do the above in a more verbose way, if we wanted:

gif.frames: c1.copy[],c2.copy[],c3.copy[]

Or perhaps we could use the recently-added ".images" attribute of rich-text fields to grab all the inline images stored in a field:

gif.frames: myRichTextField.images

Etc.

Does that help point you in the right direction?

I think I understand better now how it works! After some tests, I kinda did it, but the results were a bit different from what I was seeing on the screen. It's worth mentioning all the images are pasted into the card's background.

Here's the source image, which was exported using Decker's own "export image" in the menu. It's worth mentioning this image was generated by importing a photo with the "gray" hint and not doing anything else:


I then reimported this image into Decker using the same options, and this is what I'm seeing on the screen now. This is an OS screenshot because this time, Decker's export image isn't exporting an animated gif anymore, just a still image (shown below this one). Every diagonal line was supposed to be "moving" (which causes a really cool effect).

OS screenshot (in Decker, all the lines have movement):


Decker's exported image:

Fiddling around with trying to grab frames and delays, I arrived at this result, which doesn't seem to have any movement in this post, but it does if you open the image in another tab:

I used this code to arrive at this result:

i:read["image" "gray_frames"]
card.image.paste[i]
gif.frames:i.frames
gif.delays:i.delays
write[gif]

It's not the same thing I'm seeing on the screen, but it's progress!

Funny enough, if I try to replicate the first image using "gray_frames" in a normal photo instead of just "gray", it just imports the image as if it was using "color" instead.

Now I'm trying to figure out how to grab the animated patterns in a card's background as different frames. Maybe that will export the same thing I see on the screen? I was able to grab frames from imported gifs, from different canvases and put them together, and was successful with images in rich text fields too, but I'm not sure how to go about a background image.

(+1)

Hi, I'm thinking of making a small text adventure game with a parser, a little bit like Zork and games like that. So far, I found this post to make a parser with a button next to it. But I was wondering if it was possible to make a parser input without the button, like if you could press the return key in the field widget to check the words in it. I'm guessing that since the field widget already use the return key to go to the next line in the widget, I'd need to replace that action with the parser input, but I'm a bit clueless how to do that, if it's possible.

Developer(+1)

Hmm. I think this would be rather tricky.

Decker is designed to try to accommodate text input on devices that don't have a physical keyboard, so it avoids exposing low-level keyboard events and doesn't (presently) allow scripts to modify the contents of a text field while it has user focus. Consider how the soft keyboard overlay introduced in Decker 1.19 interacts with unlocked field widgets.  While it's easy enough to detect that a user has pressed return in a text field (and typed a newline),

on change do
 if "\n" in me.text
  # ...
 end
end

That event handler wouldn't be able to clear out the user's line of input, which would make a scripting REPL or an IF parser input awkward.

I'll give this some serious thought and see if I can come up with a good compromise to allow the sort of thing you're interested in making

(+1)

Yeah, I thought it might be tricky, especially for touchscreen users. I might stick with the version I found with the button in the meantime. Thanks for the response and for giving it some thoughts!

Developer(+2)

Decker 1.49 includes some subtle changes that I think will address this request.

It is now possible to clear a field via scripts while it is selected. In touch mode, this will additionally dismiss the on-screen keyboard and remove focus from the field.


In the above example, the script looks like this:

on change do
 if "\n" in me.text
  log.text:log.text,"\n %s%J" format me.text,list eval[me.text].value
  log.scroll:99999
  me.text:""
 end
end

There are a few details to be aware of when using this technique:

  • It takes 1/4 of a second without user input for the contents of a field to fire a change[] event, and it is also possible to fire a change[] event by pasting an arbitrary block of text into a field. Therefore it's possible for a user to type several characters following a newline, or even multiple newlines; input handling code will need to be robust to this.
  • An alternative to looking for "\n" in a change[] event would be to use the run[] event, which is fired immediately when the user presses shift+return. This may be less intuitive for users, and therefore unsuitable for many applications, but it's much less error-prone and more flexible than newline-delimited input; the run[] event makes it possible to build UIs which behave like Decker's Listener.
  • Since the user actually does type a newline, you'll need to carefully adjust the vertical sizing of the input field to match the font in order to maintain the illusion that the field is "cleared" instantly when the user presses return.

I hope this helps!

(+2)

This seems to work exactly how I wanted to, thank you! I see in the exemple that it evaluates what is written in the input and I've tried to modify the code so that if a specific word or phrase is written in the input, that it would say a specific answer in the log, but I don't quite understant the code enough to make something that works. And I was wondering if this technique would also works with triggering events, like to toggle other widgets in the card of for alerts?

Developer(+2)

OK, let's make a simple Colossal-Cave-Adventure-style two-word parser.

Lil's parsing patterns can get a little bit cryptic, but essentially what we want to break up user input is:

  • Skip any leading spaces.
  • Grab any characters up to a space or newline and call them "verb".
  • Skip any spaces.
  • Grab any characters up to a newline and call them "obj".

Which can be expressed as the pattern:

pat:"%*r %[verb]-.2r \n%*r %[obj]s\n"

I did a few tests in the Listener to make sure it was working as intended:

 pat parse "foo"
{"verb":"foo","obj":""}
 pat parse "  foo"
{"verb":"foo","obj":""}
 pat parse "foo bar"
{"verb":"foo","obj":"bar"}
 pat parse "foo bar\nbaz"
{"verb":"foo","obj":"bar"}
 pat parse "foo  bar\nbaz"
{"verb":"foo","obj":"bar"}

Now we can rework the input field's change[] handler to bundle this up and send the verb/object to the card script:

on change do
 if "\n" in me.text
  c:"%*r %[verb]-.2r \n%*r %[obj]s\n" parse me.text
  me.text:""
  typed[c.verb c.obj]
 end
end

In the card script, we'll have our actual game logic, a twisty maze of if statements:

on println x do
 log.text:log.text,x,"\n"
 log.scroll:999999
end
on typed verb obj do
 println["> %s %s" format verb,obj]
 if verb~"look"
  if obj~"flask"
   println["it is a flask."]
  elseif obj~"self"
   println["that's difficult unless your eyes are prehensile."]
  else
   println["ye see ye flask."]
  end
 elseif verb~"take"
  if obj~"flask"
   println["ye cannot take ye flask."]  
  else
   println["i don't see a %s anywhere here." format obj]
  end
 else
  println["ye is speakin' nonsense."]
 end
end

And our scintillating gameplay experience begins:

(+1)

I'm starting to understand a lot more how it works now. Thanks a lot for answering my questions!

(+1)

hi, can you somehow unlock a locked deck? 

(+1)

If you have the file on your computer you can open it with a text editor like Notepad and change:

locked:1

to

locked:0

It should be in the first few lines of text.

(+1)

Regarding conditionals, do "if" statements support "and" and "or"? I've been doing some testing in the Listener and it seems to work, but I'm unsure if I got the syntax right. The same goes for using ">" or "<" with "=". I used, for example:

if a = X and b = Y
  <code1>
elseif a < or = W
 <code2>
else
 <code3>
end

That seems to work but I've never used syntax like this, so I'm unsure if I'll be messing something up down the line depending on these conditional results.

And on an unrelated note, is there any editor that highlights Lil's syntax? I'm getting into territory where I have to click around a lot to modify things that bugged out due to some unintended modification and I always forget to change some bits of code in a widget tucked somewhere. I've been using TextEdit to quickly find and modify these, but a proper editor seems a better solution for long-term projects.

Developer(+1)

In Lil, the "&" and "|" operators provide logical AND and logical OR. Both "conform" to the elements of lists:

(1,1,0,0) & (1,0,1,0)  # -> (1,0,0,0)
(1,1,0,0) | (1,0,1,0)  # -> (1,1,1,0)

To be more precise, these operators are actually binary "minimum" and "maximum" operators for numbers:

(1,2,3,4) & 2          # -> (1,2,2,2)
(1,2,3,4) | 2          # -> (2,2,3,4)

As always, be careful to remember that Lil expressions are carried out right-to-left unless you include parentheses.

For comparisons, Lil has < (less), > (more), and = (equal). It does not have "compound" symbols like "<=", ">=", or "!=". If you want the complementary operation, you can use ! (negation):

  a > b      # a is more than b
! a > b      # a is not more than b (a <= b)
  a < b      # a is less than b
! a < b      # a is not less than b (a >= b)
  a = b      # a is equal to b
! a = b      # a is not equal to be (a != b)

Note that all of these operations work to lexicographically compare strings, too:

"Apple" < "Aardvark","Blueberry"    # -> (0,1)
"Apple" & "Aardvark","Blueberry"    # -> ("Aardvark","Apple")
"Apple" | "Aardvark","Blueberry"    # -> ("Apple","Blueberry")

Lil syntax highlighting profiles for vim, emacs, and Sublime Text are available at the Decker Github Repo.

Syntax highlighting is also available in the Lil Playground.

(+1)

Oh, I forgot about the right-to-left reading, now it makes sense why some conditionals were returning 1 even if not all conditions were true. Now, if I understood it correctly, the following code would be truthy?

a.value:4 #slider a
b.value:5 #slider b
if (a=4) & (b=5)
go[card2]
end

It seems to work in the Listener, I just want to make sure I got it.

The "!" negation also worked perfectly on some tests I did, which solved some issues for me. Thanks!

(+1)

hi!! i'm making my first game in decker and i'm wondering if there is a way of accessing a variable defined in script (ideally on card or deck level)? so far i bypassed this by using an additional card with checkboxes but maybe there's an easier way to do it?

(+1)

aand one more question.

i want to add dialogue options after the player interacts with objects on other cards

so i thought of having a widget with a table that can be updated with new options and importing it into the dd.chat function like this:

r:dd.chat [ "question" raze #insert table end]

is it possible to do?

Developer

Variables that live beyond an event handler must be stored in a widget. If you find it inconvenient to reference widgets on another card,

thatCard.widgets.has_knife.value
thatCard.widgets.has_soap.value
thatCard.widgets.has_corncob.value

You could write utility functions on the card script that aggregate information from widgets:

on get_inventory do
 ("knife","soap","corncob") dict (has_knife,has_soap,has_corncob)..value
end

And then send events at that card from elsewhere:

thatCard.event["get_inventory"]

(Or as shorthand if the event handler doesn't take any arguments,)

thatCard.event.get_inventory

In this way you can give cards an "API" of sorts. You could also define helper functions in the deck-level script to reach into specific cards:

on items do
 thatCard.event.get_inventory
end

And then call that helper function from anywhere in the deck:

items[]

(I think this is probably overkill unless you're accessing the same info in lots of places within a deck.)

You can absolutely use tables to keep track of dialog options. A grid widget stores a table in its "value" attribute. Aside from manually editing table contents as CSV or JSON, you can initialize one with a script:

myGrid.value:insert k v with
 "Tell me about Chickens"  "They cool."
 "Tell me about Daffodils" "They flower."
end

You could append a new row (or several rows) to the existing table in a grid like so:

myGrid.value:insert k v with
 "Tell me about Easter Eggs" "They rare."
into myGrid.value

And you could then later retrieve that table and convert it into the dictionary form dd.chat[] wants by razing it (which turns the first two columns into dictionary keys and values, respectively):

dd.open[deck]
dd.chat["Ask away." (raze myGrid.value)]
dd.close[]

Note that if you plan on revisiting the same dialog tree over the course of a game many times but you don't want to repeat old selections you'll need to do additional bookkeeping; dd.chat[] only remembers/winnows selections within the course of one conversation.

For more information about tables and querying, see Lil, The Query Language.

Does that help point you in the right direction?

(+1)

that's all i needed, thank you so much <33

Is there a way to replace a color on a card with another color from the palette via the Listener? 

Developer

The "image.map[]" function can be used to re-palette any Image interface, including card backgrounds. The simplest way to call it is to feed it a dictionary where the keys are the original pattern indices and the values are the replacement pattern indices:

card.image.map[colors.red dict colors.green]

Note that this operation modifies the image in-place.

(+1)

thank you!

Hi! In this post, you mentioned that:

If we wanted clicking on chester to toggle frobnicate, we'd write a script for him like so:
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

It seems that in this case, assigning to `f.value` actually modifies `home.widgets.frobnicate`.


However, when running this in the Listener:

x.a:1 x.b:2 x.c:3
show[x] # {"a":1,"b":2,"c":3}
y:x y.a:42
show[y] # {"a":42,"b":2,"c":3}
show[x] # {"a":1,"b":2,"c":3} still the same!

The behavior is different, `y.a:42` doesn't modify `x`.

Could you explain why?

Developer(+2)

The difference is that deck parts are "Interface" values, while your later example is a dictionary.

Lil's basic datatypes (numbers, strings, lists, dictionaries, tables, functions) are all immutable values: attempting to modify them will return a new value and leave the original value unchanged.

Interfaces (like most deck parts) are mutable, and accessing or modifying their attributes may have side-effects. For example, modifying the ".index" attribute of a widget to change its order on a card will also visibly modify the ".widgets" property of the container card, since these attributes are related to one another. The "sys" interface exposes "sys.now", an attribute which contains the current Unix timestamp, and may (generally) be different every time it's accessed. Lil also offers utility Interfaces like Array for those times when mutable data structures are necessary for performance reasons.

Does that make sense?

Thanks for the quick response! Initially, I thought this might be that `home.widgets.frobnicate` is copied by reference, now I see that Interface values are more to this. They look more like modifiable "values" instead of "variable names", and after assigning them to a name, making changes to the attributes under that name actually interacts with the "value" instead of replacing it with a new one.

Hello I was trying to use the decker diagolizer.

I wanted to open dialogue box when a card opens. I used inside the on view do. It works but it seems to make the same dialogue box twice instead of once :( Do you guys know how to do it ?

(+1)

Things that are inside an `on view do` script will usually continue to happen as long as you're viewing the thing it's attached to.

I sometimes set up a hidden (visibility: none) checkbox on the card and an ' if...' statement inside of the `on view do` script. 

And then I make sure that something unchecks the checkbox when the autoplaying event is finished, so it doesn't play again.

on view do
if autoplay.value #if the checkbox named 'autoplay' is checked...#
#(your dialogizer stuff goes here)#
autoplay.value:0 #uncheck the box#
end
end

You can check your checkbox manually or you can set up a script to do it before you leave the previous card. 

Something like this could be inside the previous card's exit button. (With the correct names for your project)

on click do
cardname.widgets.autoplay.value:1
go[cardname]
end
(+1)

Hi, I was wondering if there was a way to re-order widgets on a card via script (the equivalent of the Widgets > Order feature). For example if I click on a draggable canvas partially covering/overlapping with another can it be promoted to the top of the pile? 

Developer (1 edit) (+2)

You can inspect and modify the order of widgets on a card via their .index attribute, which counts from back to front, starting at 0. This is automatically clamped within the permissible range on a card, so assigning it to 0 or -1 can be a convenient shorthand for "all the way to the back" and any very large number (say, 999999) can likewise be used for "all the way to the front".

For example, you could give your draggable canvas a script like so:

on click do
 me.index:999999
end
(+1)

Thank you so much!

(5 edits) (+1)

Is it possible for a contraption to change its own size? I'm creating an animated sprite contraption that gets the sprite width and height from its attributes, and then shows an animation taken from a sprite sheet card.

I tried to do

me.size:(sprite_width.text,sprite_height.text)

on the view event, but it doesn't do anything when the contraption is running on a card (it does change its size when running inside the prototype view). I'm saving the state in hidden fields inside the prototype. Here's the whole script:

on get_spritesheet do sprite_sheet_card.text end
on set_spritesheet x do sprite_sheet_card.text: x end
on get_frames do frames.text+0 end
on set_frames x do frames.text:x end
on get_sprite_width do sprite_width.text+0 end
on set_sprite_width x do sprite_width.text:x end
on get_sprite_height do sprite_height.text+0 end
on set_sprite_height x do sprite_height.text:x end
on get_fps do fps.text+0 end
on set_fps x do fps.text:x end
on get_loop do loop.text+0 end
on set_loop x do loop.text:x end
on get_current_frame do current_frame.text+0 end
on set_current_frame x do current_frame.text:x end
on view do
 me.size:(sprite_width.text,sprite_height.text)
 c:deck.cards[sprite_sheet_card.text]
 w:c.image.size[0]
 h:c.image.size[1]
 f:current_frame.text
 i:c.image
 me.image.paste[i.copy[(w%sprite_width.text*floor f,floor sprite_width.text*floor f / w) (sprite_width.text,sprite_height.text)]]
 current_frame.text:frames.text%f+fps.text*1/60
end

The only thing I'm missing is for the contraption to resize itself to fit the configured sprite size.

EDIT: I made the prototype resizable, and it's working now. The problem is that the state is shared between different instances of the same prototype, so I'm obviously doing something very wrong here.

Developer(+1)

Don't worry- it's quite normal to stash contraption state in auxiliary hidden widgets. The contents of such widgets is distinct between contraption instances, but the background image of contraptions is shared among all instances; pasting directly onto the contraption background will cause issues if you have more than one sprite. The conventional solution would be to add a canvas widget to the contraption and draw on that instead. You'll also need to configure non-zero margins for the contraption to ensure that the canvas automatically stretches and resizes properly along with it. Both the contraption instance and the canvas will need to be set to "show transparent" if you want objects behind the sprite to show through.

Since you intend to redraw the contents of the canvas frequently, you may be able to mark it as volatile, which will ensure that having many sprite instances won't bloat deck size with unnecessary copies of sprite frames. If you aren't already doing something similar, you may find it useful to mark the canvas as "animated" so that it will automatically bubble view[] events to the contraption instance at 60fps and "locked" so that it doesn't inherit the default click-and-drag-to-scribble behavior of canvases. (Be aware that "me" will be bound to the original target of the view[] event, not necessarily the contraption itself!)

The Sokoban example deck contains a contraption called "mover" which works similarly in some ways to your "sprite" design, and the Path example deck has another variant called "follower". You might find these useful to reference.

Does any of this point you in the right direction?

(+1)

Yes, this is all very useful. Thank you. I will probably end up using zazz for animated sprites (unless I end up needing non-looping sprites, or fancy events that report when animations end, and things like that). But it's very useful to be able to understand the subtleties of working with contraptions. Decker is awesome, but a bit overwhelming at the beginning.

(+2)

Hey, IJ and Decker community

I've been messing with hyperlinks. I'm trying without much success to come up with a way to make a functional hyperlink (string + link event) show up inside a field through coding alone. Like, say, opening up the listener and making the word "Apple" show up in a field, functioning as a link to the Apple card.

Is there a way to do it?

Thanks :)

Developer(+2)

When a field is configured to display "Rich Text", it can contain text spans that are links, use different fonts, or include inline images. Rich Text is described in the reference manual here.

Rich Text is represented as a Lil table, and can be constructed like any other table so long as it has the appropriate columns. For example,

myField.value:insert text font arg with
 "Apple" "" "theAppleCard"
end

Remember, of course, that fields need to be locked for their hyperlinks to be clickable. Unless you define your own link[] handler for a field, clicking a link will call the go[] built-in function with that "arg" value, which conveniently serves either to navigate to cards by name or to prompt the user to open URLs in a new browser tab.

The "RText" utility interface (linked above) offers a number of convenience functions for creating and manipulating Rich Text tables. The "rtext.make[]" function offers a more concise alternative to the above:

myField.value:rtext.make["Apple" "" "theAppleCard"]

In the interactive docs for a variety of modules I use this sort of approach to automatically generate the index in the title card's view[] event handler:

bullet:image["%%IMG0AAYADQAAAAB49Pz8/HgAAAA="]
i:select c:key t:value..widgets.title.text where value..widgets.title from deck.cards
index.value:raze each row in rows i
 rtext.make["" "" bullet],
 rtext.make["  "],
 rtext.make[("%s\n" format row.t) "mono" row.c]
end 

Does that help?

(+1)

Yes! That does help a lot! Thank you sou much :)

I'll just ask you my next doubt already, if you don't mind.

I'm fiddling with the Mini Twine deck using a twee file of an old Twine game I made. What I wanted to do is keep a log field with the narrative text as the player makes their choices, but so far I'm having trouble leaving the link lines out of it. 

Example:

How do I break the game field string and leave the verb choices out of the log field? Like so (tampered with example):


Thank you again!

Developer (1 edit) (+3)

I think this would be difficult in the general case without making some assumptions about the structure of passages. Hyperlinks can appear anywhere within the text of a Twine passage, so it's not simply a matter of hiding or trimming off a suffix of the output to remove the listed "verbs".

If there's demand for it (and it looks like there might be) I could look into developing a more complete and robust .twee manipulation module for Decker, and maybe even a story mode specifically designed for interoperation with Lil. I don't think I'll have time for it this month, though.

It's not quite what you're asking for, but you might be able to find some useful ideas in this thread where I discuss parser-based IF systems; my example deck features an output log which distinguishes user input from responses with bold/plain fonts.

(+1)

that's fine! we're in no hurry at all :) it's great to see decker growing with each question brought here by its users. have a great december!

(2 edits) (+1)

Hey, just wanted to make sure I'm on the right track here with a speech bubble component I'm working on:


The roundrect is manually drawn on the contraption's background, with margins set to allow shrinking and growing. The bubble's "tail" is a volatile canvas because the tail needs to change both image (mirror left/right) and position. Right now I'm making those changes from the view handler:

on view do
 left: state.text in "se","w"
 mirror: state.text in "sw","w"
 if left
  me.margin: 7,25,7,7
  tail.pos: 12,tail.pos[1]
 else
  me.margin: 7,7,25,7
  tail.pos: (me.size[0]-24),tail.pos[1]
 end
 img: image["%%IMG2..."] # tail image initially points to the right
 if mirror
  img.transform["horiz"]
  end
 tail.paste[img]
end

I call view[] from each of the set_ handlers, so if the text changes or the direction changes, everything gets repositioned and redrawn. This mostly works, but it has some weird side effects during editing. For example, if I resize the widget, the tail jumps back to its default position (its position in the prototype). And sometimes the volatile tail canvas clears itself during editing, and won't be redrawn until I switch to Interact mode.

Am I on the right track here? Is there something else that should be calling view[]? Or is there a better approach you would recommend?

(+2)

hello, i come with a rookie question...

with the dialogiser module, i'd like to put tiny images instead of text as choices in the dd.ask[] function

i have done this before succesfuly by just pasting the image straight into the script but it obviously makes a great big wall of numbers and letters, so im just wondering if there is a better way to do so?

Developer(+3)

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)

Hi, I'm brand new to coding and Decker so I know my questions will be stupid, but I don't have much choice other than to ask as I've lurked and read up as much as I could (although not everything I read I understood). 

Basically I have a button to begin the game I'm working on, but I have it set up so that when I press that button a gif starts playing (instead of just going to the next card immediately). I want the gif to play for about 5 - 10 seconds before decker automatically forces the player to the next card. Frankly, I have no idea how to do that.

The small amount of progress I've made so far has been from looking at other people's decks and seeing what I can understand and use, but I've not been able to find anything that fits this particular need. I saw a few references people on here made to sys.me and stuff but idk how to code so I haven't been able to work that out. Decker is really cool so far though. Even though I don't know how to code I made a pretty neat looking title screen for my game, so I'm happy about that.

Thanks for any help anyway, sorry if it's very simple and I'm just dumb.

Developer(+4)

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?

(+2)

Thank you for your detailed reply! I was indeed using colorgif already and had worked out how to hide it and make it appear when the button is pressed as you mention, but that was as far as I had gotten with it. Thank you so much for updating the gif contraption to make this work the way I wanted. I used your code and it all works beautifully now.

I have a small follow up question if that's ok (not related to gifs). I was wondering if there's a way to do the same thing with sounds as you did with the gif. So rather than waiting until the sleep ends before the sound plays, it could play as soon as the button is pressed. I have the code to start the sound on the same button as the code that begins the gif, but of course the sound only begins playing after the transition to the next card.

Sorry for all the questions, but your last reply was very helpful so I'd be remiss not to ask.

Developer(+2)

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.

(+2)

That was surprisingly simple... thank you again :)

(+2)

I'm confused by the following session at https://beyondloom.com/tools/trylil.html

# This is from the tutorial
local x:(list 1,2,3)
local y:(list 4,5,6)
print[flip x,y]  # => 142536
# So far so good
local s:"abcdef"
local l:"" split s
print[l]  # => abcdef
print[typeof l]  # => list
print[keys l]  # => 012345
local r:random[2 count s]
print[r]  # => 100111 say
print[typeof r]  # => list
print[keys r]  # 012345
# so l and r are both lists with 6 elements each
print[flip l,r]  # abcdef100111

I expected the last line to show something like a1b0c0d1e1f1. I can't tell what difference there is in the shape of the arguments to the two calls to flip.

(2 edits) (+1)

Oh never mind, I got it running on my machine and it makes more sense at the lilt REPL. `flip` expects (at least?) 2D lists.

(+1)

How do I check for more than one condition in a single if statement?

(+2)

&  is AND,   |  is OR.

if thing1.value & thing2.value # AND
if thing1.value | thing2.value # OR
if ! thing1.value # NOT

Do these examples work for what you need? There's more in this post as well.

(3 edits)
x:1 y:1 z:1
if x=1&y=1&z=1 "success" else "failure" end

That helped me quite a lot. Thank you!


I did have an issue modifying the datePicker contraption though, but got it to work eventually.

I created an additional field with the selected date (v) versus the navigated-to date (p). I only wanted to invert the day of the month cell when it matched the exact date that was selected (not selecting new days of the month as the months and years were cycled through).

I don’t think I need to post the whole code block, but for some reason the if statement only works when I put the conditions in their own brackets:

if (index=p.day-1) & (p.month=v.month) & (p.year=v.year)
   canv.invert[cell+1 cellsize-2]
end

Any idea as to why the above code works, but removing the brackets causes it to fail?

(The line of code in question is at the bottom of the prototype script in the datePicker contraption, if someone requires more context: if index=p.day-1 canv.invert[cell+1 cellsize-2] end.)

Developer(+2)

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))))
(3 edits)

I did check out that link, but I honestly didn’t fully comprehend it. The explanation you provided has given me a lot more clarity. Thank you!

I had a sort of aha moment when I was customizing the datePicker and changed the text output to include the year beside the month at the top.

canv.text[""fuse month_names[p.month-1]," - ",p.year (0,5)+canv.size*.5,0 "top_center"]

I’m not a strong programmer, but I feel like that line’s syntax could only work evaluating right to left. Am I understanding that correctly? (More so about nesting the fuse command in there, I feel.)

Also, is the fuse command the only way to concatenate strings?


Edit: I guess I didn’t need the fuse part. I just edited it to:

canv.text[month_names[p.month-1]," - ",p.year (0,5)+canv.size*.5,0 "top_center"]

Nevermind, I have a lot more learning to do.

Developer(+1)

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

(+1)

That makes perfect sense. Thank you for your patience.

(+3)

hey IJ! I'm teaching myself coding from scratch as I make stuff on here; I've crawled this thread as well as this post regarding how Decker parses values and I'm still having a bit of trouble with a script I want to run (I'm honestly not even sure if I'm thinking about this correctly, lol). I have a little clickable, and I want to create a condition for each button pressed that when clicked, it'll set the value to 1, and when all objects in the card have been clicked, it triggers the dialogizer to play something. I have a command in the title screen that resets everything to false when the deck resets, and have confirmed that clicking the buttons makes them set their value to true, but nothing I do at the card level to read these values and trigger dd seems to be working. how would you recommend doing something like this? let me know if you need more details, and thank you in advance!

Developer(+1)

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.

of course! I've tried a few things so far, at the card level. below are the checks I've tried, and neither have worked.

while view
if blueFrame.value:1 & pottedPlant.value:1 & bookStack.value:1
dd.open[]
dd.say["The code worked!"]
dd.close[]
else
    end
end
if blueFrame.value:1
 pottedPlant.value:1
 bookStack.value:1
dd.open[]
dd.say["The code worked!"] 
dd.close[] 
else 
    end
end

below is the code I have in blueFrame. it's nearly identical in the other two objects, with just the dd text changed

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.)"]
blueFrame.value:1
dd.close[]

and this is the code in the title card that sets the values to false if they aren't already.

on view do
 cardOffice.widgets["blueFrame"].value:0
 cardOffice.widgets["pottedPlant"].value:0
 cardOffice.widgets["bookStack"].value:0

thanks for your quick reply!

Developer(+2)

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?

(1 edit) (+1)

yes, this absolutely does! I just tried it out, and it worked! I had to move the checkObjects event call below dd.close[] (I think since the check initiated a dialogue box), and that finally did it. thank you so much for your help. I'm learning something new all the time while tinkering with this, and its been a lot of fun.

(+1)

quick zazz-related question: is there any way to add a speed modifier for zazz.scroll the way you can for zazz.wave? any attempt to place a number after "0,-1" produces no effect

on view do
 zazz.scroll[canvas3 0,-1]
 go[card]
end
Developer(+2)

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
(+1)

Quick question about brushes: seems like there's a global brush[] function to add custom brushes (and see a dictionary of them).  Is there any way to (through scripting) change which brush is selected at the deck or card level?  I know there's clicking on the Style>Brush menu, but I would prefer to do it with scripting (as the brushes I've made are more like stamps and are huge and overlap quite badly on that menu).

I only see the canvas-specific x.brush[] in terms of script control over brush selection... Is that it?
thanks! june

(1 edit) (+2)

Scripts typically can only run while you're in Interact Mode (or from inside the Listener). Likewise, the canvas.brush attribute you mentioned, when it's used in a script it only meaningfully changes the brush setting for that one canvas when the canvas is clicked in Interact Mode, y'know? (I think you knew that, I'm just clarifying for anyone wandering by.)

So, yeah, I don't think it's possible to change your Drawing Mode tool or brush via scripting.

But, in case it's of use to you and your project: 

The Toolbars do extend downwards with an arrow at the bottom if you have more than the default amount of brushes.


 And as a further example, this is how the 'stamp'-style brushes from the brushes example deck appear on the toolbar:


The last one is quite a large stamp, relative to the others, but it's neatly constrained to it's own little rectangle and it's not too hard to figure out which one is which.

I hope this helps, I'm excited to hear that someone is making use of custom brushes!

Thanks! I figured out that the toolbar cuts off images that are too large thankfully, so I did get to play around a bit, but it's unfortunate drawing mode is so cut off from scripting...

(+1)

good evening all! question regarding the use of dd.chat. I've got a table (inventoryGrid) with the columns "inventory" and "itemInfo" (formatted as strings), and have written a few button scripts that add items when clicked. I made a separate button that will display a list of whatever items are in the table, and display the description when selected. so far, my code looks like this:

on click do
 dd.open[deck o]
 dd.say["Hallo, traveller."]
 if itemHave.value
  dd.close[]
  dd.open[deck r]
  dd.chat["These are the items you're carrying." (raze inventoryGrid.value)]
  dd.close[]
 else
  dd.say["Huh? My friend, you're not carrying anything. Pick something up and come back if you want me to tell you about it."]
  dd.close[]
  end
end

and I'm sure it could be simplified, but it works as intended! as for my question: I'd like to be able to have a final option at the bottom of the list that says something like "I'm leaving, now." that can be selected and trigger a final line of dialogue followed by dd.close so that you don't have to read through all of the options in order to exit the menu. I'm kinda lost trying to figure out how to script this due to using (raze.inventoryGrid.value). I'm not sure how to edit it in a way that will function. what's the best way to go about this?

Developer(+2)

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?

(+1)

thanks so much for the quick response! I think I'm beginning to get this a little more, and the various examples were super helpful! I was able to add the line by using your second suggestion, and look forward to messing around with dicts some more, haha. thanks again!

(+2)

Having a lot of fun with Decker and Lil.
Thank you.

Is this expected behaviour for joining arrays?

a1:("aa","bb","cc","dd")
a2:("x")
show[ a1 join a2 ]
--> (("aa","x"),("bb","x"),("cc","x"),("dd","x"))
show[ a2 join a1 ]
--> (("x","aa"))

Lil Playground

Developer(+1)

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?

(+1)

Is there a way to get  

(("x","aa"),("x","bb"),("x","cc"),("x","dd"))

from a1 and a2 without using each?

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

nice, thanks  🙂

(+1)

Hi guys! I was wondering if there were any video tutorials for Decker and Lil on YouTube or anywhere else. Anything is helpful, thanks!

(1 edit)

The only video I’ve found is this one.

Generating Music in Decker

It’s not bad and kind of takes you on a journey of someone figuring out Decker, while you watch it to figure out Decker. It’s very meta. ;-)

Not a video, but here is a handy list of screenshots of these Decker examples.

(1 edit) (+1)

I have this Lil code to print a table of 10 English words that remain words when the 'r's are removed from the word:

words:wods:still:0
each w in "" drop "\n" split read["words"] # /usr/share/dict/words"]
  words[w]:1
  if "r" in w
    wods["" fuse "r" drop w]:w
  end
end
each v k in wods
  if k in words
    still[v]:k
  end
end
show[select word:value wod:wods@value from random[still -10]]

which produces output like

+--------------+---------------+
| word         | wod           |
+--------------+---------------+
| "evaluated"  | "revaluated"  |
| "emigated"   | "emigrated"   |
| "boated"     | "borated"     |
| "estated"    | "restated"    |
| "expatiated" | "expatriated" |
| "elatedness" | "relatedness" |
| "pedated"    | "predated"    |
| "ungated"    | "ungrated"    |
| "pated"      | "prated"      |
| "gated"      | "grated"      |
+--------------+---------------+

if reading a 'words' file that contains only the 3k words that include "ated", this runs in 11s (lila.awk) or 16s (lilt). So, actually reading /usr/share/dict/words with its half a million words, is pretty infeasible.

Is there a much better way to do this in Lil, or is this sort of batch processing out of scope for Lil?

Oh, I had a second question about selecting from an array using an array of booleans, but I found that 'select' can do that. Which gives this code that runs in 22ms in lilt, instead of 16s:

words:extract value where 5<count@value from "\n" split read["words"]
still:select word:value wod:(on f x do "" fuse "r" drop x end)@value
    where (on f x do ("" fuse "r" drop x) in words end)@value
    where (on f x do "r" in x end)@value
  from words
show[table random[still -10]]

but this needs 4s to process only 50k words, so the full dictionary's still out.

Developer (1 edit) (+1)

Hmm. Could be tricky to make this fast in Lil.

In general, using queries is much more efficient than loops. You can slightly simplify

where (on f x do "r" in x end)@value

as

where value like "*r*"

and you could hoist that "in" out of the loop, since it accepts a list as a left argument. Together, these ideas can make the query much more concise:

on strip x do "" fuse "r" drop x end
still:select word:value wod:strip@value
 where (strip@value) in words
 where value like "*r*"
 from words

...but probably not much faster.

You can also avoid stripping the r's twice by using a "subquery"

still:select where wod in words from
 select word:value wod:strip@value
 where value like "*r*"
 from words
(+2)
wods:"\n" split "r" drop words:read["/usr/share/dict/words"]
words:"\n" split words
still:select word:words@index wod:value
  where (wods in words)*(extract value like "*r*" from words)
  from wods
show[table random[still -10]]

this gets an answer in 13min, or 880ms without the 'wod in words' test. I've tried a few alternatives (words dict 1, readdeck of a grid, parsing and reading a json of a table) and nothing seems to cut that down. It seems like building large tables is slow. Maybe due to the allocator?

But, this was only for learning purposes and I gained a better appreciation of the query language from it.

Developer(+2)

I made some localized improvements to C-Lil's implementation of the "in" operator. Using this dictionary file:

https://github.com/dwyl/english-words/blob/master/words_alpha.txt

and this version of the entire script:

words:extract where 5<count@value from "\n" split read["words_alpha.txt"]
on strip x do "" fuse "r" drop x end
still:select where wod in words from
 select word:value wod:strip@value
 where value like "*r*" from words
show[table random[still -10]]

The patch brings execution time on my laptop from about 12 minutes (oof) to about half a second.

(+1)

Is there a nicer way to skip the empty lists resulting from this query?

 str:"hello world"
 extract list value where 1<count index by value from str 
((),(),("l","l","l"),("o","o"),(),(),(),())
Right now I have

 (list ()) drop extract list value where 1<count index by value from str 
(("l","l","l"),("o","o"))

  With the aim of showing only repeat elements, so

 "" fuse first @ (list ()) drop extract list value where 1<count index by value from str 
"lo"
 "" fuse first @ (list 0) drop extract first value where 1<count index by value from str
"lo"
 "" fuse extract L where C>1 from select L:(first value) C:count value by value from str 
"lo"
Developer(+1)

You could use a conditional to compute the extracted column (this is evaluated once per group):

extract if count value first value else () end where 1<count index by value from str

Or you could use the "gindex" column to make the filter retain at most one element per group:

extract where (!gindex)&1<count index by value from str

How's that?

(+1)

Both of those work! I didn't know about gindex, and that seems useful. I kept looking for some way to have a 'where' apply after an earlier 'where' without multiple queries, and the conditional shows a way to do that.

The conditional also suggests this solution:

 extract () unless first value where 1<count index by value from str
(+1)

Hi, I have a question about sounds. In my current project, I have an options card for various things and I want to have an option to mute all sounds in the deck. So far, I thought of putting this script everytime a sound is played, with a checkbox button named "soundsandmusic" on the options card:

if options.widgets.soundsandmusic.value=1
    play["sound"]
end

And I was wondering if there was a more simple way to do this, instead of putting an if statement everytime a sound is played.

Developer(+3)

You could define a function which "wraps" the default play[] in the deck-level script, preventing the original from being called unless your flag is set:

on play x y do
  if options.widgets.soundsandmusic.value=1
    send play[x y]
  end
end

The "send" statement is specifically designed to make it easier to set up these kinds of overrides for existing functions. (Overriding go[] has some interesting potential!)

If you think there are some situations where you may want to call play[] normally, a less invasive approach would be to give your wrapper a different name instead of shadowing the built-in function, and to change e.g. all the instances of play["foo"] within your own scripts to sfx["foo"]:

on sfx x do
  if options.widgets.soundsandmusic.value=1
    play[x]
  end
end

Keep in mind that scripts within modules and contraptions don't "see" deck-level definitions, since they're supposed to be reusable from deck to deck. If you're setting up sound effects for dialogizer text, for example, you'll need to perform the same flag-check. This can also be done in a centralized place with deck-level functions.

Does that make sense?

(+2)

Thank you, it's exaclty what I needed!

Very very new to the program here. I am trying to make a system where there is a button that is unavailable to use (Whether it be locked or invisible or whatnot) until a slider has reached a certain value, but without it flickering each time that the slider gains or loses value (Which is what happens when i use the toggle function for this.).  So if its 70 or above, it can be pressed, and if its any lower than 70 it can't. 

(+2)

Hello, welcome! I have a simple script for you. Where to put it is going to vary a little bit based on what you're doing.

If you're changing the number inside the slider by having the user click on it, you could put something like this in the slider's script:

on change val do
 if slider1.value > 69 
  button1.locked:0 end end

Or, explaining what that means: "Whenever my value is changed (by the user clicking on me), check if my value is above 69. If yes, unlock the button. End (for the if statement). End (for the on change val do event)."

This only unlocks the button -- it doesn't re-lock it if the number goes back down. I'm not sure if that's even something you want, but you could have it re-lock below 70 with an added "else":

on change val do  
 if slider1.value > 69    
  button1.locked:0
  else   
  button1.locked:1  
 end
end


I'm locking and unlocking the button in this example but you can use nearly the same snippet to change the button's visibility instead. 

Rather than using .toggle here I'd prefer to use .show just to feel like I had complete control over it:

on change val do
 if slider1.value > 69 
  button1.show:"solid"  
  else  
  button1.show:"none"  
 end
end

If you end up hiding the button you'll probably want to use .show:"none" rather than .style:"invisible"  in the script.

The difference between them is that even though "None" buttons are effectively locked AND hidden from view... Invisible buttons can still be clicked. This is useful for users who are drawing scenes and want to use buttons as containers for their clickable areas without the standard appearance of a button.

The above scripts are assuming that the slider's value was changed by the user clicking on it and that the script is inside the slider.

But if the slider's value is going up because of something happening in a script somewhere else you could add the "check if >70, unlock or show button" snippet to the end of that other script instead.

For example, a script inside a button which adds +10 to the value of the slider, and also unlocks a button if the new number is more than 70... could look like this:

on click do
 slider1.value: slider1.value+10
  if slider1.value>69
  button1.locked:0
  end
end

I hope this points you in the right direction! If this isn't what you needed, then I'm happy to try again.

(+2)

Thank you so much! This is is exactly what I needed!

(not sure if this is a decker question or a lil question sorry in advance)

i'm making a small contraption with a grid widget, and i'd like to ensure that the rows are unable to be deleted but for a single column to be editable.  I'm using a column format of LLLs to lock the columns I'd like to not be edited.  I know "locking" the grid would prevent deleting, but it would also prevent editing...

And whenever I select a row or a cell, the backspace key will delete the entire row...

Any way to get the desired behavior?

(+1)

is there any way to have the "style" tab available when using Interact? if not that, then just tracing mode.

(+1)

I'm pretty sure the Style menu option is only available in drawing mode.

That said... I'm very curious what you're up to with tracing mode, if you're inclined to share.

well, i was using the Wigglypaint program, saved a drawing as .gif, and then later on thought of an edit i wanted to make to it. wigglypaint only works when using the interact tool, not the intrinsic drawing mode.

Hey! I'm trying to figure out a way to implement a simple 60 second timer that doesn't block the player from interacting with the game, like with the sleep command. Once the timer is complete, I just need it to send out an event. I tried to reverse engineer the timer on the GUI examples, but I quickly realized I had no clue what I was doing since I'm still newish to Decker and coding. Was wondering if someone could give me a hand.

(+1)

I often make a timer in a basic widget when I need one.

A simple example:

If you make a field and set it to be "animated" (select the widget, then in the menu select Widgets > Animated) it will experience 60 view events per second.

Since this example is using a field we can also ask it to store a number to keep count of how many view events have happened. And when it reaches a certain number (60 view events multiplied by the number of seconds -- for 60 seconds this is 3600) tell it to do something special.

on view do
me.text:me.text+1
if me.text=3600
#the stuff you want to happen#
 end
end

(When you're writing a script inside a widget, you can refer to that widget as 'me', which is what I'm doing in the script here.)

This may not be needed but you can also make a custom event handler pretty easily:

on view do
me.text:me.text+1
if me.text=3600
my_event[]
 end
end

I'm using the example name "my_event" but you can name it something more specific and then define what happens during this event inside of the same widget's script that uses it (or on a higher level like the card or deck-level scripts) by writing it out like any other event handler:

on my_event do
# the stuff you want to happen #
end

Also there was a recent thread about making timers for someone else's game that has some more discussion, if it's of any use to you: (link here)

(+1)

Forgot to reply. Exactly what I needed, thank you!

(+1)

Can cards call functions on each other?

For example if Card A has a function that does some operations on itself, can Card B call that function? Would it get a return value from that function?

If so, how would I do that?

Developer(+1)

Yes, by sending events. A script on CardA can call function "foo" with the argument "bar" on CardB like so:

CardB.event["foo" bar]

This expression returns the result of the function call. In this way you can design cards that act as "APIs" to be called by scripts elsewhere in the deck.

You can likewise call .event[] on widgets. This "synthetic" event is indistinguishable to the target (thatButton) from an event generated by Decker itself:

thatButton.event["click"]

When an event is sent, all the deck parts and other magic variables will be in scope from the perspective of the target: widgets will "see" other widgets on the same card, "me" will be bound to the target, etc.

(+1)

Thank you! If a card has a numerical value as a name, is the best way to access the card globally to use: 

deck.cards["123"]
Developer(+1)

Yes; That's how you'll need to do it.
As a general rule, though, I strongly recommend naming cards and widgets with valid Lil identifiers; it makes scripting much easier and less error-prone.

(+2)

Is there a way to change the widget order through a script? I want a canvas to rise to the top when dragged.

(+2)

Yes! A widget's order placement can be modified with .index

For example:

on click do
 me.index:999
end

This script moves a widget (referring to itself within it's own script as "me") to the top of the widget order when it's clicked.

index:0 places a widget at the bottom of the order, index:999, or any other sufficiently large number, places a widget at the top.

You can put this snippet into an "on drag do" event handler as well.

(+1)

That worked like a charm! Thank you!

(+1)

Hey! I love the engine and trying to understand it as much as I can so this might be a silly one, but in the guided tour you mention that single grids could be used as a selection input. How do you know what value is selected with it? With Buttons with Checkboxes you have buttonName.value that you can compare, but I don't see how that would work with the grid widget. Thank you for the help! 


I also don't understand how to add new items to a grid like an inventory system. I made an items grid that has two columns of Name And Description. I've looked at you helping someone with an inventory system yet when I try to re-create this function in the Decker script

on add_item n do 
 i:get_items[] 
 i.value:insert name:n into i.value 
end

I'm met with an error of "Expected name, but found:. It's an error that happens when I try to put this

n into i.value 

I've even tried to add this code to a button

on click do

i:inventory.widgets.items

i.value:insert name:"sworde" into i.name

alert["ye have acquired ye sworde!"]

end


But I still get the same issue as before what am I doing wrong? 

Developer (1 edit) (+2)

If you refer to the Decker Reference Manual, grid widgets have a ".rowvalue" property that can be used to obtain the data in the selected row; this will be a dictionary. The "first" of such a dictionary will obtain the value for the first column:

on click do
 f.text:first g.rowvalue
end

In this particular example it would be equivalent to ask for the "key" entry of the dictionary, as that is the name of the first column:

on click do
 f.text:g.rowvalue.key
end

As for adding rows, it looks like you were consulting a very old example from before the "insert" syntax was overhauled. Given a grid "i" with a single column "name", I think you want something closer to:

i.value:insert name with "sworde" into i.value

The Lil Reference Manual has detailed explanations and many examples of the "insert" query form.

Does that make sense?

(+2)

This makes so much sense I'm so sorry I think I just got lost digging through the documentation and must have missed it. Thank you for your time!

Is there an easy way in Lil to round a fraction to an integer?

I’m trying to render text to a canvas, centered within a given area. That’s easy enough, I can just measure the text string, subtract it from the available width, divide by 2, and use that as the left edge:

# Measure the text, minus the extra space at the end
textsize:canvas.textsize[text] - (canvas.font.space,0)
 
# Render the text centered
canvas.text[text pos+(size-textsize)/2]

If I’m drawing an even-width string into an even-width box, or an odd-width string into an odd-width box, that works perfectly. Otherwise, the coordinates wind up with a fractional part, which Decker truncates to 0, meaning that text that can’t be mathematically centred is shifted to the left.

That’s just mathematics and there’s not much to do about it, except that the strings I’m rendering tend to be Title Case, so they’re already visually weighted to the left. I think it would look a little better if fractional coordinates were rounded up rather than down, balancing the Title Case rather than reinforcing it, while leaving the mathematically-centred text (no fractional part) alone. Again that’s pretty easy:

canvas.text[text pos+(0.5,0)+(size-textsize)/2]

That works perfectly… unless the box I’m rendering into is also centred, which means it already has a 0.5 offset. In that case, the even-string-in-even-box and odd-string-in-odd-box wind up worse - instead of being rounded away, the extra 0.5 adds up to extra offset. I figure if I can round pos before using it in calculations, I can prevent errors from accumulating, something like this:

# I wish this worked!
pos:makeint[pos]

Playing around, it looks like I can probably do this with:

pos:bits.or[pos 0]

…but that seems a bit hacky. Is there a cleaner way to do this?

Developer(+2)

You may be looking for the "floor" primitive? There's no corresponding "ceiling" primitive but you can take the negation of the floor of the negation:

 select value down:(floor value) up:(-floor-value) from .1*range 21
 
+-------+------+----+
| value | down | up |
+-------+------+----+
| 0     | 0    | 0  |
| 0.1   | 0    | 1  |
| 0.2   | 0    | 1  |
| 0.3   | 0    | 1  |
| 0.4   | 0    | 1  |
| 0.5   | 0    | 1  |
| 0.6   | 0    | 1  |
| 0.7   | 0    | 1  |
| 0.8   | 0    | 1  |
| 0.9   | 0    | 1  |
| 1     | 1    | 1  |
| 1.1   | 1    | 2  |
| 1.2   | 1    | 2  |
| 1.3   | 1    | 2  |
| 1.4   | 1    | 2  |
| 1.5   | 1    | 2  |
| 1.6   | 1    | 2  |
| 1.7   | 1    | 2  |
| 1.8   | 1    | 2  |
| 1.9   | 1    | 2  |
| 2     | 2    | 2  |
+-------+------+----+
(+2)

…oh! Well, I feel silly now. I searched the docs and this forum for “round” and “truncate”, but I never thought to search for “floor”. Thanks!

(+1)

Sorry if this has been answered already, but is there a way for a widget to automatically trigger on mouseover?

(+1)

This thread has some examples of how to make things happen on mouseover/hover.

It also has an important caution about how mouseover triggers can't work the same way on mobile and touchscreen devices since they don't have a mouse pointer. It's an extra thing to think about if you'd like your project to be playable on those platforms (and if the mouseover event isn't just a optional detail that won't be missed if it doesn't work).

(+2)

Any way to run lil language as independent file format , like edit and run in vs code? I like its simple and comprehensive way to manipulate table and list.

Developer(+1)(-1)

Yes. Lilt is a command-line wrapper for the Lil programming language with a few additions to its standard library like the ability to shell out to other programs and interact with the filesystem. It's easy to build from source on MacOS, Linux, or BSD. I also provide a multi-platform prebuilt binary here on itch.io alongside Decker releases.

If you just want to try something out quickly or share a snippet, TryLilis a browser-based sandbox for Lil.

The Decker repo includes basic syntax highlighting profiles for vim, emacs, Sublime Text, and CodeMirror; you can use those as a starting point for a syntax profile for other editors.

Recently, I encountered a problem when learning text dialogue and canvas bit movement painting: 1. DD follows the steps in the tutorial, and the dialog box cannot appear
2. Zazz can't exercise according to the steps in the tutorial

Even if you copy the card from the tutorial to your local computer, it has no effect.



Developer

Did you import the zazz and dd modules into your deck using the Font/DA Mover?

(3 edits) (+3)

Hi, I have a question about alert when it can prompt for a string. Probably the most basic use case imaginable, how can I write that input into a widget? E.g.

alert ["What's your name?" "string"]

And then write that name to a widget called "field1" or something? Sorry if folks have answered this before, I found a few cool threads on logging text from a field but I genuinely couldn't find anything on how to use this for alert specifically.

(+3)

You can just say something like:

field1.text:alert["What's your name?" "string"]

Here’s an Ask button that asks the question, and sticks the response into a field:

%%WGT0{"w":[{"name":"field1","type":"field","size":[100,20],"pos":[154,108],"value":"blorp"},{"name":"button1","type":"button","size":[60,20],"pos":[174,74],"script":"on click do\n  field1.text:alert[\"What's your name?\" \"string\"]\nend","text":"Ask"}],"d":{}}
(+3)

Thank you! Those are perfect. I really appreciate it.

(+3)

This is going to be such a stupid question but I just feel so lost....how do I import an image to a canvas to use with dialogizer? Do I have to create the images in the canvas and lock it in Decker? If I try to import an image as is, it just becomes a static object I can't interact with after I'm done changing its size. 

(+3)

All questions welcome!

When you import an image into Decker, it has the selection box around it, right? And when you click outside the box, the image is placed on the back of the card.

But you can also copy (or cut) the image while it's still inside that selection box, switch to Widget Mode... and in the Edit menu select "Paste as new Canvas".

Personally I like to turn on the Toolbars (Decker > Toolbars) for this, to save a click while switching back and forth between importing/drawing and pasting my images into canvases.


Also, anytime your image is on the back of a card you can draw a selection box around it and then use the Edit menu option "Tight Selection" to shrink the selection to only contain your artwork.

(1 edit) (+2)

Hi! Probably basic question incoming, but I’m stuck…

I’m trying to track whether the player is wearing a cloak to display certain widgets or to send them to a specific card. I’ve created a checkbox for this, and when manually clicked on/off, the conditionals are tracked properly.

The issue I’m running into is that I can’t seem to change the value of that checkbox via Lil directly. As in, if the player click the Start button, the checkbox is enabled (because they wear the cloak at the start of the game), and if the player clicks the button “Hang Cloak”, the checkbox is disabled. Or it should be.

~~So far, I’ve tried:

Cloakroom.widgets.WearCloak.value:true

and

Cloakroom.widgets.WearCloak.value:true
Cloakroom.widgets.WearCloak.event["change" Cloakroom.widgets.WearCloak.value]

~~ I feel like I’m missing something but I don’t know what/how… EDIT: I FOUND THE ISSUE! I wrote true instead of 0/1….

Also, follow up question: if the display of the checkbox is “Invisible”, is the value of the Checkbox still accessible? (If not, I’ll just hide it behind some text :P )

Thanks in advance!

(3 edits) (+2)

As a follow up, I’ve tried creating variables too, but it didn’t work either.

I’m trying to make an invisible counter tracking the amount of time a player visited a card (and could be reset), so like a numerical variable…

EDIT AGAIN: Counters. That’s how it works. then add +1 to the text. Got it.

Developer(+1)

Just for the record, you can represent counters using a field widget via its .text or .data attributes, or alternatively you could use a slider widget via its .value attribute. Sliders are slightly more convenient than fields for representing numeric values within a known range, since their .value is always a number and is automatically "clamped".

Widgets set to "Show None" behave identically to visible widgets from a scripting perspective; they "remember" their contents across events and when the deck is saved. If there are any widgets whose contents you don't want preserved, you can mark them as Volatile. Sometimes volatile widgets are useful for "resetting" the state of a game.

Does that help clarify?

(+1)

It does! Thanks a lotT

(2 edits) (+2)

Not actually a question, but a thing that tripped me up and was confusing me. I wrote a bit of code to iterate over an array of strings and slow-print them to a canvas, terminal style. But I was finding some bizarre results when I went to test it: the first line would draw fine, but subsequent lines would go bananas, because somehow the Y coord was changing within the inner loop.

Some simplified code looks like this:

each line y in script
 each c x in line
  x*6,y*13
 end
end
> (((0,0),(6,0),(12,0),(18,0),(24,0),(30,0),(36,0),(42,0),(48,0),(54,0),(60,0),(66,0),(72,0),(78,0),(84,0),(90,0)),
   ((0,0),(6,13),(12,26),(18,39),(24,52),(30,65),(36,78),(42,91),(48,104),(54,117),(60,130),(66,143),(72,156),(78,169),(84,182),(90,195),(96,208),(102,221),(108,234),(114,247)),
   ((0,0),(6,26),(12,52)))

Notice how the Y coord keeps going up where it's not supposed to? 

At first I thought it was something weird with how canvas.text[] works, or that i had my offset or loop logic wrong, but then I realized after comparing this to a version without the multiplication:

each line y in script  
 each c x in line   
  x,y    
 end 
end  
> (((0,0),(1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0),(8,0),(9,0),(10,0),(11,0),(12,0),(13,0),(14,0),(15,0)),
   ((0,1),(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),(18,1),(19,1)),
   ((0,2),(1,2),(2,2)))

That looks like what you'd expect, right? 

Turns out the problem is operator precedence. Since Lil can do matrix math, what's happening is it's actually multiplying x by a vector of (6,y), then multiplying that by 13, so you get nonsense. Wrapping the two multiplication operations in parens fixes the problem. It didn't occur to me at first 'cause in most languages, `,` isn't an operator, it's just syntax.

This was enough of a weird surprise I thought I would share with folks, in case y'all run into similar.

(+2)

So I know that & and | work like a boolean AND and OR but is there anything that works like an XOR?

Developer(+1)

Depending on what you're trying to do, either a combination of ! and = (for a scalar boolean value, logical XOR is the same as "not equal to") or possibly bits.xor[].

(+1)

Aha, thank you!

TLDR eval->dd.say isn’t passing rich-text into the dialogizer In your Dialogizer deck, on the rich-text card, you mention that it’s easier to style by throwing rich-text into a field and having it parse using the eval method. So I’ve got a field, i’ve got it set to invisible in the card script, I’ve got a button that does the eval method. Everything is working well except that it isn’t showing the styling I’ve set in the field.  I’ll mention, just as I typed this I tried to add a hyperlink in the same way (just manually add it to the field) and it also didn’t pass through the parsing operation.

Forgive me, I’m one of those dudes who got by in the 00s by just manually deleting html and css stuff and adding it back to “create my own myspace profile”. Like, that’s my level of coding know-how. I’m sure this question is voiced annoyingly; I’m trying to use words I’m not fluent with. Thank you for everything you do here IG.

Ok forgive me, I didn’t follow your operation faithfully, I now see. Your method was to use a button to point at one field that itself pointed at another field (which contained the rt formatted text).  I was skipping the middle-person field and apparently, by doing that, it doesn’t render the rt formatting.  I’m leaving this post because it may come up in the future; perhaps IG (or someone else) could simply verify that this was indeed my problem and if anything maybe propose how I might’ve skipped the middle-person step and kept the RT formatting.

(+2)

eval is a handy tool for making code snippets visible in the documentation, but it's not often needed while making projects with dialogizer. And it's probably working against you here.

This:

dd.open[deck]
dd.say[yourcoolfield.value]
dd.close[]

should be enough to play a scene written in a rich text field, with all of the formatting intact.

It's important that you point dd.say at the field's .value (which includes the rich text formatting) and not .text (which is plain text only)

If you strip it down and just put the above code snippet (with "yourcoolfield" renamed to be correct for your project) does it work?

(+2)

That did it. So it must’ve been that I was using text and not value

thank you

(+1)

I’ve got another, probably totally silly question,  (I promise I look through all the documentation, the problem is that I’m basically illiterate in basic coding). As you’ll see in this video (how do you folks get those cool embedded videos to happen without using youtube?) I’m running a card script which evals the field on the left (called cardscript; yes i know the text above the field has a space, trust me it’s labled without the space).  I’ve defined a (dunno what to call it) thing called “fields” which I want to use as a bucket to hold all of the fields i wish to make invisible at when I click “eval below”.  As you can see, when I just define the thing as ’source’ it works, but when I add a comma and include ‘cardscript’, not only does it fail to include cardscript, but now source remains visible.

Secondary issue: The !alert works, but does not retain the richtext formatting.

(+1)

to get ahead of “why are you doing this in fields”, the answer is, “I’m trying to teach myself how to code AND how to code in Decker and it’s a ton of friction having to click back and forth between widgets and interact, into the card script etc. and theoretically, it shouldn’t matter provided I do this correctly."

(+3)

It's totally fine to be new and have questions! Decker is a pretty friendly environment to try things and figure them out. And I think most of the things that look like videos in the thread are actually gifs. I know I use a screen-to-gif recorder when I need to make a small example of something.

Okay, I think these are your main questions:

1) How can I make Decker use the whole list of widgets I defined earlier?

You just need a tiny change to how you're referencing them. You need two periods, instead of just one in this case.

fields..show:"none"

2) How can I make my !alert use my rich text formatting?

In-line commands run by dialogizer don't carry over any of the rich text features. I'm pretty sure they're just run as code.

However, you can point to another field's value inside of your !alert[] in the same way you point to source.value inside of dd.say[].

!alert[problem.value]

I know it's another step, and another thing to have to manage while you're editing but it should be pretty straightforward. 

Another thing that I think might help you sometimes is the ScriptViewer contraption. You can have it display the card script and let your edit it from your card in interact mode instead of having to use a workaround with eval. And because the ScriptViewer only displays scripts that exist elsewhere you can safely delete the ScriptViewer contraption when you're done.

Just another possibility, in case it helps!

(+2)

I’m just super appreciative of the work you put into helping me. I hope I can repay you or you vicariously through this community some day.

(2 edits) (+1)

Hi, I noticed a discrepancy in behaviour between native decker and exported HTML decker  and was wondering if anyone knew why.

Basically wanted a looping sound to play “on view” of the very first card in the deck:

on view do

play[“sound1””loop”]

end

In native decker, this seems to work. I believe I also got it to work in web decker directly. But I am finding in a web decker protected export from native, it only plays the sound once (and weirdly, after clicking into the deck). However, if you do it as a second card you view through some other trigger, the same  code works in web / native.

Are there a special considerations for doing “view” actions on the very first card? 

Many thanks in advance if anyone knows…

Developer(+2)

As a general rule, web applications are not allowed to start playing sound without user interaction. Web-Decker attempts to start its audio subsystem when the user first clicks or types on the keyboard. It's possible there's some additional inconsistency in behavior, but what you're describing sounds to me like normal webapp sandboxing constraints.

(+2)

Ah thank you that would explain it!

(1 edit) (+1)

Hello! I'm discovering Lil and setting myself some little challenges. While I'm used to a lot of other languages, I'm wondering if there is a simple way to declare lists of lists. 

My intuition would be

(0,1), (2, 3)

but this actually is the same as (0, 1, 2, 3). I guess that while () is the empty list, parenthesis aren't lists delimiters as [] in other languages. I understand that comma is the concatenator for lists and that the correct solution to my problem would be

(list 0 1), (list 2,3)

but I'm wondering if there's a more elegant solution.

Thank you!

Developer (1 edit)

Lil doesn't have a per se list literal syntax. Your example is one straightforward way of making nested lists:

(list 0,1),(list 2,3)

Another fairly general way of declaring static data is to use "parse" on a string of JSON/LOVE:

"%J" parse "[[1,2,3],[4,5],[6,7,8,9]]"
# ((1,2,3),(4,5),(6,7,8,9))

Within Decker it's often a good idea to store lengthy static data in a Field widget (where it can be encoded/decoded to/from LOVE via the .data attribute), or as a table stored in a Grid widget, where it can be viewed and directly manipulated. Modules (Lil libraries for Decker) don't have direct access to a deck, so they have a dedicated mechanism called a KeyStore.

If the sublists are of equal size, you could turn a flat list into a nested one using a primitive like "window":

2 window range 6
# ((0,1),(2,3),(4,5))

You could also construct a nested list piecemeal by index in a named variable:

a[0]:1,2,3
a[1]:4,5
a
# ((1,2,3),(4,5))

This last approach is particularly handy for nested dictionaries; see the code examples in the reference manual entry for the Array Interface.

Does any of that help?

(+2)

Thank you! Yes all of these help and are very interesting. I had noticed the JSON parsing style too (with which I would feel at home) and SQL-like tables but was interested to learn more about the other native options. I really like window! The other tips are very welcome as well! 

Thanks again!

With the "Make Magnet" contraption in the "All About Draggables" deck, I'd like to make the draggable canvases made with it spawn with a script. How can I do this?

Also, how can I implement a "whitelist" for the "Overlaps", that draws off a canvases' name/textual content?

(+1)

Hi! This isn't an answer yet, just some clarifying questions:

Are you working on using the "Make Magnet" example to make magnets that are created with scripts in them that use things like rect.overlaps and rect.constrain from the same example deck?

And did you want to make it so that only some of them do something when they're overlapping, and others don't? (based on some condition, or your mention of a whitelist).

Actually if you could give a specific description of what you're trying to do then I think we could help make sure you have all of the pieces of it that you need.

Yes, the example contraption, and I more specifically want to apply a general script to all of the canvases made with it to make them work with the Overlaps mechanic. It's the mechanic with a script applied to a draggable canvas - 

on release do

 if rect.overlaps[me target]

  alert["You hit the target!"]

 end

end

It would then execute an action if placed on the specifically named widget.

To make a project as a beginner, I'd just like to collate different contraptions to make games and potentially study the code and provided documentation.

(+1)

The script on the “Make Magnet” button just creates a new Canvas widget and adds it to the card. You can set or retrieve the script of a widget with the .script attribute. So:

mycanvas.script:"on click do alert[\"boo\"] end"

Alternatively, when a widget doesn’t have a handler for a particular event, that event bubbles up to the card, then the deck. If you want a bunch of widgets that all have the same behaviour, you can put the on click ... end handler in the card, and not have to worry about adding it to each widget individually.

(+1)

Thanks, I'll try the provided code with information considered. But, I'd like to ask what "bubbling up" is, if that is okay?

(+2)

A deck contains cards, cards contain widgets.

When you click a button in the “interact” mode, or a script runs somebutton.event["click"], Decker looks inside the button’s script for an on click do ... handler.

If it can’t find one, the event “bubbles up” to the card, and Decker looks for an on click do ... handler there.

If it can’t find one, the event “bubbles up” to the deck, and Decker looks for an on click do ... handler there.

If it can’t find one, Decker has built-in default handlers for certain events (like dragging on a canvas to draw on it), but most events wind up being ignored.

Thanks for the explanation about "bubbling up", but I've tried the provided script, I'm not sure where to place it or if there is a value to be filling in. Could you explain, please?

Trying to make an audio loop whenever a checkbox is enabled, but no idea how to make the script actually know if the checkbox is enabled or disabled. Right now, all I have is

```

on click do      

  play["bg1" "loop"]      

end

```

How do I add some sort of "if true" function, I've looked in documentations and tutorials and there's still nothing.

(2 edits) (+3)

The usual place I double-check how to write things in Lil is the Lil: A Scripting Language page.

So, what you need is an If statement. 

on click do
    if checkbox.value
    play["sound" "loop"]
    end
end

(I called the audio tracking checkbox "checkbox" but please call it whatever your widget is called.)

If the button that's being clicked to play audio is the checkbox and you'd like music to turn off when it's unchecked you might write it like this:

on click do
    if me.value
    play["sound" "loop"]
    else
    play[0 "loop"]
    end
end

Does this work for what you needed? I'm happy to give a different example if you have it set up in a different way.

Viewing posts 48 to 107 of 107 · Previous page · First page