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: 24,686 Replies: 399
Viewing posts 101 to 124 of 124 · Previous page · First page
(+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.

(+1)

I want to create a cipher of sorts. Be able to replace letters with other letters. If possible, I would also like to be able to replace sequences of letters with either individual letters or sequences.

So far, I was able to create a code that copies text from one field into another. I'd display it, but this is my first time replying to this forum and I don't know how to format text yet.

Developer(+3)

The simplest way to perform ciphering would be to use a dictionary. We can make a dictionary that maps characters to other characters like so:

rot13: "abcdefghijklmnopqrstuvwxyz" dict "nopqrstuvwxyzabcdefghijklm"

Given a string, we can use each character to index into our dictionary with the "@" operator:

rot13 @ "something else"
# ("f","b","z","r","g","u","v","a","t",nil,"r","y","f","r")

For characters like spaces, which aren't defined in the rot13 dictionary, we get a nil. As a catchall, we can use "fill" to substitute a generic replacement for all nils:

" " fill rot13 @ "something else"
# ("f","b","z","r","g","u","v","a","t"," ","r","y","f","r")

And if we wanted to collapse that sequence of characters into a flat string, we can use "fuse". Note that this step isn't necessary if we're stashing the result in a field's .text attribute; it implicitly fuses lists of strings as a convenience:

"" fuse " " fill rot13 @ "something else"
# "fbzrguvat ryfr"

Putting that all together, given fields "input" and "output", give "input" a change[] handler:

on change do
 rot13: "abcdefghijklmnopqrstuvwxyz" dict "nopqrstuvwxyzabcdefghijklm"
 output.text:" " fill rot13 @ input.text
end

And now we have a (crude) rot13 translator. It would probably be much more useful if we preserved all the non-alphabetic characters intact instead of turning them all into spaces. We can be a little fancier about how we construct the translation dictionary and address this:

on change do
 a1:"abcdefghijklmnopqrstuvwxyz"
 a2:"nopqrstuvwxyzabcdefghijklm"
 A1:"%u" format a1
 A2:"%u" format a2
 chars:"%a" format list range 256
 rot13:(chars dict chars),(a1 dict a2),(A1 dict A2)
 output.text:rot13 @ input.text
end

If you want to do a translation based on fixed-size bigrams or trigrams, or something along those lines, "window" might be useful:

2 window "SomeWords!"
#("So","me","Wo","rd","s!")

And you can use "drop" to exclude characters you wish to ignore (or conversely, "take" to retain only characters you care about):

(" ","!") drop "Some Words!"
# ("S","o","m","e","W","o","r","d","s")

For even fancier find-and-replace scenarios you could use RText.replace[]. Note that this returns a rich-text table and should be assigned to the .value attribute of a field, rather than .text, or converted into a string with rtext.string[].

rtext.replace["some text" ("me","ex") ("you","post")]
# +----------------+------+-----+-----+
# | text           | font | arg | pat |
# +----------------+------+-----+-----+
# | "soyou tpostt" | ""   | ""  | 1   |
# +----------------+------+-----+-----+
rtext.string[rtext.replace["some text" ("me","ex") ("you","post")]]
# "soyou tpostt"

Does any of that help?

(+2)

Oh yeah! This really helps with single letter replacements and gives me something to play with for a good while.

Some questions I have about grids:
1. What are some ways that I could "lock," for lack of a better term, a column of a grid? I mean, I suspect I can use a format function to make it so that whenever a cell in that column gets changed to something else that it would replace the original letter, but I'm only half aware on how to pull that off and curious as to whether their exists a lock function for parts of a grid. But I digress.

2. Follow-up question to the first one, how would I rearrange the order of values in a column via button pressing? For example, changing the arrangement of values in a column, letters specifically, from alphabetical to by frequency, etc.

3. Is it possible to extract the values from a column in a grid and turn it into a string that can be used in dictionary functions?

Developer

If you give columns in a grid widget a format character of "L", "I", or "T", the column will not be user-editable. L displays cells in a column as plain strings (preformatted), I interprets cells as an icon index, and T displays cells as non-editable Rich Text.

Grids are containers for tables. To rearrange the rows of a table you probably want a query with an orderby clause. For example, sorting the contents of a grid "myGrid" by the "price" column, ascending, might require a button with a script like

on click do
 myGrid.value:select orderby price asc from myGrid.value
end

Note that the default order[] event handler for grids offers something like this out of the box; you could overload this on a grid-by-grid basis if you wanted more elaborate behavior:

on order col do
 if !me.locked
  me.value:select orderby me.value[col] asc from me.value
 end
end

If you have a column in a table, you can index it by name to get a list. The "fuse" operator can convert any list into a flat string:

myGrid.value.fruit
# ("cherry","banana","elderberry","apple","durian")
"" fuse myGrid.value.fruit
# "cherrybananaelderberryappledurian"
"|" fuse myGrid.value.fruit
# cherry|banana|elderberry|apple|durian

How do I make a canvas move via arrow keys being pressed? Also, how do I go to another card when the canvas is in a certain position. Do I need a grid to be able to do both of these things?

Developer(+1)

When the arrow keys are pressed, a navigate[] event is sent to the current card with an argument of "up", "down", "left", or "right". If you wanted to make these events move a canvas, you might have an event handler in the card-level script something like

on navigate dir do
 deltas:("up","down","left","right") dict ((list 0,-1),(list 0,1),(list -1,0),(list 1,0))
 myCanvas.pos:myCanvas.pos+deltas[dir]*32
end

If you wanted that canvas to trigger "exits" when it matches certain positions, you could mark them with buttons (perhaps set to "Show None") and then send them a "click" event on an overlap; the buttons could in turn go[] to other cards, trigger alert boxes, etc.

on navigate dir do
 deltas:("up","down","left","right") dict ((list 0,-1),(list 0,1),(list -1,0),(list 1,0))
 myBug.pos:myBug.pos+deltas[dir]*32
 each button in extract value where value..type="button" from card.widgets
  if button.pos~myBug.pos button.event["click"] end
 end
end


For a fully-worked example of a game that uses arrow keys for tile-based movement, you should take a look at Decker Sokoban.

Thank you!

Is this intended or a bug? Assigning get/set with the keystore appears to strip a level of list.

Or am I missing something?

lib: ()

lib.test: on _ do
  a: (list (list (list 1,2,3)))
  data.a: a
  show[data.a]
  data.a: data.a
  show[data.a]
  data.a: data.a
  show[data.a]
  data.a: data.a
  show[data.a]
  
  x: (list (list (list 4,5,6)))
  y: (list (list 7,8,9))
  z: (list 10,11,12)
  w: 13,14,15
  
  data.x: x
  show[data.x]
  data.y: y
  show[data.y]
  data.z: z
  show[data.z]
  data.w: w
  show[data.w]
  
  nil
end

will produce:

(((1,2,3)))
((1,2,3))
(1,2,3)
1
(((4,5,6)))
((7,8,9))
(10,11,12)
13
nil

This was run in v1.62 Decker

Developer(+1)

Confirmed; Keystores were not properly serializing raw lists. I've patched the issue in the source repo.

Dictionaries, tables, and strings do not exhibit this problem, so any can be used as a workaround.

Thanks!

Yeah, I have a slight workaround serializing first and then wrapping in a list:

data.key: "%J" format foo
foo: list "%J" parse data.key

How would one go about creating an inventory, or at least a tracker to ensure you've already clicked something? I tried going about it like this

on click do
t+1
if  t>1
alert["You already grabbed the pie tin!"]
alert[t]
else
 t+1
 alert["You found the pie tin! Weirdcat put it out for you already, it looks like!"]
 alert[t]
 end
end

, but I don't want all the clicks to set t:1, and it doesn't look like it's storing it in other widgets?
 
Developer

If you want to increment a local variable, like "t" in your example, you must use the assignment operator ":" (read aloud "becomes" or "gets") like so:

t:t+1

Local variables exist only within the lifespan of a single event.

Persistent state, like an inventory or a system for tracking a user's action, must reside in a widget somewhere. This has been discussed many times and places within this forum. I recommend starting with this thread: https://itch.io/t/2702720/how-to-have-interaction-between-cards

(1 edit) (+1)

Hello! I think this question is probably rooted in my coding experience being from Java, so I keep trying to apply incorrect formatting here. 

I have a field widget called "name"  on card "A" where I want the player to type their name, and I have a locked field widget called "hellotext" on card "B" where I want it to say "hello [name] how are you?" 

Ive been trying to do this with the following code in B's card script but I keep getting the output "0" in "hellotext"

hellotext.text: "Hi " + deck.cards.A.widgets.name.text + " how are you?"

I also tried 

hellotext.text: fuse "Hi ",deck.cards.A.widgets.name.text, " how are you?"

but that gave me the error " 'fuse' is a keyword, and cannot be used for a variable name."

Sorry for such a simple question but I'm really stumped!

Developer (1 edit) (+1)

The "+" operator in Lil is exclusively for addition. It will coerce strings into numbers in a best-effort fashion, which is why you're getting zero:

"5"+3               # 8
" 2.3" + "1Hello"   # 3.3
0+"Hello"           # 0

The "fuse" operator is binary; it expects a left string argument to intercalate between elements of a right list argument:

":" fuse "A","B","C"   # "A:B:C"
"" fuse "A","B","C"    # "ABC"

Assigning a list to a field's ".text" attribute will implicitly concatenate the elements of a list together, as a convenience. The following are equivalent:

hellotext.text:"" fuse "Hi","There"
hellotext.text:        "Hi","There"

Cards are normally in scope by name, so you don't typically need a fully-qualified path to reach a widget on another card. The following will be equivalent from a card/widget level script:

deck.cards.A.widgets.name.text
           A.widgets.name.text

Does that help?

If you aren't already, I strongly recommend using The Listener to try expressions out piece-by-piece as you develop.

(+1)

That does! Thank you so much :)

