Skip to main content

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

Screwtapello

112
Posts
5
Topics
33
Followers
7
Following
A member registered Oct 02, 2017 · View creator page →

Creator of

Recent community posts

This is very short, but the presentation is wonderful - I love the art, and the sounds, and Hardcastle’s dialogue is written in a voice with the nauticality cranked up to 11, which is charming in its own way.

That is a lovely font, and the presentation is lovely - I love the pixel art, I love the palette, you’ve really gone above and beyond.

This drips with so much 2000s skate culture it’s ridiculous, like somebody took a bunch of screenshots of Tony Hawk Pro Skater and used them to assemble a mosaic portrait of Tony Hawk.

I absolutely believe this took you a year to make.

Guess I gotta go watch Street Cinema now.

At first I didn’t realise that I could click on the thought-bubbles. Once I figured it out, my opinion went up 1000%.

“b-ah-m” also makes a bubble that doesn’t lead anywhere?

That’s super-cute. Did you draw all those things on paper and scan them in?

That was short, but the art and animations are lovely. Well done!

Those are some really lovely palettes.

A trick I just figured out: if you have a bunch of widgets that you want to have the same behaviour, you can put the event-handler in the card’s script instead of the widgets’. But then if you accidentally open a widget’s script, it may get populated with a template handler that overrides the card-level one.

So for my act-alike widgets, I open up the script and replace the template handler with a comment like:

# See card-level event handlers

Once there’s a non-empty widget script, examining the widget script won’t create the template handler. And as a benefit, if I examined the widget script to remind myself how things work, I see a reminder instead of a mysteriously blank script. :)

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?

There’s a few typos, and sometimes a button’s label overflows its edges, but wow, that was great. I love how the birds were animated and eye-catching, just as they would have been to the cat.

This is surprisingly fun, I like the way you flash the background to give feedback. Is there an end-game, or just typing forever?

Inspired by some Millie’s “Janky” musical-instrument decks, and the inclusion of an Amen Break sample in the All About Sound example deck, I’m trying to make my own drum-machine deck based on chopping up the Amen Break into individual samples.

So far, I’ve learned a lot about using the waveform and spectrum views in Audacity to figure out where to make the cuts, and I’ve also learned that it’s a bad idea to have a button named play on a card where you also want to use the play[] built-in function.

In Decker’s list of built-in functions, the last function in the list is:

write[x hint] Open a modal dialog prompting the user to save x.

…with a pointer to additional information lower down. That additional information includes:

image interfaces are written as GIF89a images, and a .gif extension will be appended to any filename without it.

So if we can get an “image interface”, we can let the user save that as a GIF file. Where do image interfaces come from?

Searching the docs for “image interface”, it looks like we can create one from scratch with image[], we can get one by read[]ing a GIF file, you can get images that already exist in a rich-text widget, but none of those are what we want. However, there’s another hit in the App Interface section:

x.render[x] Draw the visual appearance of card or widget x as an Image interface.

…so if we can get an “app interface” from somewhere, we can use it to turn any card or widget into an image interface, which we can pass to write[] to save it as a GIF. A bit earlier in the App Interface docs, it says:

It is available as a global constant named app.

…so we can just say app to refer to the app interface. If a card has a name that’s valid for a Lil variable, then that name is also globally available. If your results card is called, for example, results, then you should be able to save it as a GIF like this:

write[app.render[results]]

Unfortunately, there’s no way to suggest a filename, or where it gets saved. That’s partially for simplicity, but also because Decker can be run in a browser, and browsers are pretty strict about what you’re allowed to do for security reasons.

Wow. I had thought about this, but I didn’t know how to do it and I figured Decker might not be fast enough anyway. That’s… really impressive.

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!

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)

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?

The little maze-navigating character is cute!

It’s an interesting choice having the character move on a grid, and having the maze on a grid, but having them be different grids. I was still able to get down each tunnel I wanted to go through, though sometimes it felt like I should have been able to pass when I was blocked, or I shouldn’t have been able to pass when I could.

I also liked the little “bump” animation when you try to go through a wall.

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.

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.

I got sick of having to switch to Widget mode, lock the contraption, and switch back to see what the curve “really” looked like, then do the dance again to resume editing. Now there’s a little “pin” button in the top left to hide and disable the handles in Interact mode. Locking the contraption now hides the handles and the pin.

I also updated the rendering code to try rounding coordinates to the nearest pixel, which might make lines a bit smoother.

