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: 28,048 Replies: 444
Viewing posts 121 to 132 of 132 · Previous page · First page
(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.

(+2)

show[52 % 2] returns '2' instead of '0' - is that expected behavior for the modulo operation?

Developer (1 edit) (+2)

In Lil, the modulus is the left argument; the opposite order of many other programming languages:

range 15
# (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14)
2 % range 15
# (0,1,0,1,0,1,0,1,0,1,0,1,0,1,0)
(+1)

Im very confused as to how I am meant to do something like a basic inventory, or even a basic variable to keep track of progression?

Im going over the documentation and it feels like it's getting way too in depth with things way too quick, but I cant for the life of me figure that out?

So i assume i need to have a card at the beginning that has variables, so i tried setting some in the on-click of the "start" button i made for my game, but then i tried referencing it and it doesnt seem like it does anything?


is there a good example of something like this setup with more full context than the "primer" in the documentation? one that actually shows all of the relevant scripts, and where they need to go in order for this to work right?

(+2)

If you’re used to other programming languages, Decker is a bit unusual. In most languages, if you wanted to set up a score counter on-screen, you might do something like this:

# Executed at startup
score:0

# In some kind of button
on click do
 score:score+1
end

# Called to redraw the screen
on view do
 canvas.text[score 0,0]
end

This doesn’t work in Decker, because Lil variables do not persist. If you set up a variable inside a function, it’s forgotten when the function returns. If you set up a variable in the top-level of a script (as in the example above), it gets re-executed every time any event is triggered.

Instead, if you want to store state, you need to use widgets. Put a text-field on your card, name it score, position it where you want the score to be visible on-screen, lock it so that the player can’t just edit it. Then you can do:

# In the "New Game" button
on click do
 score.text:0
end

# In some kind of button
on click do
 score.text:score.text+1
end

Instead of the on-screen display being the result of some rendering and calculation, it’s The Actual Thing. If you want to have state that isn’t displayed to the player (things like “has the player opened the doorway hidden behind the bookcase”), you can make them “Show None”, or put them on an entirely different card (sometimes called backstage) that the player can’t get to, and then scripts on other cards can refer to backstage.widgets.score.text.

(+3)

Thank you so much, while I was able to eventually figure this out on my own, I genuinely feel like this little breakdown would have introduced me to the way of doing things in Lil a lot better than what I was able to find in the documentation myself


the specific vernacular of "(cardname).(widgets).(widgetName).text" in particular just completely eluded me in the documentation for some reason

(+4)

One thing that I also find useful for variables is to have one card for all your variables and to write this line of code on the script of your deck:

var:variables.widgets

That way, you don't have to write variables.widgets every time and simply write var.myvariable.value for exemple.

(+1)

I get a different result using pointer.pos-pointer.prev when the pointer is held down vs when it isn’t, but I’d like them to be the same always, how would I do that?

How are you detecting the difference? What pointing device are you using?

Here’s a canvas that draws a line from its centre to pointer.pos-pointer.prev to visualise how far that points, and in which direction:

%%WGT0{"w":[{"name":"canvas","type":"canvas","size":[100,100],"pos":[206,121],"locked":1,"animated":1,"volatile":1,"script":"on view do\n me.clear[] me.line[me.lsize/2 (pointer.pos-pointer.prev)+me.lsize/2]\nend","scale":1}],"d":{}}

The patterns I get (in Native Decker) when waving my mouse around are pretty similar whether I am holding the button down or not. Maybe the “button down” lines are a bit longer? But it’s hard to tell whether that’s just because my hand posture changes and I’m holding it differently.

(3 edits)

For reference, I’m using my mouse to detect inputs in Native Decker on Linux Mint.

(I added an animated button on top of the canvas to show when my mouse is being held.)

On my end, it’s a pretty substantial difference. Previously, I was using this slider widget to specifically detect changes in Y pos:

%%WGT0{"w":[{"name":"slider","type":"slider","size":[38,151],"pos":[163,94],"locked":1,"animated":1,"script":"on view do\n diff:(pointer.pos[1]-pointer.prev[1])\n me.value:(diff/1)\nend","interval":[-50,50],"style":"vert"}],"d":{}}

Here’s an even fancier version:

%%WGT0{"w":[{"name":"canvas","type":"canvas","size":[100,100],"pos":[206,121],"locked":1,"animated":1,"volatile":1,"script":"on view do\n p:colors.black,colors.red\n i:me.copy[]\n me.clear[]\n me.paste[i (-1,0)]\n me.pattern:p[pointer.held]\n dy:(pointer.pos-pointer.prev)[1]\n c:(1,0.5)*me.lsize\n me.line[c c+(0,dy)]\nend","pattern":47,"brush":1,"scale":1}],"d":{}}

It only draws the Y component of the delta, but it draws in red when the pointer is held and black otherwise, and it slowly scrolls old values off to the left instead of clearing each frame, so you can get a sense of whether the black spikes are smaller than the red spikes.

I do see that in Native Decker (also on Linux), but I don’t see it in Web Decker. In Native Decker, I see about the same difference regardless of whether it’s in a tiny window or full-screen, so I don’t think some scaling factor is being applied the wrong number of times.

I have a directory with files named like:

  • snare-2.wav
  • snare-3.wav
  • snare.wav

I think the files are in that order because that’s how naïve sorting works (the - sorts before .) so that’s the order that Lilt’s dir[] built-in returns them.

I would like to process them according to the obvious order:

  • snare
  • snare-2
  • snare-3

…so I think I need to do two things: trim the last four characters off the filename, and sort by the result.

My first attempt was:

select basename:(-4 drop name)
where type=".wav"
orderby basename asc
from dir["samples"]

…but this doesn’t work. For starters, -4 drop name drops the last 4 item from the name column, not the last 4 characters of each value in the column.

My first attempt was to try -4 drop @ name to “push” the drop deeper into the array, in the same way that sum @ items sums each item individually. Unfortunately, Lil doesn’t seem to understand that syntax, and the docs suggest it should only work for unary operators like sum, not binary operators like drop.

My second attempt was to do the dropping inside a loop:

each r in rows select name
  where type=".wav"
  from dir["samples"]
 -4 drop r.name
end

…but then I went to figure out how to sort the resulting list, and it seems like there’s no way to sort lists, only tables? So this seems to do what I want:

select
orderby name asc
from table (list "name") dict list
 each r in rows
  select name
    where type=".wav"
    from dir["samples"]
  -4 drop r.name
 end

…but that seems like a mess. Surely there’s a better way?

Developer(+1)

Let's say we have a table of directory information that looks like this, for consistency of the following examples:

files:insert dir name type with
 0 "snare-2.wav" ".wav"
 0 "foo.txt"     ".txt"
 0 "snare-3.wav" ".wav"
 0 "snare.wav"   ".wav"
end

Lil query clauses are executed right-to-left, like primitive operators within an expression. We can't define a result column and then reference it in clauses to its right, but we can chain queries, as I'll demonstrate shortly.

You're correct that @ is only useful for spreading unary operations down to the elements of a list, not spreading binary operators. Many binary operators in Lil such as "like", "in", "parse", and "format" generalize to listy left and right arguments in order make them easier to use within queries, but for "drop" this would be ambiguous, so there's no free lunch.

If I wanted to shave off the last four characters of each string in a list of strings I might use an explicit "each" loop:

each x in files.name -4 drop x end
# ("snare-2","foo","snare-3","snare")

In context (since loops are expressions),

select basename:each x in name -4 drop x end type from files
# +-----------+--------+
# | basename  | type   |
# +-----------+--------+
# | "snare-2" | ".wav" |
# | "foo"     | ".txt" |
# | "snare-3" | ".wav" |
# | "snare"   | ".wav" |
# +-----------+--------+

Or, I could factor the basename extraction into a unary helper function, and subsequently use "@"; this is a good approach if "data cleaning" operations like this are ever used in multiple places, and can also clarify intent by giving the operation an explicit name:

on baseof x do -4 drop x end
baseof @ files.name
# ("snare-2","foo","snare-3","snare")

In context,

select basename:baseof @ name type from files
# +-----------+--------+
# | basename  | type   |
# +-----------+--------+
# | "snare-2" | ".wav" |
# | "foo"     | ".txt" |
# | "snare-3" | ".wav" |
# | "snare"   | ".wav" |
# +-----------+--------+

If the filenames you're working with only contain a single "." you could also consider using "parse", which automatically spreads itself to a rightward list of strings and greedily stops consuming characters for the "%s" pattern when it encounters the next literal in the format string:

"%s." parse files.name
# ("snare-2","foo","snare-3","snare")
"%s." parse ("one.wav","two.wav","three.four.wav")
# ("one","two","three")

I'll optimistically use this last strategy going forward; your mileage may vary:

select basename:"%s." parse name type from files
# +-----------+--------+
# | basename  | type   |
# +-----------+--------+
# | "snare-2" | ".wav" |
# | "foo"     | ".txt" |
# | "snare-3" | ".wav" |
# | "snare"   | ".wav" |
# +-----------+--------+

I can then write another query against that table to sort and filter it. Note that by not specifying result columns I get all the columns of the input table:

select orderby basename asc where type=".wav" from select basename:"%s." parse name type from files
# +-----------+--------+
# | basename  | type   |
# +-----------+--------+
# | "snare"   | ".wav" |
# | "snare-2" | ".wav" |
# | "snare-3" | ".wav" |
# +-----------+--------+

If I was only interested in that first column as a list, I could use "extract" instead of "select". If you don't specify a result column for "extract", it defaults to peeling out the first column:

extract orderby basename asc where type=".wav" from select basename:"%s." parse name type from files
# ("snare","snare-2","snare-3")

If that's a daunting query to look at, I could name the subquery and write it like this instead:

bases:select basename:"%s." parse name type from files
extract orderby basename asc where type=".wav" from bases

You can use queries to filter, sort, and aggregate tables, lists, or dicts; the input will be "widened" into a table in the process as needed:

select from "One","Two","Three"
# +---------+
# | value   |
# +---------+
# | "One"   |
# | "Two"   |
# | "Three" |
# +---------+

Thus, if you want to sort a plain list you can query against it and then use "extract". Here's a different formulation to the original problem:

extract "%s." parse name where type=".wav" from files
# ("snare-2","snare-3","snare")
extract orderby value asc from extract "%s." parse name where type=".wav" from files
# ("snare","snare-2","snare-3")

Does that help clear things up?

(+1)

Since Lil queries look like SQL queries, I’ve been trying to use them as SQL queries with all the confusing execution order that implies. It hadn’t occurred to me to think about them as “a pipeline of operations executing right-to-left” but now that you mention it, not only does that make a lot of sense, it’s how I always wished SQL worked anyway.

Having worked with Decker transitions and on loop (both of which have strict limits on how much calculation you can do) I’ve learned to fear each expressions as they can consume a lot of the quota. Since this is a Lilt script those restrictions don’t apply, so I should probably relax and not worry so much about each.

I think I was also worried about chaining queries for similar reasons - calculation quota and my first attempt being clunky and awkward. I guess I need to trust Lil’s automatic conversions more - your chained extract example is much tidier than what I came up with, and basically what I wanted.

You’ve given me a lot to think about, thanks!

(+1)

Hi! Deck is SUPER cool and I'm really digging it, but hooo boy am I not a coder. I feel like this is an extremely simple task, but I just can't figure out how to make a button change the text on itself when clicked. I could do it in like 30 seconds in Twine, so I feel kinda dumb ^^;

The specific use case is I'm trying to have a title page where part of the title is a button that cycles through different words (or chooses one from a list at random). It's a thing I'm going to need to utilize multiple times throughout my project, so any help you can provide is greatly appreciated.

(+1)

A button that randomly chooses a label is pretty straightforward. Paste this as the button’s “click” handler:

on click do
 # Different texts the button might show
 texts:"Click me!","Poke","Nudge","Boop"
 
 # Remove the button's current text from the list,
 # so we don't show the same label twice
 texts:me.text drop texts
 
 # Pick a random text and apply it to the button.
 me.text:random[texts]
end

Cycling through texts is a little more complicated, because we have to figure out where we currently are in the list of options, and what to do when we reach the end:

on click do
 # Different texts the button might show
 texts:"5","4","3","2","1","Boom!"

 # Search through texts looking for
 # the one that matches the old button text 
 oldindex:-1
 each t i in texts
  # If we have found the old text,
  # save its index number as the old index
  if me.text=t oldindex:i end
 end
 
 # If the old index was the last text...
 if oldindex = (count texts)-1
  # ...restart the cycle
  me.text:first texts
  # (you might choose to do something else,
  # like go to the next card)
  
 # If the current text wasn't found...
 elseif oldindex = -1
  # ...start from the beginning
  me.text:first texts
  
 # Otherwise just pick the next text.
 else
  me.text:texts[oldindex+1]
 end

end

(A compact and cryptic way to calculate oldindex might be sum (me.text = texts) * range count texts, I don’t know if there’s an even more compact way)

Developer(+2)

I might write the second example as

on click do
 t:"|" split "5|4|3|2|1|Boom!"
 me.text:t[(count t)%1+sum(me.text=t)*keys t]
end

Using the mod operator (%), taking advantage that for a list x, "keys x" is equivalent to "range count x", and that a failure to find a match for the current button text is harmlessly equivalent to matching the first option.

(3 edits) (+1)

Thanks to both of you!! This example worked perfectly for what I need the title card to do, thank you!

If I'm understanding things correctly, variables are only reserved in the same widget / card? So, for example, if I wanted to invoke this same list of text options elsewhere, I would need to direct it to titlebutton.texts? Can I do that across cards, too?

EDIT: Also, I'm having trouble getting in-line links in fields to work? I want to link to the PUSH SRD  and I've highlighted the relevant text and inserted the link with the Text -> Link menu, but clicking on it with the interact tool doesn't do anything.

Developer(+2)

Variables in scripts within Decker only persist for the duration of an event handler. Static data that you just want to be able to reference from multiple places throughout a deck could be placed in the Deck-level script; go to File -> Properties... in the main menu, click "Script..." and then you can enter a declaration like

title_texts:"First","Second","Third","Fourth"

And then refer to the variable 'title_texts' from any card or widget script.

Again, this is for static data. For information that can change over time- like status flags or an inventory system in a game- you'll need to store the information in widgets somewhere on a card. This is a key idea in Decker: state and code live in physical, observable places within a deck. 

For example, you could also place this list within a field named 'texts' on a card named 'title' in JSON format:

["First","Second","Third","Fourth"]

And then access the contents of that field as a list from elsewhere in a script like

title.widgets.texts.data

For links in a rich text field to be clickable, you need to lock the field. In widget mode, select the field and choose "Widgets -> Locked" from the main menu.

If you haven't seen it already, I highly recommend reading through Phinxel's Phield Notes; it's very beginner-friendly and full of examples you can play with and borrow from for your own projects.

(+1)

I have seen Phinxels! I remembered seeing something about how to lock widgets in there but when I went to find it, I couldn't, so thank you.

One last question and I should hopefully be able to figure the rest out on my own— is there a way to make a button invisible until something else happens? I saw in some other comments in here the idea to make a "gamestate" card with checkboxes for things I want to track, and I'd like to be able to hide a button until a "gamestate.widgets.X.value" is checked.

To provide probably more context than necessary, I'm making a faux-ttrpg as an "about me" for a job application for a creative project. I want the user to click on a button next to the Name field of the "character sheet" to get a story about my name to appear in a text field, and log that they've seen that to make the button for the next character sheet field appear. Once all the fields have been filled, out, I want a "Begin Adventure" button to appear to go to the next section of the game.

I see that there's an "invisible" setting for buttons, but I'm not sure how I could toggle the button type based on a variable. Changing the text field is probably just an "if card.widgets.variable.value then print: "whatever"" kinda deal, so I'm mostly worried about the button visibility.

(1 edit) (+2)

To start with, you probably need the "Show" attribute. Specifically to set things to .show:"none" 

Invisible buttons are more useful for things you want to be able to click that don't look like buttons (for example, navigating around a scene in a point-and-click game). And a "None" button is truly hidden and unclickable until you change their setting, so that seems like what you might be after.

To set a widget to "none" visibility you can do it in code or with the menu while you're editing.  
While Editing you deck: Select a widget and use the menu Widgets > Show None ]