(1 edit)

I’m not sure how sys.ms is represented, but I think it has overflowed and wrapped into negative. It’s currently returning a negative value.

updated: does appear to be signed 32-bit overflow, adding 2^31 hacks around it

(2 edits)

You left Decker running for 24.8 days?

EDIT: Wait, no, I see what’s going on.

Native Decker computes sys.ms as a float, Web Decker takes a float from the browser and truncates it into a 32-bit integer.

I guess it shouldn’t matter if you’re using sys.ms for performance timing (duration:sys.ms - start) but it does make problems if you’re trying to divide the clock with a modulus operator.

(6 edits)

I was using it for frame timing for the bpm.

current time - last beat > bpm gap -> send a bang

so, i just hacked it by reoverflowing to positive by adding 2^31.

i just realized it’s still not 100% correct, because i’m not accounting for the timing gap (jitter) that is getting lost due to 60 Hz quantization.


update: oh, right. it wasn’t working because the “last frame” time was never initialized - so “current time” - “last frame” will always be negative, since it’s “current time” - nil (0). that’s the real reason.

so, anyways - i don’t know if negative sys.ms is supposed to be a thing, but it certainly wasn’t expected. it’s how i usually time gaps and don’t worry about initializing the first frame time for the possibility of negative time.

(+2)

