Skip to main content

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

Internet Janitor

670
Posts
45
Topics
1,610
Followers
17
Following
A member registered Aug 02, 2018 · View creator page →

Creator of

Recent community posts

The two ways of concatenating strings in Lil are fuse, which takes a simple string to intercalate between the elements of a list:

 " : " fuse "Alpha","Beta"             # -> "Alpha : Beta"

And format, which offers a richer printf-like string formatting language:

 "%s : %s" format "Alpha","Beta"       # -> "Alpha : Beta"  

The format primitive is useful in many situations, including gluing a fixed prefix and/or suffix onto strings:

"Prefix %s Suffix" format "MyString"   # -> "Prefix MyString Suffix"

In situations where a function, operator, or interface attribute expects a string, you can often get away with simply providing a list of strings (perhaps joined together into a list with the comma (,) operator) which will be implicitly fused together. Per The Lil Reference Manual for type coercions:

When a string is required, numbers are formatted, and lists are recursively converted to strings and joined. Otherwise, the empty string is used.

This is why you're able to elide the fuse in your example; "field.text" always treats a written value as a string

As noted in the previous post Ahm linked, Lil has uniform operator precedence; expressions are evaluated right-to-left unless parentheses are used.

The expression

x=1&y=1&z=1

In your first example is only working by coincidence; It is not equivalent to

(x=1)&(y=1)&(z=1)

But rather

x=(1&(y=(1&(z=1))))

A fellow containment breach? Wonder where they'll turn up next...

I haven't changed my mind about this design choice, but I did add an escape-hatch to Web-Decker which offers another possible workaround:  The Forbidden Library includes a module called "kb" which pumps raw key events to the active card. This module will not function in Native-Decker, but that may be acceptable if you otherwise plan to offer graceful degradation to alternative input methods.

(1 edit)

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

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

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

In C, expressions are explicitly terminated with a semicolon (;). In Lil, expressions are implicitly terminated when they are no longer "incomplete"; that is, their rightmost token is not a primitive or other syntactic form which "expects" an additional subexpression to the right. Outside comments and string literals, all whitespace in Lil is equivalent, so we can sequence "statements" with newlines between them:

ax:2+3
bx:ax*5

...or we could run them together on a single line:

ax:2+3 bx:ax*5

In this particular example we could even remove the space separating the two statements, because Lil identifiers may not begin with a number; splitting the "3" from the "bx" is unambiguous. I do not recommend using such a style in practice:

ax:2+3bx:ax*5

Lil interprets all three variations as the sequence of tokens (shown separated by whitespace):

ax : 2 + 3 bx : ax * 5

Whitespace is only required when Lil would otherwise "see" a single token but multiple tokens are intended:

onfoo   # reference to a variable named "onfoo"
on foo  # the keyword "on" followed by the identifier "foo"

The bracketed argument list for a function call is not a special case; just like the "do...end" of a function body, it accepts any sequence of expressions, each implicitly terminated. If the argument-expressions are complex, it may be stylistically clearer to parenthesize them and/or use whitespace to produce a visually apparent grouping, but this does not change how Lil interprets the expression. Consider:

x:11
y:first range 10
z:3*5
foo[x y z]

Versus the following equivalent alternatives:

foo[11 first range 10 3 * 5]
foo[(11) (first range 10) (3 * 5)]
foo[
 11
 first range 10
 3 * 5
]

Or, my preference for this particular situation,

foo[11 (first range 10) 3*5]

The comma operator (,) should always be understood as a primitive operator, rather than special syntax; it's of the same nature as the operators plus (+) or times (*).

Does that help clarify?

It is possible to directly manipulate the card background (card.image) instead of using a canvas widget, but it's limited in some ways; see the Image Interface documentation. If you have a hand-drawn card background this can also make it easy to accidentally destroy your doodles, so use caution! For a quite elaborate example of such an animation, try clicking the first "e" in "Decker" on the title screen of the guided tour deck.

When I'm frequently making changes to a script I do often defer logic to the card-level script for editing convenience, possibly by giving the widget a stub script like:

on click do
 reset_game[]
end

