Skip to main content

On Sale: GamesAssetsToolsTabletopComics
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,874 Replies: 346
Viewing posts 1 to 32 of 107 · Next page · Last page
Developer(+1)

This thread is for small questions about Lil, the Decker scripting language. If you have a more involved question, feel free to start your own thread. If we accumulate enough information for an FAQ I'll edit this post to include a link.

The Lil manual can be found here.

(+1)

Lil performance question: I have used LiveCode for years, and its built-in language is not fast. So when I try out a new language, the first thing I do after Hello World is a for loop to get a sense of the speed of the language. I tried that with Lil on an M1 MacBook, and found that 

on click do
each x in range 1000000
end
 alert["Done!"]
end

takes about 2 seconds to run. This is about 30x slower than even LiveCode. This is not a criticism, just a question: any thoughts on ways to speed up Lil?

Developer (4 edits) (+2)

There's a lot of room for improvement in Lil performance. The each loop in your example is particularly expensive because "each" is a map operation yielding a result list, range eagerly creates an entire list, and each loop body is executed in its own scope. Contrast with "while", which is considerably faster due to involving less bookkeeping and keeping much less in memory at once:

while x<1000000
  x:1+x
end

Or, when it's possible, just using the natural "conforming" over lists:

1+range 1000000

Performance for each over a huge list is particularly bad in c-lil; I'll do some investigating.

Interesting -- I'm not terribly familiar with list generation: I've played with Python, so I know it's a thing, but LiveCode has nothing like it built in, and I would never do something like

repeat with i = 1 to 1000000
    put i,"" after aList
end repeat
repeat for each item i in aList
    -- do something
end repeat

I just checked and found that the while loop was even slower :-)

I'm not sure this accomplishes the same task, albeit that the task is synthetic in the first place, but in any case, this is about 20x faster:

on click do
range 10000000
 alert["Done!"]
end
Developer (1 edit) (+1)

Oh, there's one other thing I should note that's probably making us talk past one another somewhat: Lil in Decker is deliberately capped at a specific number of "ops" (VM bytecode steps) per "frame" (display updates at 60fps) . This is an arbitrary creative constraint intended to help decks behave more consistently across different machines with wildly different levels of performance. The "FRAME_QUOTA" constant in the JavaScript implementation controls this cutoff.

Oh, HA! Okay, that makes sense. "constant in the JavaScript implementation" -- meaning updating it requires rebuilding Decker from source? Or is it configurable?

Developer

You can rebuild from source, or, in a web build, just open the file in your favorite text editor, search for "FRAME_QUOTA", and tweak as desired. I may raise the limit and/or make it configurable at runtime in the future.

Is there an API to check “fraction of script steps remaining this frame” from inside a Lil script? (For operations with shorter limits, like accessing a property on a Contraption, is there one that checks the operation limit?)

I’m consider9ing Decker to write a loosely-chess-inspired roguelike, and I’d want to use this to know when to stop searching for a move on the current frame and advance a “thinking” animation so the deck does not act frozen during the opponent’s turn.

Developer (1 edit) (+1)

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

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

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

(1 edit)

LiveCode script (and the GPLv3 community fork OpenXTalk) has arrays which can be considerably faster then using comma-seperated-values for  text container 'lists' (but when I do I use tab as a delimiter).
Also LC / OXT has a second language, the Extension Builder lang for making 'Widgets' and wrapping external code libraries, which does have an actual List type where the ordered elements can be of any type, Text, Numbers, JSON, Java, or C and ObjC types, Pointers, etc.

What's a good way to get an image string? I was trying `read["image"]` in c-lil and I was only getting the contents of the file as a byte string.

so I tried to get it from js-lil, and I couldn’t manage to copy the string out of the listener. After copying it and pasting it in the script editor, the string appears as an image within the script..

for reference, the image string is

%%IMG2ACIAGCAuAQwgEgEEIAYBAiAEAQIgEAEEIAYBAiAEAQIgDgECIAgBAiAIAQIgDAECIAgBAiAIAQIgCgECIAoBAiAGAQIgAgECIAgBAiAKAQIgBgECIAIBAiAGAQIgDAECIAYBAiACAQIgBgECIAwBAiAGAQIgAgECIAYBCiAGAQIgCAECIAYBCiAGAQIgCAECIAQBAiAKAQIgBgEMIAIBAiAKAQIgBgEMIAIBAiAKAQIgBAECIAwBBCAKAQIgBAECIAwBAiACAQogBAECIAIBDCAEAQogBAECIAIBDCAGAQIgDAECIAoBAiAGAQIgDAECIAoBAiAIAQQgCgEKIAoBBCAKAQogDgEKIBgBCiAO

and this is a screenshot of what happens:

Developer(+1)

Pasting images in non-rich fields was a bug; this should be fixed now. Image strings are how images are represented in the clipboard, so you should now be able to make image-strings using normal drawing tools.

Lilt's read[] has a slightly different signature than Decker, since Decker prompts the user instead of taking a path argument; that might be what was tripping you up there.

(+2)

What is a good way to order a query by two values? I’m making a flash-card deck and want to sort the cards based on some “score” I calculate, and if they’re equal fall back on the time they were last accessed. Tupling the values is not an option, since anything but an integer will be converted to string for comparison. I could just do that with nested queries, but that doesn’t feel very good.

Developer(+1)

Well, you could've formatted columns together to form keys with a natural lexicographic comparison, but that's horrible.

I made a tweak to the behavior of "orderby" which borrows from how the "grade" operators work in K: lists are now given a lexicographic comparison, so tupling will now work. The "join" operator will zip together columns for this purpose. As a contrived example:

    e:insert c:""split"ABBBABABABABAAABA" n:(7,7,2,4,4,0,3,4,2,0,9,7,4,6,8,2,3) into 0 
    select c n orderby (c join n) asc from e