I have done exactly this - Decker Font Importer is updated with a new layout that starts off with “where to get cool fonts” to spark your imagination, and then shows you how to import the one you want.

Hopefully people find this version more approachable!

I’ve updated the contraption to version 3, which leans harder on Lil’s “conforming” to calculate the curve rendering. As a result, the ten-handle curve that was “very laggy” on my computer before (with the Script Profiler capping out at 100% CPU usage) is now smooth (capping out at 64% CPU) even at the default step-size of 2. The new version still has a step-size property, though, because somebody might want to draw an even bigger curve. :)

Have a Bézier kitty for your hard work

Wow, thank you!

When using a larger contraption size / more handles on the Bézier curve, the script profiler shoots up anywhere between 20%-70%

Yeah, I was working on Native Decker, with only three or four handles total. Computing the curve position can be expensive (up to the square of the number of handles), and the original contraption I posted tried to do it for every pixel along the curve for maximum smoothness, so I’m not surprised it might be expensive.

Ah, that’s really handy! Thanks for pointing it out!

(4 edits)

I would like to have some swoopy lines decorating my deck, but I do not have a graphics tablet to draw them with, and drawing large curves pixel-at-a-time can be tedious. Some graphics apps have a “curve” or “Bézier curve” tool, which is like a regular line-drawing tool except you get some extra “handles” you can move around to affect how the line bends. Decker doesn’t have a “curve” tool either, but it does have contraptions, so I made a Bézier contraption.

Edit: Version 2 changes how you set the pattern used to draw the curve, and adds the “step size” property to speed up rendering. Version 3 significantly speeds up rendering, so the “step size” is less necessary. Version 4 adds a little “pin” button in the top left to quickly hide the handles so you can see what the curve looks like without having to switch modes twice, lock and unlock.

Normally, the contraption draws the curve, plus a little circle at each handle, and a dotted line connecting them so you can see which one is which. In interact mode, you can drag the handles around and see how they affect the shape of the curve.

Bezier curve with handles visible, and a little push-pin at the top left

If you want to quickly see what the curve looks like without the handles, you can click the “pin” button in the top-left, then click it again to unpin:

Bezier curve with no handles visible, and the push-pin pushed in

If you switch to Widget mode, lock the contraption, then switch back to Interact, the curve is drawn without the handles or the pin, and clicking won’t change the shape. You can just use this as a decoration directly, or go back into Widget mode and do Edit → Copy as Image to get something you can paste into your own artwork, or touch up with Decker’s tools.

Bezier curve unadorned

In Widget mode, you can use Widget → Pattern… to set the pattern used to draw the curve.

The properties are pretty simple: you can set the brush and set the handle coordinates specifically. You can have as many handles as you like, if you want a lot of control over the shape of the curve.

Contraption properties, listing “Brush”, “Coords” (coordinate pairs, one per line) and Step Size

If you do have a lot of handles (or make the contraption very large) it may take a while to calculate the exact position of the curve, which can make editing laggy. Here is a curve with 10 handles, and on my computer dragging one gets very laggy.

A very complicated Bézier curve

If I set the “Step size” property up to 50, the line gets very chunky, but dragging handles around becomes buttery smooth:

If I set the “Step size” back down to 10, it’s nearly as smooth as the original but still a lot less laggy:

The exact number you use depends on how fast your computer is, how big you make the contraption, how many handles you want, and what brush you use (smaller brushes make the chunkiness more visible). There’s also nothing stopping you from bumping the number up for editing, and then back down for display (or to use Copy as Image).