And in code, this sets the visibility of a widget:

widgetname.show:"none"

To make it visible again you would need to set it's show attribute to one of the other visibilities, so this...

widgetname.show:"solid" 

...would set it back to the default visibility it had when you made it. (Other options: "transparent" and "invert")

Just a thought, but if you only need to track the progress on one card for now and you don't need to check back on it later you could just make things appear one by one in the button scripts.

For example, here's a simple example script for a button that sets some text in a field then makes the next button visible:

on click do
myname.text:"Pyrefly Studio"
favefoodbutton.show:"solid"
end

and then script in the next button (favefoodbutton, which just became visible) could be something like

on click do
favefood.text:"Blueberry Ice Cream"
coolfactbutton.show:"solid"
end

etc, etc. These widget names (fields: myname, favefood / buttons: favefoodbutton, coolfactbutton) are made up, of course, so you'll want to make any names match the widgets that actually exist. 

But I hope this helps you get started changing widget visibility and setting the text of a field using scripts.

(+1)

I want to make an isometric game in Decker. I'm able to make things move. But there are some places where I don't want to move beyond.

In other words, I want to have collision mechanics. How do I program that? I vaguely remember someone saying to use the Path module to text whether a patter is or is not to be moved into, but I don't know how that works. I tried looking into Sokoban, but I can't make heads nor tails of it and, after looking into its resources, cannot find anything on the mover module within it which might be able to help me, but I am not sure.