+-----+---+
| c   | n |
+-----+---+
| "A" | 2 |
| "A" | 3 |
| "A" | 3 |
| "A" | 4 |
| "A" | 4 |
| "A" | 6 |
| "A" | 7 |
| "A" | 8 |
| "A" | 9 |
| "B" | 0 |
| "B" | 0 |
| "B" | 2 |
| "B" | 2 |
| "B" | 4 |
| "B" | 4 |
| "B" | 7 |
| "B" | 7 |
+-----+---+
(+4)

Would be cool if Decker had a discord server, or a channel on the Fantasy Consoles server

(+2)

You could ask in the Fantasy Consoles server to add a channel?

(+1)

Is there a good way of returning multiple values from a function? For now the best I have is to pack them in a list and manually unpack to variables. Something like Lua’s unpacking would be really handy, but it conflicts with the current syntax: a, b: 1, 2 is a list of a, 1, 2 and assignment of 1 to b. In a world of pure functions it’s really hard to avoid multiple output.

(4 edits) (+2)

Not really an answer to my question, but a solution to my problem: I found a way to store mutable state. Using closures it’s possible to write “constructors” that return “objects” which have persistent mutable state, and calling “methods” of the “object” will change its state for all references to the object. Here’s an example of a stack constructor I’m using in my current project:

on new_stack do
 state: ()
 ("stack","push","peek","pop") dict
 (on _ do state end
 ,on _ x do state[count state]: x end
 ,on _ do (-1 take state)[0] end
 ,on _ do ans:-1 take state state:-1 drop state ans[0] end
 )
end

This method is used in the example module in the documentation, although outside the function so it only creates a single global mutable state.

Edit: A universal minimal mutable variable:

on new_var state do
 ("get","set") dict
 (on _ do state end
 ,on _ new do state:new end
 )
end
(+1)

my question is kinda rudimentary in nature, so how do I make a sound play continuously in a card? I want it to be played after the button click sound ends(we arrive at card) and play until another button is interacted with, thanks in advance! 

Developer

Waiting for all sounds to finish is straightforward: sleep["play"]

So, playing a sound during a transition, letting the sound finish, and then playing another might have a script like:

play["firstSound"]
go[anotherCard "BoxIn"]
sleep["play"]
play["secondSound"]

Decker isn't really designed with looped audio in mind. Keep in mind that individual sounds in Decker are capped at 10 seconds, so they wouldn't work especially well for background music. You might be able to get continuous background noise by monitoring sys.ms and periodically issuing another play[], but it isn't straightforward. Perhaps a feature for the future...

(+1)

as someone who procrastinates a lot, i'm building a timer in decker using a field and a button to both act as a focus tool and also to apply some newfound programming knowledge. i have the basic countdown, but i'm wondering how i can make the program delay a second before going through the while loop again. any help is appreciated!

on click do  
    while display.text>0   
        display.text: display.text - 1  
        #delay goes here   
    end 
end
Developer(+1)

You could use the sleep[] function to wait a given number of frames. "sleep[60]" would delay for approximately one second.

A different approach for timing (which could be more accurate over longer time periods) would be to use `sys.now` or `sys.ms` to record a starting time and then consult them periodically to determine how much time has passed, possibly using "sleep[]" to delay between checks. (See: the System interface)

(+1)

Thank you to the both of you! This helps a lot.

(3 edits) (+2)

sleep[] looks a bit unsatisfying, as the card is not interactive as it waits, so the label isn’t that readable.

I have tried this:

Make a textfield to store a timer (here I used “timer”) initially set to 0. You can put it on another card if you want to hide it out the way.

Card:

on view do
 timer.text:timer.text+1
 if (timer.text<0) & ((10%timer.text)=0)
   display.text:display.text - 1 
 end
 go[card]
end

Button:

on click do
 timer.text:(display.text+1)*-10
end

Edit: I was late to the party. Using sys.now as suggested above sounds like a more suitable approach :)

Developer(+2)

Using the same "view[] refreshing" strategy is definitely the best way to do something like this if you want to be able to interact with the rest of the card while the timer is running.

You can also make the "secret timer" field invisible if you want to hide it on the current card.

(+1)

https://cptnqusr.itch.io/super-cool-timer

here's the finished timer! not exactly the greatest thing ever but as a first project i'm quite happy with it

i know that lil has fuse, but how would i do something like `{x,"\n",y}/("hello";"world";"etc")` in k? i have a list of strings of arbitrary length and i want to combine them all into a single string by a delimiter. is there a way to do that?

(+1)

nevermind, i figured it out! somehow i missed that part of the lil docs.

Developer (1 edit)

To use K terminology, fuse is a dyad which takes a delimiter as its left argument:

 "\n" fuse ("hello","world","etc")
"hello\nworld\netc"
  ":ANYTHING:" fuse ("hello","world","etc")
"hello:ANYTHING:world:ANYTHING:etc"

And you can handle even fancier cases with a recursive "format":

  (list "ITEM<%s>") format ("hello","world","etc")
("ITEM<hello>","ITEM<world>","ITEM<etc>")
  ("\n","ITEM<%s>") format ("hello","world","etc")
"ITEM<hello>\nITEM<world>\nITEM<etc>"
(+2)

I may be missing something obvious, but how do you actually make a hyperlink in a field? The guided tour doesn't seem to have a script loaded into the field and the documentation doesn't specifically mention inline hyperlinks unless I missed it.

Developer(+2)

To make a hyperlink in a field, ensure that it is a "Rich Text" field (the default for new fields), switch to the Interact tool, and select a region of text within the field. You can then use the "Text -> Link..." menu item to create a hyperlink, which will be shown with a dotted underline. When you're finished editing, to make the hyperlinks "clickable" you must lock the field, by switching to the Widget tool, selecting the field, and choosing the "Widgets -> Locked" menu item.