With "reset_game" defined on the card or deck script. It is possible to quickly jump to a widget's script by enabling "File -> X-Ray Specs" from within the script editor and then ctrl+clicking in the widget's bounding box to view its script; ctrl+clicking outside any widgets switches to the card script. This is a very useful tool for getting a "bird's eye view" of what's happening on a card. Alternatively you could use the F-keys on your keyboard to switch between Interact and Widgets mode (F1 and F2, respectively), click a widget, and press cmd/ctrl+R to edit its script.

I think in your example you may be tripping over Lil's order of operations (or lack thereof). Without parentheses, expressions are carried out strictly right-to-left. Parens may be useful for visually grouping arguments, but do not inherently denote lists. The following expressions are equivalent:

(me.size/2, me.size)
me.size/(2, me.size)
me.size/2,me.size

I think you intended:

(me.size/2),me.size

When the position for canvas.text[] is specified as an (x,y) pair, anchors control the positioning of the text relative to that point: "center" means the string will be centered upon that point.

When the position for canvas.text[] is instead specified as a (x,y,w,h) rectangle, anchors control the alignment and justification of text within that rectangle. Thus, to wrap and center text within the bounds of the canvas you'd want something like 

canvas.text[somestring 0,0,canvas.size "center"]

Or, incorporating a margin,

local margin:15
canvas.text[somestring margin,margin,me.size-margin*2 "center"]


How's that?

(2 edits)

A card itself cannot be marked animated; only widgets.

With the "Widgets" tool active, you can set this property from the "Widgets -> Animated" menu item.

If you want an "animated card" you can either use a self-refreshing approach with go[card] like in your original script, or add an animated widget to the card that acts as an "event pump" by allowing the view[] event to bubble up to the card. The Decker Sokoban example uses something similar to the latter approach.

(2 edits)

If you use "Card -> Copy Card" from the menu, Decker will store a text-based serialization of the card, its widgets, and any fonts or contraption prototypes those widgets reference in the clipboard. This also works with "Edit -> Copy Widgets" for a group of widgets. As in the examples in The Contraption Bazaar, this can be a handy way of sharing a chunk of a project that Decker users can immediately paste into their own deck and try out. Here's how that might look for your example:

%%CRD0{"c":{"name":"card1","script":"local s: \"abcdef\"\non view do\n  if ! sys.frame < next_frame.text\n   canvas.clear[]\n   local t: flip (list \"\" split s),(list random[2 count s])\n   local y: \"\" fuse each u in t if u[1] \"%u\" else \"%s\" end format u[0] end\n   canvas.text[y (50,50, 200,100)]\n   next_frame.text: sys.frame + 5\n  end\n  go[card]\nend","widgets":{"canvas":{"type":"canvas","size":[100,100],"pos":[206,121],"volatile":1,"scale":1},"next_frame":{"type":"field","size":[100,20],"pos":[63,128],"volatile":1}}},"d":{}}

(Note that sometimes the itch.io WYSIWYG editor will scramble code that it confuses for embedded HTML; this can be quite annoying, but is not a bug in Decker.)

I have marked the canvas as "volatile" to keep the snippet smaller, since the script clears and repaints it constantly.

There are a few ways we can simplify and improve the script:

  • Instead of using a "next_frame" field to keep track of the most recent refresh, we could repaint when sys.frame modulo 5 is 0; then there's no extra state to worry about. This also avoids the latent problem that saving a deck (and thus serializing the field) may mean that the number in that field is much larger than sys.frame on the next work session, causing the animation to appear to "freeze" for an arbitrary amount of time before playing. This can be avoided (as in the snippet above) by also making the next_frame field volatile.
  • By marking the canvas as "animated", Decker will automatically send "view" events to the canvas, allowing us to move the script within the canvas and make it self-contained, referring to itself as "me". Using a card-level event pump may be easier in some cases; use whatever approach you prefer.
  • If you want to "zipper" a pair of lists into a list of pairs, you can use the Lil "join" operator instead of transposing a pair of lists:

 (1,2,3) join "ABC"
((1,"A"),(2,"B"),(3,"C"))
 local s: "abcdef" s join random[2 count s]
(("a",0),("b",1),("c",0),("d",0),("e",0),("f",0))
  • In this situation, however, we don't actually need to build that list of pairs; we could instead randomly choose a case-formatting string while iterating over the letters. The random[] function can be called with any list, producing a random choice from that list:
 random["%u","%l"]
"%u"
 random["%u","%l"]