%%WGT0{"w":[{"name":"bezier1","type":"contraption","size":[189,137],"pos":[16,32],"show":"transparent","def":"bezier","widgets":{"canvas":{"size":[189,137]},"brush":{"size":[64,27],"pos":[205,0],"value":"2"},"coords":{"size":[96,137],"pos":[285,0],"value":{"x":[21,150,45,143],"y":[93,116,33,56]},"row":3},"sprites":{"size":[64,27],"pos":[205,55]},"stepsize":{"size":[64,28],"pos":[205,27]},"pinned":{"size":[64,28],"pos":[205,82]}}}],"d":{"bezier":{"name":"bezier","size":[80,80],"resizable":1,"margin":[0,0,0,0],"description":"Draws a Bezier curve. Use Widgets -> Pattern to set the colour, click the pin to hide the handles, lock the contraption to prevent editing.","version":4,"script":"on lerp t a b do a+(b-a)*0|1&t end\n\non bezier t ps do\n # Use lerping to combine adjacent pairs of positions\n # until there's only one left\n while 1 < count ps ps:lerp[t 1 drop ps -1 drop ps] end\n \n # Return the one that's left\n first ps\nend\n\non get_brush      do brush.data        end\non set_brush b    do brush.data:b      end\non get_stepsize   do stepsize.data     end\non set_stepsize s do stepsize.data:1|s end\n\non get_coords do\n (\"\\n\", \"%i,%i\") format coords.value.x join coords.value.y\nend\n\non set_coords t do\n coords.value:table \"xy\" dict flip \"%i,%i\" parse \"\\n\" split t\nend\n\non view do\n if !brush.data set_brush[0] end\n \n canvas.clear[]\n handles:coords.value.x join coords.value.y\n steps:ceil (max canvas.size)/stepsize.data\n canvas.pattern:card.pattern\n canvas.brush:brush.data\n canvas.line[0.5+each t in (range steps+1)/steps bezier[t handles] end]\n \n if ! card.locked\n  if pinned.value\n   # Draw the pinned sprite\n   canvas.paste[sprites.images[2] 0,0]\n  else\n   # Draw a dotted line connecting the handles in order\n   canvas.pattern:13\n   canvas.brush:0\n   canvas.line[handles]\n   \n   # Draw the handle sprite on top of each handle\n   sprite:sprites.images[0]\n   # Paste the handle sprite on top of each handle\n   each pos in handles canvas.paste[sprite pos-(floor sprite.size/2) 1] end\n   \n   # Draw the unpinned sprite\n   canvas.paste[sprites.images[1] 0,0]\n  end\n end\nend","attributes":{"name":["brush","coords","stepsize"],"label":["Brush","Coords","Step size"],"type":["number","code","number"]},"widgets":{"canvas":{"type":"canvas","size":[80,80],"pos":[0,0],"volatile":1,"script":"on click pos do\n if ! (pinned.value|card.locked)\n  best_row:-1\n  best_mag:me.size[0]*me.size[1] # an impossibly large number\n  \n  each handle i in coords.value.x join coords.value.y\n   handle_mag:mag (handle - pos)\n   if best_mag > handle_mag\n    best_row:i\n    best_mag:handle_mag\n   end\n  end\n  \n  coords.row:best_row\n end\nend\n\non drag pos do\n if ! (pinned.value|card.locked)\n  coords.rowvalue:\"xy\" dict pos\n  view[]\n end\nend\n\non release pos do\n if ! card.locked\n  # If the pointer was released in the top left 8x8 pixels...\n  if min pos < 8,8\n   pinned.value:!pinned.value\n   view[]\n  end\n end\nend","pattern":13,"show":"transparent","border":0,"scale":1},"brush":{"type":"field","size":[64,16],"pos":[96,0],"locked":1,"style":"code","value":"0"},"coords":{"type":"grid","size":[96,80],"pos":[176,0],"locked":1,"scrollbar":0,"lines":0,"format":"ii","value":{"x":[9,27,69],"y":[15,55,35]},"row":0},"sprites":{"type":"field","size":[64,16],"pos":[96,32],"locked":1,"value":{"text":["","i","i","i"],"font":["","","",""],"arg":["","%%IMG3AAUABQZAQGAoBBmHRhDyKCQGAQ==","%%IMG3AAgACAZAgBAACoCGwqKRCFIGjM9okygdOo/U5hKwxQKC","%%IMG3AAgACAZAgBAQKA6JoGSAWEyCmk8n9GkMOJ9Dq/JYXAqD"]}},"stepsize":{"type":"field","size":[64,16],"pos":[96,16],"locked":1,"style":"code","value":"2"},"pinned":{"type":"button","size":[64,16],"pos":[96,48],"locked":1,"text":"Pinned","style":"check","value":0}}}}}

Fun fact: Bézier curves are named for Pierre Bézier, the second person to discover them and use them to design more attractive vehicles at a French car manufacturer.

According to the documentation for the Grid interface, there’s three relevant attributes:

  • .bycell controls whether individual cells in the grid can be selected
  • .row controls which row of the grid is selected
  • .col controls which column of the grid is selected

You can select row 1, column 2 of a grid like this:

grid1.bycell:1
grid1.row:1
grid1.col:2

The “select by cell” option can be set in the properties window for the grid, but if it can get cleared by some other code, you’ll need to re-set it.