Fields emit a "link" event when a hyperlink is clicked, which you can intercept with a script to do as you please. If you don't write a script, the default behavior is to go[] to the text you provided when you created the hyperlink. If that's the name of a card, it will navigate to that card. If it's a url like "http://google.com" it will ask the web browser to open a new tab.

Hope that helps clear things up!

(+2)

Ah, gotcha! Thank you so much!

Why does distinct not always return a list?

In the example for distinct elements given:

extract first value by value from "ABBAAC"

when all items are equal, this provides a single element instead of a single element list. I don’t know why, but in my head it seems more flexible to return a single element list.

This is what i ended up using instead, since select always returns a list. There is another workaround here since selecting the first element of each group in () will give (0), the default value, but at least that makes sense.

on uniq x do
  if x~() () else
    t:select list first value by value from x
    t.c0
  end
end
Developer (2 edits) (+1)

I think the simplest way to get the edge cases you want would be using "()," to coerce lists or scalars to lists, and using "() unless" to coerce an empty result with "first" to an empty list.

  (),extract () unless first value by value from "ABBBCD"
("A","B","C","D")
  (),extract () unless first value by value from "AAAA"
("A")
  (),extract () unless first value by value from ""
()

The former coercion is always valid, but the latter does require some care depending on the data; this is the nasty side of unifying nullity and a numeric value:

  (),extract () unless first value by value from 1,1,0,5,1,2,0,1
(1,5,2)

Edit: and another approach entirely would be to use "dict":

  range (11,22,33,0,11,22) dict ()
(11,22,33,0)
  range (11,11) dict ()
(11)
  range (0) dict ()
(0)
  range () dict ()
()

Depending upon the context, you might not even need the "range".

seems like dict is the most foolproof method. unless has the problem of ignoring zeroes.

  (),extract () unless first value by value from 0,0,0
()
Developer

Among other things, Decker 1.32 revises the behavior of extract to remove its problematic "automatic de-listing".

extract first value by value from ()

Now returns (), like it ought to have from the beginning.

Is there a better way to get the characters of a string other than this?

each x in "str" x end
Developer(+2)

I would probably use "split" with an empty string as the left argument:

  "" split "str"
("s","t","r")
(+1)

How to display an image after a delay such as sys.ms? i understand display.text but dont know how to approach image displaying, thanks for the help in advance guys!

(+2)

If you have a canvas containing the image, you can change visibility using the show attribute:

canvas_name.show: "none"
canvas_name.show: "solid"

To show or hide it on a delay, there is a simple way and a more complex way.

The simple way uses the sleep function. It (mostly) pauses the whole program until it’s finished sleeping. For example, you could use it in a button’s click action:

# Simple, e.g. button script
on click do
  card.widgets.canvas_name.show: "none"
  sleep[60] # number of frames to sleep for
  card.widgets.canvas_name.show: "solid"
end

Complex uses sys.ms and recursive go[] functions to allow other things to occur in the meanwhile. You’d need a hidden field to store some extra data. Here the image shows after 1s.

# Complex, with a field called hidden_time
# Button script
on click do
  card.widgets.canvas_name.show: "none"
  card.widgets.hidden_time.text: sys.ms
  go[card]
end
# Card script
on view do
  elapsed: sys.ms - card.widgets.hidden_time.text
  if elapsed > 1000
    card.widgets.canvas_name.show: "solid"
  else
    go[card]
  end
end
(+2)

thank you so much sunil! this was very informative, i tried the sleep method.. but i goofed it lol, this explained it very well, thank you

(2 edits) (+1)

Edit: solved!

Is there a way to include multiple where conditions?

Example:

data: select num:("1","2","3","4","5") parity:("odd","even","odd","even","odd") prime:(0,1,1,0,1) from 0

What I want:

extract num where parity="even" and prime from data

Edit: Of course I find it as soon as I post. The answer is brackets!

extract num where (parity="even") & prime from data

how does one find the upper and lower bounds of numbers in Lil?

(+1)

are there any plans to add the ability to plot a pixel in code? maybe it exists, and i haven't found it. but i think it'd be cool.

Developer

Plotting (or reading out) individual pixels on a Canvas (or an Image interface) is possible; just index or assign through them with an (x,y) pair as if they were a list or dictionary.

Plotting a large number of pixels will be fairly slow, since doing so will force the Lil interpreter to do a large number of serial operations. Both canvases and images provide a variety of higher-level methods for scaling, transforming, and drawing which operate upon pixels in bulk, and should generally be preferred, especially if the goal is any sort of realtime animation.

(+1)

thanks for the advice! really loving your work here.

(+1)

I couldn't find it in the manual, is there a way to hide the menubar?


PS I discovered this a few days ago, so awesome! Loved hypercard, and I'll join the decker jam :)

Developer (3 edits) (+1)

You can hide Decker's menu bar by "locking" a deck. In the main menu, choose "File -> Properties..". and then click "Protect..." to save a locked copy of the current deck.

In the deck file itself, this adds a line like:

locked:1

You can also manipulate whether a deck is locked on the fly by setting "deck.locked" in a script:

on click do
 deck.locked:!deck.locked
end

(Careful, though; if you use scripts to lock a deck you haven't saved yet you might get yourself stuck!)

Edit: oh, and if you meant hide the menu bar while editing, pressing "m" on your keyboard while using drawing tools will temporarily toggle the visibility of the main menu, allowing you to draw "underneath" it.

(+1)

Thanks a lot! Ow yes that second one is convenient too :)

Lilt’s write[x y] says it will write a value to a file, can it deal with image and audio interfaces? Can lil inside decker export data to a well known image/audio format?