In all honesty, I don't even know where to start and could appreciate some pointers.

(+1)

This is not a complete answer, this is kind of just a gesture towards one. The actual complete answer will kind of depend on what's going on in your game, y'know? But I can point to a couple of things.

Here's some recent discussion of the sokoban example specifically: /post/16368974

And for the path module here's the card that has an example that gives a visual example of the forbidden color thing: https://beyondloom.com/decker/path.html#followeranim (click the checkbox at the bottom) 

Some of the code for this is inside the module, not the deck's scripts but I believe it uses .hist to check parts of the movement grid for a specific color, and forbids movement onto that square of the grid if it finds any.

It should be possible to do something similar with a different kind of movement system too, if the path module itself doesn't have the kind of movement you had in mind.

Hm... I have an idea. In that I could have a bunch of canvases, shaped into a rough hexagonal shape, which I will call the "Player," like this:


And then have different canvasses act as, I don't know, geometry for the Player to "collide" with. Like this:


I theorize that I can use the Draggable module to collide with the other canvases.

Again, this is theory for me and probably labor intensive, but better than nothing, lol.

(+1)

The thing is, “an isometric game” covers a lot of ground. Some isometric games are just regular top-down 2D games that happen to use sprites drawn from an isometric perspective, even though the games use normal rectangular hit-boxes for collision and movement (though I can’t think of a well-known example off the top of my head). Other isometric games (such as the original Diablo and Diablo II) have everything happen on a regular square grid, then they just draw the grid with isometric graphics. That way, game logic and collisions are still pretty straight-forward, but the game looks more interesting with more overlap than a regular 2D top-down view. Still other games use a real hexagonal grid, which makes logic and collisions more complicated in exchange for being mechanically isometric (the same cost to travel in any direction) as well as visually isometric (a thing looks the same size no matter which way it’s pointing).