"%l"
  • canvas.text[], like many functions in Lil which expect a string argument, will implicitly fuse lists of strings together, so you don't need to do so explicitly.
  • Assuming the goal was to center the string within the canvas, you can do this generically by using the third "anchor" parameter for canvas.text[].

With these tweaks, the script would be:

local s: "abcdef"
on view do
 if !5%sys.frame
  local y:each c in s random["%u","%l"] format c end
  me.clear[]
  me.text[y me.size/2 "center"]
 end
end

If we wanted to "code golf" there are a few more ways we could shorten this script, but whether you'd consider them improvements is subjective:

  • The "local" keyword for variable declarations is optional; we only need it explicitly as documentation of intent to human readers and to force Lil to shadow, rather than inherit, variable declarations of the same name in a higher scope. In this situation it doesn't change the semantics of the program.
  • The variable "s" is now used only once, so we can inline its value.
  • Likewise the variable "y" is now used only once, so we can inline the entire loop into its use site.
on view do
 if !5%sys.frame
  me.clear[]
  me.text[
   each c in "abcdef" random["%u","%l"] format c end
   me.size/2 "center"
  ]
 end
end

And as a widget snippet attached to a volatile canvas:

%%WGT0{"w":[{"name":"canvas","type":"canvas","size":[100,100],"pos":[206,121],"animated":1,"volatile":1,"script":"on view do\n if !5%sys.frame\n  me.clear[]\n  me.text[\n   each c in \"abcdef\" random[\"%u\",\"%l\"] format c end\n   me.size/2 \"center\"\n  ]\n end\nend","scale":1}],"d":{}}

Does that help?

Decker's touch mode is primarily designed with tablets in mind. Some users have reported a good experience on those phones which support a stylus for more precise input.

Decker uses a custom soft keyboard in order to provide a consistent experience on all devices and operating systems in both web-decker and native-decker, with the ability to easily type any of the characters used in Lil scripts. Having fine control over the behavior of this soft keyboard is also important for supporting some functionality.

Various mobile browsers often report misleading or inaccurate information about the screen and its DPI to webapps, including invisibly reserving space for their own UI elements or introducing their own scaling factors. On iPhones specifically, Decker's "fullscreen mode" functionality does not work, because iOS Safari intentionally does not implement the standard fullscreen APIs available on other devices. I try to avoid adding device-specific or browser-specific code to web-decker whenever I can, because that kind of work is inherently brittle.

There's no need at all to apologize for asking questions, especially when you've clearly done some experimenting and reading on your own before reaching out!

In a script like so:

on click do
 sleep[5*60]
 play["sosumi"]
end

Decker will wait five seconds before playing the sound. If you reverse the order of those operations:

on click do
 play["sosumi"]
 sleep[5*60]
end

The sound will begin to play immediately, with its playback overlapping the 5-second sleep.

You can also use sleep["play"] to ask Decker to sleep until all sound clips have finished play[]ing:

on click do
 play["sosumi"]
 sleep["play"]
end

See All About Sound for more detail and examples.

There's a lot of material in this thread that might be helpful.

There are a number of possible approaches for what you're describing. I'll assume that when you say "play a GIF" you intend to use a gif or colorgif contraption?

If you have set up a button to take you to another card, its script might look something like the following:

on click do
 go["otherCard"]
end

If you simply wanted to wait for time to elapse before changing cards, you could use the sleep[] function, which accepts a number of frames to wait as an argument. Decker runs at 60 frames per second, so a 5 second delay would look like this:

on click do
 sleep[5 * 60]
 go["otherCard"]
end

While Decker is sleeping, the user can't interact with other widgets, and contraptions that normally animate or otherwise update themselves on every frame will appear to be "frozen". Some contraptions are designed to allow an external script to explicitly tell them to update themselves; by convention this will often take the form of exposing an .animate[] function which can be called from the outside. I have updated the gif and colorgif contraptions (see above) to support this convention. (If you already have a gif or colorgif contraption in your deck, re-pasting the updated definition from the bazaar will "upgrade" any existing instances of the contraption.)

If the card contained a gif widget named "mygif", we could rewrite the above script to give it a chance to keep running while Decker waits for 5 seconds by using a loop and only sleeping one frame at a time:

on click do
 each in range 5 * 60
  mygif.animate[]
  sleep[1]
 end
 go["otherCard"]
end