Developer

The "write[]" functions in both Decker and Lilt can save image interfaces as .GIF images (including transparency and animation, if desired), and it can save sound interfaces as 8khz monophonic .WAV files.

The GIF files emitted by Lilt/Decker tend to be quite large, as they make no effort to compress their image data, so it may be desirable to use ImageMagick, Gifsicle, or a similar GIF optimizer to process their output.

It's also possible to write out arbitrary binary files by using an Array interface, but this is more involved.

(+2)

So excited to be working with Decker! I couldn’t find it listed elsewhere, is there a way to control the default volume a sound, or multiple sounds play at in a deck?

Developer (1 edit) (+1)

There isn't currently any kind of volume control; volumes are effectively "baked into" the amplitude of a sound's samples.

A slightly clumsy workaround would be to make a copy of an existing sound on the fly and use sound.map[] to rescale its samples. Assuming the deck contains a sound clip named "sosumi":

on play_scaled name vol do
  r:-128+range 256
  play[sound[deck.sounds[name].encoded].map[r dict floor vol*r]]
end
on click do
  play_scaled["sosumi"   1] # normal  volume
  sleep["play"]
  play_scaled["sosumi"  .5] # half    volume
  sleep["play"]
  play_scaled["sosumi" .25] # quarter volume
  sleep["play"]
  play_scaled["sosumi" .10] # 1/10th  volume
end
(+1)

got it, thanks so much for the quick response!

for an interactive adventure, how would you save a text input as a string variable for the player character's name ? i understand that it would start with a field.. say the variable for the string is playerName, and the field itself is called inputPlayerName.. would it just be the following in the script for the field?

playerName: ""

playerName: inputPlayerName.text

?? i can't test it because i also don't know how to print a string variable in a field.. simple things but still getting to grips!

Developer(+1)

In Decker, persistent state lives in widgets. If you want to remember anything beyond the scope of an event handler, it should be stored in a widget. For example:

Type of Data to StoreAppropriate Representations
StringField text
Ranged NumberSlider value
Arbitrary NumberField text
Boolean (true or false)Button value, any Widget's visibility
ImageCanvas, Rich Text Field (encoded as inline image)
TableGrid value, Field text (encoded as CSV)
Dictionary or ListField text (encoded as JSON)
Position or Sizeany Widget's bounding box

If you want to remember something without showing it to a user, you could use invisible widgets, or widgets on a hidden card.

The walkthrough and examples in this thread might be helpful to you.

I recommend checking out The Listener as a way of interactively trying short snippets of code. You can use the Listener to poke and prod at the contents of a deck or card and verify behaviors before you write a script.

The print[] and show[] Built-In Functions can be used to log information (formatted text or arbitrary Lil values, respectively) to the Listener for debugging. The alert[] function can sometimes be handy for debugging because it pauses the current script and displays text to the user. The panic[] function stops scripts completely, but can likewise be a tool for peering into the workings of complex scripts.

Does any of that help?

(+1)

yes that is really helpful! thank you for the functions and guidance about the listener- i'll keep tinkering with it! and thank you for making decker- super cool project, and it's really nice how you always answer questions

(+1)

hello- i'm so sorry to do this, but i've just spent the last 4 hours trying to figure out how to put a string variable in a field (i.e. player types in their character's name -> that name appears in text when the game or NPCs address the player's character) and i just can't? figure it out?? thank you so much for your response, i did manage to grok that you can just use the field itself as the variable (using the listener to figure that out!!) but actually taking that text and putting it in a sentence in a field is just beyond me.. i've read the reference manuals, looked at examples, gone thru the community threads and have now decided to swallow my pride and ask how you would 'embed' a string variable in a different field? like "hi [playerName], it's nice to meet you" or something like that?

sorry, i am very aware that i've been asking a lot of questions.. this is the most ambitious thing i want to do, so i don't imagine i'll be asking too many more!

Developer(+1)

No worries. Asking "obvious" questions in a public forum like this helps future users and lets me know about potential documentation/usability problems so I can continue to improve Decker.

There are a few different ways we could approach using the value of one field to update another. For starters, let's take a look at string formatting.

The Lil "format" operator takes a formatting string on the left and one or more parameters on the right. Let's see a few examples in the listener:

"Hello, %s. How's it hanging?" format "Alice"
"Hello, Alice. How's it hanging?"
"%s missed your call; they were busy %s." format ("Phil","gardening")
"Phil missed your call; they were busy gardening."

Each "%s" in the formatting string is replaced with a string on the right, in order of appearance. There are lots of other formatting codes and features available for dealing with numbers, zero-padding, case conversion, etc, but for the moment we can ignore them.

Now, let's say we have two fields on the same card: "name" and "reply":

There are a few ways we could approach updating "reply" when "name" is changed. One way would be to add a script to "name" and use the "on change" event:

on change do
 reply.text:"Hi, %s. I hope this example makes sense!" format name.text
end

It would also be possible to do the same thing when a button is clicked, etc. If "reply" was on another card, we might have to specify the "path" to it:

on change do
 otherCard.widgets.reply.text:"Hi, %s- how's it going?" format name.text
end

Both of these approaches are "push"-based: something explicitly happens to the name field and our script reaches out to other fields in response. A different way to think about it would be "pull"-based: logic on individual cards which reach out to other widgets and update themselves. For example, we could have an "on view" script on our "other card" which updates reply whenever a user travels to that card:

on view do
 reply.text:"Hi, %s- how's it going?" format firstCard.widgets.name.text
end

Either way works.

Does that get you "unstuck"?

(+1)

this is incredibly helpful! it did get me unstuck- thank you so much, i really appreciate it !!

(+1)

just throwing out a “this obvious question wasn’t so obvious to me and helped me greatly, thank you to the asker and, as always, to IG"