There must be an answer to this that I've missed, but:
How do I grab the length of a string in lil? Trying to get the index of a random character in a field's text attribute.

(+3)

Nevermind, it's `count`. Oops.

Developer(+1)

the random[] function can select from sequences, too:

random[]                 # random float [0,1)
random[5]                # random integer [0,4]
random[5 10]             # list of 10 random integers [0,4]
random["abc"]            # random character "a","b", or "c"
random["abc" 10]         # list of 10 random characters "a","b", or "c"
random[("apple","pear")] # random string "apple" or "pear"
random["abcdef" -3]      # list of 3 shuffled characters "a"-"f" without repeats

See the built-in functions section of the Decker Reference Manual.

(+1)

Sorry if this question is a bit dumb - I'm very fresh to coding in general and lil. 

Is there a way to check if text in a field includes words? I've been playing around with Love Letter example deck and wanted to make a function to detect certain words, but I'm having a hard time wrapping my head around it. In other words, is there a way to check for matches, for example, from the "adjective" group inside the text box after it's been generated, instead of checking if the full text matches?   

Developer (1 edit) (+1)

It's a perfectly reasonable question!

The binary "in" operator can- among other things- check for the presence of a substring in a larger string:

"beef" in "ham & beef"      # 1
"apple" in "ham & beef"     # 0

If the left argument is a list of strings, you'll get back a list of 1s or 0s indicating whether each substring was found:

("beef","ham","apple") in "ham & beef"   # (1,1,0)

If you're just asking whether "any" word is found, you can take the "max" (maximum) of the result:

max ("beef","ham","apple") in "ham & beef"   # 1
max ("bone","apple","tea") in "ham & beef"   # 0

Note that all these comparisons are case-sensitive! If you want a case-insensitive comparison, the easiest way is to lowercase the string you're searching and make sure all your substrings are already lowercase:

("beef","ham","apple") in "%l" format "Ham & BEEF"   # (1,1,0)

Does that make sense?

For other kinds of string-searching you might want to consult the Lil reference manual on the "like" operator (which performs glob-matching with * wildcards) and the Decker reference manual on the "rtext.find[]" or "rtext.replace[]" utility functions, which retrieve the index of found words or perform substitutions, respectively. Everything in the "rtext" interface will work on a plain string, too.

(+2)

Thank you so much! I was able to make exactly what I wanted to. I'll be sure to look over the manual again - it's a bit intimidating, but I hope to understand more as I go along. 

(+1)

I'm trying to rotate a square  360 degrees, and I currently have the following code attached to a slider:

on change val do
 r.rotate[(pi/4)*val]
end

But when it rotates 90 degrees, it looks slightly off compared to 180 and 270:

How would I go about fixing this? ^^;

Developer(+2)

Hmm. I can tinker with the internals of image.rotate[] to try to make it better behaved. When you need exact 90 degree rotation increments, image.transform[] is more efficient, and will always be precise. Here's a routine you could use as a workaround for now:

on rotated img steps do
 # modify 'img' in-place based on 45 degree steps:
 if     steps~0 # do nothing
 elseif steps~2 img.transform["right"]
 elseif steps~4 img.transform["right"].transform["right"]
 elseif steps~6 img.transform["left"]
 else           img.rotate[steps*pi/4]
 end
 img
end
(+2)

The workaround worked perfectly for what I needed, thank you! ^^

How do I have a grid execute different scripts based on which row is clicked?

Developer(+1)

When a row or cell of a grid widget is clicked, it is sent a click[] event with the active row as an argument. See Events in the Decker reference manual.

on click row do
 if row~0
  alert["you clicked the first one"]
 elseif row~1
  alert["you clicked the second one"]
 else
  alert["you clicked something else"]
 end
end
(+1)

Thank you!

(+1)