You alluded to wanting the GIF to start playing only when the button was clicked in the first place. Perhaps you meant you want the contraption to appear during that interval? This sort of thing can be done by manipulating the .show attribute of the widget. Supposing the contraption was initially set to "Show None",

on click do
 mygif.show:"solid"
 each in range 5 * 60
  mygif.animate[]
  sleep[1]
 end
 mygif.show:"none"
 go["otherCard"]
end

(Note that I reset the GIF to be invisible again at the end; this is not essential, but makes testing easier!)

I'd also like to point out that it's very straightforward to script simple "slideshow" animations with sleep[] by putting each frame on its own card:

on click do
 go["firstCard"]
 sleep[30]
 go["secondCard"]
 sleep[30]
 go["thirdCard"]
 sleep[30]
 # and so on
end

Or more concisely, for many frames of the same delay,

on click do
 each cardName in ("firstCard","secondCard","thirdCard","fourthCard","fifthCard")
  go[cardName]
  sleep[30]
 end
end

Of course, having a very large number of frames in such an animation can also make your deck quite large!

Does any of this point you in the right direction?

I found both the story and its presentation riveting and highly atmospheric; well done!

Dec(k)-Month The Second is now complete. We've had another bumper crop of toys, games, and zines: please check out the submissions, leave some nice comments and constructive criticism for your fellow deckbuilders, and share their creations with your friends. Don't forget to give a rating to submissions to help increase their visibility here on itch, and if you're a participant please remember to tag your creations with #decker!

If anyone didn't quite make the deadline and needs an extension, feel free to contact me within the next few days for help with late submissions.

Millie's correct; I'm not intentionally doing anything special beyond lowering the bit depth and sampling rate. Converting 10-second-or-less audio clips in this way and saving them from Decker is pretty easy; something like the following in the Listener or a buttons script:

write[read["sound"]]

If you use Native-Decker this will only support reading .WAV files, but for Web-Decker it ought to work for any audio format your browser can handle.

Dealing with arbitrarily long audio is possible, but would be more complicated from within Decker.

This should be fixed in Ply v1.0.1.

(1 edit)

The Sims 2 is real???

Amazing; 6 blinking on my cell phone out of 5 stars.

This is fantastic; an immersive point-and-click collage stuffed with puzzles. I did find ringing the bell a bit treacherous: clicking it quickly seems to make the game unwinnable. You might want to use >2 instead of =3 somewhere in your scripts. ;)

This is looking amazing. The subtle animation on the Christmas lights adds a lot, and everything has so much texture!

dd.say[] and dd.ask[] accept rich-text, so one alternative might be to represent your input options as hidden rich-text fields. You could also use rtext.cat[] to convert an image stored in a canvas into a rich-text table, or use any other method of constructing it on the fly.

Suppose you have a pair of fields named "op1" and "op2". Their "value" attribute is their rich-text, which is represented in Lil as a table of text runs and attributes. It is important to wrap each table in a list with the "list" primitive like so; otherwise the comma operator will join the rows of those tables and make all the options in the prompt "stick together":

dd.ask[
 "Some Question"
 (list op1.value),(list op2.value)
] 

(2 edits)

To de-mystify a little, when Dialogizer is displaying a dd.say[] modal, it's doing something similar to the following:

while !pointer.down
 blink_cursor[]
 deck.card.event["animate"]
 sleep[1]
end

The "animate" event which is being sent to the current card provides the opportunity to make other things happen while the dialog box is idling and waiting for user input. Puppeteer uses this hook to animate puppets, and it can be used to give other modules and contraptions a chance to do their thing as well.

Any "blocking" scripts you write yourself can use a similar approach; a synchronous animation script becomes responsible for explicitly routing the events to everything that needs to do work on every frame, whereas an asynchronous script allows Decker to handle some of that plumbing automatically.

Decker community · Posted in Fonts

As Millie says, Decker uses its own font format; adapting e.g. an existing TrueType font for Decker will require some manual work, and perhaps a bit of script-wrangling. Your first stop should probably be fontedit.deck, which includes a very simple font editor. As discussed quite recently, since this editor is a deck, you can feel free to extend and customize it to suit your needs.

I wrote an article some time ago which describes how one could build their own DIY importer for pre-existing bitmapped fonts.