(1 edit)

I am running decker on bash in linux with ./c/build/decker, How do I print to stdout from that process?

I have tried

shell["/usr/bin/env bash -c \"echo asdas\""]
shell["echo asdas"]
shell["echo asdas > /dev/fd/1"]

which simply returns 0 and do nothing. I am not sure how it works.

print["adasda"] just prints inside the decker interface.

i’d also like to know how to receive from stdin, if that will be possible.

Developer (1 edit) (+1)

Decker does not have the ability to execute shell commands or otherwise interact with the host system without explicit user permission; "shell[]" and similar functions are part of Lilt, which has a similar but distinct set of APIs from Decker itself.

As it happens, there is a way to print to stdout from Decker in the latest source revisions- the "app.print[]" and "app.show[]" functions. Note that this feature is not part of Decker v1.31, the current release at time of writing.

There is  presently no mechanism for polling from stdin. The closest analogy might be to use the alert[] function:

alert["please input a string:" "string"]

hmm, guess i’ll have to mod C decker to do what I want then. Thanks for the info.

Is there a function for creating a font interface from a font string, similar to image[]?

Developer (2 edits) (+1)

Not directly, no.

The Array, Image and Sound interfaces can be encoded as strings or reconstituted from strings via their "constructor" functions- array[], image[], sound[]. All of these are "free-floating" value-like objects that have no connection to or awareness of a deck.

Fonts are always part of a deck. The deck.add[] function can be used to create new fonts or make a copy of existing fonts, and deck.remove[] can likewise remove an existing font from a deck, which will as a side effect modify any widgets previously referencing said font. In Lilt, you can have access to multiple deck interfaces at the same time, so it's possible to copy fonts between decks.

It is technically possible to obtain encoded font strings- indirectly- via deck.copy[] and card.copy[], since those functions produce the same JSON-encoded string blobs you get when you copy cards or widgets manually, and correspondingly it is possible to use deck.paste[] and card.paste[] to indirectly add fonts or prototypes to a deck. In either case, manually parsing the copied representation of cards or widgets is a hack; the format used is subject to change in the future.

The ideal way to distribute Decker fonts, like modules, is to package them as decks. The other alternative is to copy a widget and share the "%%WGT0 " representation of that widget, along with any fonts and/or prototypes it may depend upon.

(1 edit)

In Interact mode, is it intentional that text copied between rich text enabled fields does not retain font formatting?

Developer

Yes. If a text selection consists solely of whitespace and one image, Decker will copy the image to the clipboard. Otherwise, Decker will copy the plain-text interpretation of the selection.

(+1)

Having a blast working within Decker, but coming up short in trying to figure out how to turn a substring within a rich text field into a hyperlink from an onclick event. I know how to make text a hyperlink from the Text menu option, but I want to call a function that will sometimes change the text property of a field to a string containing a hyperlink. Hopefully that makes sense!

Developer(+1)

I think I follow you.

Copying rich text from one field to another requires accessing their "value" property. Let's say we have rich text fields name "target", "a", and "b". The "a" field contains a link, and the "b" field does not:


In the button above I have a script like this which randomly picks between the value of "a" and "b":

on click do
  target.value:random[(list a.value),(list b.value)]
end

In this example, the values each need to be wrapped in a sublist with the "list" operator before joining them together with "," to prevent the rich text tables from being fused together into a single table.

The "a" and "b" fields could be hidden from view (Widgets -> Show None), and this approach generalizes to any number of alternative texts.

If you want to programmatically modify existing rich text to insert links it's a bit more complicated. Rich text is represented in Decker as a table, and the rtext interface contains some functions that can make it easier to manipulate such a table. If you wanted to change the styling of a specific range of characters within a field, you could use rtext.span[] to grab the text before and after your region of interest, retaining their existing styling, rtext.string[] to extract a region of interest without its styling, rtext.make[] to create a new styled chunk (including a hyperlink or inline image), and rtext.cat[] to glue all the pieces back together. If this is a road you need to go down I can try to furnish a more detailed example if you clarify your requirements. The dialogizer demo deck uses rtext functions to build its index on the fly.

Does that help at all?

(+1)

That absolutely does, thank you! I realize though that I could have done a better job providing context. I'm migrating a project that generates content for a tabletop RPG from something I built in Twine to Decker. A lot of random[] functions.

For a lot of the cards I'm working on, there's a contextual link between them that's based around a single word. In the below case, the word "Alien" in the Anecdote field would ideally be hyperlinked to a card titled Aliens, which would have a similar list of fields with randomly selected values in fields. I initially tried including link[aliens] in the array, but that just caused the aliens card to load upon clicking the button that contained the array.

These results are never a composite of multiple results, but just a fixed list of potential results, just in some cases there's a "contextual link" to another card. So as far as the solutions you provided, I could have "link fields" that contain the few instances of when a link makes sense within the card and just use *.text as the array value in those cases, right?  I'm willing to do this programmatically if it'll be a cleaner solution, though. Thank you again!

Developer(+2)

Just to make sure you're clear on the distinction, a field's .value attribute is a table, and a field's .text attribute is a string:

An rtext table can contain hyperlinks, inline images, and multiple fonts, but a plain string cannot. In many situations that ask for an rtext table you can supply a string and it will be "widened" into rtext, but it will all be in the default font. If you copy the .value of one rich text field to another it will preserve its formatting, but if you copy the .text you will flatten it out into a plain text representation.

(For anyone with web development experience, field.value versus field.text is loosely similar to element.innerHTML versus element.innerText.)

Decker 1.34 introduced a new "rtext.replace[]" utility function that might be handy. If you're working with a lot of text, links, and cards, it might be useful to write a deck-level utility function that finds certain keywords in a string or rtext table and replaces them with appropriate links, which you could then call whenever you populate a field. For example, perhaps something like this:

on contextualize text do
  db:insert keyword replacement with
   "Alien"  rtext.make["Alien"  "" "About Aliens"     ]
   "Weapon" rtext.make["Weapon" "" "Weaponry"         ]
   "Snacks" rtext.make["Snacks" "" "Delicious Treats" ]
   "Zombo"  rtext.make["Zombo"  "" "https://zombo.com"]
  end
  rtext.replace[text db.keyword db.replacement]
end

There's basically no limit to the possible complexity here (for example, you could automatically populate the keyword list by inspecting the titles of cards in the current deck), so it's up to you to choose what makes sense to you and is reasonably convenient for your purposes.

(+1)

First off I really appreciate your time in providing practical solutions. I gave this my best shot before returning here but every attempt I made at implementing the utility function you provided, at either the deck level down to the control itself, did not seem to work. I could get it to work within the Listener, but any table expressions seemed to do nothing outside of Listener. I'm sure I'm missing something, but I couldn't find a solution or workaround. 

Developer(+1)

Are you writing the result into a widget?

If you had a rich text field named "foo" which contained some of the words defined in the table for contextualize[], you'd apply it to the field something like this:

foo.value:contextualize[foo.value]
  • reading "foo.value" produces a rich text table.
  • "contextualize[foo.value] " calls the contextualize function with that rich text table and returns a modified rich text table.
  • the colon (:) is the assignment operator in Lil.
(+1)

As I anticipated, my issue was a syntax one and I wasn't properly calling "contextualize". I also unnecessarily added a comma delimiter to each of the rtext.make[] functions (I use Power Apps and JSON a lot with work). Everything is working as intended now, thank you!

(+2)

Hi I'm working on a small game very loosely inspired by Her Story. The player types a keyword into a search bar "display.text" and if valid it takes them to a new part of the game. Very simply I am doing this by creating cards and if the player types in the name of the card it will take them there. I used the following script on the button.

on click do

go[ display.text "SlideLeft"]

end

Is there a way for me to check for invalid entries so I can display an alert? Currently, it doesn't do anything. I'm not sure if there's a way to create a list of valid keywords and check from that, or else check from the list of cards that are there and go from there.


Thanks!

Developer(+1)

Sure- there are several ways to approach something like this!

The "in" operator can be used to check a string against a hardcoded list of valid options:

on click do
  if display.text in ("Keyword1","Keyword2","Keyword3")
   go[display.text "SlideLeft"]
  else
   alert["404: Page not found."]
  end
end

And it is also possible to obtain a list of valid card names from the deck; deck.cards is a dictionary from card names to cards:

on click do
  if display.text in deck.cards
   go[display.text "SlideLeft"]
  else
   alert["No such card, I'm afraid."]
  end
end

It might be a good idea to make the comparison case-insensitive if a user is typing free input. The easiest way to handle this would be to make sure the card names are all lowercase and then to convert the user input to lowercase with the "format" operator before doing any checks:

on click do
  t:"%l" format display.text
  if t in keys deck.cards
   go[t "SlideLeft"]
  else
   alert["I don't know anything about that."]
  ends
end

Yet another option is to make a dictionary to associate one or more keywords with destination cards; this provides more options for "forgiveness" in input handling:

on click do
  words["reindeerflotilla" ]:"puzzle1"
  words["reindeer flotilla"]:"puzzle1"
  words["reindeer"         ]:"puzzle1"
  words["smashthestate"    ]:"puzzle2"
  words["smash the state"  ]:"puzzle2"
  
  t:"%l" format display.text
  if t in words
   go[words[t] "BoxIn"]
  else
   alert["that's bogus!"]
  end
end

Does that make sense?

(+1)

Yes that does, thanks so much!

(1 edit)

I running into a somewhat unexpected behavior for a list of lists. Here is what happens in the listener:

eqs:((list 4, 2, 1, 3), (list 16, 4, 1, 12), (list 8, 1, 0, 0))
# listener prints back the list ((4, 2, 1, 3), (16, 4, 1, 12), (8, 1, 0, 0)). So far so good
e:eqs
# listener nicely print the list again ((4, 2, 1, 3), (16, 4, 1, 12), (8, 1, 0, 0))
e
# listener prints the actual value of e: 2.718282. Why???
# I have also tried, with the same amount of success :(
e:each eq in eqs end

How can I copy a list of lists? And why do I get this decimal value assigned to e even though the listener prints back the list that I'm trying to copy into e?


Edit:

I found the issue, e is a constant so even though Lil doesn't complain about me assigning a value to it the assignment doesn't actually happen and fails silently.

Deleted 1 year ago
Developer

Could you describe what you're trying to accomplish in a bit more detail?

Is the idea that you'd just be stacking copies of images on top of one another repeatedly, or is it more like "gradually reveal a series of layered images"? Do you want this effect on a single card, or is it something you want to repeat in a variety of places with different images?

(+1)

Oh sorry, I deleted my post because I found a solution (which is, I believe, really messy but it works approximatively...) 