Hi everyone, I have a type question, I'm pretty sure it's a silly one, but it has been half an hour, and I could not find anything that answer my question. (I'm french so my english is bad). 
What is the simplest way to convert a string into a number ?

a:"1"
                "1" typeof a                 "string" <- Okay a:"%i" format a                 "1" <- Why not a number ? a:a+0                 1 <- number
Developer(+1)

When the distinction between a number and a string matters, the simplest way to convert the string into a number is with an arithmetic identity operation, like adding zero, as in your last example.

The format operator is for converting data into strings (or lists of strings); its counterpart for decomposing strings into other values is "parse":

 "%i" parse "123"
123
 "%i|%i|%i" parse "123|34|89"
(123,34,89)
(+1)

Thank you so much ! I was completely lost!

(1 edit)

I found a bit of behavior I found unintuitive and wanted to highlight it, since it took me a bit of time to understand and work around. Apologies if this is already a known factor.

First: The empty list () evaluates to false.

But also: !() evaluates to false.

I think what’s happening here is that !, as a unary operator, is propagated to every element of the list. For the empty list, the result of that operation is the empty list. The reason this came up is because I was trying to test for the presence of a value in a Decker grid, like so:

extract where index=idx from grid.value

This returns a list that contains the value of the first column where index=idx if it exists and an empty list if it doesn’t. I only wanted to take action if there was no entry, so I tried to negate it like so…

if !(extract where index=idx from grid.value)
 # do something
end

…which didn’t work. So now I’m using my workaround:

if extract where index=idx from grid.value
 0 # essentially a no-op here
else
 # do something
end

…which does work.

Developer

If I understand correctly, you should be able to do this more straightforwardly with

if grid.value[idx]
 # ...
end

Indexing a table with a number fetches the corresponding row of the table as a dictionary, which will be "truthy" so long as the table has at least one column. If the row does not exist, indexing in this manner will result in nil, which is a "falsey" value:

t:insert a b with 11 22 33 44 55 66 end 
# +----+----+
# | a  | b  |
# +----+----+
# | 11 | 22 |
# | 33 | 44 |
# | 55 | 66 |
# +----+----+
t[2]
# {"a":55,"b":66}
t[-1]~nil
# 1
t[3]~nil
# 1

Another approach would be to use the "count" operator to determine the number of rows in the table, and compare that to "idx":

count t
# 3

Does that help at all?

(+1)

It doesn’t, but only because I simplified the example in my comment. I’m actually testing based on a column value, and I probably should have called my fake column “id” instead of “index”. In that case, a little testing in the listener gave me a workable pattern like the one you suggested. For example, testing if a row with id=5 exists looks like:

if 5 in grid.value.id
 # ...
end

…which looks much better, but unfortunately, my actual grid is using a compound key. Consider:

t:insert page id text with 1 1 "frst" 1 2 "scnd" 2 1 "pg2" end
# +------+------+------+
# | page |  id  | text |
# +------+------+------+
# |    1 |    1 | frst |
# |    1 |    2 | scnd |
# |    2 |    1 | pg2  |
# +------+------+------+

And what I want to do is check to see if a given entry is the last entry for that page, which I’m doing by checking to see if the entry afterwards exists. So:

p:1
i:2
if extract where page=p where id=i+1 from t
 0
else
 # this executes, as page 1 id 3 is not in the table
end

i:1
if extract where page=p where id=i+1 from t
 0
else
 # this doesn't, as page 1 id 2 is in the table
end

Maybe there’s a cleaner way to do this, but if there is, I haven’t found it yet.

Developer

Well, you can apply the "join" operator to a pair of lists to zip them together into tuples:

t.page join t.id
# ((1,1),(1,2),(2,1))

You can use the "in" operator to search for a tuple, but you'll have to enlist the tuple and then unpack the result from a count-1 list to avoid searching for individual components of the tuple in the zipped list:

(1,2) in t.page join t.id
# (0,0)
first (list 1,2) in t.page join t.id
# 1
first (list 1,3) in t.page join t.id
# 0
(1 edit)

I want to use a slider in X card to copy # canvas image in Y then paste it in X canvas. Here's the code:

on change val do
   if me.value = #
      X.paste[deck.Y.widget.#.copy[]]
   end
end

But it doesn't work when I chage the slider value. Am I doing something wrong?

Developer

"#" is not a valid Lil identifier; it is used in Lil scripts to indicate a comment, which ignores the remainder of the line. Lil identifiers are described in the Lil Reference Manual as follows:

Variable and function names may contain any alphanumeric characters (as well as ? and _), but must not start with a digit. 

If you give a widget a name which is not a valid identifier, it will not be automatically available as a local variable, but it can be accessed by name from the card's ".widgets" dictionary:

card.widgets["#"]

Your description of what you're trying to do is inconsistent and unclear. If the idea is to- for example- index into images stored in a rich-text field named Y and paste the image corresponding to the value of the slider ("me") onto a canvas named X, you could use something like:

on change val do
 X.paste[Y.images[val]]
end
(1 edit)

I'm using a slider to switch between images that I want to copy from one set of canvases into a single canvas. I'm not using a ritch text field.

Developer (1 edit)

Presuming the canvases are on the same card and have a naming convention like "c1", "c2", "c3", "c4" and the slider is set to an integer range between 0 and 3 you could write something like

on change val do
 canvases:c1,c2,c3,c4
 X.paste[canvases[val].copy[]]
end

I ended up using a field anyways, lol.

That said, I got a new question, how do I script a button to add or subtract from the existing value of the slider?

Developer(+1)

Presuming a slider named "slider1", you could give a button a script like

on click do
 slider1.value:slider1.value + 1
end

This situation is very similar to one of the examples in The Decker Guided Tour and one of the examples in the introductory primer in the Lil Reference Manual.

(+1)

Thank you.

on click do
 slider1.value:slider1.value - 1
 slider1.event.change[val]
end

I tried using this code to change the value of the slider and execute this script:

on change val do
 Sphere.paste[Assets.images[val]]
 if me.value = 0
    button1.show:"solid"
    button2.show:"solid"
 else
    button1.show:"none"
    button2.show:"none"
 end
end

There is a range from 0 to 4 for the images in a field going down. But, for some reason, the 0th image gets pasted and not the third in the field.

I want it to paste the images in the order of values in the slider with each click and not skip to zero.

(+2)

I think you need to send the event to slider1 in a slightly different way:

slider1.event["change" val]

There's another post on the forums explaining more here.

And you'll also need to define what val is in this context. That could be in either script.

For now I put both changes in button script:

on click do
 slider1.value:slider1.value - 1
 val: slider1.value
 slider1.event["change" val]
end
(+1)

Thank you!

I'm trying to make it so that in my current project of a little visual novel, that if you aren't comfortable with specific content, it boots your back to the 1st card, and if you're okay with said content, it moves to the third page. I have it set up as a boolean, but there may be better ways to go about it, any help I can get with this?

adult_ok:true
on view do
 dd.open[deck]
 dd.say["This game may contain adult themes not suitable for all audiences"]
 r:dd.ask[
  "If you're okay with these terms, proceed forward:"
  ("YES", "NO")
 ]
 if r~0
  adult_ok:true
  dd.say["Then, the Contract has been Sealed."]
 else r~1
  adult_ok:false
  dd.say["Then, the World was covered in Darkness."]
 end
 dd.close[]
end
# later in the game, after the dialog
if not adult_ok
 go["home"]
else
 # continue game normally
end
(+1)

The basic logic you have there is sound, but I can spot a few problems that are likely to trip you up.

The most basic is, Decker doesn’t have true and false constants - those are just variable names and are nil by default, so if true alert["hello"] end will never show an alert, unless you happen to have defined true:1 somewhere else. Where you would write true and false, just change them to 1 and 0 respectively.

The next problem you’re likely to hit is that the value of the adult_ok variable is not preserved between events. If you want to store that value somewhere, you need to store it in something - in this case, probably a button with the “Checkbox” appearance on a card somewhere. If you don’t already have a card to store the state of the game, you can make a new one called, say “gamestate”, put a button named adult_ok on it, give it the “Checkbox” appearance, and then in your code you can do:

gamestate.widgets.adult_ok.value:alert["Are you OK with seeing adult content?" "bool" "Yes"]

…to ask the user and store the resurt in gamestate.widgets.adult_ok.value. Then you can later check it:

if gamestate.widgets.adult_ok.value
 go["adultcontent"]
else
 go["nextmorning"]
end

Of course, that’s assuming you actually want to store the answer and adjust the game accordingly. If you just want to make a disclaimer and only proceed to the actual game if the player clicks “yes”, then you don’t actually need to store that anywhere: if the player winds up anywhere but the first two cards, you can assume they must have clicked “yes” at some point in the past. Then you can just write:

if alert["Are you OK with seeing adult content?" "bool" "Yes"]
 go["thesagabegins"]
else
 go["titlescreen"]
end

…and never worry about it again.

(1 edit) (+1)

Are there in-built functions for reading arrays from image interfaces? I have an interest in adding a 'save as .png' and 'save as .cur' to my deck, minart, but I'm not sure where to start when it comes to image conversion. Is this something that should be done externally?

Developer (1 edit) (+2)

That's a rather expansive question!

Exporting GIF images with write[] and asking users to convert them to other image formats as desired will generally be the simplest approach. Decker natively supports GIF because it's a simple and universally-supported format that is also a good match for Decker's paletted-color model.

If you decide to dig deeper, the image interface has a .pixels attribute which, when read, will give you the pixel values of that image as a list of lists of numbers (a matrix).

These numbers will be Decker pattern indices, so you'd need to do some work to "flatten out" 1-bit patterns and animated patterns (if applicable) and then look up the corresponding RGB colors from Decker's active palette. The internals of the PDF module might be a useful reference.

In principle, you could use an Array interface to construct your own PNG encoder in pure Lil, but this could be a significant amount of work! The CUR format is a bit simpler than PNG and (so far as I'm aware) builds on BMP, which is also relatively straightforward. An encoder is, at least, quite a bit simpler than a decoder, since you can avoid implementing all the features and options you don't need for your intended application.

If you only care about PNG export from web builds of your tools, you could also consider writing a module that uses the danger zone to call some JavaScript from Lil and use ordinary web APIs to perform the conversion. See The Forbidden Library for examples of doing this kind of JS/Lil interop.

Does any of that point you in the right direction?

(+1)

This response gave me more than enough information to work off of - thank you! ^^ I'll most likely look into BMP first and see if I can move my way up from there.

Viewing posts 101 to 124 of 124 · Previous page · First page