The structure Decker uses to represent fonts is described in The Decker Document Format, but this information is only necessary if you wanted to produce Decker fonts from some external tool. The Font Interface in the Decker Reference Manual describes the APIs for manipulating fonts on the fly from within Decker.

absolutely wild aesthetic; very creative!

Just in case it helps, the Decker reference manual has a section about scripted animation that describes "synchronous" vs. "asynchronous" approaches and the tradeoffs therein. I also recently wrote an article about making action games in Decker that talks about similar issues through a more practical/applied lens.

If you haven't seen it previously, Learn Lil in 10 Minutes is a fast-paced overview of most of Lil's features targeted at folks who already have some exposure to other programming languages.

(1 edit)

A slightly simpler equivalent would be to use an "each" loop:

each i in range 96
 f[i]:f[i].translate[0,1 1]
end

If "f" were a list of Images, it would be possible to use ".." notation to manipulate every element in-place in the same way:

f..translate[0,1 1]

But this notation cannot be applied to an Interface type like a Font to implicitly iterate over every glyph. Alas!

(1 edit)

I just corrected the inconsistency in how the modal fill/stroke palettes draw the first swatch while in transparency mask mode; the fix will be included in the next Decker release; probably this Friday. The behavior of the toolbar is as intended.

Well done. The nonlinear navigation structure is a visceral way of representing how all these issues and thoughts are inter-related and knotted together.

Have you considered also submitting this piece to Dec(k)-Month?

By default, Lil fragments in a Ply passage execute in an isolated scope that only has access to variables defined among passages and the variables I explicitly note in the documentation, which means no references to the widgets on the current card, the deck, etc. This is an important default, because otherwise it would not be possible to run these scripts in the stripped down "standalone" environment of the Twine Story Format where no surrounding deck exists.

You can get around this in several ways if you want to; for example you could modify the "Twine Player" example and install references to deck parts, functions, or anything else you want via the third "vars" argument to twee.render[]; you just have to explicitly opt into the things you want, and once you start doing this your stories will no longer work in Twine or the plyPlayer contraption:

# instead of this...
r:twee.render[story.value there vars.data]
# ...you could do this...
v:vars.data
v.deck :deck
v.print:print
v.pi   :pi
r:twee.render[story.value there v]

It's not a great idea to put function-calls with side effects in here, since all the fragments of a passage will be re-executed every time you revisit or refresh the passage. Beware!

From an "outside-in" perspective you could also naturally have other controls and scripts in the deck tweak or consult the flags this example stashes in the "vars" field.

Another way of making stuff happen in response to the player's actions is to add logic to the "link[]" event for the field where the rtext is rendered. In the "Twine Player" example there's some code that consults the current passage's "tags" and alters the "show" and "align" properties of the field:

# bonus: tag-based formatting cues:
p:(story.value.name dict rows story.value)[there]
output.show:if "inverted" in p.tags "invert" else "solid" end
output.align:if "center" in p.tags "center" elseif "right" in p.tags "right" else "left" end

You could have any sort of logic you want consulting the name, tags, or metadata of the just-visited passage.

If you use the plyPlayer contraption you're more limited, but it still offers a user-interceptable event if you edit the script of the contraption instance. The built-in example updates the "location" label to reflect the current passage name:

on passage name body tags meta do
 location.text:name
end

There's some other stuff I intended to work on first, but, well, once the idea started rattling around in my head...

I just completed a first pass at a utility module that's been on my mind for quite a while: a library for working with Twine's .twee story file format from within Decker:

http://beyondloom.com/decker/twee.html

The "twee" module is a more robust and general version of the code previously seen in this prototype. In addition to being able to parse and emit .twee files, it can handle Twine's HTML Output Format, it can render a simple RText-oriented story format called "Ply", and it comes packaged with a self-contained contraption for playing Ply-based stories within a deck:

Finally, there's a simple Ply Story Format available for use with Twine itself:


http://beyondloom.com/decker/ply/format.js

I hope that these will provide some interesting possibilities for using these tools in concert!

Very informative zine; I learned many things I didn't previously know about how the games industry handles localization!

Including the option to export a PDF is a great feature! One possible refinement would be to temporarily hide the navigation buttons while taking screenshots of each card, like so:

 images:each card in range deck.cards
  w:if card.index
   card.widgets@("button1","button2","button3")
  end
  w..show:"none"
  i:pdf.flatten[app.render[card] deck]
  w..show:"solid"
  i
 end

 