To summarize : I launch a song (divided into 6 part (witch 3-witch4 -witch5-witch6-witch7-TapeOut)
I want to display images and texts one after the other, they appear one on top of the other in a somewhat chaotic fashion. 

I tried this on my play button :

on click do
 play["witch3"]
 card.widgets.canvas1.show: "none"
 card.widgets.canvas2.show: "none"
 card.widgets.canvas3.show: "none"
 card.widgets.canvas4.show: "none"
 card.widgets.canvas5.show: "none"
 card.widgets.canvas6.show: "none"
 card.widgets.field1.show: "none"
 card.widgets.field2.show: "none"
 card.widgets.field3.show: "none"
 card.widgets.field4.show: "none"
 card.widgets.field5.show: "none"
 card.widgets.field6.show: "none"
 sleep[120]
 card.widgets.canvas1.show: "solid"
 sleep[120]
 card.widgets.field1.show: "invert"
 sleep["play"]
 play["witch4"]
 sleep[120]
 card.widgets.canvas2.show: "solid"
 sleep[120]
 card.widgets.field2.show: "invert"
 sleep[100]
 card.widgets.canvas3.show: "solid"
 sleep[100]
 card.widgets.field3.show: "invert"
 sleep["play"]
 play["witch5"]
 sleep[120]
 card.widgets.canvas4.show: "solid"
 sleep[120]
 card.widgets.field4.show: "invert"
 sleep[120]
 card.widgets.canvas5.show: "solid"
 sleep[120]
 card.widgets.field5.show: "invert"
 sleep["play"]
 play["witch6"]
 sleep[120]
 card.widgets.canvas6.show: "solid"
 sleep[120]
 card.widgets.field6.show: "invert"
 sleep[120]
 card.widgets.canvas7.show: "solid"
 sleep[120]
 card.widgets.field7.show: "invert"
 sleep["play"]
 play["witch7"]
 sleep["play"]
 play["TapeOut"]
 card.widgets.canvas1.show: "none"
 card.widgets.canvas2.show: "none"
 card.widgets.canvas3.show: "none"
 card.widgets.canvas4.show: "none"
 card.widgets.canvas5.show: "none"
 card.widgets.canvas6.show: "none"
 card.widgets.canvas7.show: "none"
 card.widgets.field1.show: "none"
 card.widgets.field2.show: "none"
 card.widgets.field3.show: "none"
 card.widgets.field4.show: "none"
 card.widgets.field5.show: "none"
 card.widgets.field6.show: "none"
 card.widgets.field7.show: "none"
end
Developer

This approach absolutely works, and is very straightforward. A "synchronous" animation script like this is often the easiest.

I can offer a few tips that could make such a script shorter and easier to maintain:

  • If your script is on the same card as the widgets it references, it isn't necessary to use a fully-qualified name like card.widgets.canvas1; canvas1 will do just as well.
  • If you want to modify a large group of widgets at the same time, as in the beginning and ending of your script where you hide all your animation frames, you could make a list of the widgets and then take advantage of the ".." syntax. For example:
on click do
 parts:(canvas1,canvas2,canvas3,canvas4,canvas5,canvas6,field1,field2,field3,field4,field5,field6)
 parts..show:"none"
 # ...the rest of the animation goes here...
 parts..show:"none"
end
  • It's possible to introduce "helper" functions to factor out repeated patterns. We could, for example, make a function that sleeps for a few frames and then displays a widget- inverted if it's a field, and otherwise solid:
on reveal delay target do
  sleep[delay]
  target.show:if target.type~"field" "invert" else "solid" end
end

Which would then turn the main script into:

on click do
 parts:(canvas1,canvas2,canvas3,canvas4,canvas5,canvas6,field1,field2,field3,field4,field5,field6)
 parts..show:"none"
 play["witch3"]
 reveal[120 canvas1]
 reveal[120 field1 ]
 sleep["play"]
 play["witch4"]
 reveal[120 canvas2]
 reveal[120 field2 ]
 reveal[100 canvas3]
 reveal[100 field3 ]
 sleep["play"]
 play["witch5"]
 reveal[120 canvas4]
 reveal[120 field4 ]
 reveal[120 canvas5]
 reveal[120 field5 ]
 sleep["play"]
 play["witch6"]
 reveal[120 canvas6]
 reveal[120 field6 ]
 reveal[120 canvas7]
 reveal[120 field7 ]
 sleep["play"]
 play["witch7"]
 sleep["play"]
 play["TapeOut"]
 parts..show:"none"
end

Perhaps you could go even further, making a function that played a music segment, revealed several items in sequence, and then waited for the segment to complete:

on phrase audio parts do
  play[audio]
  each row in parts
   sleep[row.delay]
   row.part.show:if row.part.type~"field" "invert" else "solid" end
  end
  sleep["play"]
end
on click do
 parts:(canvas1,canvas2,canvas3,canvas4,canvas5,canvas6,field1,field2,field3,field4,field5,field6)
 parts..show:"none"
 phrase["witch3" insert delay part with
  120 canvas1
  120 field1
 end]
 phrase["witch4" insert delay part with
  120 canvas2
  120 field2 
  100 canvas3
  100 field3 
 end]
 phrase["witch5" insert delay part with
  120 canvas4
  120 field4 
  120 canvas5
  120 field5 
 end]
 phrase["witch6" insert delay part with
  120 canvas6
  120 field6 
  120 canvas7
  120 field7 
 end]
 phrase["witch7" insert delay part with
 end]
 play["TapeOut"]
 parts..show:"none"
end

Of course, abstraction adds some complexity, and might make it harder to introduce new exceptions to the rule if you continue to modify the script. Always choose the approach that feels the simplest to you!

(1 edit) (+1)

A question a day ! (I'm trying hard to have something completed for the deck-month !!)

So, I'm using two things : 

1. The Interior Contraption (I can drag a canvas to show what's behind a card)

2. The Decker Dialogizer (I can display text on the bottom of the screen as in a narrative adventure)

What I'm trying to do is to display a text only once, when I enter a card. For this I use a checkbox called tooted, and I use this script on my card : 

on go card trans delay do
 c:deck.card
 send go[card trans (30 unless delay)]
 if tooted.value.1
  dd.open[deck o]
  dd.say["Premier texte."]
  dd.close[]
  tooted.value:!tooted.value
 end
end

The text is only displayed once BUT my interior contraption can't be drag anymore ! And if I delete this lines of script, I can move the interior contraption. 

Have you got an idea ? 

Developer

Hmm.

I suspect "tooted.value.1" should probably be "tooted.value~1" or even just "tooted.value", and you don't appear to be doing anything with the variable "c" or defining anything for the variable "o".

Dialogizer runs "synchronously", blocking most forms of input while its dialogs are open. You shouldn't be able to drag widgets around, click buttons, etc, while a dialog is open, but when you close the dialog and the initiating script finishes everything should be back to normal.

Is the problem that you want to drag the Interior while the dialog is open, or does it somehow cease working even after the dialog has been closed?

(2 edits)

A button "Start" on another card leads me to the "bedroom_n" card and set all tooted checkbox to 1.
Once inside "bedroom_n", a text is displayed, I click and it disappear, but after this, I can't drag the Interior. 

Developer(+1)

I'm not completely certain what's going on here without really digging into the whole deck, but it looks like there's a conflict between your use of "go[card]" to drive non-blocking animation and bob the navigation arrows and your wrapper for "on go ... end" that conditionally triggers a dialog.

It might be better to make those buttons contraptions so they can handle their animation in a self-contained way and reduce the amount of scripting you need on each card; perhaps you could modify the bob contraption to expose a click event?

Looks like a really ambitious project so far!

(+1)

Indeed, the problem was a conflict between the bob contraption and the dialogizer ! Now I can do what I wanted to ! Thanks !!

(2 edits) (+1)

I got a little problem ! 
I want to randomly play 4 sounds, but only when I'm on a specific card

I'm using this code on the script of my card

on loop do  
random["dove_1","dove_2","dove_3","dove_4"] 
end

But even when I'm moving to another card, the script keeps on playing randomly the 4 files.

How should I do to stop the sound playing when I'm not on my card ? 


EDIT : I found it ! I needed to add - play[0 "loop"] - when moving to another page ! sorry for this useless post 

Developer(+1)

The default handler for the "loop" event provided by Decker looks like this:

on loop prev do
 prev
end

This handler is why the background loop plays forever by default; every time the loop ends, this function asks for the same sound to be played again.

If you write a deck-level script (File -> Properties -> Script...) which defines a replacement:

on loop do
 0
end

Then the loop will stop on all cards that do not have their own "on loop ... end" handler which overrides the above.

Does that make sense?

(+1)

absolutely !

(2 edits) (+1)

And how do I lock and unlock a button with a command ?
I tried 

on click do
card1.widgets.button1.locked.1
end

but I can see it's wrong

(sorry for being that level of noob)

EDIT : damn, again I found it : card1.widgets.button1.locked:1

There's a weird issue with the tangent function, where tan(pi/4) = 0, but using approximate values, like tan(0.7854), you get an answer close to 1. sin(pi/4) and cos(pi/4) are unaffected. I guess this might be some kind of floating point issue with the pi constant, and for that reason I'm not sure if it's a bug or a kind of user error. 


Developer

Looks like a problem with prettyprinting floats, rather than arithmetic itself. Applied a patch.

Developer

The number formatting issue has been patched in v1.37

(+1)

That was quick! Thank you! 

Hi! I need to authorize access to a card only on condition that all the other cards have been visited. Do you have any tips on how I can do this easily? 

Developer (1 edit)

Hm. Well, the first step (if you haven't done it already) would probably be stubbing out keyboard navigation, with a card- or deck-level script that overrides navigate[] to do nothing, to make sure the player can't accidentally go to your "protected" card without using editing tools:

on navigate do
end

Then you need some way of keeping track of whether cards have been "visited". Several ways to handle this. If you had an invisible checkbox on every card that needed to be visited named "visited", you could record visits something like so in each card's script:

on view do
  visited.value:1
end

Or even handle it automatically with a deck-level script fragment (not inside a function handler, just bare):

deck.card.widgets.visited.value:1

Left bare, it will execute whenever *any* event is processed. On cards without a "visited" checkbox it will have  no effect.

Check if every card with such a checkbox was checked using a query:

if min extract value..value where value from deck.cards..widgets.visited
  # ...
end

The ".." can be read as "at every index". The minimum of a list of 1/0 values is equivalent to logically ANDing them all together.

And you might want a button somewhere for resetting all the visited checks:

on click do
  deck.cards..widgets.visited.value:0
end

The nice thing about this approach is that as you add new cards in the future, you can decide on an individual basis whether they need to be counted toward "visiting everywhere".

Make sense?

I don’t get where i’m supposed to enter the minimal number of cards visited to allow a new thing ? 

If i have a button, and if its clicked whereas all cards have not been visisted i want to display a text (with dialogizer) / and if the value is high enough another text is displayed, then go to the new unlocked card 

Developer(+1)

You originally stated your goal as "all other cards have been visited".

In the model I describe above, cards whose visitation can count toward the total are each given an invisible checkbox (with a consistent name) that keeps track of whether that particular card has been visited yet. Cards that do not have such a checkbox don't count toward the total, so for example you may not need one on a "title screen" card.

If you only want to know whether some threshold has been exceeded, the query I showed for checking whether all such cards have been visited can be modified to instead count how many have been visited, and compared to some threshold (say, 10) like so:

if 10<sum extract value..value where value from deck.cards..widgets.visited
  # let the player go to another card...
else
  # tell the player they can't go yet...
end

If you only kept a single counter somewhere to track card visits, there would be no way to identify repeat visits to the same card, which is probably not what you have in mind.

If you used a different naming convention (call some checkboxes 'visited1', some 'visited2', etc?) and modified the queries I describe accordingly, you could track several disjoint or partially-overlapping groups of "visited" cards.

(+1)

It's good for me !!!! 

Viewing posts 1 to 32 of 107 · Next page · Last page