Which of those seem the closest to what you want?

(1 edit)

I couldn’t figure out a good way to benchmark it, but this feels snappier, unless I’m experiencing placebo. Certainly, I wasn’t seeing any speedup in the listener operations (did a simple fibonacci) but I could do something like paste a lot of text into a field and the UI wouldn’t completely bog down. It was still responsive.

Another one is Wigglypaint marker didn’t skip for me. Normally with my laptop (old Thinkpad) Wigglypaint JS, the marker is not a fluid line. I’m pretty sure this isn’t placebo, but again, I couldn’t figure out a good benchmark other than “feeling” the snappiness.

patch to lil.js that turns operation look ups to be inverse hashmaps:

diff --git a/js/lil.js b/js/lil.js
old mode 100755
new mode 100644
index 5c89b4d..07163fa
--- a/js/lil.js
+++ b/js/lil.js
@@ -237,6 +237,7 @@ monad={
 		return r
 	},
 }
+monadk=Object.fromEntries(Object.keys(monad).map((e,i)=>[e,i]))
 dyad={
 	'+':  vd((x,y)=>lmn(ln(x)+ln(y))),
 	'-':  vd((x,y)=>lmn(ln(x)-ln(y))),
@@ -453,6 +454,7 @@ table_swap_rows=(tab,a,b)=>{
 	if(a==b||a<0||b<0||a>tab_rowcount(tab)-1||b>tab_rowcount(tab)-1)return tab
 	const pv=monad.range(lmn(tab_rowcount(tab)));pv.v[a]=lmn(b),pv.v[b]=lmn(a);return dyad.take(pv,tab)
 }
+dyadk=Object.fromEntries(Object.keys(dyad).map((e,i)=>[e,i]))
 triad={
 	'@orderby': (col,tab,order_dir)=>{
 		const rt=orderby(tab,dyad.take(lmn(count(tab)),lml(ll(col))).v,ln(order_dir))
@@ -477,8 +479,9 @@ triad={
 		return lin(x)?r:dyad[','](lt(x),r)
 	},
 }
+triadk=Object.fromEntries(Object.keys(triad).map((e,i)=>[e,i]))
 
-findop=(n,prims)=>Object.keys(prims).indexOf(n), as_enum=x=>x.split(',').reduce((x,y,i)=>{x[y]=i;return x},{})
+findop=(n,prims)=>prims[n], as_enum=x=>x.split(',').reduce((x,y,i)=>{x[y]=i;return x},{})
 let tnames=0;tempname=_=>lms(`@t${tnames++}`)
 op=as_enum('JUMP,JUMPF,JUMPT,LIT,DUP,DROP,SWAP,OVER,BUND,OP1,OP2,OP3,GET,SET,LOC,LOCS,AMEND,TAIL,CALL,BIND,ITER,EACH,NEXT,COL,IPRE,IPOST,FIDX,FMAP')
 oplens=   [ 3   ,3    ,3    ,3  ,1  ,1   ,1   ,1   ,3   ,3  ,3  ,3  ,3  ,3  ,3  ,3   ,3    ,1   ,1   ,1   ,1   ,3   ,3   ,1  ,3   ,3    ,3   ,3    ]
@@ -492,9 +495,9 @@ blk_gets=(x,i  )=>0xFFFF&(blk_getb(x,i)<<8|blk_getb(x,i+1))
 blk_op  =(x,o  )=>{blk_addb(x,o);if(o==op.COL)blk_addb(x,op.SWAP)}
 blk_opa =(x,o,i)=>{blk_addb(x,o),blk_adds(x,i);return blk_here(x)-2}
 blk_imm =(x,o,k)=>{let i=x.locals.findIndex(x=>match(x,k));if(i==-1)i=x.locals.length,x.locals.push(k);blk_opa(x,o,i)}
-blk_op1 =(x,n)=>blk_opa(x,op.OP1,findop(n,monad))
-blk_op2 =(x,n)=>blk_opa(x,op.OP2,findop(n,dyad ))
-blk_op3 =(x,n)=>blk_opa(x,op.OP3,findop(n,triad))
+blk_op1 =(x,n)=>blk_opa(x,op.OP1,findop(n,monadk))
+blk_op2 =(x,n)=>blk_opa(x,op.OP2,findop(n,dyadk ))
+blk_op3 =(x,n)=>blk_opa(x,op.OP3,findop(n,triadk))
 blk_lit =(x,v)=>blk_imm(x,op.LIT,v)
 blk_set =(x,n)=>blk_imm(x,op.SET,n)
 blk_loc =(x,n)=>blk_imm(x,op.LOC,n)
@@ -659,10 +662,10 @@ parse=text=>{
 			blk_op3(b,'@ins');return
 		}
 		if(matchsp('(')){if(matchsp(')')){blk_lit(b,lml([]));return}expr(b),expect(')');return}
-		const s=peek().v;if(findop(s,monad)>=0&&({'symbol':1,'name':1})[peek().t]){
+		const s=peek().v;if(findop(s,monadk)!==undefined&&({'symbol':1,'name':1})[peek().t]){
 			next();if(matchsp('@')){
 				let depth=0,l=lmblk();while(matchsp('@'))depth++
-				expr(b),blk_opa(l,op.FMAP,findop(s,monad))
+				expr(b),blk_opa(l,op.FMAP,findop(s,monadk))
 				while(depth-->0){const t=tempname(),m=lmblk();blk_loop(m,[ls(t)],_=>{blk_get(m,t),blk_cat(m,l)}),l=m}
 				blk_cat(b,l)
 			}else{expr(b),blk_op1(b,s)};return
@@ -678,7 +681,7 @@ parse=text=>{
 			blk_sets(l,fidx,blk_here(l))
 			while(depth-->0){const t=tempname(),m=lmblk();blk_loop(m,[ls(t)],_=>{blk_get(m,t),blk_cat(m,l)}),l=m}
 			blk_cat(b,l);return
-		}const s=peek().v;if(findop(s,dyad)>=0&&({'symbol':1,'name':1})[peek().t]){next(),expr(b),blk_op2(b,s)}
+		}const s=peek().v;if(findop(s,dyadk)!==undefined&&({'symbol':1,'name':1})[peek().t]){next(),expr(b),blk_op2(b,s)}
 	}
 	const b=lmblk();if(hasnext())expr(b);while(hasnext())blk_op(b,op.DROP),expr(b)
 	if(blk_here(b)==0)blk_lit(b,NIL);return b
(3 edits)

Oh - the hashes probably aren’t right if you try to do a git apply. It’s from my fork of decker that I use for WebXDC. And, I fixed a small thing just now in the patch that does !==undefined where it was doing >=0 before (from indexOf to an Object/hashmap).

And, yeah, I loaded a Wigglypaint in the old version, and I can tell the difference between the old and new. It’s a lot smoother.

(1 edit)

I’m less sure if this helps, because on top of the other one, I can’t tell if it does. I’ll have to test without the other optimization.

I know if I do this sort of lookup dispatch in my C forths/lisps, they don’t help. Usually the switch-case is best - probably due to inlining rather than function pointer tables. I’ve never sat down to look, just empirical testing. But, maybe it helps in JS.

turns the switch-case into a lookup table:

diff --git a/js/lil.js b/js/lil.js
index 31b10f4..a88963c 100644
--- a/js/lil.js
+++ b/js/lil.js
@@ -714,50 +714,52 @@ docall=(f,a,tail)=>{
 	issue(f.a.length==1&&f.a[0][0]=='.'?env_bind(f.c,[f.a[0].slice(3)],monad.list(a)): env_bind(f.c,f.a,a),f.b)
 	calldepth=max(calldepth,state.e.length)
 }
+ops={
+	[op.DROP ]:(o,imm,b)=>{arg()},
+	[op.DUP  ]:(o,imm,b)=>{const a=arg();ret(a),ret(a);},
+	[op.SWAP ]:(o,imm,b)=>{const a=arg();b=arg();ret(a),ret(b);},
+	[op.OVER ]:(o,imm,b)=>{const a=arg();b=arg();ret(b),ret(a),ret(b);},
+	[op.JUMP ]:(o,imm,b)=>{setpc(imm)},
+	[op.JUMPF]:(o,imm,b)=>{if(!lb(arg()))setpc(imm)},
+	[op.JUMPT]:(o,imm,b)=>{if( lb(arg()))setpc(imm)},
+	[op.LIT  ]:(o,imm,b)=>{ret(blk_getimm(b,imm))},
+	[op.GET  ]:(o,imm,b)=>{ret(env_get(getev(),blk_getimm(b,imm)));},
+	[op.SET  ]:(o,imm,b)=>{const v=arg();env_set(getev(),blk_getimm(b,imm),v),ret(v);},
+	[op.LOC  ]:(o,imm,b)=>{const v=arg();env_local(getev(),blk_getimm(b,imm),v),ret(v);},
+	[op.LOCS ]:(o,imm,b)=>{env_locals(getev(),blk_getimm(b,imm));},
+	[op.BUND ]:(o,imm,b)=>{const r=[];for(let z=0;z<imm;z++)r.push(arg());r.reverse(),ret(lml(r));},
+	[op.OP1  ]:(o,imm,b)=>{                      ret(monadi[imm](arg()));},
+	[op.OP2  ]:(o,imm,b)=>{const         y=arg();ret(dyadi [imm](arg(),y));},
+	[op.OP3  ]:(o,imm,b)=>{const z=arg(),y=arg();ret(triadi[imm](arg(),y,z));},
+	[op.IPRE ]:(o,imm,b)=>{const s=arg(),i=arg();ret(i),docall(s,i.v[imm]);if(lion(s)||lii(s)||linat(s)){for(let z=0;z<=imm;z++)i.v[z]=null}},
+	[op.IPOST]:(o,imm,b)=>{const s=arg(),i=arg(),r=arg();ret(i.v[imm]?r:s),ret(i),ret(s);},
+	[op.AMEND]:(o,imm,b)=>{
+			let v=arg(),r=arg(),i=ll(arg()),ro=arg(),n=blk_getimm(b,imm),t={v:1}
+			if(i.length&&!i[0]){i=i.filter(x=>x),t.v=0}r=amendv(ro,i,v,0,t);if(t.v&&!lin(n))env_set(getev(),n,r);ret(r);
+		},
+	[op.CALL ]:(o,imm,b)=>{const a=arg(),f=arg();docall(f,a,o==op.TAIL);}, // same as tail
+	[op.TAIL ]:(o,imm,b)=>{const a=arg(),f=arg();docall(f,a,o==op.TAIL);},
+	[op.BIND ]:(o,imm,b)=>{const f=arg(),r=lmon(f.n,f.a,f.b);r.c=getev(),env_local(getev(),lms(f.n),r),ret(r);},
+	[op.ITER ]:(o,imm,b)=>{const x=arg();ret(lil(x)?x:ld(x));ret(lid(x)?lmd():lml([]));},
+	[op.FIDX ]:(o,imm,b)=>{const x=arg(),f=arg();if((lid(f)||lil(f)||lis(f))&&lil(x)){ret(lml(x.v.map(x=>l_at(f,x))));setpc(imm)}else{ret(x)};},
+	[op.FMAP ]:(o,imm,b)=>{const x=arg(),f=monadi[imm];ret(lid(x)?lmd(x.k,x.v.map(f)):lml(ll(x).map(f)));},
+	[op.EACH ]:(o,imm,b)=>{
+			const n=arg(),r=arg(),s=arg();if(count(r)==count(s)){setpc(imm),ret(r);}
+			else{const z=count(r), v=lml([s.v[z],lid(s)?s.k[z]:lmn(z),lmn(z)]);
+			state.e.push(env_bind(getev(),n,v)),ret(s),ret(r);}
+		},
+	[op.NEXT ]:(o,imm,b)=>{const v=arg(),r=arg(),s=arg();state.e.pop();if(lid(r))r.k.push(s.k[r.v.length]);r.v.push(v),ret(s),ret(r),setpc(imm);},
+	[op.COL  ]:(o,imm,b)=>{
+			const ex=arg(),t=arg(),n=tab_cols(t),v=ll(monad.cols(t));ret(t)
+			n.push('column'),v.push(t),issue(env_bind(getev(),n,lml(v)),ex);
+  },
+}
 runop=_=>{
 	op_count+=1
 	const b=getblock();if(!liblk(b))ret(state.t.pop())
 	const pc=getpc(),o=blk_getb(b,pc),imm=(oplens[o]==3?blk_gets(b,1+pc):0); setpc(pc+oplens[o])
-	switch(o){
-		case op.DROP :arg();break
-		case op.DUP  :{const a=arg();ret(a),ret(a);break}
-		case op.SWAP :{const a=arg(),b=arg();ret(a),ret(b);break}
-		case op.OVER :{const a=arg(),b=arg();ret(b),ret(a),ret(b);break}
-		case op.JUMP :setpc(imm);break
-		case op.JUMPF:if(!lb(arg()))setpc(imm);break
-		case op.JUMPT:if( lb(arg()))setpc(imm);break
-		case op.LIT  :ret(blk_getimm(b,imm));break
-		case op.GET  :{ret(env_get(getev(),blk_getimm(b,imm)));break}
-		case op.SET  :{const v=arg();env_set(getev(),blk_getimm(b,imm),v),ret(v);break}
-		case op.LOC  :{const v=arg();env_local(getev(),blk_getimm(b,imm),v),ret(v);break}
-		case op.LOCS :{env_locals(getev(),blk_getimm(b,imm));break}
-		case op.BUND :{const r=[];for(let z=0;z<imm;z++)r.push(arg());r.reverse(),ret(lml(r));break}
-		case op.OP1  :{                      ret(monadi[imm](arg()    ));break}
-		case op.OP2  :{const         y=arg();ret(dyadi [imm](arg(),y  ));break}
-		case op.OP3  :{const z=arg(),y=arg();ret(triadi[imm](arg(),y,z));break}
-		case op.IPRE :{const s=arg(),i=arg();ret(i),docall(s,i.v[imm]);if(lion(s)||lii(s)||linat(s)){for(let z=0;z<=imm;z++)i.v[z]=null}break}
-		case op.IPOST:{const s=arg(),i=arg(),r=arg();ret(i.v[imm]?r:s),ret(i),ret(s);break}
-		case op.AMEND:{
-			let v=arg(),r=arg(),i=ll(arg()),ro=arg(),n=blk_getimm(b,imm),t={v:1}
-			if(i.length&&!i[0]){i=i.filter(x=>x),t.v=0}r=amendv(ro,i,v,0,t);if(t.v&&!lin(n))env_set(getev(),n,r);ret(r);break
-		}
-		case op.CALL : // fall through:
-		case op.TAIL :{const a=arg(),f=arg();docall(f,a,o==op.TAIL);break}
-		case op.BIND :{const f=arg(),r=lmon(f.n,f.a,f.b);r.c=getev(),env_local(getev(),lms(f.n),r),ret(r);break}
-		case op.ITER :{const x=arg();ret(lil(x)?x:ld(x));ret(lid(x)?lmd():lml([]));break}
-		case op.FIDX :{const x=arg(),f=arg();if((lid(f)||lil(f)||lis(f))&&lil(x)){ret(lml(x.v.map(x=>l_at(f,x))));setpc(imm)}else{ret(x)};break}
-		case op.FMAP :{const x=arg(),f=monadi[imm];ret(lid(x)?lmd(x.k,x.v.map(f)):lml(ll(x).map(f)));break}
-		case op.EACH :{
-			const n=arg(),r=arg(),s=arg();if(count(r)==count(s)){setpc(imm),ret(r);break}
-			const z=count(r), v=lml([s.v[z],lid(s)?s.k[z]:lmn(z),lmn(z)]);
-			state.e.push(env_bind(getev(),n,v)),ret(s),ret(r);break
-		}
-		case op.NEXT :{const v=arg(),r=arg(),s=arg();state.e.pop();if(lid(r))r.k.push(s.k[r.v.length]);r.v.push(v),ret(s),ret(r),setpc(imm);break}
-		case op.COL  :{
-			const ex=arg(),t=arg(),n=tab_cols(t),v=ll(monad.cols(t));ret(t)
-			n.push('column'),v.push(t),issue(env_bind(getev(),n,lml(v)),ex);break
-		}
-	}while(running()&&getpc()>=blk_here(getblock()))descope()
+	ops[o](o,imm,b)
+	while(running()&&getpc()>=blk_here(getblock()))descope()
 }
 
 fchar=x=>x=='I'?'i': x=='B'?'b': x=='L'?'s': x=='t'?'J': x=='T'?'J': x
Deleted 18 days ago

Both in conjunction appear to help. Wigglypaint is the smoothest with both patches. I can draw with the pen and the marker doesn’t skip when it paints under it. Normally, it skips a lot for me. Also, the other way I can tell is if I load my Huffman deck and paste a large amount of text it it. Usually the menu and the listener are nearly impossible to use. With these, it’s somewhat acceptable, though still slow.

I didn’t know what was the best way other than eyeballing it. I can tell it’s smoother, but for “raw computation” it doesn’t make a difference. I don’t know if that’s due to how things get timesliced between rendering and compute.

If you can tell me a proper way to benchmark this, I can do the hard work to test it if it’s worthwhile - I know empirical eyeballing is a bit hard to judge.

https://codeberg.org/woodring/decker.decker-xdc/src/branch/decker-xdc/noxdc.html contains both fixes for comparison

(+1)

I want the button to make a sound, change the variable and hide the flower. Button should do all that if the variable = 0 wich should be like it from the start of the game. Then when we click the button it should change the variable and so make it impossible to click it again. But all the things doesn't want to work when I add conditions to the play... and I am so confused and I did read everything... but I just can't , it's all so complicated :_(

Also would like the game to "save" the variable to the next cards, so we can... give the flower to somebody but only if we have it, you know?.. Please, please, pretty please, explain to me how it needs to be done, but in a simplest manner imaginable, Im so dumb 😭

(+2)

Hello! This looks like a charming game already.

When you want to create a variable in decker to be referred back to later you need to store them in a widget. A temporary variable can be created within a specific script while it's running, but these temporary variables aren't stored anywhere and can't easily be edited unless you put them inside a widget.

But it's pretty easy to do exactly that!

For things where you want to track true/false (has the flower been picked?) a checkbox-style button is really useful. If you created a checkbox called "pickedflower" and gave it the value 1 like this:

pickedflower.value:1

.value means different things for different widgets, but generally it refers to the kind of information stored in them. I'll keep it simple and say that in the case of buttons (including checkboxes) they can only store a boolean 0/1 (false/true) as their value.

To check this value in your if statement you can write it like this:

if pickedflower.value
# the rest of your script here
end

But... since the checkbox is a widget that will live on a specific card, you'll need to put it somewhere. 

From what you explained about your game you want to set the variable while picking up the flower on one card... and then check the variable on another card later to give the flower to someone later, right?

So we also have to talk about how to refer to a widget that's on another card.

If the "pickedflower" checkbox lived on a card called "garden", you could refer to it this way on your scripts, when you need to refer to it's stored value from other cards:

garden.widgets.pickedflower.value

It's a little long to write it like this, but I think that's okay when you only have a couple things to check. If you have a lot of things to track, let me know and I'll come up with some suggestions for how to store them and refer to them more easily!

Also, you can set your checkbox to show:"none" and it'll be completely hidden to your player.  
Or you can store all your game state-related widgets on a card that the player never sees (I recommended this if you have a lot of them!)

Optionally, if you don't want to store a variable this way at all... there's other ways to do something similar. 

For example: you can check the current visibility of your flower canvas to see if it's been hidden or not:

if theflower.show="none"
# something that happens only if the flower's canvas is hidden
end

And you can also lock a button if you no longer want it to be clickable.

thebutton.locked:1

If you do this make sure to add unlocking it (thebutton.locked:0) to your reset button too.

I'm happy to explain more if it would be helpful. But I wanted to get an answer for you quickly so you could get started playing around with it.

(1 edit)

THANK YOU  SM! As long as I can tell right now it works in different cards!

But there is a problem with the lock button thingy you told about in the last paragraph. It doesn't want to lock, like... the sound plays anyway, even if we already grabbed the flower!

But maybe I oversimplicated it and you meant it as a variable for the button that works only in the script of the button itself?

(+2)

I was rushing a bit at the end of my post so let's see....

You figured it out (correctly) that I meant "thebutton" as the name of the button. I didn't know what yours was called, but I also didn't make it clear that I was making up a name for it. Sorry about that! But something is still not right, huh...

Is it possible there's an extra space at the beginning or end of the widget name? I do that all the time... and it will technically affect what the name of the button.

If the button's name is just "GrabFlowerButton", then that's how you should be able to write it in the code.

GrabFlowerButton.locked:1

Also! Since you mentioned things that only work inside a widget's script, I'll also mention this in case it's useful: 

When you're writing a script for a widget you can always refer to that specific widget as "me"

So for the script inside the button you can write it like this:

me.locked:1

Though you'll still have to use its name whenever you're referring to it in scripts that live anywhere else in your project.

I hope one of these things can help! I'll check back again later. 🫡

(+1)

OMG! Thank you again! :^3

It finally works how I intended it to! 

(When I created the first question I thought that no one will answer me, cause first questions in this topic were soo long before and I assumed everyone just forgot about this program ":-D )

Would it be ok if I had any questions much later and answered to your last reply with new ones?

(+1)

Hooray! I'm so glad!

Things are sometimes a little quieter here in the forum during August because one of the official decker jams just happened in July. But there's always enough people stopping by to make sure that questions get answered.

Feel free to reply here or leave a new comment on the thread, I'll keep an eye out for you. :)

Hi again! Yeah it wasn't so long as I thought I would text you, but yeah.

Could you help me again please? 😅

So eventually I ran into the problem with the need of usage a variable that needs to be changed and saved, but it would have a different event at each number.

By the way... I used it only in this script but it doesn't want to work. Firstly it was called in a short nickname, but it came to "DormitoryHall1.widgets"...... and so on because I tried to make it be saveble since I guess Everytime script ends widget forgets it.

SO how do I make a variable that has more than 1/0 in it that can be saved... or just work at least in this script how I wanted it to?.. But I know you might say that in this situation I could use just 1 and 0, but in the end I want to use 1,2,3 here! 

Also a question I got is how can I make a >= (≥) and ect, cause I read in the doc that this engine don't have that? Or maybe I just misread that...

Thanks in advance, my saviour! ;3

(+1)

Hello! And yeah absolutely!

For starters: referring to widgets by a shorter nickname. I think you were on the right track already, but I'll explain it in more detail anyway.

One option is to create a variable which is just the full path to a widget somewhere else in the deck:

doorknock: DormitoryHall1.widgets.DoorDepr

And that should make it possible to use "doorknock" as a shortcut for the longer name.

Or a project that has many info tracking widgets on a secret storage card could just simplify part of the path:

status:secretstoragecard.widgets

And then be able to use that in your scripts like this:

if status.pickedflower.value

And while you can define these shortcuts in the specific scripts where you're using them... you could also put them in the Deck-level script if you use them a lot. Just like Widgets and Cards can have scripts, you can also define events and variables at the Deck level.

Things put in the Deck script are always visible to every widget in the project (unless overridden more locally, but that's a different subject).

You can access this script  with File > Properties > [Script...]

Or if you're already in a script editor for something else you can use File > Go to Deck to move there directly.

And this kind of nickname variable doesn't need to be in an event handler or anything, just define the variable and you're done.

Okay, now on to your real questions....

Just like a checkbox can hold 1 and 0 as true/false... other widgets are good at holding other kinds of information.

In this case I recommend a slider widget, specifically. They're very good at storing numbers within a specific range, and you can choose the minimum and maximum of that range, and how big of a step is allowed between each point on the slider.

They're my go-to widget when I'm keeping count of something!

I'd recommend setting the style of your slider to "Compact" in the properties dialog to make the number easier to read while you're testing, and then you can also set the min and max of the range of numbers you want to use.

A nice thing about using a slider for this job is that your existing script doesn't need to change much.

The current number stored in a slider is also called .value in scripts. 

And you can adjust it by doing things you already understand how to do:

yourslider.value: yourslider.value +1

If you need to reset it back to the beginning you can just assign it the number you want to start at. Assuming that's zero at the beginning of the game, just do this:

yourslider.value:0

(And, as always, make the names match your actual project)

For the other part... It's true that we can't use combined operators but you can usually get the effect you need by writing things a little differently. I'm not completely sure all the ways you were thinking about using >= so this may not fully answer your question...

But for this script example... I think things could be simplified a little with the power of "else".

(I'm not copying your full script here so I'll just leave placeholders for the different scene possibilities, okay?)

if doorknock.value =1
 # "Erm, Hello?"
else
 # "...."
end

Basically else is "If the condition wasn't true...  do this other thing instead." 

Or you could add more possible outcomes with elseif

The first true thing in the series of possibilities will be the one that happens, so if two things could technically be true at the same time, make sure to put the higher priority one earlier in the list:

if doorknock.value =1
 # "Erm, Hello?"
elseif doorknock.value =20
# "Please stop knocking!!"
elseif doorknock.value > 15
# "...!! >:O"
else
 # "...."
end

20 is more than 15, so the event for ">15" could have happened at value=20..... but =20 was earlier in the order of possibilities, so only that one will happen. It's kind of a silly example, but I hope it makes sense.

And while I'm thinking about it, you can also move your dd.open[] and dd.close[] lines to be before and after your branching dialog possibilities if you want. Like this:

on click do
dd.open[deck]
 if doorknock.value =1  
 dd.say["Erm, Hello?"]
 else  
 dd.say["...."] 
 end
dd.close[]
end

I've got to stop here for now but I'm happy to come back and clarify if I wrote things in a confusing way, or if I didn't answer your real question!

(1 edit) (+1)

Hey! Wanted to tell thank you again, you're extremely nice person!

I didnt understand first part at frst glance cause I was sleepy that time, but now I understand! Thanks for bonus knowledge ;)

(+2)

Well, gosh, thank you too! :D 

I love to share bonus knowledge, even when I'm not sure how helpful it will be. I'm very glad it was helpful this time!

Viewing posts 121 to 132 of 132 · Previous page · First page