Did you read this post in the Bazaar?

(1 edit)

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

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

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

Decker community · Created a new topic Breakout, Three Ways

I just wrote a new Decker tutorial which walks through three different approaches for implementing the arcade classic Breakout, and along the way touches on a range of considerations for programming action games. Note that this is targeted at an audience that is already somewhat comfortable with programming. Hopefully this will contain some useful information for our more ambitious deckbuilders and lilateers!

http://beyondloom.com/blog/breakout.html


Any follow-up questions? Was there anything you learned, or felt was missing? I'd love to hear your thoughts!

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

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

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

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

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

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

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

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

Does that help?

All of the modules and examples in the Decker source repo and homepage are available under the MIT license. Any non-code assets for which the MIT license cannot be logically applied should be understood as available under the CC0.

I also formally extend the same to WigglyPaint and all of its components. If you're interested in making something wigglypaint-like, you should be sure to check out WigglyKit, a collection of contraptions that are specifically designed as more modular/reusable versions of the components in WigglyPaint.

Hope that helps!

(1 edit)

Just to flesh out the implied details of Millie's example, I have an experimental setup with four audio clips named "sound1" through "sound4" (I used Decker to record myself saying the numbers one through 4 to make it clear), a field named "loopcounter", a button for starting the loop with a script like this:

on click do
 loopcounter.text:0
 play["sound1" "loop"]
end

and a button for stopping it like this:

on click do
 play[0 "loop"]
end

That leaves us with the loop[] event handler, which for this setup ought to be defined on the card. If you wanted the music to keep playing across many cards, defining a handler on the deck-level script could also work, but it would then be important to specify the path to the loopcounter field as e.g. "deck.cards.thatCard.widgets.loopcounter" instead of just "loopcounter".

We can simplify Millie's example in a few ways. Firstly, by indexing from 0 instead of 1, we can use the modulus operator (%) to wrap the incremented value of loopcounter between 0 and the length of the list of audio clip names, avoid the need for a "dummy" element at the beginning of the list, and save an "if" statement. We can also coerce a string like "3" to the number 3 by adding 0 to it (or any equivalent arithmetic identity operation) instead of parsing it:

music1:("sound1","sound2","sound3","sound4","sound3","sound3","sound1","sound2")
on loop do
 loopcounter.text:(count music1)%1+loopcounter.text
 music1[0+loopcounter.text]
end

We could make the script more concise still by defining the list of pattern names as a single string that we split on pipes (|) and stashing the loop index in a temporary variable so we don't have to refer to "loopcounter.text" three times or turn the string-ified index back into a number:

on loop do
 m:"|" split "sound1|sound2|sound3|sound4|sound3|sound3|sound1|sound2"
 loopcounter.text:i:(count m)%1+loopcounter.text
 m[i]
end

Since there's a very simple pattern to the names of audio clips we want to play, we could be even more concise by only building a list of the part that varies- the number at the end of the string- and formatting that at the end of the function:

on loop do
 m:1,2,3,4,3,3,1,2
 loopcounter.text:i:(count m)%1+loopcounter.text
 "sound%i" format m[i]
end

Of course, the most important thing is always to use the approach that makes sense to you! Longer code isn't necessarily worse if you find it clearer or easier to modify.

Decker community · Created a new topic All About Brushes

In Decker 1.52 I overhauled the brushes.deck example to make it a full interactive tutorial: All About Brushes!


I hope that this sheds some light on a feature that has remained a bit obscure and under-utilized so far. I'd love to hear your thoughts on this new tutorial. Do you find it helpful? Is anything unclear, or is it missing information you'd like to see?

As a bonus, here's an extra little Lil fragment that describes a brush which cycles through a configurable "rainbow" palette. Give it a spin:

i:image["%%IMG0AAsADQ+AP8B/wP/g/+D/4P/g/+D/4P/g/8B/gD4A"]
#c:47,44,32,37
c:39,36,32,36
n:0
brush[on rainbow do
 n:(5*count c)%n+1
 i.copy[].map[1 dict c[n/5]]
end]


What brushes can you folks dream up?

Sounds like the ImageEnum contraption might do what you have in mind, more or less. If you specifically wanted to display thumbnails of other existing cards you could write a script to populate the enum using "app.render[card]" to ask Decker to produce "screenshots" for you.