There’s one other quirk I noticed. Normally, if something in Decker needs to be a number, Decker will automatically convert it. For example, grid1.row:"1" will select a cell in row 1 though "1" is a string, because the row number needs to be an integer so it gets converted. However, the .col property does not work the same way. grid1.col:2 will select the cell in column 2, but grid1.col:"2" selects nothing. This can be a problem if the column number you’re trying to set comes from a field value, or gets read from a file. To force it to be handled as a number, you can do something like add zero: grid1.col:0+"2"

Here’s a card you can paste into your deck to play with reading a grid’s selected cell, and selecting one with code.

%%CRD0{"c":{"name":"home","script":"on view do\n bycell.value:grid1.bycell\n row.text:grid1.row\n col.text:grid1.col\nend","widgets":{"grid1":{"type":"grid","size":[96,64],"pos":[48,64],"script":"on click row do\n view[]\nend","bycell":0,"value":{"a":["1","4","7"],"b":["2","5","8"],"c":["3","6","9"]},"row":2,"col":2},"bycell":{"type":"button","size":[64,16],"pos":[160,64],"script":"on click do\n grid1.bycell:me.value\nend","text":"By Cell","style":"check","value":0},"field1":{"type":"field","size":[32,16],"pos":[160,80],"locked":1,"font":"menu","border":0,"style":"plain","value":"Row"},"field2":{"type":"field","size":[48,16],"pos":[160,96],"locked":1,"font":"menu","border":0,"style":"plain","value":"Column"},"row":{"type":"field","size":[32,16],"pos":[208,80],"script":"on change val do\n grid1.row:val\nend","style":"plain","value":"2"},"col":{"type":"field","size":[32,16],"pos":[208,96],"script":"on change val do\n grid1.col:0+val\nend","style":"plain","value":"2"}}},"d":{}}

Note:

  • the card has a view handler that copies the state of the grid’s properties into the the bycell, row and col fields
  • the bycell, row, and col fields have code that set the state of the grid when the field is changed
  • the col field has the extra 0+ trick to make it work like row does

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.

It’s heartening to see the transition and font from Screen Melt Transitions being used in a far more wholesome context than the source material. Well done!

Decker cards are 512×342 pixels, but by default (in “unlocked” mode) the top 16 pixels are covered by the menu bar.

When you save deck as “locked”, that menu-bar no longer appears, and you get to see the pixels that were under it instead.

Obviously it’s not great to have pixels that will be visible in the final product that you can’t see while working on it, so if you’re using one of the drawing tools (selection, pencil, lasso, line, etc.) and you press the “m” key, the menu bar will be hidden so you can draw underneath it. You can get the menu back by pressing “m” again, or by switching to the “interact” or “widget” tools - by pressing the F1 or F2 keys, or by clicking on them in the toolbar if you have that visible.

It occurs to me that maybe it should be organised around “where did you get your fonts” rather than “what format are they in”. Although grouping by format makes sense from an implementation point of view, these formats are all niche enough that nobody’s going to say “oh, my favourite font creator published a font in FZX format, how can I use that with Decker?”

To be clear, things like the OFL’s “Reserved Font Name” or the GPL’s “must distribute source code” clause don’t actually do anything to prevent abuse like this. They just give you (in this case, IJ) the legal right to sue people who do break those rules, which means preventing abuse depends on IJ having the time and money to hire lawyers to do the suing, and investigators to figure out who needs to be sued, and possibly more lawyers to figure out which of those people live in countries where international treaties allow them to be sued from whatever country IJ lives in.

Without all that additional stuff, those clauses just amount to saying “don’t be a jerk” in fancier language, which.. I think was already implied.

To get SDL_version.h on regular Fedora, you’d need to sudo dnf install sdl2-compat-devel and that should do it.

Unfortunately, Fedora Atomic is more like Android or iOS, where you can’t just install something into the system, you need to set up a special isolated development environment. I’m afraid that’s not something I’m familiar with. If nobody here has experience with development on Fedora Atomic, you might be able to get more help in a Fedora-specific community.

Decker community · Created a new topic Decker Font Importer

As hinted at previously I’ve been working on a deck that imports pixel fonts from various indie gaming and retrocomputing formats into Decker, for use in your Decker decks.

Supported formats include:

  • Raw ZX Spectrum font data
  • FZX, commonly used with the ZX Spectrum
  • UF1, UF2, and UF3, used with the uxn/varvara virtual console
  • YAFF fonts
  • Glyphs files, as exported from FontStruct

If you’re not familiar with any of those formats, the deck links to sources of fonts in each of those formats for you to explore and play with.

I still have a few items on my to-do list, but it works for the use-cases I originally wanted (getting more classic Macintosh fonts to go alongside Decker’s built-in fonts) and I would like to see what bugs other people encounter and what features they’d like to see.

https://screwtapello.itch.io/decker-font-importer

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.

Thanks!

I don’t know of one off the top of my head. Digging around in Decker’s source-code, I managed to get this list of shortcuts from the code that sets up menus:

Cards...           C
Copy               c
Copy Image         c
Copy Rich Text     r
Copy Sound         c
Copy Table         c
Copy Widgets       c
Cut Image          x
Cut Sound          x
Cut Widgets        x
Cut                x
Darken Image       k
Fullscreen         f
Invert             i
Keycaps...         k
Lighten Image      j
Listener           l
Open...            o
Order...           O
Paste Card         v
Paste Image        v
Paste Inline Image v
Paste Rich Text    v
Paste Sound        v
Paste Table        v
Paste              v
Paste Widgets      v
Prototypes...      T
Query...           u
Quit               q
Redo               Z
Rotate Left        ,
Rotate Right       .
Script...          e
Script...          r
Select All         a
Snap to Grid       p
Sounds...          S
Tight Selection    g
Toggle Comment     /
Undo               z
X-Ray Specs        r

Most of those shortcuts are only available at certain times (for example, ^/ to toggle comments only works in the script editor) and there’s shortcuts that aren’t in that list (like m to toggle the menu while in drawing mode).

It might be a fun graphic-design challenge to make a Decker keyboard shortcut zine with Millie’s zine template.

As far as I can see, ^d is not being used for anything, so if that could be “Go to Deck” (and especially if ^e could be “Go to Card” even in the script editor), that would be very handy.

I figured there’d be a Good Reason (probably browser-based) why read[] works the way it does, I just couldn’t guess what it might be. Apparently file extensions also count as Unique File Type Specifiers, but I do not doubt it’s a mess on mobile browsers. I guess this is just a nice-to-have, since even filename extensions can lie.

As for returning a name, I guess the hacky thing would be to give the Array interface a .name property that’s normally nil but can be assigned any string value, and have the read[] builtin try to set it before returning the result. A binary-data array isn’t necessarily a file, though, and it doesn’t make a whole lot of sense for it to have a .name property otherwise.

Just for the heck of it, I’m expanding my UXN fonts deck into one that can import various font formats into Decker. As I’ve been working on it, I’ve bumped into a few things that annoy me.

I can’t find an easy way to edit the Deck script

I can edit the card’s script with ^e. I can edit a widget’s script by selecting it and hitting ^r (and the shortcut for switching widgets with ^r in the editor and control-clicking the ghost of a widget is very cool). But I don’t know how to quickly get to the deck script, and I occasionally have to flip back and forth between them when I have a card script that calls handlers defined in the deck script.

For some reason, my fingers have decided that ^e should toggle back and forth between the Deck and Card scripts. It doesn’t work, but I keep trying it and being disappointed.

read[] doesn’t filter by extension

If you read["image"] or read["sound"], the file-picker restricts you to selecting filetypes Decker can read, which makes sense. If you write[array[] ".dat"] then Decker writes array data and automatically sets the file-extension to .dat. However, if you read["array" ".dat"], the file-picker doesn’t restrict you to reading .dat files - you get to pick any file on the system, whether you understand it or not.

Some font file formats have a magic number I can check to see whether it’s a format I recognise, but some are just straight binary data and I have to try it and see. It would be more friendly if I could say “this button only imports .ch8 files”.

read[] can’t tell me the filename it opened

Very simple font formats also often don’t have a font name embedded in them; the font name is effectively the filename. But read[] doesn’t tell me the name of the file it opened, I just get Array interface, so I have to import the font as “New Font”, then let the user rename it. That’s not impossible, but it is annoying.

In addition, if the user clicks Cancel in the file-picker, I’d expect it to return nil or some other indicator that things didn’t work out. Instead, it returns a zero-sized array, just like I’d tried to open an empty file. Again, not a deal-breaker, but “You cancelled, or chose an empty file” is not the most helpful error message.

I don’t believe so, no. Even though it looks a bit like Lua or Python, Lil’s functional-language heritage means there’s no shortcuts, no return or break or continue. This can make some things more difficult, but once you get used to thinking in that style you’re writing much more vectorisation-friendly code, and Lil’s vector operations are much more efficient than regular loops.